kabu_defi_pools/
curvepool.rs

1use crate::protocols::{CurveCommonContract, CurveContract, CurveProtocol};
2use alloy::primitives::{Address, Bytes, U256, address};
3use alloy::providers::{Network, Provider};
4use alloy::sol_types::SolCall;
5use alloy_evm::EvmEnv;
6use eyre::Result;
7use kabu_defi_abi::IERC20;
8use kabu_defi_address_book::TokenAddressEth;
9use kabu_evm_db::KabuDBError;
10use kabu_evm_utils::evm_call;
11use kabu_types_market::{
12    CurveError, Pool, PoolAbiEncoder, PoolClass, PoolError, PoolId, PoolProtocol, PreswapRequirement, RequiredState, SwapDirection,
13};
14use lazy_static::lazy_static;
15use revm::DatabaseRef;
16use std::any::Any;
17use std::sync::Arc;
18use tracing::error;
19
20lazy_static! {
21    static ref U256_ONE: U256 = U256::from(1);
22}
23
24pub struct CurvePool<P, N, E = CurvePoolAbiEncoder<P, N>>
25where
26    N: Network,
27    P: Provider<N> + Send + Sync + Clone + 'static,
28    E: PoolAbiEncoder + Send + Sync + 'static,
29{
30    address: Address,
31    pool_contract: Arc<CurveContract<P, N>>,
32    balances: Vec<U256>,
33    tokens: Vec<Address>,
34    underlying_tokens: Vec<Address>,
35    lp_token: Option<Address>,
36    abi_encoder: Option<Arc<E>>,
37    is_meta: bool,
38    is_native: bool,
39}
40
41impl<P, N, E> Clone for CurvePool<P, N, E>
42where
43    N: Network,
44    P: Provider<N> + Send + Sync + Clone + 'static,
45    E: PoolAbiEncoder + Send + Sync + Clone + 'static,
46{
47    fn clone(&self) -> Self {
48        Self {
49            address: self.address,
50            pool_contract: Arc::clone(&self.pool_contract),
51            balances: self.balances.clone(),
52            tokens: self.tokens.clone(),
53            underlying_tokens: self.underlying_tokens.clone(),
54            lp_token: self.lp_token,
55            abi_encoder: self.abi_encoder.clone(),
56            is_meta: self.is_meta,
57            is_native: self.is_native,
58        }
59    }
60}
61
62impl<P, N, E> CurvePool<P, N, E>
63where
64    N: Network,
65    P: Provider<N> + Send + Sync + Clone + 'static,
66    E: PoolAbiEncoder + Send + Sync + Clone + 'static,
67{
68    pub fn is_meta(&self) -> bool {
69        self.is_meta
70    }
71    pub fn curve_contract(&self) -> Arc<CurveContract<P, N>> {
72        self.pool_contract.clone()
73    }
74
75    pub fn lp_token(&self) -> Option<Address> {
76        self.lp_token
77    }
78
79    pub fn with_encoder(self, e: E) -> Self {
80        Self { abi_encoder: Some(Arc::new(e)), ..self }
81    }
82
83    pub fn get_meta_coin_idx(&self, address: Address) -> Result<u32> {
84        match self.get_coin_idx(address) {
85            Ok(i) => Ok(i),
86            Err(_) => match self.get_underlying_coin_idx(address) {
87                Ok(i) => Ok(self.tokens.len() as u32 + i - 1),
88                Err(e) => Err(e),
89            },
90        }
91    }
92    pub fn get_coin_idx(&self, address: Address) -> Result<u32> {
93        for i in 0..self.tokens.len() {
94            if address == self.tokens[i] {
95                return Ok(i as u32);
96            }
97        }
98        Err(CurveError::TokenNotInPool { token: address }.into())
99    }
100    pub fn get_underlying_coin_idx(&self, address: Address) -> Result<u32> {
101        for i in 0..self.underlying_tokens.len() {
102            if address == self.underlying_tokens[i] {
103                return Ok(i as u32);
104            }
105        }
106        Err(CurveError::TokenNotInPool { token: address }.into())
107    }
108
109    pub async fn fetch_out_amount(&self, token_address_from: Address, token_address_to: Address, amount_in: U256) -> Result<U256> {
110        let i = self.get_coin_idx(token_address_from)?;
111        let j = self.get_coin_idx(token_address_to)?;
112
113        self.pool_contract.get_dy(i, j, amount_in).await
114    }
115
116    pub async fn fetch_pool_data(client: P, pool_contract: CurveContract<P, N>) -> Result<Self> {
117        let pool_contract = Arc::new(pool_contract);
118
119        let mut tokens = CurveCommonContract::coins(client.clone(), pool_contract.get_address()).await?;
120        let mut is_native = false;
121
122        for tkn in tokens.iter_mut() {
123            if *tkn == address!("EeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE") {
124                //return Err(eyre!("ETH_CURVE_POOL_NOT_SUPPORTED"));
125                *tkn = TokenAddressEth::WETH;
126                is_native = true;
127            }
128        }
129
130        let lp_token = (CurveCommonContract::<P, N>::lp_token(pool_contract.get_address()).await).ok();
131
132        let (underlying_tokens, is_meta) = match pool_contract.as_ref() {
133            CurveContract::I128_2ToMeta(_interface) => (CurveProtocol::<P, N>::get_underlying_tokens(tokens[1])?, true),
134            _ => (vec![], false),
135        };
136
137        let balances = CurveCommonContract::balances(client.clone(), pool_contract.get_address()).await?;
138
139        // let abi_encoder = Arc::new(CurveAbiSwapEncoder::new(
140        //     pool_contract.get_address(),
141        //     tokens.clone(),
142        //     if !underlying_tokens.is_empty() { Some(underlying_tokens.clone()) } else { None },
143        //     lp_token,
144        //     is_meta,
145        //     is_native,
146        //     pool_contract.clone(),
147        // ));
148
149        Ok(CurvePool {
150            address: pool_contract.get_address(),
151            abi_encoder: None,
152            pool_contract,
153            balances,
154            tokens,
155            underlying_tokens,
156            lp_token,
157            is_meta,
158            is_native,
159        })
160    }
161}
162
163impl<P, N> CurvePool<P, N, CurvePoolAbiEncoder<P, N>>
164where
165    N: Network,
166    P: Provider<N> + Send + Sync + Clone + 'static,
167{
168    pub async fn fetch_pool_data_with_default_encoder(client: P, pool_contract: CurveContract<P, N>) -> Result<Self> {
169        let pool_contract = Arc::new(pool_contract);
170
171        let mut tokens = CurveCommonContract::coins(client.clone(), pool_contract.get_address()).await?;
172        let mut is_native = false;
173
174        for tkn in tokens.iter_mut() {
175            if *tkn == address!("EeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE") {
176                //return Err(eyre!("ETH_CURVE_POOL_NOT_SUPPORTED"));
177                *tkn = TokenAddressEth::WETH;
178                is_native = true;
179            }
180        }
181
182        let lp_token = (CurveCommonContract::<P, N>::lp_token(pool_contract.get_address()).await).ok();
183
184        let (underlying_tokens, is_meta) = match pool_contract.as_ref() {
185            CurveContract::I128_2ToMeta(_interface) => (CurveProtocol::<P, N>::get_underlying_tokens(tokens[1])?, true),
186            _ => (vec![], false),
187        };
188
189        let balances = CurveCommonContract::balances(client.clone(), pool_contract.get_address()).await?;
190
191        let mut pool = CurvePool {
192            address: pool_contract.get_address(),
193            abi_encoder: None,
194            pool_contract,
195            balances,
196            tokens,
197            underlying_tokens,
198            lp_token,
199            is_meta,
200            is_native,
201        };
202
203        let abi_encoder = Arc::new(CurvePoolAbiEncoder::new(&pool));
204
205        pool.abi_encoder = Some(abi_encoder);
206
207        Ok(pool)
208    }
209}
210
211impl<P, N, E> Pool for CurvePool<P, N, E>
212where
213    N: Network,
214    P: Provider<N> + Send + Sync + Clone + 'static,
215    E: PoolAbiEncoder + Send + Sync + Clone + 'static,
216{
217    fn as_any<'a>(&self) -> &dyn Any {
218        self
219    }
220    fn get_class(&self) -> PoolClass {
221        PoolClass::Curve
222    }
223
224    fn get_protocol(&self) -> PoolProtocol {
225        PoolProtocol::Curve
226    }
227
228    fn get_address(&self) -> PoolId {
229        PoolId::Address(self.address)
230    }
231
232    fn get_pool_id(&self) -> PoolId {
233        PoolId::Address(self.address)
234    }
235
236    fn get_fee(&self) -> U256 {
237        U256::ZERO
238    }
239
240    fn get_tokens(&self) -> Vec<Address> {
241        self.tokens.clone()
242    }
243
244    fn get_swap_directions(&self) -> Vec<SwapDirection> {
245        let mut ret: Vec<SwapDirection> = Vec::new();
246        if self.is_meta {
247            ret.push((self.tokens[0], self.tokens[1]).into());
248            ret.push((self.tokens[1], self.tokens[0]).into());
249            for j in 0..self.underlying_tokens.len() {
250                ret.push((self.tokens[0], self.underlying_tokens[j]).into());
251                ret.push((self.underlying_tokens[j], self.tokens[0]).into());
252            }
253        } else {
254            for i in 0..self.tokens.len() {
255                for j in 0..self.tokens.len() {
256                    if i == j {
257                        continue;
258                    }
259                    ret.push((self.tokens[i], self.tokens[j]).into());
260                }
261                if let Some(lp_token_address) = self.lp_token {
262                    ret.push((self.tokens[i], lp_token_address).into());
263                    ret.push((lp_token_address, self.tokens[i]).into());
264                }
265            }
266        }
267        ret
268    }
269
270    fn calculate_out_amount(
271        &self,
272        db: &dyn DatabaseRef<Error = KabuDBError>,
273        evm_env: &EvmEnv,
274        token_address_from: &Address,
275        token_address_to: &Address,
276        in_amount: U256,
277    ) -> Result<(U256, u64), PoolError> {
278        let token_address_from = *token_address_from;
279        let token_address_to = *token_address_to;
280
281        let call_data = if self.is_meta {
282            let i: Result<u32> = self.get_coin_idx(token_address_from);
283            let j: Result<u32> = self.get_coin_idx(token_address_to);
284            if let (Ok(i), Ok(j)) = (i, j) {
285                self.pool_contract
286                    .get_dy_call_data(i, j, in_amount)
287                    .map_err(|_e| PoolError::InvalidInput { reason: "Failed to get dy call data" })?
288            } else {
289                let i: u32 = self
290                    .get_meta_coin_idx(token_address_from)
291                    .map_err(|_e| PoolError::Curve(CurveError::TokenNotInPool { token: token_address_from }))?;
292                let j: u32 = self
293                    .get_meta_coin_idx(token_address_to)
294                    .map_err(|_e| PoolError::Curve(CurveError::TokenNotInPool { token: token_address_to }))?;
295                self.pool_contract
296                    .get_dy_underlying_call_data(i, j, in_amount)
297                    .map_err(|_e| PoolError::InvalidInput { reason: "Failed to get dy underlying call data" })?
298            }
299        } else if let Some(lp_token) = self.lp_token {
300            if token_address_from == lp_token {
301                let i: u32 = self
302                    .get_coin_idx(token_address_to)
303                    .map_err(|_e| PoolError::Curve(CurveError::TokenNotInPool { token: token_address_to }))?;
304                self.pool_contract
305                    .calc_withdraw_one_coin_call_data(i, in_amount)
306                    .map_err(|_e| PoolError::InvalidInput { reason: "Failed to calc withdraw one coin call data" })?
307            } else if token_address_to == lp_token {
308                let i: u32 = self
309                    .get_coin_idx(token_address_from)
310                    .map_err(|_e| PoolError::Curve(CurveError::TokenNotInPool { token: token_address_from }))?;
311                self.pool_contract
312                    .calc_token_amount_call_data(i, in_amount)
313                    .map_err(|_e| PoolError::InvalidInput { reason: "Failed to calc token amount call data" })?
314            } else {
315                let i: u32 = self
316                    .get_coin_idx(token_address_from)
317                    .map_err(|_e| PoolError::Curve(CurveError::TokenNotInPool { token: token_address_from }))?;
318                let j: u32 = self
319                    .get_coin_idx(token_address_to)
320                    .map_err(|_e| PoolError::Curve(CurveError::TokenNotInPool { token: token_address_to }))?;
321                self.pool_contract
322                    .get_dy_call_data(i, j, in_amount)
323                    .map_err(|_e| PoolError::InvalidInput { reason: "Failed to get dy call data" })?
324            }
325        } else {
326            let i: u32 = self
327                .get_coin_idx(token_address_from)
328                .map_err(|_e| PoolError::Curve(CurveError::TokenNotInPool { token: token_address_from }))?;
329            let j: u32 = self
330                .get_coin_idx(token_address_to)
331                .map_err(|_e| PoolError::Curve(CurveError::TokenNotInPool { token: token_address_to }))?;
332            self.pool_contract
333                .get_dy_call_data(i, j, in_amount)
334                .map_err(|_e| PoolError::InvalidInput { reason: "Failed to get dy call data" })?
335        };
336
337        let (value, gas_used, _) = evm_call(db, evm_env.clone(), self.address, call_data.to_vec())?;
338
339        let ret = if value.len() > 32 { U256::from_be_slice(&value[0..32]) } else { U256::from_be_slice(&value[0..]) };
340
341        if ret.is_zero() {
342            Err(CurveError::OutAmountIsZero.into())
343        } else {
344            Ok((ret.checked_sub(*U256_ONE).ok_or(PoolError::InvalidInput { reason: "SUB_OVERFLOWN" })?, gas_used))
345        }
346    }
347
348    fn calculate_in_amount(
349        &self,
350        db: &dyn DatabaseRef<Error = KabuDBError>,
351        evm_env: &EvmEnv,
352        token_address_from: &Address,
353        token_address_to: &Address,
354        out_amount: U256,
355    ) -> Result<(U256, u64), PoolError> {
356        let token_address_from = *token_address_from;
357        let token_address_to = *token_address_to;
358
359        if self.pool_contract.can_calculate_in_amount() {
360            let i: u32 = self
361                .get_coin_idx(token_address_from)
362                .map_err(|_e| PoolError::Curve(CurveError::TokenNotInPool { token: token_address_from }))?;
363            let j: u32 = self
364                .get_coin_idx(token_address_to)
365                .map_err(|_e| PoolError::Curve(CurveError::TokenNotInPool { token: token_address_to }))?;
366            let call_data = self
367                .pool_contract
368                .get_dx_call_data(i, j, out_amount)
369                .map_err(|_e| PoolError::InvalidInput { reason: "Failed to get dx call data" })?;
370
371            let (value, gas_used, _) = evm_call(db, evm_env.clone(), self.address, call_data.to_vec())?;
372
373            let ret = if value.len() > 32 { U256::from_be_slice(&value[0..32]) } else { U256::from_be_slice(&value[0..]) };
374
375            if ret.is_zero() {
376                Err(CurveError::InAmountIsZero.into())
377            } else {
378                Ok((ret.checked_add(*U256_ONE).ok_or(PoolError::InvalidInput { reason: "ADD_OVERFLOWN" })?, gas_used))
379            }
380        } else {
381            Err(PoolError::NotImplemented)
382        }
383    }
384
385    fn can_flash_swap(&self) -> bool {
386        false
387    }
388
389    fn can_calculate_in_amount(&self) -> bool {
390        self.pool_contract.can_calculate_in_amount()
391    }
392
393    fn get_abi_encoder(&self) -> Option<&dyn PoolAbiEncoder> {
394        let r = self.abi_encoder.as_ref().unwrap().as_ref();
395        Some(r as &dyn PoolAbiEncoder)
396    }
397
398    fn get_read_only_cell_vec(&self) -> Vec<U256> {
399        Vec::new()
400    }
401
402    fn get_state_required(&self) -> Result<RequiredState> {
403        let mut state_reader = RequiredState::new();
404
405        if self.is_meta {
406            match &self.pool_contract.as_ref() {
407                CurveContract::I128_2ToMeta(_interface) => {
408                    for j in 0..self.underlying_tokens.len() {
409                        let value = self.balances[0] / U256::from(10);
410                        match self.pool_contract.get_dy_call_data(0_u32, (j + self.tokens.len()) as u32, value) {
411                            Ok(data) => {
412                                state_reader.add_call(self.address, data);
413                            }
414                            Err(e) => {
415                                error!("{}", e);
416                            }
417                        }
418                    }
419                }
420                _ => {
421                    error!("CURVE_META_POOL_NOT_SUPPORTED")
422                }
423            }
424        } else {
425            if let Some(_lp_token) = self.lp_token {
426                for i in 0..self.tokens.len() {
427                    let value = self.balances[i] / U256::from(10);
428                    match self.pool_contract.get_add_liquidity_call_data(i as u32, value, Address::ZERO) {
429                        Ok(data) => {
430                            state_reader.add_call(self.address, data);
431                        }
432                        Err(e) => {
433                            error!("{}", e);
434                        }
435                    }
436                }
437            }
438
439            for i in 0..self.tokens.len() {
440                for j in 0..self.tokens.len() {
441                    if i == j {
442                        continue;
443                    }
444                    if let Some(balance) = self.balances.get(i) {
445                        let value = balance / U256::from(100);
446                        match self.pool_contract.get_dy_call_data(i as u32, j as u32, value) {
447                            Ok(data) => {
448                                state_reader.add_call(self.address, data);
449                            }
450                            Err(e) => {
451                                error!("{}", e);
452                            }
453                        }
454                    } else {
455                        error!("Cannot get curve pool balance {} {}", self.address, i);
456                        return Err(CurveError::InvalidPoolState.into());
457                    }
458                }
459            }
460        }
461
462        // Add a call to fetch the pool contract bytecode
463        // Use the first get_dy call that we're already making
464        if !self.tokens.is_empty() && self.tokens.len() >= 2 {
465            // Try to add get_dy(0, 1, 1) to ensure contract bytecode is fetched
466            if let Ok(data) = self.pool_contract.get_dy_call_data(0, 1, U256::from(1)) {
467                state_reader.add_call(self.address, data);
468            }
469        }
470
471        state_reader.add_slot_range(self.address, U256::from(0), 0x20);
472
473        for token_address in self.get_tokens() {
474            state_reader.add_call(token_address, IERC20::balanceOfCall { account: self.address }.abi_encode());
475        }
476        Ok(state_reader)
477    }
478
479    fn is_native(&self) -> bool {
480        self.is_native
481    }
482
483    fn preswap_requirement(&self) -> PreswapRequirement {
484        PreswapRequirement::Allowance
485    }
486}
487
488#[allow(dead_code)]
489#[derive(Clone)]
490pub struct CurvePoolAbiEncoder<P, N>
491where
492    N: Network,
493    P: Provider<N> + Send + Sync + Clone + 'static,
494{
495    pool_address: Address,
496    tokens: Vec<Address>,
497    underlying_tokens: Option<Vec<Address>>,
498    lp_token: Option<Address>,
499    is_meta: bool,
500    is_native: bool,
501    curve_contract: Arc<CurveContract<P, N>>,
502}
503
504impl<P, N> CurvePoolAbiEncoder<P, N>
505where
506    N: Network,
507    P: Provider<N> + Send + Sync + Clone + 'static,
508{
509    pub fn new(pool: &CurvePool<P, N>) -> Self {
510        Self {
511            pool_address: pool.address,
512            tokens: pool.tokens.clone(),
513            underlying_tokens: if pool.underlying_tokens.is_empty() { None } else { Some(pool.underlying_tokens.clone()) },
514            lp_token: pool.lp_token,
515            is_meta: pool.is_meta,
516            is_native: pool.is_native,
517            curve_contract: pool.pool_contract.clone(),
518        }
519    }
520
521    pub fn get_meta_coin_idx(&self, address: Address) -> Result<u32> {
522        match self.get_coin_idx(address) {
523            Ok(idx) => Ok(idx),
524            _ => match self.get_underlying_coin_idx(address) {
525                Ok(idx) => Ok(idx + self.tokens.len() as u32 - 1),
526                Err(_) => Err(CurveError::TokenNotInPool { token: address }.into()),
527            },
528        }
529    }
530
531    pub fn get_coin_idx(&self, address: Address) -> Result<u32> {
532        for i in 0..self.tokens.len() {
533            if address == self.tokens[i] {
534                return Ok(i as u32);
535            }
536        }
537        Err(CurveError::TokenNotInPool { token: address }.into())
538    }
539
540    pub fn get_underlying_coin_idx(&self, address: Address) -> Result<u32> {
541        match &self.underlying_tokens {
542            Some(underlying_tokens) => {
543                for (i, token_address) in underlying_tokens.iter().enumerate() {
544                    if address == *token_address {
545                        return Ok(i as u32);
546                    }
547                }
548                Err(CurveError::InvalidPoolState.into())
549            }
550            _ => Err(CurveError::InvalidPoolState.into()),
551        }
552    }
553}
554
555impl<P, N> PoolAbiEncoder for CurvePoolAbiEncoder<P, N>
556where
557    N: Network,
558    P: Provider<N> + Send + Sync + Clone + 'static,
559{
560    fn encode_swap_in_amount_provided(
561        &self,
562        token_from_address: Address,
563        token_to_address: Address,
564        amount: U256,
565        recipient: Address,
566        _payload: Bytes,
567    ) -> Result<Bytes> {
568        if self.is_meta {
569            let i: Result<u32> = self.get_coin_idx(token_from_address);
570            let j: Result<u32> = self.get_coin_idx(token_to_address);
571
572            match (i, j) {
573                (Ok(i), Ok(j)) => self.curve_contract.get_exchange_call_data(i, j, amount, U256::ZERO, recipient),
574                _ => {
575                    let meta_i: u32 = self.get_meta_coin_idx(token_from_address)?;
576                    let meta_j: u32 = self.get_meta_coin_idx(token_to_address)?;
577                    self.curve_contract.get_exchange_underlying_call_data(meta_i, meta_j, amount, U256::ZERO, recipient)
578                }
579            }
580        } else if let Some(lp_token) = self.lp_token {
581            if token_from_address == lp_token {
582                let i: u32 = self.get_coin_idx(token_to_address)?;
583                self.curve_contract.get_remove_liquidity_one_coin_call_data(i, amount, recipient)
584            } else if token_to_address == lp_token {
585                let i: u32 = self.get_coin_idx(token_from_address)?;
586                self.curve_contract.get_add_liquidity_call_data(i, amount, recipient)
587            } else {
588                let i: u32 = self.get_coin_idx(token_from_address)?;
589                let j: u32 = self.get_coin_idx(token_to_address)?;
590                self.curve_contract.get_exchange_call_data(i, j, amount, U256::ZERO, recipient)
591            }
592        } else {
593            let i: u32 = self.get_coin_idx(token_from_address)?;
594            let j: u32 = self.get_coin_idx(token_to_address)?;
595            self.curve_contract.get_exchange_call_data(i, j, amount, U256::ZERO, recipient)
596        }
597    }
598
599    fn encode_swap_out_amount_provided(
600        &self,
601        _token_from_address: Address,
602        _token_to_address: Address,
603        _amount: U256,
604        _recipient: Address,
605        _payload: Bytes,
606    ) -> Result<Bytes> {
607        Err(PoolError::NotImplemented.into())
608    }
609
610    fn swap_in_amount_offset(&self, _token_from_address: Address, _token_to_address: Address) -> Option<u32> {
611        Some(0x44)
612    }
613
614    fn swap_out_amount_offset(&self, _token_from_address: Address, _token_to_address: Address) -> Option<u32> {
615        None
616    }
617    fn swap_out_amount_return_offset(&self, _token_from_address: Address, _token_to_address: Address) -> Option<u32> {
618        None
619    }
620    fn swap_in_amount_return_offset(&self, _token_from_address: Address, _token_to_address: Address) -> Option<u32> {
621        None
622    }
623    fn swap_out_amount_return_script(&self, _token_from_address: Address, _token_to_address: Address) -> Option<Bytes> {
624        None
625    }
626
627    fn swap_in_amount_return_script(&self, _token_from_address: Address, _token_to_address: Address) -> Option<Bytes> {
628        None
629    }
630}
631
632#[cfg(test)]
633mod tests {
634    use eyre::Result;
635
636    use crate::CurvePool;
637    use crate::protocols::CurveProtocol;
638    use alloy::primitives::U256;
639    use alloy::providers::Provider;
640    use alloy::rpc::types::BlockNumberOrTag;
641    use alloy_evm::EvmEnv;
642    use env_logger::Env as EnvLog;
643    use kabu_evm_db::{DatabaseKabuExt, KabuDBType};
644    use kabu_node_debug_provider::AnvilDebugProviderFactory;
645    use kabu_types_market::MarketState;
646    use kabu_types_market::Pool;
647    use kabu_types_market::RequiredStateReader;
648    use revm::database::CacheDB;
649    use tracing::{debug, error};
650
651    #[ignore]
652    #[tokio::test]
653    async fn test_pool() -> Result<()> {
654        let _ = env_logger::try_init_from_env(EnvLog::default().default_filter_or("info,alloy_rpc_client=off"));
655
656        dotenvy::from_filename(".env.test").ok();
657        let node_url = std::env::var("MAINNET_WS")?;
658
659        let client = AnvilDebugProviderFactory::from_node_on_block(node_url, 20045799).await?;
660
661        let mut market_state = MarketState::new(KabuDBType::new());
662
663        let curve_contracts = CurveProtocol::get_contracts_vec(client.clone());
664
665        for curve_contract in curve_contracts.into_iter() {
666            debug!("Loading Pool : {} {:?}", curve_contract.get_address(), curve_contract);
667            let pool = CurvePool::fetch_pool_data_with_default_encoder(client.clone(), curve_contract).await.unwrap();
668            let state_required = pool.get_state_required().unwrap();
669
670            let state_required = RequiredStateReader::fetch_calls_and_slots(client.clone(), state_required, Some(20045799)).await.unwrap();
671            debug!("Pool state fetched {} {}", pool.address, state_required.len());
672
673            market_state.state_db.apply_geth_update(state_required);
674            debug!(
675                "Pool : {} Accs : {} Storage : {}",
676                pool.address,
677                market_state.state_db.accounts_len(),
678                market_state.state_db.storage_len()
679            );
680
681            let block_header = client.get_block_by_number(BlockNumberOrTag::Number(20045799)).await.unwrap().unwrap().header;
682            debug!("Block {} {}", block_header.number, block_header.timestamp);
683
684            let cache_db = CacheDB::new(market_state.state_db.clone());
685
686            let tokens = pool.tokens.clone();
687            let balances = pool.balances.clone();
688            for i in 0..tokens.len() {
689                for j in 0..tokens.len() {
690                    if i == j {
691                        continue;
692                    }
693                    let in_amount = balances[i] / U256::from(100);
694                    let token_in = tokens[i];
695                    let token_out = tokens[j];
696                    let (out_amount, gas_used) =
697                        match pool.calculate_out_amount(&cache_db, &EvmEnv::default(), &token_in, &token_out, in_amount) {
698                            Ok(result) => result,
699                            Err(e) => {
700                                error!("Failed to calculate out amount for pool {:?}: {}", pool.get_address(), e);
701                                (U256::ZERO, 0)
702                            }
703                        };
704                    debug!(
705                        "Calculated : {:?} {} -> {} : {} -> {} gas : {}",
706                        pool.get_address(),
707                        tokens[i],
708                        tokens[j],
709                        in_amount,
710                        out_amount,
711                        gas_used
712                    );
713
714                    let out_amount_fetched = pool.fetch_out_amount(token_in, token_out, in_amount).await.unwrap();
715                    debug!("Fetched {:?} {} -> {} : {} -> {}", pool.get_address(), tokens[i], tokens[j], in_amount, out_amount_fetched);
716                    assert_eq!(out_amount, out_amount_fetched - U256::from(1));
717                }
718            }
719
720            if let Some(lp_token) = pool.lp_token {
721                for i in 0..tokens.len() {
722                    let in_amount = balances[i] / U256::from(1000);
723                    let token_in = tokens[i];
724                    let (out_amount, gas_used) =
725                        pool.calculate_out_amount(&cache_db, &EvmEnv::default(), &token_in, &lp_token, in_amount).unwrap_or_default();
726                    debug!("LP {:?} {} -> {} : {} -> {} gas : {}", pool.get_address(), token_in, lp_token, in_amount, out_amount, gas_used);
727                    assert!(gas_used > 50000);
728
729                    let (out_amount2, gas_used) =
730                        pool.calculate_out_amount(&cache_db, &EvmEnv::default(), &lp_token, &token_in, out_amount).unwrap_or_default();
731                    debug!(
732                        "LP {:?} {} -> {} : {} -> {} gas : {}",
733                        pool.get_address(),
734                        lp_token,
735                        token_in,
736                        out_amount,
737                        out_amount2,
738                        gas_used
739                    );
740                    assert!(gas_used > 50000);
741                    assert_ne!(out_amount, U256::ZERO);
742                    assert_ne!(out_amount2, U256::ZERO);
743                }
744            }
745
746            if pool.is_meta {
747                let underlying_tokens = pool.underlying_tokens.clone();
748                for underlying_token in underlying_tokens {
749                    let in_amount = balances[0] / U256::from(1000);
750                    let token_in = tokens[0];
751                    let token_out = underlying_token;
752                    let (out_amount, gas_used) =
753                        pool.calculate_out_amount(&cache_db, &EvmEnv::default(), &token_in, &token_out, in_amount).unwrap_or_default();
754                    debug!(
755                        "Meta {:?} {} -> {} : {} -> {} gas: {}",
756                        pool.get_address(),
757                        token_in,
758                        token_out,
759                        in_amount,
760                        out_amount,
761                        gas_used
762                    );
763                    let (out_amount2, gas_used) =
764                        pool.calculate_out_amount(&cache_db, &EvmEnv::default(), &token_out, &token_in, out_amount).unwrap_or_default();
765                    debug!(
766                        "Meta {:?} {} -> {} : {} -> {} gas : {} ",
767                        pool.get_address(),
768                        token_out,
769                        token_in,
770                        out_amount,
771                        out_amount2,
772                        gas_used
773                    );
774                    assert_ne!(out_amount, U256::ZERO);
775                    assert_ne!(out_amount2, U256::ZERO);
776                }
777            }
778        }
779        Ok(())
780    }
781}