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