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 *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 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 *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 if !self.tokens.is_empty() && self.tokens.len() >= 2 {
427 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}