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