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