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