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