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