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 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 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}