kabu_defi_pools/
curvepool.rs

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