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