1use crate::state_readers::UniswapV3EvmStateReader;
2use alloy::primitives::{Address, Bytes, U128, U256};
3use alloy::providers::{Network, Provider};
4use alloy::sol_types::{SolCall, SolInterface};
5use alloy_evm::EvmEnv;
6use eyre::{eyre, ErrReport, OptionExt, Result};
7use kabu_defi_abi::maverick::IMaverickPool::{getStateCall, IMaverickPoolCalls, IMaverickPoolInstance};
8use kabu_defi_abi::maverick::IMaverickQuoter::{calculateSwapCall, IMaverickQuoterCalls};
9use kabu_defi_abi::maverick::{IMaverickPool, IMaverickQuoter, State};
10use kabu_defi_abi::IERC20;
11use kabu_defi_address_book::PeripheryAddress;
12use kabu_evm_db::KabuDBError;
13use kabu_evm_utils::evm_call;
14use kabu_types_entities::required_state::RequiredState;
15use kabu_types_entities::{Pool, PoolAbiEncoder, PoolClass, PoolId, PoolProtocol, PreswapRequirement, SwapDirection};
16use lazy_static::lazy_static;
17use revm::DatabaseRef;
18use std::any::Any;
19use tracing::error;
20
21lazy_static! {
22 static ref U256_ONE: U256 = U256::from(1);
23}
24
25#[allow(dead_code)]
26#[derive(Clone)]
27pub struct MaverickPool {
28 address: Address,
30 pub token0: Address,
31 pub token1: Address,
32 liquidity0: U256,
33 liquidity1: U256,
34 fee: U256,
35 spacing: u32,
36 slot0: Option<State>,
37 factory: Address,
38 protocol: PoolProtocol,
39 encoder: MaverickAbiSwapEncoder,
40}
41
42impl MaverickPool {
43 pub fn new(address: Address) -> Self {
44 MaverickPool {
45 address,
46 token0: Address::ZERO,
47 token1: Address::ZERO,
48 liquidity0: U256::ZERO,
49 liquidity1: U256::ZERO,
50 fee: U256::ZERO,
51 spacing: 0,
52 slot0: None,
53 factory: Address::ZERO,
54 protocol: PoolProtocol::Maverick,
55 encoder: MaverickAbiSwapEncoder::new(address),
56 }
57 }
58
59 pub fn get_tick_bitmap_index(tick: i32, spacing: u32) -> i32 {
60 let tick_bitmap_index = tick / (spacing as i32);
61
62 if tick_bitmap_index < 0 {
63 ((tick_bitmap_index + 1) / 256) - 1
64 } else {
65 tick_bitmap_index >> 8
66 }
67 }
68
69 pub fn get_price_limit(token_address_from: &Address, token_address_to: &Address) -> U256 {
70 if *token_address_from < *token_address_to {
71 U256::from(4295128740u64)
72 } else {
73 U256::from_str_radix("1461446703485210103287273052203988822378723970341", 10).unwrap()
74 }
75 }
76
77 pub fn get_zero_for_one<T: Ord>(token_address_from: &T, token_address_to: &T) -> bool {
78 token_address_from.lt(token_address_to)
79 }
80
81 fn get_protocol_by_factory(_factory_address: Address) -> PoolProtocol {
82 PoolProtocol::Maverick
83 }
84
85 pub async fn fetch_pool_data<N: Network, P: Provider<N> + Send + Sync + Clone + 'static>(client: P, address: Address) -> Result<Self> {
86 let pool = IMaverickPoolInstance::new(address, client.clone());
87
88 let token0: Address = pool.tokenA().call().await?;
89 let token1: Address = pool.tokenB().call().await?;
90 let fee: U256 = pool.fee().call().await?;
91 let slot0 = pool.getState().call().await?;
92 let factory: Address = pool.factory().call().await?;
93 let spacing: u32 = pool.tickSpacing().call().await?.to();
94
95 let token0_erc20 = IERC20::IERC20Instance::new(token0, client.clone());
96 let token1_erc20 = IERC20::IERC20Instance::new(token1, client.clone());
97
98 let liquidity0: U256 = token0_erc20.balanceOf(address).call().await?;
99 let liquidity1: U256 = token1_erc20.balanceOf(address).call().await?;
100
101 let protocol = MaverickPool::get_protocol_by_factory(factory);
102
103 let ret = MaverickPool {
104 address,
105 token0,
106 token1,
107 fee,
108 slot0: Some(slot0),
109 liquidity0,
110 liquidity1,
111 factory,
112 protocol,
113 spacing,
114 encoder: MaverickAbiSwapEncoder { pool_address: address },
115 };
116
117 Ok(ret)
118 }
119 pub fn fetch_pool_data_evm<DB: DatabaseRef<Error = KabuDBError> + ?Sized>(db: &DB, address: Address) -> Result<Self> {
120 let token0: Address = UniswapV3EvmStateReader::token0(db, address)?;
121 let token1: Address = UniswapV3EvmStateReader::token1(db, address)?;
122 let fee = UniswapV3EvmStateReader::fee(db, address)?;
123 let factory: Address = UniswapV3EvmStateReader::factory(db, address)?;
124 let spacing: u32 = UniswapV3EvmStateReader::tick_spacing(db, address)?;
125
126 let protocol = Self::get_protocol_by_factory(factory);
127
128 let ret = MaverickPool {
129 address,
130 token0,
131 token1,
132 liquidity0: Default::default(),
133 liquidity1: Default::default(),
134 fee: U256::from(fee),
135 spacing,
136 slot0: None,
137 factory,
138 protocol,
139 encoder: MaverickAbiSwapEncoder { pool_address: address },
140 };
141
142 Ok(ret)
143 }
144}
145
146impl Pool for MaverickPool {
147 fn as_any<'a>(&self) -> &dyn Any {
148 self
149 }
150
151 fn get_class(&self) -> PoolClass {
152 PoolClass::Maverick
153 }
154
155 fn get_protocol(&self) -> PoolProtocol {
156 self.protocol
157 }
158
159 fn get_address(&self) -> PoolId {
160 PoolId::Address(self.address)
161 }
162
163 fn get_pool_id(&self) -> PoolId {
164 PoolId::Address(self.address)
165 }
166
167 fn get_fee(&self) -> U256 {
168 self.fee
169 }
170
171 fn get_tokens(&self) -> Vec<Address> {
172 vec![self.token0, self.token1]
173 }
174
175 fn get_swap_directions(&self) -> Vec<SwapDirection> {
176 vec![(self.token0, self.token1).into(), (self.token1, self.token0).into()]
177 }
178
179 fn calculate_out_amount(
180 &self,
181 db: &dyn DatabaseRef<Error = KabuDBError>,
182 token_address_from: &Address,
183 token_address_to: &Address,
184 in_amount: U256,
185 ) -> Result<(U256, u64), ErrReport> {
186 if in_amount >= U256::from(U128::MAX) {
187 error!("IN_AMOUNT_EXCEEDS_MAX {}", self.address.to_checksum(None));
188 return Err(eyre!("IN_AMOUNT_EXCEEDS_MAX"));
189 }
190
191 let token_a_in = MaverickPool::get_zero_for_one(token_address_from, token_address_to);
192 let call_data_vec = IMaverickQuoterCalls::calculateSwap(calculateSwapCall {
195 pool: self.address,
196 amount: in_amount.to(),
197 tokenAIn: token_a_in,
198 exactOutput: false,
199 sqrtPriceLimit: U256::ZERO,
200 })
201 .abi_encode();
202
203 let (value, gas_used, _) = evm_call(db, EvmEnv::default(), PeripheryAddress::MAVERICK_QUOTER, call_data_vec)?;
204
205 let ret = calculateSwapCall::abi_decode_returns(&value)?;
206
207 if ret.is_zero() {
208 Err(eyre!("ZERO_OUT_AMOUNT"))
209 } else {
210 Ok((ret.checked_sub(*U256_ONE).ok_or_eyre("SUBTRACTION_OVERFLOWN")?, gas_used))
211 }
212 }
213
214 fn calculate_in_amount(
215 &self,
216 db: &dyn DatabaseRef<Error = KabuDBError>,
217 token_address_from: &Address,
218 token_address_to: &Address,
219 out_amount: U256,
220 ) -> Result<(U256, u64), ErrReport> {
221 if out_amount >= U256::from(U128::MAX) {
222 error!("OUT_AMOUNT_EXCEEDS_MAX {} ", self.get_address());
223 return Err(eyre!("OUT_AMOUNT_EXCEEDS_MAX"));
224 }
225
226 let token_a_in = MaverickPool::get_zero_for_one(token_address_from, token_address_to);
227 let call_data_vec = IMaverickQuoterCalls::calculateSwap(calculateSwapCall {
230 pool: self.address,
231 amount: out_amount.to(),
232 tokenAIn: token_a_in,
233 exactOutput: true,
234 sqrtPriceLimit: U256::ZERO,
235 })
236 .abi_encode();
237
238 let (value, gas_used, _) = evm_call(db, EvmEnv::default(), PeripheryAddress::MAVERICK_QUOTER, call_data_vec)?;
239
240 let ret = calculateSwapCall::abi_decode_returns(&value)?;
241
242 if ret.is_zero() {
243 Err(eyre!("ZERO_IN_AMOUNT"))
244 } else {
245 Ok((ret.checked_add(*U256_ONE).ok_or_eyre("ADD_OVERFLOWN")?, gas_used))
246 }
247 }
248
249 fn can_flash_swap(&self) -> bool {
250 true
251 }
252
253 fn can_calculate_in_amount(&self) -> bool {
254 true
255 }
256
257 fn get_abi_encoder(&self) -> Option<&dyn PoolAbiEncoder> {
258 Some(&self.encoder)
259 }
260
261 fn get_read_only_cell_vec(&self) -> Vec<U256> {
262 Vec::new()
263 }
264
265 fn get_state_required(&self) -> Result<RequiredState> {
266 let tick = self.slot0.clone().unwrap().activeTick;
267
268 let quoter_swap_0_1_call = IMaverickQuoterCalls::calculateSwap(calculateSwapCall {
269 pool: self.address,
270 amount: (self.liquidity0 / U256::from(100)).to(),
271 tokenAIn: true,
272 exactOutput: false,
273 sqrtPriceLimit: U256::ZERO,
274 })
275 .abi_encode();
276
277 let quoter_swap_1_0_call = IMaverickQuoterCalls::calculateSwap(calculateSwapCall {
280 pool: self.address,
281 amount: (self.liquidity1 / U256::from(100)).to(),
282 tokenAIn: false,
283 exactOutput: false,
284 sqrtPriceLimit: U256::ZERO,
285 })
286 .abi_encode();
287
288 let tick_bitmap_index = tick;
290
291 let pool_address = self.address;
292
293 let mut state_required = RequiredState::new();
294 state_required
295 .add_call(self.address, IMaverickPoolCalls::getState(getStateCall {}).abi_encode())
296 .add_call(
297 PeripheryAddress::MAVERICK_QUOTER,
298 IMaverickQuoterCalls::getBinsAtTick(IMaverickQuoter::getBinsAtTickCall { pool: pool_address, tick: tick_bitmap_index - 4 })
299 .abi_encode(),
300 )
301 .add_call(
302 PeripheryAddress::MAVERICK_QUOTER,
303 IMaverickQuoterCalls::getBinsAtTick(IMaverickQuoter::getBinsAtTickCall { pool: pool_address, tick: tick_bitmap_index - 3 })
304 .abi_encode(),
305 )
306 .add_call(
307 PeripheryAddress::MAVERICK_QUOTER,
308 IMaverickQuoterCalls::getBinsAtTick(IMaverickQuoter::getBinsAtTickCall { pool: pool_address, tick: tick_bitmap_index - 2 })
309 .abi_encode(),
310 )
311 .add_call(
312 PeripheryAddress::MAVERICK_QUOTER,
313 IMaverickQuoterCalls::getBinsAtTick(IMaverickQuoter::getBinsAtTickCall { pool: pool_address, tick: tick_bitmap_index - 1 })
314 .abi_encode(),
315 )
316 .add_call(
317 PeripheryAddress::MAVERICK_QUOTER,
318 IMaverickQuoterCalls::getBinsAtTick(IMaverickQuoter::getBinsAtTickCall { pool: pool_address, tick: tick_bitmap_index })
319 .abi_encode(),
320 )
321 .add_call(
322 PeripheryAddress::MAVERICK_QUOTER,
323 IMaverickQuoterCalls::getBinsAtTick(IMaverickQuoter::getBinsAtTickCall { pool: pool_address, tick: tick_bitmap_index + 1 })
324 .abi_encode(),
325 )
326 .add_call(
327 PeripheryAddress::MAVERICK_QUOTER,
328 IMaverickQuoterCalls::getBinsAtTick(IMaverickQuoter::getBinsAtTickCall { pool: pool_address, tick: tick_bitmap_index + 2 })
329 .abi_encode(),
330 )
331 .add_call(
332 PeripheryAddress::MAVERICK_QUOTER,
333 IMaverickQuoterCalls::getBinsAtTick(IMaverickQuoter::getBinsAtTickCall { pool: pool_address, tick: tick_bitmap_index + 3 })
334 .abi_encode(),
335 )
336 .add_call(
337 PeripheryAddress::MAVERICK_QUOTER,
338 IMaverickQuoterCalls::getBinsAtTick(IMaverickQuoter::getBinsAtTickCall { pool: pool_address, tick: tick_bitmap_index + 4 })
339 .abi_encode(),
340 )
341 .add_call(PeripheryAddress::MAVERICK_QUOTER, quoter_swap_0_1_call)
342 .add_call(PeripheryAddress::MAVERICK_QUOTER, quoter_swap_1_0_call)
343 .add_slot_range(self.address, U256::from(0), 0x20);
344
345 for token_address in self.get_tokens() {
346 state_required.add_call(token_address, IERC20::balanceOfCall { account: pool_address }.abi_encode());
347 }
348
349 Ok(state_required)
350 }
351
352 fn is_native(&self) -> bool {
353 false
354 }
355
356 fn preswap_requirement(&self) -> PreswapRequirement {
357 PreswapRequirement::Callback
358 }
359}
360
361#[allow(dead_code)]
362#[derive(Clone, Copy)]
363struct MaverickAbiSwapEncoder {
364 pool_address: Address,
365}
366
367impl MaverickAbiSwapEncoder {
368 pub fn new(pool_address: Address) -> Self {
369 Self { pool_address }
370 }
371}
372
373impl PoolAbiEncoder for MaverickAbiSwapEncoder {
374 fn encode_swap_in_amount_provided(
375 &self,
376 token_from_address: Address,
377 token_to_address: Address,
378 amount: U256,
379 recipient: Address,
380 payload: Bytes,
381 ) -> Result<Bytes> {
382 let token_a_in = MaverickPool::get_zero_for_one(&token_from_address, &token_to_address);
385
386 let swap_call = IMaverickPool::swapCall {
387 recipient,
388 amount,
389 tokenAIn: token_a_in,
390 exactOutput: false,
391 sqrtPriceLimit: U256::ZERO,
392 data: payload,
393 };
394
395 Ok(Bytes::from(IMaverickPoolCalls::swap(swap_call).abi_encode()))
396 }
397
398 fn encode_swap_out_amount_provided(
399 &self,
400 token_from_address: Address,
401 token_to_address: Address,
402 amount: U256,
403 recipient: Address,
404 payload: Bytes,
405 ) -> Result<Bytes> {
406 let token_a_in = MaverickPool::get_zero_for_one(&token_from_address, &token_to_address);
407 let sqrt_price_limit_x96 = MaverickPool::get_price_limit(&token_from_address, &token_to_address);
408
409 let swap_call = IMaverickPool::swapCall {
410 recipient,
411 amount,
412 tokenAIn: token_a_in,
413 exactOutput: true,
414 sqrtPriceLimit: sqrt_price_limit_x96,
415 data: payload,
416 };
417
418 Ok(Bytes::from(IMaverickPoolCalls::swap(swap_call).abi_encode()))
419 }
420
421 fn swap_in_amount_offset(&self, _token_from_address: Address, _token_to_address: Address) -> Option<u32> {
422 Some(0x24)
423 }
424 fn swap_out_amount_offset(&self, _token_from_address: Address, _token_to_address: Address) -> Option<u32> {
425 Some(0x24)
426 }
427 fn swap_out_amount_return_offset(&self, _token_from_address: Address, _token_to_address: Address) -> Option<u32> {
428 Some(0x20)
429 }
430 fn swap_in_amount_return_offset(&self, _token_from_address: Address, _token_to_address: Address) -> Option<u32> {
431 Some(0x20)
432 }
433}
434
435#[cfg(test)]
436mod tests {
437 use super::*;
438 use alloy::rpc::types::BlockNumberOrTag;
439 use kabu_defi_abi::maverick::IMaverickQuoter::IMaverickQuoterInstance;
440 use kabu_evm_db::KabuDBType;
441 use kabu_node_debug_provider::AnvilDebugProviderFactory;
442 use kabu_types_blockchain::KabuDataTypesEthereum;
443 use kabu_types_entities::required_state::RequiredStateReader;
444 use revm::database::CacheDB;
445 use std::env;
446 use tracing::debug;
447
448 #[ignore]
449 #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
450 async fn test_pool() -> Result<()> {
451 let _ = env_logger::try_init_from_env(env_logger::Env::default().default_filter_or("info,defi_pools=off"));
452
453 dotenvy::from_filename(".env.test").ok();
454 let node_url = env::var("MAINNET_WS")?;
455
456 let client = AnvilDebugProviderFactory::from_node_on_block(node_url, 20045799).await?;
457
458 let pool_address: Address = "0x352B186090068Eb35d532428676cE510E17AB581".parse().unwrap();
459
460 let pool = MaverickPool::fetch_pool_data(client.clone(), pool_address).await.unwrap();
461
462 let state_required = pool.get_state_required()?;
463
464 let state_required =
465 RequiredStateReader::<KabuDataTypesEthereum>::fetch_calls_and_slots(client.clone(), state_required, Some(20045799)).await?;
466 debug!("{:?}", state_required);
467
468 let block_number = 20045799u64;
469
470 use kabu_types_entities::MarketState;
471 let mut market_state = MarketState::new(KabuDBType::default());
472 market_state.state_db.apply_geth_update(state_required);
473 let _block = client.get_block_by_number(BlockNumberOrTag::Number(block_number)).await?.unwrap();
474 let cache_db = CacheDB::new(market_state.state_db.clone());
475
476 let amount = U256::from(pool.liquidity1 / U256::from(1000));
477
478 let quoter = IMaverickQuoterInstance::new(PeripheryAddress::MAVERICK_QUOTER, client.clone());
479
480 let resp = quoter.calculateSwap(pool_address, amount.to(), false, false, U256::ZERO).call().await?;
481 debug!("Router call : {:?}", resp);
482 assert_ne!(resp, U256::ZERO);
483
484 let (out_amount, gas_used) =
485 pool.calculate_out_amount(&cache_db, &pool.token1, &pool.token0, U256::from(pool.liquidity1 / U256::from(1000))).unwrap();
486 debug!("{} {} {}", pool.get_protocol(), out_amount, gas_used);
487 assert_ne!(out_amount, U256::ZERO);
488 assert!(gas_used > 100000);
489
490 let (out_amount, gas_used) =
491 pool.calculate_out_amount(&cache_db, &pool.token0, &pool.token1, U256::from(pool.liquidity0 / U256::from(1000))).unwrap();
492 debug!("{} {} {}", pool.get_protocol(), out_amount, gas_used);
493 assert_ne!(out_amount, U256::ZERO);
494 assert!(gas_used > 100000);
495
496 Ok(())
497 }
498}