kabu_defi_pools/
maverickpool.rs

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    //contract_storage : ContractStorage,
29    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 sqrt_price_limit = MaverickPool::get_price_limit(token_address_from, token_address_to);
193
194        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 sqrt_price_limit = MaverickPool::get_price_limit(token_address_from, token_address_to);
228
229        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 sqrt_price_limit = MaverickPool::get_price_limit(&self.token1, &self.token0);
278
279        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 = MaverickPool::get_tick_bitmap_index(tick, self.spacing.as_u32());
289        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 sqrt_price_limit_x96 = MaverickPool::get_price_limit(&token_from_address, &token_to_address);
383
384        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}