kabu_defi_pools/
uniswapv3pool.rs

1use std::any::Any;
2use std::fmt::Debug;
3use std::ops::Sub;
4
5use crate::state_readers::UniswapV3QuoterV2StateReader;
6use crate::state_readers::{UniswapV3EvmStateReader, UniswapV3QuoterV2Encoder};
7use crate::virtual_impl::UniswapV3PoolVirtual;
8use alloy::primitives::aliases::U24;
9use alloy::primitives::{Address, Bytes, I256, U160, U256};
10use alloy::providers::{Network, Provider};
11use alloy::sol_types::{SolCall, SolInterface};
12use alloy_evm::EvmEnv;
13use eyre::{OptionExt, Result};
14use kabu_defi_abi::IERC20;
15use kabu_defi_abi::uniswap_periphery::ITickLens;
16use kabu_defi_abi::uniswap3::IUniswapV3Pool;
17use kabu_defi_abi::uniswap3::IUniswapV3Pool::slot0Return;
18use kabu_defi_address_book::{FactoryAddress, PeripheryAddress};
19use kabu_evm_db::KabuDBError;
20use kabu_types_market::{
21    Pool, PoolAbiEncoder, PoolClass, PoolError, PoolId, PoolProtocol, PreswapRequirement, RequiredState, SwapDirection, UniswapV3Error,
22};
23use lazy_static::lazy_static;
24use revm::DatabaseRef;
25use tracing::debug;
26#[cfg(feature = "debug-calculation")]
27use tracing::error;
28
29lazy_static! {
30    static ref U256_ONE: U256 = U256::from(1);
31    static ref LOWER_LIMIT: U160 = U160::from(4295128740u64);
32    static ref UPPER_LIMIT: U160 = U160::from_str_radix("1461446703485210103287273052203988822378723970341", 10).unwrap();
33}
34
35#[allow(dead_code)]
36#[derive(Clone, Debug, Default)]
37pub struct Slot0 {
38    pub tick: i32,
39    pub fee_protocol: u8,
40    pub sqrt_price_x96: U256,
41    pub unlocked: bool,
42    pub observation_index: u16,
43    pub observation_cardinality: u16,
44    pub observation_cardinality_next: u16,
45}
46
47impl From<slot0Return> for Slot0 {
48    fn from(value: slot0Return) -> Self {
49        Self {
50            tick: value.tick.try_into().unwrap_or_default(),
51            fee_protocol: value.feeProtocol,
52            observation_cardinality: value.observationCardinality,
53            observation_cardinality_next: value.observationCardinalityNext,
54            sqrt_price_x96: value.sqrtPriceX96.to(),
55            unlocked: value.unlocked,
56            observation_index: value.observationIndex,
57        }
58    }
59}
60#[allow(dead_code)]
61#[derive(Clone)]
62pub struct UniswapV3Pool {
63    //contract_storage : ContractStorage,
64    address: Address,
65    pub token0: Address,
66    pub token1: Address,
67    pub liquidity: u128,
68    pub fee: u32,
69    pub slot0: Option<Slot0>,
70    liquidity0: U256,
71    liquidity1: U256,
72    factory: Address,
73    protocol: PoolProtocol,
74    encoder: UniswapV3AbiSwapEncoder,
75}
76
77impl UniswapV3Pool {
78    pub fn new(address: Address) -> Self {
79        UniswapV3Pool {
80            address,
81            token0: Address::ZERO,
82            token1: Address::ZERO,
83            liquidity: 0,
84            liquidity0: U256::ZERO,
85            liquidity1: U256::ZERO,
86            fee: 0,
87            slot0: None,
88            factory: Address::ZERO,
89            protocol: PoolProtocol::UniswapV3Like,
90            encoder: UniswapV3AbiSwapEncoder::new(address),
91        }
92    }
93
94    pub fn new_with_data(
95        address: Address,
96        token0: Address,
97        token1: Address,
98        liquidity: u128,
99        fee: u32,
100        slot0: Option<Slot0>,
101        factory: Address,
102    ) -> Self {
103        UniswapV3Pool {
104            address,
105            token0,
106            token1,
107            liquidity,
108            liquidity0: U256::ZERO,
109            liquidity1: U256::ZERO,
110            fee,
111            slot0,
112            factory,
113            protocol: PoolProtocol::UniswapV3Like,
114            encoder: UniswapV3AbiSwapEncoder::new(address),
115        }
116    }
117
118    pub fn tick_spacing(&self) -> u32 {
119        Self::get_price_step(self.fee)
120    }
121
122    pub fn get_price_step(fee: u32) -> u32 {
123        match fee {
124            10000 => 200,
125            3000 => 60,
126            500 => 10,
127            100 => 1,
128            _ => 0,
129        }
130    }
131
132    pub fn get_tick_bitmap_index(tick: i32, spacing: u32) -> i16 {
133        let tick_bitmap_index = tick / (spacing as i32);
134
135        if tick_bitmap_index < 0 { (((tick_bitmap_index + 1) / 256) - 1) as i16 } else { (tick_bitmap_index >> 8) as i16 }
136    }
137
138    pub fn get_price_limit(token_address_from: &Address, token_address_to: &Address) -> U160 {
139        if token_address_from.lt(token_address_to) { *LOWER_LIMIT } else { *UPPER_LIMIT }
140    }
141
142    pub fn get_zero_for_one(token_address_from: &Address, token_address_to: &Address) -> bool {
143        token_address_from.lt(token_address_to)
144    }
145
146    fn get_protocol_by_factory(factory_address: Address) -> PoolProtocol {
147        if factory_address == FactoryAddress::UNISWAP_V3 {
148            PoolProtocol::UniswapV3
149        } else if factory_address == FactoryAddress::SUSHISWAP_V3 {
150            PoolProtocol::SushiswapV3
151        } else {
152            PoolProtocol::UniswapV3Like
153        }
154    }
155
156    pub fn fetch_pool_data_evm<DB: DatabaseRef<Error = KabuDBError> + ?Sized>(db: &DB, evm_env: &EvmEnv, address: Address) -> Result<Self> {
157        let token0 = UniswapV3EvmStateReader::token0(db, evm_env, address)?;
158        let token1 = UniswapV3EvmStateReader::token1(db, evm_env, address)?;
159        let fee: u32 = UniswapV3EvmStateReader::fee(db, evm_env, address)?.to();
160        let liquidity = UniswapV3EvmStateReader::liquidity(db, evm_env, address)?;
161        let factory = UniswapV3EvmStateReader::factory(db, evm_env, address).unwrap_or_default();
162        let protocol = UniswapV3Pool::get_protocol_by_factory(factory);
163
164        let ret = UniswapV3Pool {
165            address,
166            token0,
167            token1,
168            liquidity,
169            liquidity0: Default::default(),
170            liquidity1: Default::default(),
171            fee,
172            slot0: None,
173            factory,
174            protocol,
175            encoder: UniswapV3AbiSwapEncoder { pool_address: address },
176        };
177        debug!("fetch_pool_data_evm {:?} {:?} {} {:?} {}", token0, token1, fee, factory, protocol);
178
179        Ok(ret)
180    }
181
182    pub async fn fetch_pool_data<N: Network, P: Provider<N> + Send + Sync + Clone + 'static>(client: P, address: Address) -> Result<Self> {
183        let uni3_pool = IUniswapV3Pool::IUniswapV3PoolInstance::new(address, client.clone());
184
185        let token0: Address = uni3_pool.token0().call().await?;
186        let token1: Address = uni3_pool.token1().call().await?;
187        let fee: u32 = uni3_pool.fee().call().await?.try_into()?;
188        let liquidity: u128 = uni3_pool.liquidity().call().await?;
189        let slot0 = uni3_pool.slot0().call().await?;
190        let factory: Address = uni3_pool.factory().call().await?;
191
192        let token0_erc20 = IERC20::IERC20Instance::new(token0, client.clone());
193        let token1_erc20 = IERC20::IERC20Instance::new(token1, client.clone());
194
195        let liquidity0: U256 = token0_erc20.balanceOf(address).call().await?;
196        let liquidity1: U256 = token1_erc20.balanceOf(address).call().await?;
197
198        let protocol = UniswapV3Pool::get_protocol_by_factory(factory);
199
200        let ret = UniswapV3Pool {
201            address,
202            token0,
203            token1,
204            fee,
205            liquidity,
206            slot0: Some(slot0.into()),
207            liquidity0,
208            liquidity1,
209            factory,
210            protocol,
211            encoder: UniswapV3AbiSwapEncoder::new(address),
212        };
213
214        Ok(ret)
215    }
216}
217
218impl Pool for UniswapV3Pool {
219    fn as_any<'a>(&self) -> &dyn Any {
220        self
221    }
222    fn get_class(&self) -> PoolClass {
223        PoolClass::UniswapV3
224    }
225
226    fn get_protocol(&self) -> PoolProtocol {
227        self.protocol
228    }
229
230    fn get_address(&self) -> PoolId {
231        PoolId::Address(self.address)
232    }
233    fn get_pool_id(&self) -> PoolId {
234        PoolId::Address(self.address)
235    }
236
237    fn get_fee(&self) -> U256 {
238        U256::from(self.fee)
239    }
240
241    fn get_tokens(&self) -> Vec<Address> {
242        vec![self.token0, self.token1]
243    }
244
245    fn get_swap_directions(&self) -> Vec<SwapDirection> {
246        vec![(self.token0, self.token1).into(), (self.token1, self.token0).into()]
247    }
248
249    fn calculate_out_amount(
250        &self,
251        db: &dyn DatabaseRef<Error = KabuDBError>,
252        evm_env: &EvmEnv,
253        token_address_from: &Address,
254        token_address_to: &Address,
255        in_amount: U256,
256    ) -> Result<(U256, u64), PoolError> {
257        let (ret, gas_used) = if self.get_protocol() == PoolProtocol::UniswapV3 {
258            let ret_virtual = UniswapV3PoolVirtual::simulate_swap_in_amount_provider(db, self, token_address_from, in_amount)?;
259
260            #[cfg(feature = "debug-calculation")]
261            {
262                // TODO check gas limit issue
263                let (ret_evm, gas_used) = UniswapV3QuoterV2StateReader::quote_exact_input(
264                    db,
265                    evm_env,
266                    PeripheryAddress::UNISWAP_V3_QUOTER_V2,
267                    *token_address_from,
268                    *token_address_to,
269                    U24::from(self.fee),
270                    in_amount,
271                )?;
272                println!("calculate_out_amount ret_evm: {ret_evm:?} ret: {ret_virtual:?} gas_used: {gas_used:?}");
273                if ret_virtual != ret_evm {
274                    error!(%ret_virtual, %ret_evm, "calculate_out_amount RETURN_RESULT_IS_INCORRECT");
275                };
276            }
277            (ret_virtual, 150_000)
278        } else {
279            // TODO check gas limit issue
280
281            let (ret_evm, gas_used) = UniswapV3QuoterV2StateReader::quote_exact_input(
282                db,
283                evm_env,
284                PeripheryAddress::UNISWAP_V3_QUOTER_V2,
285                *token_address_from,
286                *token_address_to,
287                U24::from(self.fee),
288                in_amount,
289            )?;
290            (ret_evm, gas_used)
291        };
292
293        if ret.is_zero() {
294            Err(UniswapV3Error::OutAmountIsZero.into())
295        } else {
296            Ok((ret.checked_sub(*U256_ONE).ok_or(UniswapV3Error::UnderflowInCalculation)?, gas_used))
297            // value, gas_used
298        }
299    }
300
301    fn calculate_in_amount(
302        &self,
303        db: &dyn DatabaseRef<Error = KabuDBError>,
304        evm_env: &EvmEnv,
305        token_address_from: &Address,
306        token_address_to: &Address,
307        out_amount: U256,
308    ) -> Result<(U256, u64), PoolError> {
309        let (ret, gas_used) = if self.get_protocol() == PoolProtocol::UniswapV3 {
310            let ret_virtual = UniswapV3PoolVirtual::simulate_swap_out_amount_provided(db, self, token_address_from, out_amount)?;
311
312            #[cfg(feature = "debug-calculation")]
313            {
314                // TODO : Gas limit issue
315                let (ret_evm, gas_used) = UniswapV3QuoterV2StateReader::quote_exact_output(
316                    db,
317                    evm_env,
318                    PeripheryAddress::UNISWAP_V3_QUOTER_V2,
319                    *token_address_from,
320                    *token_address_to,
321                    U24::from(self.fee),
322                    out_amount,
323                )?;
324                println!("calculate_out_amount ret_evm: {ret_evm:?} ret: {ret_virtual:?} gas_used: {gas_used:?}");
325
326                if ret_virtual != ret_evm {
327                    error!(%ret_virtual, %ret_evm,"calculate_in_amount RETURN_RESULT_IS_INCORRECT");
328                }
329            }
330            (ret_virtual, 150000)
331        } else {
332            // TODO : Gas limit issue
333
334            let (ret_evm, gas_used) = UniswapV3QuoterV2StateReader::quote_exact_output(
335                db,
336                evm_env,
337                PeripheryAddress::UNISWAP_V3_QUOTER_V2,
338                *token_address_from,
339                *token_address_to,
340                U24::from(self.fee),
341                out_amount,
342            )?;
343            (ret_evm, gas_used)
344        };
345
346        if ret.is_zero() {
347            Err(UniswapV3Error::InAmountIsZero.into())
348        } else {
349            Ok((ret.checked_add(*U256_ONE).ok_or(UniswapV3Error::OverflowInCalculation)?, gas_used))
350        }
351    }
352
353    fn can_flash_swap(&self) -> bool {
354        true
355    }
356
357    fn can_calculate_in_amount(&self) -> bool {
358        true
359    }
360
361    fn get_abi_encoder(&self) -> Option<&dyn PoolAbiEncoder> {
362        Some(&self.encoder)
363    }
364
365    fn get_read_only_cell_vec(&self) -> Vec<U256> {
366        Vec::new()
367    }
368
369    fn get_state_required(&self) -> Result<RequiredState> {
370        let tick = self.slot0.as_ref().ok_or_eyre("SLOT0_NOT_SET")?.tick;
371        let price_step = UniswapV3Pool::get_price_step(self.fee);
372        let mut state_required = RequiredState::new();
373        if price_step == 0 {
374            return Err(UniswapV3Error::BadPriceStep.into());
375        }
376        let tick_bitmap_index = UniswapV3Pool::get_tick_bitmap_index(tick, price_step);
377
378        //debug!("Fetching state {:?} tick {} tick bitmap index {}", self.address, tick, tick_bitmap_index);
379
380        let balance_call_data = IERC20::IERC20Calls::balanceOf(IERC20::balanceOfCall { account: self.address }).abi_encode();
381
382        let pool_address = self.address;
383
384        state_required
385            .add_call(pool_address, IUniswapV3Pool::IUniswapV3PoolCalls::slot0(IUniswapV3Pool::slot0Call {}).abi_encode())
386            .add_call(pool_address, IUniswapV3Pool::IUniswapV3PoolCalls::liquidity(IUniswapV3Pool::liquidityCall {}).abi_encode());
387
388        for i in -4..=3 {
389            state_required.add_call(
390                PeripheryAddress::UNISWAP_V3_TICK_LENS,
391                ITickLens::ITickLensCalls::getPopulatedTicksInWord(ITickLens::getPopulatedTicksInWordCall {
392                    pool: pool_address,
393                    tickBitmapIndex: tick_bitmap_index + i,
394                })
395                .abi_encode(),
396            );
397        }
398        state_required
399            .add_call(self.token0, balance_call_data.clone())
400            .add_call(self.token1, balance_call_data)
401            .add_slot_range(self.address, U256::from(0), 0x20)
402            .add_empty_slot_range(self.address, U256::from(0x10000), 0x20);
403
404        for token_address in self.get_tokens() {
405            state_required.add_call(token_address, IERC20::balanceOfCall { account: pool_address }.abi_encode());
406        }
407
408        if self.protocol == PoolProtocol::UniswapV3 {
409            let amount = self.liquidity0 / U256::from(100);
410            let price_limit = UniswapV3Pool::get_price_limit(&self.token0, &self.token1);
411            let quoter_swap_0_1_call =
412                UniswapV3QuoterV2Encoder::quote_exact_input_encode(self.token0, self.token1, self.fee.try_into()?, price_limit, amount);
413
414            let price_limit = UniswapV3Pool::get_price_limit(&self.token1, &self.token0);
415            let amount = self.liquidity1 / U256::from(100);
416
417            let quoter_swap_1_0_call =
418                UniswapV3QuoterV2Encoder::quote_exact_input_encode(self.token1, self.token0, self.fee.try_into()?, price_limit, amount);
419
420            // TODO: How about Sushiswap?
421            state_required
422                .add_call(PeripheryAddress::UNISWAP_V3_QUOTER_V2, quoter_swap_0_1_call)
423                .add_call(PeripheryAddress::UNISWAP_V3_QUOTER_V2, quoter_swap_1_0_call);
424        }
425
426        Ok(state_required)
427    }
428
429    fn is_native(&self) -> bool {
430        false
431    }
432
433    fn preswap_requirement(&self) -> PreswapRequirement {
434        PreswapRequirement::Callback
435    }
436}
437
438#[allow(dead_code)]
439#[derive(Clone, Copy)]
440struct UniswapV3AbiSwapEncoder {
441    pool_address: Address,
442}
443
444impl UniswapV3AbiSwapEncoder {
445    pub fn new(pool_address: Address) -> Self {
446        Self { pool_address }
447    }
448}
449
450impl PoolAbiEncoder for UniswapV3AbiSwapEncoder {
451    fn encode_swap_in_amount_provided(
452        &self,
453        token_from_address: Address,
454        token_to_address: Address,
455        amount: U256,
456        recipient: Address,
457        payload: Bytes,
458    ) -> Result<Bytes> {
459        let zero_for_one = UniswapV3Pool::get_zero_for_one(&token_from_address, &token_to_address);
460        let sqrt_price_limit_x96 = UniswapV3Pool::get_price_limit(&token_from_address, &token_to_address);
461        let swap_call = IUniswapV3Pool::swapCall {
462            recipient,
463            zeroForOne: zero_for_one,
464            amountSpecified: I256::from_raw(amount),
465            sqrtPriceLimitX96: sqrt_price_limit_x96,
466            data: payload,
467        };
468
469        Ok(Bytes::from(IUniswapV3Pool::IUniswapV3PoolCalls::swap(swap_call).abi_encode()))
470    }
471
472    fn encode_swap_out_amount_provided(
473        &self,
474        token_from_address: Address,
475        token_to_address: Address,
476        amount: U256,
477        recipient: Address,
478        payload: Bytes,
479    ) -> Result<Bytes> {
480        let zero_for_one = UniswapV3Pool::get_zero_for_one(&token_from_address, &token_to_address);
481        let sqrt_price_limit_x96 = UniswapV3Pool::get_price_limit(&token_from_address, &token_to_address);
482        let swap_call = IUniswapV3Pool::swapCall {
483            recipient,
484            zeroForOne: zero_for_one,
485            amountSpecified: I256::ZERO.sub(I256::from_raw(amount)),
486            sqrtPriceLimitX96: sqrt_price_limit_x96,
487            data: payload,
488        };
489
490        Ok(Bytes::from(IUniswapV3Pool::IUniswapV3PoolCalls::swap(swap_call).abi_encode()))
491    }
492
493    fn swap_in_amount_offset(&self, _token_from_address: Address, _token_to_address: Address) -> Option<u32> {
494        Some(0x44)
495    }
496
497    fn swap_out_amount_offset(&self, _token_from_address: Address, _token_to_address: Address) -> Option<u32> {
498        Some(0x44)
499    }
500
501    fn swap_in_amount_return_offset(&self, token_from_address: Address, token_to_address: Address) -> Option<u32> {
502        if token_from_address < token_to_address { Some(0x20) } else { Some(0x0) }
503    }
504
505    fn swap_in_amount_return_script(&self, _token_from_address: Address, _token_to_address: Address) -> Option<Bytes> {
506        Some(Bytes::from(vec![0x8, 0x2A, 0x00]))
507    }
508}
509
510// The test are using the deployed contracts for comparison to allow to adjust the test easily
511#[cfg(test)]
512mod test {
513    use super::*;
514    use alloy::primitives::{BlockNumber, address};
515    use alloy::rpc::types::{BlockId, BlockNumberOrTag};
516    use kabu_defi_abi::uniswap_periphery::IQuoterV2;
517    use kabu_defi_abi::uniswap_periphery::IQuoterV2::{QuoteExactInputSingleParams, QuoteExactOutputSingleParams};
518    use kabu_defi_address_book::{PeripheryAddress, UniswapV3PoolAddress};
519    use kabu_evm_db::{AlloyDB, KabuDB};
520    use kabu_evm_db::{KabuDBError, KabuDBType};
521    use kabu_node_debug_provider::{AnvilDebugProviderFactory, AnvilDebugProviderType};
522    use kabu_types_market::RequiredStateReader;
523    use revm::database::EmptyDBTyped;
524    use std::env;
525    use std::ops::Add;
526
527    const POOL_ADDRESSES: [Address; 4] = [
528        address!("15153da0e9e13cfc167b3d417d3721bf545479bb"), // Neiro/WETH pool 3000
529        UniswapV3PoolAddress::USDC_WETH_3000,                 // USDC/WETH pool
530        UniswapV3PoolAddress::WETH_USDT_3000,                 // WETH/USDT pool
531        address!("11950d141ecb863f01007add7d1a342041227b58"), // PEPE/WETH pool 3000
532    ];
533    const BLOCK_NUMBER: u64 = 20935488u64;
534
535    #[tokio::test]
536    async fn test_pool_tokens() -> Result<()> {
537        dotenvy::from_filename(".env.test").ok();
538        let node_url = env::var("MAINNET_WS")?;
539        let client = AnvilDebugProviderFactory::from_node_on_block(node_url, BlockNumber::from(BLOCK_NUMBER)).await?;
540
541        for pool_address in POOL_ADDRESSES {
542            let pool_contract = IUniswapV3Pool::new(pool_address, client.clone());
543            let token0 = pool_contract.token0().call().await?;
544            let token1 = pool_contract.token1().call().await?;
545
546            let pool = UniswapV3Pool::fetch_pool_data(client.clone(), pool_address).await?;
547
548            assert_eq!(token0, pool.token0);
549            assert_eq!(token1, pool.token1);
550        }
551
552        Ok(())
553    }
554
555    async fn fetch_original_contract_amounts(
556        client: AnvilDebugProviderType,
557        pool_address: Address,
558        token_in: Address,
559        token_out: Address,
560        amount: U256,
561        block_number: u64,
562        is_amount_out: bool,
563    ) -> Result<U256> {
564        let router_contract = IQuoterV2::new(PeripheryAddress::UNISWAP_V3_QUOTER_V2, client.clone());
565        let pool_contract = IUniswapV3Pool::new(pool_address, client.clone());
566        let pool_fee = pool_contract.fee().call().block(BlockId::from(block_number)).await?;
567
568        if is_amount_out {
569            let contract_amount_out = router_contract
570                .quoteExactInputSingle(QuoteExactInputSingleParams {
571                    tokenIn: token_in,
572                    tokenOut: token_out,
573                    amountIn: amount,
574                    fee: pool_fee,
575                    sqrtPriceLimitX96: U160::ZERO,
576                })
577                .call()
578                .block(BlockId::from(block_number))
579                .await?;
580            Ok(contract_amount_out.amountOut.sub(U256::from(1)))
581        } else {
582            let contract_amount_in = router_contract
583                .quoteExactOutputSingle(QuoteExactOutputSingleParams {
584                    tokenIn: token_in,
585                    tokenOut: token_out,
586                    amount,
587                    fee: pool_fee,
588                    sqrtPriceLimitX96: U160::ZERO,
589                })
590                .call()
591                .block(BlockId::from(block_number))
592                .await?;
593            Ok(contract_amount_in.amountIn.add(U256::from(1)))
594        }
595    }
596
597    #[ignore]
598    #[tokio::test]
599    async fn test_calculate_out_amount() -> Result<()> {
600        // Verify that the calculated out amount is the same as the contract's out amount
601        dotenvy::from_filename(".env.test").ok();
602        let node_url = env::var("MAINNET_WS")?;
603        let client = AnvilDebugProviderFactory::from_node_on_block(node_url, BlockNumber::from(BLOCK_NUMBER)).await?;
604
605        for pool_address in POOL_ADDRESSES {
606            let pool = UniswapV3Pool::fetch_pool_data(client.clone(), pool_address).await?;
607            let state_required = pool.get_state_required()?;
608            let state_update = RequiredStateReader::fetch_calls_and_slots(client.clone(), state_required, Some(BLOCK_NUMBER)).await?;
609
610            let mut state_db = KabuDBType::default();
611            state_db.apply_geth_update(state_update);
612
613            let token0_decimals = IERC20::new(pool.token0, client.clone()).decimals().call().block(BlockId::from(BLOCK_NUMBER)).await?;
614            let token1_decimals = IERC20::new(pool.token1, client.clone()).decimals().call().block(BlockId::from(BLOCK_NUMBER)).await?;
615
616            //// CASE: token0 -> token1
617            let amount_in = U256::from(10u64).pow(token0_decimals);
618            let contract_amount_out =
619                fetch_original_contract_amounts(client.clone(), pool_address, pool.token0, pool.token1, amount_in, BLOCK_NUMBER, true)
620                    .await?;
621
622            // under test
623            let (amount_out, gas_used) =
624                match pool.calculate_out_amount(&state_db, &EvmEnv::default(), &pool.token0, &pool.token1, amount_in) {
625                    Ok((amount_out, gas_used)) => (amount_out, gas_used),
626                    Err(e) => {
627                        panic!("Calculation error for pool={pool_address:?}, amount_in={amount_in}, e={e:?}");
628                    }
629                };
630            assert_eq!(
631                amount_out, contract_amount_out,
632                "Mismatch for pool={:?}, token_out={}, amount_in={}",
633                pool_address, &pool.token1, amount_in
634            );
635            assert_eq!(gas_used, 150_000);
636
637            //// CASE: token1 -> token0
638            let amount_in = U256::from(10u64).pow(token1_decimals);
639            let contract_amount_out =
640                fetch_original_contract_amounts(client.clone(), pool_address, pool.token1, pool.token0, amount_in, BLOCK_NUMBER, true)
641                    .await?;
642
643            // under test
644            let (amount_out, gas_used) =
645                match pool.calculate_out_amount(&state_db, &EvmEnv::default(), &pool.token1, &pool.token0, amount_in) {
646                    Ok((amount_out, gas_used)) => (amount_out, gas_used),
647                    Err(e) => {
648                        panic!("Calculation error for pool={pool_address:?}, amount_in={amount_in}, e={e:?}");
649                    }
650                };
651            assert_eq!(
652                amount_out, contract_amount_out,
653                "Mismatch for pool={:?}, token_out={}, amount_in={}",
654                pool_address, &pool.token0, amount_in
655            );
656            assert_eq!(gas_used, 150_000);
657        }
658
659        Ok(())
660    }
661
662    #[ignore]
663    #[tokio::test]
664    async fn test_calculate_in_amount() -> Result<()> {
665        // Verify that the calculated out amount is the same as the contract's out amount
666        dotenvy::from_filename(".env.test").ok();
667        let node_url = env::var("MAINNET_WS")?;
668        let client = AnvilDebugProviderFactory::from_node_on_block(node_url, BlockNumber::from(BLOCK_NUMBER)).await?;
669
670        for pool_address in POOL_ADDRESSES {
671            let pool = UniswapV3Pool::fetch_pool_data(client.clone(), pool_address).await?;
672            let state_required = pool.get_state_required()?;
673            let state_update = RequiredStateReader::fetch_calls_and_slots(client.clone(), state_required, Some(BLOCK_NUMBER)).await?;
674
675            let mut state_db = KabuDBType::default().with_ext_db(EmptyDBTyped::<KabuDBError>::new());
676            state_db.apply_geth_update(state_update);
677
678            let token0_decimals = IERC20::new(pool.token0, client.clone()).decimals().call().block(BlockId::from(BLOCK_NUMBER)).await?;
679            let token1_decimals = IERC20::new(pool.token1, client.clone()).decimals().call().block(BlockId::from(BLOCK_NUMBER)).await?;
680
681            //// CASE: token0 -> token1
682            let amount_out = U256::from(10u64).pow(token1_decimals);
683            let contract_amount_in =
684                fetch_original_contract_amounts(client.clone(), pool_address, pool.token0, pool.token1, amount_out, BLOCK_NUMBER, false)
685                    .await?;
686
687            // under test
688            let (amount_in, gas_used) =
689                match pool.calculate_in_amount(&state_db, &EvmEnv::default(), &pool.token0, &pool.token1, amount_out) {
690                    Ok((amount_in, gas_used)) => (amount_in, gas_used),
691                    Err(e) => {
692                        panic!("Calculation error for pool={pool_address:?}, amount_out={amount_out}, e={e:?}");
693                    }
694                };
695            assert_eq!(
696                amount_in, contract_amount_in,
697                "Mismatch for pool={:?}, token_in={:?}, amount_out={}",
698                pool_address, &pool.token0, amount_out
699            );
700            assert_eq!(gas_used, 150_000);
701
702            //// CASE: token1 -> token0
703            let amount_out = U256::from(10u64).pow(token0_decimals);
704            let contract_amount_in =
705                fetch_original_contract_amounts(client.clone(), pool_address, pool.token1, pool.token0, amount_out, BLOCK_NUMBER, false)
706                    .await?;
707
708            // under test
709            let (amount_in, gas_used) =
710                match pool.calculate_in_amount(&state_db, &EvmEnv::default(), &pool.token1, &pool.token0, amount_out) {
711                    Ok((amount_in, gas_used)) => (amount_in, gas_used),
712                    Err(e) => {
713                        panic!("Calculation error for pool={pool_address:?}, amount_out={amount_out}, e={e:?}");
714                    }
715                };
716            assert_eq!(
717                amount_in, contract_amount_in,
718                "Mismatch for pool={:?}, token_in={:?}, amount_out={}",
719                pool_address, &pool.token1, amount_out
720            );
721            assert_eq!(gas_used, 150_000);
722        }
723
724        Ok(())
725    }
726
727    #[ignore]
728    #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
729    async fn test_calculate_in_amount_with_ext_db() -> Result<()> {
730        // Verify that the calculated out amount is the same as the contract's out amount
731        dotenvy::from_filename(".env.test").ok();
732        let node_url = env::var("MAINNET_WS")?;
733        let client = AnvilDebugProviderFactory::from_node_on_block(node_url, BlockNumber::from(BLOCK_NUMBER)).await?;
734
735        for pool_address in POOL_ADDRESSES {
736            let pool = UniswapV3Pool::fetch_pool_data(client.clone(), pool_address).await?;
737
738            let alloy_db = AlloyDB::new(client.clone(), BlockNumberOrTag::Number(BLOCK_NUMBER).into()).unwrap();
739
740            let state_db = KabuDB::new().with_ext_db(alloy_db);
741
742            let token0_decimals = IERC20::new(pool.token0, client.clone()).decimals().call().block(BlockId::from(BLOCK_NUMBER)).await?;
743            let token1_decimals = IERC20::new(pool.token1, client.clone()).decimals().call().block(BlockId::from(BLOCK_NUMBER)).await?;
744
745            //// CASE: token0 -> token1
746            let amount_out = U256::from(10u64).pow(token1_decimals);
747            let contract_amount_in =
748                fetch_original_contract_amounts(client.clone(), pool_address, pool.token0, pool.token1, amount_out, BLOCK_NUMBER, false)
749                    .await?;
750
751            // under test
752            let (amount_in, gas_used) =
753                match pool.calculate_in_amount(&state_db, &EvmEnv::default(), &pool.token0, &pool.token1, amount_out) {
754                    Ok((amount_in, gas_used)) => (amount_in, gas_used),
755                    Err(e) => {
756                        panic!("Calculation error for pool={pool_address:?}, amount_out={amount_out}, e={e:?}");
757                    }
758                };
759            assert_eq!(
760                amount_in, contract_amount_in,
761                "Mismatch for pool={:?}, token_in={:?}, amount_out={}",
762                pool_address, &pool.token0, amount_out
763            );
764            assert_eq!(gas_used, 150_000);
765
766            //// CASE: token1 -> token0
767            let amount_out = U256::from(10u64).pow(token0_decimals);
768            let contract_amount_in =
769                fetch_original_contract_amounts(client.clone(), pool_address, pool.token1, pool.token0, amount_out, BLOCK_NUMBER, false)
770                    .await?;
771
772            // under test
773            let (amount_in, gas_used) =
774                match pool.calculate_in_amount(&state_db, &EvmEnv::default(), &pool.token1, &pool.token0, amount_out) {
775                    Ok((amount_in, gas_used)) => (amount_in, gas_used),
776                    Err(e) => {
777                        panic!("Calculation error for pool={pool_address:?}, amount_out={amount_out}, e={e:?}");
778                    }
779                };
780            assert_eq!(
781                amount_in, contract_amount_in,
782                "Mismatch for pool={:?}, token_in={:?}, amount_out={}",
783                pool_address, &pool.token1, amount_out
784            );
785            assert_eq!(gas_used, 150_000);
786        }
787
788        Ok(())
789    }
790}