kabu_defi_pools/
pancakev3pool.rs

1use crate::state_readers::UniswapV3EvmStateReader;
2use alloy::primitives::aliases::{I24, U24};
3use alloy::primitives::{Address, Bytes, I256, U160, U256};
4use alloy::providers::{Network, Provider};
5use alloy::sol_types::{SolCall, SolInterface};
6use alloy_evm::EvmEnv;
7use eyre::{eyre, ErrReport, OptionExt, Result};
8use kabu_defi_abi::pancake::IPancakeQuoterV2::IPancakeQuoterV2Calls;
9use kabu_defi_abi::pancake::IPancakeV3Pool::slot0Return;
10use kabu_defi_abi::pancake::{IPancakeQuoterV2, IPancakeV3Pool};
11use kabu_defi_abi::uniswap3::IUniswapV3Pool;
12use kabu_defi_abi::uniswap_periphery::ITickLens;
13use kabu_defi_abi::IERC20;
14use kabu_defi_address_book::PeripheryAddress;
15use kabu_evm_db::KabuDBError;
16use kabu_evm_utils::evm_call;
17use kabu_types_entities::required_state::RequiredState;
18use kabu_types_entities::{Pool, PoolAbiEncoder, PoolClass, PoolId, PoolProtocol, PreswapRequirement, SwapDirection};
19use revm::DatabaseRef;
20use std::any::Any;
21use std::fmt::Debug;
22use std::ops::Sub;
23
24#[allow(dead_code)]
25#[derive(Clone, Debug, Default)]
26pub struct Slot0 {
27    pub tick: I24,
28    pub fee_protocol: u32,
29    pub sqrt_price_x96: U160,
30    pub unlocked: bool,
31    pub observation_index: u16,
32    pub observation_cardinality: u16,
33    pub observation_cardinality_next: u16,
34}
35
36impl From<slot0Return> for Slot0 {
37    fn from(value: slot0Return) -> Self {
38        Self {
39            tick: value.tick,
40            fee_protocol: value.feeProtocol,
41            observation_cardinality: value.observationCardinality,
42            observation_cardinality_next: value.observationCardinalityNext,
43            sqrt_price_x96: value.sqrtPriceX96,
44            unlocked: value.unlocked,
45            observation_index: value.observationIndex,
46        }
47    }
48}
49
50#[allow(dead_code)]
51#[derive(Clone)]
52pub struct PancakeV3Pool {
53    //contract_storage : ContractStorage,
54    address: Address,
55    pub token0: Address,
56    pub token1: Address,
57    liquidity0: U256,
58    liquidity1: U256,
59    fee: U24,
60    fee_u32: u32,
61    slot0: Option<Slot0>,
62    factory: Address,
63    protocol: PoolProtocol,
64    encoder: PancakeV3AbiSwapEncoder,
65}
66
67impl PancakeV3Pool {
68    pub fn new(address: Address) -> Self {
69        PancakeV3Pool {
70            address,
71            token0: Address::ZERO,
72            token1: Address::ZERO,
73            liquidity0: U256::ZERO,
74            liquidity1: U256::ZERO,
75            fee: U24::ZERO,
76            fee_u32: 0,
77            slot0: None,
78            factory: Address::ZERO,
79            protocol: PoolProtocol::PancakeV3,
80            encoder: PancakeV3AbiSwapEncoder::new(address),
81        }
82    }
83
84    pub fn get_price_step(fee: u32) -> u32 {
85        match fee {
86            10000 => 200,
87            2500 => 50,
88            500 => 10,
89            100 => 1,
90            _ => 0,
91        }
92    }
93
94    pub fn get_tick_bitmap_index(tick: i32, spacing: u32) -> i16 {
95        let tick_bitmap_index = tick / (spacing as i32);
96
97        if tick_bitmap_index < 0 {
98            (((tick_bitmap_index + 1) / 256) - 1) as i16
99        } else {
100            (tick_bitmap_index >> 8) as i16
101        }
102    }
103
104    pub fn get_price_limit<T: Ord>(token_address_from: &T, token_address_to: &T) -> U160 {
105        if token_address_from.lt(token_address_to) {
106            U160::from(4295128740u64)
107        } else {
108            U160::from_str_radix("1461446703485210103287273052203988822378723970341", 10).unwrap()
109        }
110    }
111
112    pub fn get_zero_for_one(token_address_from: &Address, token_address_to: &Address) -> bool {
113        *token_address_from < *token_address_to
114    }
115
116    fn get_protocol_by_factory(factory_address: Address) -> PoolProtocol {
117        let pancake3_factory: Address = "0x0BFbCF9fa4f9C56B0F40a671Ad40E0805A091865".parse().unwrap();
118        if factory_address == pancake3_factory {
119            PoolProtocol::PancakeV3
120        } else {
121            PoolProtocol::UniswapV3Like
122        }
123    }
124    pub fn fetch_pool_data_evm<DB: DatabaseRef<Error = KabuDBError> + ?Sized>(db: &DB, address: Address) -> Result<Self> {
125        let token0: Address = UniswapV3EvmStateReader::token0(db, address)?;
126        let token1: Address = UniswapV3EvmStateReader::token1(db, address)?;
127        let fee = UniswapV3EvmStateReader::fee(db, address)?;
128        let fee_u32: u32 = fee.to();
129        let factory = UniswapV3EvmStateReader::factory(db, address)?;
130        let protocol = Self::get_protocol_by_factory(factory);
131
132        let ret = PancakeV3Pool {
133            address,
134            token0,
135            token1,
136            liquidity0: Default::default(),
137            liquidity1: Default::default(),
138            fee,
139            fee_u32,
140            slot0: None,
141            factory,
142            protocol,
143            encoder: PancakeV3AbiSwapEncoder::new(address),
144        };
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 uni3_pool = IPancakeV3Pool::IPancakeV3PoolInstance::new(address, client.clone());
151
152        let token0: Address = uni3_pool.token0().call().await?;
153        let token1: Address = uni3_pool.token1().call().await?;
154        let fee = uni3_pool.fee().call().await?;
155        let fee_u32: u32 = fee.to();
156        let slot0 = uni3_pool.slot0().call().await?;
157        let factory: Address = uni3_pool.factory().call().await?;
158
159        let token0_erc20 = IERC20::IERC20Instance::new(token0, client.clone());
160        let token1_erc20 = IERC20::IERC20Instance::new(token1, client.clone());
161
162        let liquidity0: U256 = token0_erc20.balanceOf(address).call().await?;
163        let liquidity1: U256 = token1_erc20.balanceOf(address).call().await?;
164
165        let protocol = PancakeV3Pool::get_protocol_by_factory(factory);
166
167        let ret = PancakeV3Pool {
168            address,
169            token0,
170            token1,
171            fee,
172            fee_u32,
173            slot0: Some(slot0.into()),
174            liquidity0,
175            liquidity1,
176            factory,
177            protocol,
178            encoder: PancakeV3AbiSwapEncoder::new(address),
179        };
180
181        Ok(ret)
182    }
183}
184
185impl Pool for PancakeV3Pool {
186    fn as_any<'a>(&self) -> &dyn Any {
187        self
188    }
189    fn get_class(&self) -> PoolClass {
190        PoolClass::PancakeV3
191    }
192
193    fn get_protocol(&self) -> PoolProtocol {
194        self.protocol
195    }
196
197    fn get_address(&self) -> PoolId {
198        PoolId::Address(self.address)
199    }
200
201    fn get_pool_id(&self) -> PoolId {
202        PoolId::Address(self.address)
203    }
204
205    fn get_fee(&self) -> U256 {
206        U256::from(self.fee)
207    }
208
209    fn get_tokens(&self) -> Vec<Address> {
210        vec![self.token0, self.token1]
211    }
212
213    fn get_swap_directions(&self) -> Vec<SwapDirection> {
214        vec![(self.token0, self.token1).into(), (self.token1, self.token0).into()]
215    }
216
217    fn calculate_out_amount(
218        &self,
219        db: &dyn DatabaseRef<Error = KabuDBError>,
220        token_address_from: &Address,
221        token_address_to: &Address,
222        in_amount: U256,
223    ) -> Result<(U256, u64), ErrReport> {
224        let call_data = IPancakeQuoterV2Calls::quoteExactInputSingle(IPancakeQuoterV2::quoteExactInputSingleCall {
225            params: IPancakeQuoterV2::QuoteExactInputSingleParams {
226                tokenIn: *token_address_from,
227                tokenOut: *token_address_to,
228                amountIn: in_amount,
229                fee: self.fee,
230                sqrtPriceLimitX96: PancakeV3Pool::get_price_limit(token_address_from, token_address_to),
231            },
232        })
233        .abi_encode();
234
235        let (value, gas_used, _) = evm_call(db, EvmEnv::default(), PeripheryAddress::PANCAKE_V3_QUOTER, call_data)?;
236
237        let ret = IPancakeQuoterV2::quoteExactInputSingleCall::abi_decode_returns(&value)?;
238
239        if ret.amountOut.is_zero() {
240            Err(eyre!("ZERO_OUT_AMOUNT"))
241        } else {
242            Ok((ret.amountOut - U256::from(1), gas_used))
243        }
244    }
245
246    fn calculate_in_amount(
247        &self,
248        db: &dyn DatabaseRef<Error = KabuDBError>,
249        token_address_from: &Address,
250        token_address_to: &Address,
251        out_amount: U256,
252    ) -> Result<(U256, u64), ErrReport> {
253        let call_data = IPancakeQuoterV2Calls::quoteExactOutputSingle(IPancakeQuoterV2::quoteExactOutputSingleCall {
254            params: IPancakeQuoterV2::QuoteExactOutputSingleParams {
255                tokenIn: *token_address_from,
256                tokenOut: *token_address_to,
257                amount: out_amount,
258                fee: self.fee,
259                sqrtPriceLimitX96: PancakeV3Pool::get_price_limit(token_address_from, token_address_to),
260            },
261        })
262        .abi_encode();
263
264        let (value, gas_used, _) = evm_call(db, EvmEnv::default(), PeripheryAddress::PANCAKE_V3_QUOTER, call_data)?;
265
266        let ret = IPancakeQuoterV2::quoteExactOutputSingleCall::abi_decode_returns(&value)?;
267
268        if ret.amountIn.is_zero() {
269            Err(eyre!("ZERO_IN_AMOUNT"))
270        } else {
271            Ok((ret.amountIn + U256::from(1), gas_used))
272        }
273    }
274
275    fn can_flash_swap(&self) -> bool {
276        true
277    }
278
279    fn can_calculate_in_amount(&self) -> bool {
280        true
281    }
282
283    fn get_abi_encoder(&self) -> Option<&dyn PoolAbiEncoder> {
284        Some(&self.encoder)
285    }
286
287    fn get_read_only_cell_vec(&self) -> Vec<U256> {
288        vec![U256::from(0x10008)]
289    }
290
291    fn get_state_required(&self) -> Result<RequiredState> {
292        let tick = self.slot0.as_ref().ok_or_eyre("SLOT0_NOT_SET")?.tick;
293        let price_step = PancakeV3Pool::get_price_step(self.fee_u32);
294        if price_step == 0 {
295            return Err(eyre!("BAD_PRICE_STEP"));
296        }
297        let tick_bitmap_index = PancakeV3Pool::get_tick_bitmap_index(tick.as_i32(), PancakeV3Pool::get_price_step(self.fee_u32));
298
299        let quoter_swap_0_1_call = IPancakeQuoterV2Calls::quoteExactInputSingle(IPancakeQuoterV2::quoteExactInputSingleCall {
300            params: IPancakeQuoterV2::QuoteExactInputSingleParams {
301                tokenIn: self.token0,
302                tokenOut: self.token1,
303                amountIn: self.liquidity0 / U256::from(100),
304                fee: self.fee,
305                sqrtPriceLimitX96: PancakeV3Pool::get_price_limit(&self.token0, &self.token1),
306            },
307        })
308        .abi_encode();
309
310        let quoter_swap_1_0_call = IPancakeQuoterV2Calls::quoteExactInputSingle(IPancakeQuoterV2::quoteExactInputSingleCall {
311            params: IPancakeQuoterV2::QuoteExactInputSingleParams {
312                tokenIn: self.token1,
313                tokenOut: self.token0,
314                amountIn: self.liquidity1 / U256::from(100),
315                fee: self.fee,
316                sqrtPriceLimitX96: PancakeV3Pool::get_price_limit(&self.token1, &self.token0),
317            },
318        })
319        .abi_encode();
320
321        let pool_address = self.address;
322
323        let mut state_required = RequiredState::new();
324        state_required
325            .add_call(self.address, IUniswapV3Pool::IUniswapV3PoolCalls::slot0(IUniswapV3Pool::slot0Call {}).abi_encode())
326            .add_call(self.address, IUniswapV3Pool::IUniswapV3PoolCalls::liquidity(IUniswapV3Pool::liquidityCall {}).abi_encode())
327            .add_call(
328                PeripheryAddress::PANCAKE_V3_TICK_LENS,
329                ITickLens::ITickLensCalls::getPopulatedTicksInWord(ITickLens::getPopulatedTicksInWordCall {
330                    pool: pool_address,
331                    tickBitmapIndex: tick_bitmap_index - 4,
332                })
333                .abi_encode(),
334            )
335            .add_call(
336                PeripheryAddress::PANCAKE_V3_TICK_LENS,
337                ITickLens::ITickLensCalls::getPopulatedTicksInWord(ITickLens::getPopulatedTicksInWordCall {
338                    pool: pool_address,
339                    tickBitmapIndex: tick_bitmap_index - 3,
340                })
341                .abi_encode(),
342            )
343            .add_call(
344                PeripheryAddress::PANCAKE_V3_TICK_LENS,
345                ITickLens::ITickLensCalls::getPopulatedTicksInWord(ITickLens::getPopulatedTicksInWordCall {
346                    pool: pool_address,
347                    tickBitmapIndex: tick_bitmap_index - 2,
348                })
349                .abi_encode(),
350            )
351            .add_call(
352                PeripheryAddress::PANCAKE_V3_TICK_LENS,
353                ITickLens::ITickLensCalls::getPopulatedTicksInWord(ITickLens::getPopulatedTicksInWordCall {
354                    pool: pool_address,
355                    tickBitmapIndex: tick_bitmap_index - 1,
356                })
357                .abi_encode(),
358            )
359            .add_call(
360                PeripheryAddress::PANCAKE_V3_TICK_LENS,
361                ITickLens::ITickLensCalls::getPopulatedTicksInWord(ITickLens::getPopulatedTicksInWordCall {
362                    pool: pool_address,
363                    tickBitmapIndex: tick_bitmap_index,
364                })
365                .abi_encode(),
366            )
367            .add_call(
368                PeripheryAddress::PANCAKE_V3_TICK_LENS,
369                ITickLens::ITickLensCalls::getPopulatedTicksInWord(ITickLens::getPopulatedTicksInWordCall {
370                    pool: pool_address,
371                    tickBitmapIndex: tick_bitmap_index + 1,
372                })
373                .abi_encode(),
374            )
375            .add_call(
376                PeripheryAddress::PANCAKE_V3_TICK_LENS,
377                ITickLens::ITickLensCalls::getPopulatedTicksInWord(ITickLens::getPopulatedTicksInWordCall {
378                    pool: pool_address,
379                    tickBitmapIndex: tick_bitmap_index + 2,
380                })
381                .abi_encode(),
382            )
383            .add_call(
384                PeripheryAddress::PANCAKE_V3_TICK_LENS,
385                ITickLens::ITickLensCalls::getPopulatedTicksInWord(ITickLens::getPopulatedTicksInWordCall {
386                    pool: pool_address,
387                    tickBitmapIndex: tick_bitmap_index + 3,
388                })
389                .abi_encode(),
390            )
391            .add_call(
392                PeripheryAddress::PANCAKE_V3_TICK_LENS,
393                ITickLens::ITickLensCalls::getPopulatedTicksInWord(ITickLens::getPopulatedTicksInWordCall {
394                    pool: pool_address,
395                    tickBitmapIndex: tick_bitmap_index + 4,
396                })
397                .abi_encode(),
398            )
399            .add_call(PeripheryAddress::PANCAKE_V3_QUOTER, quoter_swap_0_1_call)
400            .add_call(PeripheryAddress::PANCAKE_V3_QUOTER, quoter_swap_1_0_call)
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        Ok(state_required)
408    }
409
410    fn is_native(&self) -> bool {
411        false
412    }
413
414    fn preswap_requirement(&self) -> PreswapRequirement {
415        PreswapRequirement::Callback
416    }
417}
418
419#[allow(dead_code)]
420#[derive(Clone, Copy)]
421struct PancakeV3AbiSwapEncoder {
422    pool_address: Address,
423}
424
425impl PancakeV3AbiSwapEncoder {
426    pub fn new(pool_address: Address) -> Self {
427        Self { pool_address }
428    }
429}
430
431impl PoolAbiEncoder for PancakeV3AbiSwapEncoder {
432    fn encode_swap_in_amount_provided(
433        &self,
434        token_from_address: Address,
435        token_to_address: Address,
436        amount: U256,
437        recipient: Address,
438        payload: Bytes,
439    ) -> Result<Bytes> {
440        let zero_for_one = PancakeV3Pool::get_zero_for_one(&token_from_address, &token_to_address);
441        let sqrt_price_limit_x96 = PancakeV3Pool::get_price_limit(&token_from_address, &token_to_address);
442        let swap_call = IUniswapV3Pool::swapCall {
443            recipient,
444            zeroForOne: zero_for_one,
445            sqrtPriceLimitX96: sqrt_price_limit_x96,
446            amountSpecified: I256::from_raw(amount),
447            data: payload,
448        };
449
450        Ok(Bytes::from(IUniswapV3Pool::IUniswapV3PoolCalls::swap(swap_call).abi_encode()))
451    }
452
453    fn encode_swap_out_amount_provided(
454        &self,
455        token_from_address: Address,
456        token_to_address: Address,
457        amount: U256,
458        recipient: Address,
459        payload: Bytes,
460    ) -> Result<Bytes> {
461        let zero_for_one = PancakeV3Pool::get_zero_for_one(&token_from_address, &token_to_address);
462        let sqrt_price_limit_x96 = PancakeV3Pool::get_price_limit(&token_from_address, &token_to_address);
463        let swap_call = IUniswapV3Pool::swapCall {
464            recipient,
465            zeroForOne: zero_for_one,
466            sqrtPriceLimitX96: sqrt_price_limit_x96,
467            amountSpecified: I256::ZERO.sub(I256::from_raw(amount)),
468            data: payload,
469        };
470
471        Ok(Bytes::from(IUniswapV3Pool::IUniswapV3PoolCalls::swap(swap_call).abi_encode()))
472    }
473
474    fn swap_in_amount_offset(&self, _token_from_address: Address, _token_to_address: Address) -> Option<u32> {
475        Some(0x44)
476    }
477
478    fn swap_out_amount_offset(&self, _token_from_address: Address, _token_to_address: Address) -> Option<u32> {
479        Some(0x44)
480    }
481
482    fn swap_in_amount_return_offset(&self, token_from_address: Address, token_to_address: Address) -> Option<u32> {
483        if token_from_address < token_to_address {
484            Some(0x20)
485        } else {
486            Some(0x0)
487        }
488    }
489
490    fn swap_in_amount_return_script(&self, _token_from_address: Address, _token_to_address: Address) -> Option<Bytes> {
491        Some(Bytes::from(vec![0x8, 0x2A, 0x00]))
492    }
493}
494
495#[cfg(test)]
496mod tests {
497    use env_logger::Env as EnvLog;
498    use kabu_evm_db::KabuDBType;
499    use kabu_node_debug_provider::AnvilDebugProviderFactory;
500    use kabu_types_blockchain::KabuDataTypesEthereum;
501    use kabu_types_entities::required_state::RequiredStateReader;
502    use kabu_types_entities::MarketState;
503    use revm::database::CacheDB;
504    use std::env;
505    use tracing::debug;
506
507    use super::*;
508
509    #[tokio::test]
510    async fn test_pool() {
511        let _ = env_logger::try_init_from_env(EnvLog::default().default_filter_or("info"));
512        dotenvy::from_filename(".env.test").ok();
513        let node_url = env::var("MAINNET_WS").unwrap();
514
515        let client = AnvilDebugProviderFactory::from_node_on_block(node_url, 19931897).await.unwrap();
516
517        //let weth_address : Address = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2".parse().unwrap();
518        //let usdc_address : Address = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48".parse().unwrap();
519        //let pool_address : Address = "0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640".parse().unwrap();
520
521        //let pool_address : Address = "0xCAD4b51069a150a77D3a1d381d2D768769F7D195".parse().unwrap();
522        //let pool_address : Address = "0x1ac1A8FEaAEa1900C4166dEeed0C11cC10669D36".parse().unwrap();
523        //let pool_address : Address = "0x7ca3EdB2c8fb3e657E282e67F4008d658aA161D2".parse().unwrap();
524        let pool_address: Address = "0x9b5699d18dff51fc65fb8ad6f70d93287c36349f".parse().unwrap();
525
526        let pool = PancakeV3Pool::fetch_pool_data(client.clone(), pool_address).await.unwrap();
527
528        let state_required = pool.get_state_required().unwrap();
529        debug!("{:?}", state_required);
530
531        let state_update =
532            RequiredStateReader::<KabuDataTypesEthereum>::fetch_calls_and_slots(client.clone(), state_required, None).await.unwrap();
533
534        let mut market_state = MarketState::new(KabuDBType::default());
535
536        market_state.state_db.apply_geth_update(state_update);
537
538        let cache_db = CacheDB::new(market_state.state_db);
539
540        let (out_amount, gas_used) =
541            pool.calculate_out_amount(&cache_db, &pool.token0, &pool.token1, U256::from(pool.liquidity0 / U256::from(100))).unwrap();
542
543        debug!("{} {} ", out_amount, gas_used);
544        assert_ne!(out_amount, U256::ZERO);
545        assert!(gas_used > 100_000, "gas used check failed");
546
547        let (out_amount, gas_used) =
548            pool.calculate_out_amount(&cache_db, &pool.token1, &pool.token0, U256::from(pool.liquidity1 / U256::from(100))).unwrap();
549        debug!("{} {} ", out_amount, gas_used);
550        assert_ne!(out_amount, U256::ZERO);
551        assert!(gas_used > 100_000, "gas used check failed");
552    }
553}