From f5bcd31d66d20f62ba547334f4ce807248f406c6 Mon Sep 17 00:00:00 2001 From: TAMARA LIPOWSKI Date: Thu, 4 Sep 2025 00:39:40 -0400 Subject: [PATCH] feat: Rust testing SDK - implement get_amount_out simulation --- protocol-testing/Cargo.lock | 2 + protocol-testing/Cargo.toml | 2 + protocol-testing/src/test_runner.rs | 62 +++++++++++++++++++++++------ 3 files changed, 54 insertions(+), 12 deletions(-) diff --git a/protocol-testing/Cargo.lock b/protocol-testing/Cargo.lock index b2a8f31..a6cb008 100644 --- a/protocol-testing/Cargo.lock +++ b/protocol-testing/Cargo.lock @@ -5429,6 +5429,8 @@ dependencies = [ "glob", "hex", "miette", + "num-bigint", + "num-traits", "postgres", "serde", "serde_json", diff --git a/protocol-testing/Cargo.toml b/protocol-testing/Cargo.toml index c38f8f8..aa0462c 100644 --- a/protocol-testing/Cargo.toml +++ b/protocol-testing/Cargo.toml @@ -14,6 +14,8 @@ tycho-client = "0.82.0" tycho-simulation = { git = "https://github.com/propeller-heads/tycho-simulation.git", tag = "0.156.0", features = ["evm"] } ## TODO: for local development #tycho-simulation = { path = "../../tycho-simulation" } +num-bigint = "0.4" +num-traits = "0.2" # EVM dependencies alloy = { version = "1.0.27", features = ["arbitrary", "json", "dyn-abi", "sol-types", "contract", "provider-http"] } tokio = { version = "1", features = ["full"] } diff --git a/protocol-testing/src/test_runner.rs b/protocol-testing/src/test_runner.rs index 75bb944..b9fe8a4 100644 --- a/protocol-testing/src/test_runner.rs +++ b/protocol-testing/src/test_runner.rs @@ -6,6 +6,8 @@ use figment::{ Figment, }; use miette::{miette, IntoDiagnostic, WrapErr}; +use num_bigint::BigUint; +use num_traits::Zero; use postgres::{Client, Error, NoTls}; use tokio::runtime::Runtime; use tracing::{debug, error, info}; @@ -406,18 +408,54 @@ fn validate_state( .map(|price| info!("Spot price {:?}: {:?}", formatted_token_str, price)) .map_err(|e| info!("Error calculating spot price for Pool {:?}: {:?}", id, e)) .ok(); - // let amount_in = - // BigUint::from(1u32) * BigUint::from(10u32).pow(tokens[0].decimals as u32); - // state - // .get_amount_out(amount_in, &tokens[0], &tokens[1]) - // .map(|result| { - // println!( - // "Amount out for trading 1 {:?} -> {:?}: {:?} (takes {:?} gas)", - // &tokens[0].symbol, &tokens[1].symbol, result.amount, result.gas - // ) - // }) - // .map_err(|e| eprintln!("Error calculating amount out for Pool {:?}: {:?}", id, - // e)) .ok(); + + // Test get_amount_out with different percentages of limits. The reserves or limits are + // relevant because we need to know how much to test with. We don’t know if a pool is + // going to revert with 10 or 10 million USDC, for example, so by using the limits we + // can use “safe values” where the sim shouldn’t break. + let percentages = [0.001, 0.01, 0.1]; + for percentage in &percentages { + // Get limits for this token pair + let limits = state + .get_limits(tokens[0].address.clone(), tokens[1].address.clone()) + .map_err(|e| info!("Error getting limits for Pool {:?}: {:?}", id, e)) + .ok(); + + if let Some((max_input, _max_output)) = limits { + // Calculate test amount as percentage of max input + let percentage_biguint = BigUint::from((percentage * 1000.0) as u32); + let thousand = BigUint::from(1000u32); + let amount_in = (&max_input * &percentage_biguint) / &thousand; + + // Skip if amount is zero + if amount_in.is_zero() { + continue; + } + + state + .get_amount_out(amount_in.clone(), &tokens[0], &tokens[1]) + .map(|result| { + info!( + "Amount out for trading {:.1}% of max ({} -> {}): {} {} (gas: {})", + percentage * 100.0, + &tokens[0].symbol, + &tokens[1].symbol, + result.amount, + &tokens[1].symbol, + result.gas + ) + }) + .map_err(|e| { + info!( + "Error calculating amount out for Pool {:?} at {:.1}%: {:?}", + id, + percentage * 100.0, + e + ) + }) + .ok(); + } + } } }