kabu_defi_pools/
uniswapv2pool.rs

1use crate::state_readers::UniswapV2EVMStateReader;
2use alloy::primitives::{Address, Bytes, U256};
3use alloy::providers::{Network, Provider};
4use alloy::rpc::types::BlockNumberOrTag;
5use alloy::sol_types::SolInterface;
6use alloy_evm::EvmEnv;
7use eyre::Result;
8use kabu_defi_abi::IERC20;
9use kabu_defi_abi::uniswap2::IUniswapV2Pair;
10use kabu_defi_address_book::FactoryAddress;
11use kabu_evm_db::KabuDBError;
12use kabu_types_market::{
13    Pool, PoolAbiEncoder, PoolClass, PoolError, PoolId, PoolProtocol, PreswapRequirement, RequiredState, SwapDirection, UniswapV2Error,
14};
15use lazy_static::lazy_static;
16use revm::DatabaseRef;
17use std::any::Any;
18use std::ops::Div;
19use tracing::debug;
20
21lazy_static! {
22    static ref U112_MASK: U256 = (U256::from(1) << 112) - U256::from(1);
23    static ref U256_ONE: U256 = U256::from(1);
24}
25#[allow(dead_code)]
26#[derive(Clone)]
27pub struct UniswapV2Pool {
28    address: Address,
29    token0: Address,
30    token1: Address,
31    factory: Address,
32    protocol: PoolProtocol,
33    fee: U256,
34    encoder: UniswapV2PoolAbiEncoder,
35    reserves_cell: Option<U256>,
36    liquidity0: U256,
37    liquidity1: U256,
38}
39
40impl UniswapV2Pool {
41    pub fn new(address: Address) -> UniswapV2Pool {
42        UniswapV2Pool {
43            address,
44            token0: Address::ZERO,
45            token1: Address::ZERO,
46            factory: Address::ZERO,
47            protocol: PoolProtocol::UniswapV2Like,
48            fee: U256::from(9970),
49            encoder: UniswapV2PoolAbiEncoder {},
50            reserves_cell: None,
51            liquidity0: U256::ZERO,
52            liquidity1: U256::ZERO,
53        }
54    }
55
56    pub fn new_with_data(
57        address: Address,
58        token0: Address,
59        token1: Address,
60        factory: Address,
61        liquidity0: U256,
62        liquidity1: U256,
63    ) -> UniswapV2Pool {
64        UniswapV2Pool {
65            address,
66            token0,
67            token1,
68            factory,
69            protocol: PoolProtocol::UniswapV2Like,
70            fee: U256::from(9970),
71            encoder: UniswapV2PoolAbiEncoder {},
72            reserves_cell: None,
73            liquidity0,
74            liquidity1,
75        }
76    }
77
78    pub fn set_fee(self, fee: U256) -> Self {
79        Self { fee, ..self }
80    }
81
82    pub fn get_zero_for_one(token_address_from: Address, token_address_to: Address) -> bool {
83        token_address_from < token_address_to
84    }
85
86    fn get_uni2_protocol_by_factory(factory_address: Address) -> PoolProtocol {
87        if factory_address == FactoryAddress::UNISWAP_V2 {
88            PoolProtocol::UniswapV2
89        } else if factory_address == FactoryAddress::SUSHISWAP_V2 {
90            PoolProtocol::Sushiswap
91        } else if factory_address == FactoryAddress::NOMISWAP {
92            PoolProtocol::NomiswapStable
93        } else if factory_address == FactoryAddress::DOOARSWAP {
94            PoolProtocol::DooarSwap
95        } else if factory_address == FactoryAddress::SAFESWAP {
96            PoolProtocol::Safeswap
97        } else if factory_address == FactoryAddress::MINISWAP {
98            PoolProtocol::Miniswap
99        } else if factory_address == FactoryAddress::SHIBASWAP {
100            PoolProtocol::Shibaswap
101        } else if factory_address == FactoryAddress::OG_PEPE {
102            PoolProtocol::OgPepe
103        } else if factory_address == FactoryAddress::ANTFARM {
104            PoolProtocol::AntFarm
105        } else if factory_address == FactoryAddress::INTEGRAL {
106            PoolProtocol::Integral
107        } else {
108            PoolProtocol::UniswapV2Like
109        }
110    }
111
112    fn get_fee_by_protocol(protocol: PoolProtocol) -> U256 {
113        match protocol {
114            PoolProtocol::DooarSwap | PoolProtocol::OgPepe => U256::from(9900),
115            _ => U256::from(9970),
116        }
117    }
118
119    fn storage_to_reserves(value: U256) -> (U256, U256) {
120        //let uvalue : U256 = value.convert();
121        ((value >> 0) & *U112_MASK, (value >> (112)) & *U112_MASK)
122    }
123
124    pub fn fetch_pool_data_evm<DB: DatabaseRef<Error = KabuDBError> + ?Sized>(db: &DB, evm_env: &EvmEnv, address: Address) -> Result<Self> {
125        let token0 = UniswapV2EVMStateReader::token0(db, evm_env, address)?;
126        let token1 = UniswapV2EVMStateReader::token1(db, evm_env, address)?;
127        let factory = UniswapV2EVMStateReader::factory(db, evm_env, address)?;
128        let protocol = Self::get_uni2_protocol_by_factory(factory);
129
130        let fee = Self::get_fee_by_protocol(protocol);
131
132        let ret = UniswapV2Pool {
133            address,
134            token0,
135            token1,
136            fee,
137            factory,
138            protocol,
139            encoder: UniswapV2PoolAbiEncoder {},
140            reserves_cell: None,
141            liquidity0: Default::default(),
142            liquidity1: Default::default(),
143        };
144        debug!("fetch_pool_data_evm {:?} {:?} {} {:?} {}", token0, token1, fee, factory, protocol);
145
146        Ok(ret)
147    }
148
149    pub async fn fetch_pool_data<N: Network, P: Provider<N> + Send + Sync + Clone + 'static>(client: P, address: Address) -> Result<Self> {
150        let uni2_pool = IUniswapV2Pair::IUniswapV2PairInstance::new(address, client.clone());
151
152        let token0: Address = uni2_pool.token0().call().await?;
153        let token1: Address = uni2_pool.token1().call().await?;
154        let factory: Address = uni2_pool.factory().call().await?;
155        let reserves = uni2_pool.getReserves().call().await?.clone();
156
157        let storage_reserves_cell = client.get_storage_at(address, U256::from(8)).block_id(BlockNumberOrTag::Latest.into()).await.unwrap();
158
159        let storage_reserves = Self::storage_to_reserves(storage_reserves_cell);
160
161        let reserves_cell: Option<U256> =
162            if storage_reserves.0 == U256::from(reserves.reserve0) && storage_reserves.1 == U256::from(reserves.reserve1) {
163                Some(U256::from(8))
164            } else {
165                debug!("{storage_reserves:?} {reserves:?}");
166                None
167            };
168
169        let protocol = UniswapV2Pool::get_uni2_protocol_by_factory(factory);
170
171        let fee = Self::get_fee_by_protocol(protocol);
172
173        let ret = UniswapV2Pool {
174            address,
175            token0,
176            token1,
177            factory,
178            protocol,
179            fee,
180            reserves_cell,
181            liquidity0: U256::from(reserves.reserve0),
182            liquidity1: U256::from(reserves.reserve1),
183            encoder: UniswapV2PoolAbiEncoder {},
184        };
185        Ok(ret)
186    }
187
188    pub fn fetch_reserves<DB: DatabaseRef<Error = KabuDBError> + ?Sized>(
189        &self,
190        db: &DB,
191        evm_env: &EvmEnv,
192    ) -> Result<(U256, U256), PoolError> {
193        UniswapV2EVMStateReader::get_reserves(db, evm_env.clone(), self.address)
194    }
195}
196
197impl Pool for UniswapV2Pool {
198    fn as_any<'a>(&self) -> &dyn Any {
199        self
200    }
201    fn get_class(&self) -> PoolClass {
202        PoolClass::UniswapV2
203    }
204
205    fn get_protocol(&self) -> PoolProtocol {
206        self.protocol
207    }
208
209    fn get_address(&self) -> PoolId {
210        PoolId::Address(self.address)
211    }
212    fn get_pool_id(&self) -> PoolId {
213        PoolId::Address(self.address)
214    }
215
216    fn get_fee(&self) -> U256 {
217        self.fee
218    }
219
220    fn get_tokens(&self) -> Vec<Address> {
221        vec![self.token0, self.token1]
222    }
223
224    fn get_swap_directions(&self) -> Vec<SwapDirection> {
225        vec![(self.token0, self.token1).into(), (self.token1, self.token0).into()]
226    }
227
228    fn calculate_out_amount(
229        &self,
230        db: &dyn DatabaseRef<Error = KabuDBError>,
231        evm_env: &EvmEnv,
232        token_address_from: &Address,
233        token_address_to: &Address,
234        in_amount: U256,
235    ) -> Result<(U256, u64), PoolError> {
236        let (reserves_0, reserves_1) = self.fetch_reserves(db, evm_env)?;
237
238        let (reserve_in, reserve_out) = match token_address_from < token_address_to {
239            true => (reserves_0, reserves_1),
240            false => (reserves_1, reserves_0),
241        };
242
243        let amount_in_with_fee = in_amount.checked_mul(self.fee).ok_or(UniswapV2Error::AmountInWithFeeOverflow)?;
244        let numerator = amount_in_with_fee.checked_mul(reserve_out).ok_or(UniswapV2Error::NumeratorOverflow)?;
245        let denominator = reserve_in.checked_mul(U256::from(10000)).ok_or(UniswapV2Error::DenominatorOverflow)?;
246        let denominator = denominator.checked_add(amount_in_with_fee).ok_or(UniswapV2Error::DenominatorOverflowFee)?;
247
248        let out_amount = numerator.checked_div(denominator).ok_or(UniswapV2Error::CannotCalculateZeroReserve)?;
249        if out_amount > reserve_out {
250            Err(UniswapV2Error::ReserveExceeded.into())
251        } else if out_amount.is_zero() {
252            Err(UniswapV2Error::OutAmountIsZero.into())
253        } else {
254            Ok((out_amount, 100_000))
255        }
256    }
257
258    fn calculate_in_amount(
259        &self,
260        db: &dyn DatabaseRef<Error = KabuDBError>,
261        evm_env: &EvmEnv,
262        token_address_from: &Address,
263        token_address_to: &Address,
264        out_amount: U256,
265    ) -> Result<(U256, u64), PoolError> {
266        let (reserves_0, reserves_1) = self.fetch_reserves(db, evm_env)?;
267
268        let (reserve_in, reserve_out) = match token_address_from.lt(token_address_to) {
269            true => (reserves_0, reserves_1),
270            false => (reserves_1, reserves_0),
271        };
272
273        if out_amount > reserve_out {
274            return Err(UniswapV2Error::ReserveOutExceeded.into());
275        }
276        let numerator = reserve_in.checked_mul(out_amount).ok_or(UniswapV2Error::NumeratorOverflow)?;
277        let numerator = numerator.checked_mul(U256::from(10000)).ok_or(UniswapV2Error::NumeratorOverflowFee)?;
278        let denominator = reserve_out.checked_sub(out_amount).ok_or(UniswapV2Error::DenominatorUnderflow)?;
279        let denominator = denominator.checked_mul(self.fee).ok_or(UniswapV2Error::DenominatorOverflowFee)?;
280
281        if denominator.is_zero() {
282            Err(UniswapV2Error::CannotCalculateZeroReserve.into())
283        } else {
284            let in_amount = numerator.div(denominator); // We assure before that denominator is not zero
285            if in_amount.is_zero() {
286                Err(UniswapV2Error::InAmountIsZero.into())
287            } else {
288                let in_amount = in_amount.checked_add(U256::ONE).ok_or(UniswapV2Error::InAmountOverflow)?;
289                Ok((in_amount, 100_000))
290            }
291        }
292    }
293
294    fn can_flash_swap(&self) -> bool {
295        true
296    }
297
298    fn can_calculate_in_amount(&self) -> bool {
299        true
300    }
301
302    fn get_abi_encoder(&self) -> Option<&dyn PoolAbiEncoder> {
303        Some(&self.encoder)
304    }
305
306    fn get_read_only_cell_vec(&self) -> Vec<U256> {
307        Vec::new()
308    }
309
310    fn get_state_required(&self) -> Result<RequiredState> {
311        let mut state_required = RequiredState::new();
312
313        let reserves_call_data_vec = IUniswapV2Pair::IUniswapV2PairCalls::getReserves(IUniswapV2Pair::getReservesCall {}).abi_encode();
314        let factory_call_data_vec = IUniswapV2Pair::IUniswapV2PairCalls::factory(IUniswapV2Pair::factoryCall {}).abi_encode();
315
316        state_required.add_call(self.address, reserves_call_data_vec).add_call(self.address, factory_call_data_vec).add_slot_range(
317            self.address,
318            U256::from(0),
319            0x20,
320        );
321
322        for token_address in self.get_tokens() {
323            state_required
324                .add_call(token_address, IERC20::IERC20Calls::balanceOf(IERC20::balanceOfCall { account: self.address }).abi_encode());
325        }
326
327        Ok(state_required)
328    }
329
330    fn is_native(&self) -> bool {
331        false
332    }
333
334    fn preswap_requirement(&self) -> PreswapRequirement {
335        PreswapRequirement::Transfer(self.address)
336    }
337}
338
339#[derive(Clone, Copy)]
340struct UniswapV2PoolAbiEncoder {}
341
342impl PoolAbiEncoder for UniswapV2PoolAbiEncoder {
343    fn encode_swap_out_amount_provided(
344        &self,
345        token_from_address: Address,
346        token_to_address: Address,
347        amount: U256,
348        recipient: Address,
349        payload: Bytes,
350    ) -> Result<Bytes> {
351        let swap_call = if token_from_address < token_to_address {
352            IUniswapV2Pair::swapCall { amount0Out: U256::ZERO, amount1Out: amount, to: recipient, data: payload }
353        } else {
354            IUniswapV2Pair::swapCall { amount0Out: amount, amount1Out: U256::ZERO, to: recipient, data: payload }
355        };
356
357        Ok(Bytes::from(IUniswapV2Pair::IUniswapV2PairCalls::swap(swap_call).abi_encode()))
358    }
359
360    fn swap_out_amount_offset(&self, token_from_address: Address, token_to_address: Address) -> Option<u32> {
361        if token_from_address < token_to_address { Some(0x24) } else { Some(0x04) }
362    }
363
364    fn swap_out_amount_return_offset(&self, token_from_address: Address, token_to_address: Address) -> Option<u32> {
365        if token_from_address < token_to_address { Some(0x20) } else { Some(0x00) }
366    }
367}
368
369// The test are using the deployed contracts for comparison to allow to adjust the test easily
370#[cfg(test)]
371mod test {
372    use super::*;
373    use alloy::primitives::{BlockNumber, address};
374    use alloy::rpc::types::BlockId;
375    use kabu_defi_abi::uniswap2::IUniswapV2Router;
376    use kabu_defi_address_book::PeripheryAddress;
377    use kabu_evm_db::KabuDBType;
378    use kabu_node_debug_provider::{AnvilDebugProviderFactory, AnvilDebugProviderType};
379    use kabu_types_market::RequiredStateReader;
380    use rand::Rng;
381    use std::env;
382
383    const POOL_ADDRESSES: [Address; 4] = [
384        address!("322BBA387c825180ebfB62bD8E6969EBe5b5e52d"), // ITO/WETH pool
385        address!("b4e16d0168e52d35cacd2c6185b44281ec28c9dc"), // USDC/WETH pool
386        address!("0d4a11d5eeaac28ec3f61d100daf4d40471f1852"), // WETH/USDT pool
387        address!("ddd23787a6b80a794d952f5fb036d0b31a8e6aff"), // PEPE/WETH pool
388    ];
389
390    #[tokio::test]
391    async fn test_fetch_reserves() -> Result<()> {
392        let _ = env_logger::try_init_from_env(env_logger::Env::default().default_filter_or(
393            "info,kabu_types_entities::required_state=trace,kabu_types_blockchain::state_update=off,alloy_rpc_client::call=off,tungstenite=off",
394        ));
395
396        let block_number = 20935488u64;
397
398        dotenvy::from_filename(".env.test").ok();
399        let node_url = env::var("MAINNET_WS")?;
400        let client = AnvilDebugProviderFactory::from_node_on_block(node_url, BlockNumber::from(block_number)).await?;
401
402        for pool_address in POOL_ADDRESSES {
403            let pool_contract = IUniswapV2Pair::new(pool_address, client.clone());
404            let contract_reserves = pool_contract.getReserves().call().block(BlockId::from(block_number)).await?;
405            let reserves_0_original = U256::from(contract_reserves.reserve0);
406            let reserves_1_original = U256::from(contract_reserves.reserve1);
407
408            let pool = UniswapV2Pool::fetch_pool_data(client.clone(), pool_address).await?;
409            let state_required = pool.get_state_required()?;
410            let state_update = RequiredStateReader::fetch_calls_and_slots(client.clone(), state_required, Some(block_number)).await?;
411
412            let mut state_db = KabuDBType::default();
413            state_db.apply_geth_update(state_update);
414
415            // under test
416            let (reserves_0, reserves_1) = pool.fetch_reserves(&state_db, &EvmEnv::default())?;
417
418            assert_eq!(reserves_0, reserves_0_original, "{}", format!("Missmatch for pool={:?}", pool_address));
419            assert_eq!(reserves_1, reserves_1_original, "{}", format!("Missmatch for pool={:?}", pool_address));
420        }
421        Ok(())
422    }
423
424    async fn fetch_original_contract_amounts(
425        client: AnvilDebugProviderType,
426        pool_address: Address,
427        amount: U256,
428        block_number: u64,
429        amount_out: bool,
430    ) -> Result<U256> {
431        let router_contract = IUniswapV2Router::new(PeripheryAddress::UNISWAP_V2_ROUTER, client.clone());
432
433        // get reserves
434        let pool_contract = IUniswapV2Pair::new(pool_address, client.clone());
435        let contract_reserves = pool_contract.getReserves().call().block(BlockId::from(block_number)).await?;
436
437        let token0 = pool_contract.token0().call().await?;
438        let token1 = pool_contract.token1().call().await?;
439
440        let (reserve_in, reserve_out) = match token0 < token1 {
441            true => (U256::from(contract_reserves.reserve0), U256::from(contract_reserves.reserve1)),
442            false => (U256::from(contract_reserves.reserve1), U256::from(contract_reserves.reserve0)),
443        };
444
445        if amount_out {
446            let contract_amount_out =
447                router_contract.getAmountOut(amount, reserve_in, reserve_out).call().block(BlockId::from(block_number)).await?;
448            Ok(contract_amount_out)
449        } else {
450            let contract_amount_in =
451                router_contract.getAmountIn(amount, reserve_in, reserve_out).call().block(BlockId::from(block_number)).await?;
452            Ok(contract_amount_in)
453        }
454    }
455
456    #[tokio::test]
457    async fn test_calculate_out_amount() -> Result<()> {
458        // Verify that the calculated out amount is the same as the contract's out amount
459        let block_number = 20935488u64;
460
461        dotenvy::from_filename(".env.test").ok();
462        let node_url = env::var("MAINNET_WS")?;
463        let client = AnvilDebugProviderFactory::from_node_on_block(node_url, BlockNumber::from(block_number)).await?;
464
465        let amount_in = U256::from(133_333_333_333u128) + U256::from(rand::rng().random_range(0..100_000_000_000u64));
466        for pool_address in POOL_ADDRESSES {
467            let pool = UniswapV2Pool::fetch_pool_data(client.clone(), pool_address).await?;
468            let state_required = pool.get_state_required()?;
469            let state_update = RequiredStateReader::fetch_calls_and_slots(client.clone(), state_required, Some(block_number)).await?;
470
471            let mut state_db = KabuDBType::default();
472            state_db.apply_geth_update(state_update);
473
474            // fetch original
475            let contract_amount_out = fetch_original_contract_amounts(client.clone(), pool_address, amount_in, block_number, true).await?;
476
477            let (amount_out, gas_used) = pool.calculate_out_amount(&state_db, &EvmEnv::default(), &pool.token0, &pool.token1, amount_in)?;
478
479            assert_eq!(amount_out, contract_amount_out, "{}", format!("Missmatch for pool={:?}, amount_in={}", pool_address, amount_in));
480            assert_eq!(gas_used, 100_000);
481        }
482        Ok(())
483    }
484
485    #[tokio::test]
486    async fn test_calculate_in_amount() -> Result<()> {
487        // Verify that the calculated in amount is the same as the contract's in amount
488        let block_number = 20935488u64;
489
490        dotenvy::from_filename(".env.test").ok();
491        let node_url = env::var("MAINNET_WS")?;
492        let client = AnvilDebugProviderFactory::from_node_on_block(node_url, BlockNumber::from(block_number)).await?;
493
494        let amount_out = U256::from(133_333_333_333u128) + U256::from(rand::rng().random_range(0..100_000_000_000u64));
495        for pool_address in POOL_ADDRESSES {
496            let pool = UniswapV2Pool::fetch_pool_data(client.clone(), pool_address).await?;
497            let state_required = pool.get_state_required()?;
498            let state_update = RequiredStateReader::fetch_calls_and_slots(client.clone(), state_required, Some(block_number)).await?;
499
500            let mut state_db = KabuDBType::default();
501            state_db.apply_geth_update(state_update);
502
503            // fetch original
504            let contract_amount_in = fetch_original_contract_amounts(client.clone(), pool_address, amount_out, block_number, false).await?;
505
506            // under test
507            let (amount_in, gas_used) = pool.calculate_in_amount(&state_db, &EvmEnv::default(), &pool.token0, &pool.token1, amount_out)?;
508
509            assert_eq!(amount_in, contract_amount_in, "{}", format!("Missmatch for pool={:?}, amount_out={}", pool_address, amount_out));
510            assert_eq!(gas_used, 100_000);
511        }
512        Ok(())
513    }
514}