refactor: Misc improvements to code (#277)

* refactor: Misc improvements to code

- Decouple validating logic from TychoRunner
- Move all data fetching and decoding the tycho message into the same method
- Split validate_state into validate_state, validate_token_balances and simulate_and_execute
- Make rpc_provider and runtime attributes of TestRunner
- Add references where possible to avoid clones
- Remove unnecessary code
- Make clippy happy

#time 2h 36m

#time 0m


#time 3m

* chore: Use tycho deps and foundry from tycho_simulation

This is to try to decrease the risk of using conflicting versions in the different repositories

#time 32m


#time 0m

* chore: Read RPC_URL in main.rs

#time 10m

* fix: Support eth trades (skip balance and allowance overwrites) and set balance overwrite to amount in

For tokens like USDC setting the balance super high was making us getting blacklisted

#time 1h 12m

* fix: Fix curve tests and filter components_by_id with the expected_component_ids

#time 1h 30m


#time 0m

* fix: Don't use all the possible executor addresses. Hardcode just one for the test

Refactor overwrites logic:
- renamed functions
- moved logic around that fits together
- don't use StateOverrides and then convert to alloy overrides. Use alloy's directly

#time 1h 21m

* fix: Assume that the executors mapping starts at storage value 1

Move setup_router_overwrites away from the rpc and into the execution file
Delete unnecessary get_storage_at

#time 33m
This commit is contained in:
dianacarvalho1
2025-09-25 17:27:05 +01:00
committed by GitHub
parent 12369c3981
commit b577e7d6b2
13 changed files with 1161 additions and 1087 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -8,13 +8,7 @@ glob = "0.3.0"
miette = { version = "7.6.0", features = ["fancy"] }
# Logging & Tracing
tracing = "0.1.37"
# Tycho dependencies
tycho-common = { git = "https://github.com/propeller-heads/tycho-indexer.git", rev = "28d013a906c497d95e27f01f48fc887fb22dbbbc" }
tycho-client = { git = "https://github.com/propeller-heads/tycho-indexer.git", rev = "28d013a906c497d95e27f01f48fc887fb22dbbbc" }
tycho-simulation = { git = "https://github.com/propeller-heads/tycho-simulation.git", rev = "6585823a859a29bd64600cc1d2fa7d502d48d3e6" }
## TODO: for local development
#tycho-simulation = { path = "../../tycho-simulation", features = ["evm"] }
tycho-ethereum = { git = "https://github.com/propeller-heads/tycho-indexer.git", rev = "28d013a906c497d95e27f01f48fc887fb22dbbbc", features = ["onchain_data"] }
tycho-simulation = { git = "https://github.com/propeller-heads/tycho-simulation.git", rev = "f73c2ef28328abdde791edf1fb21748f78dbee6a", features = ["evm"] }
num-bigint = "0.4"
num-traits = "0.2"
num-rational = "0.4.2"
@@ -36,7 +30,3 @@ colored = "3.0.0"
similar = "2.7.0"
termsize = "0.1.9"
itertools = "0.14.0"
# Foundry dependencies (same versions as tycho-simulation)
foundry-config = { git = "https://github.com/foundry-rs/foundry", rev = "5a552bb0de7126fa35170fd84532bbd3d40cd348" }
foundry-evm = { git = "https://github.com/foundry-rs/foundry", rev = "5a552bb0de7126fa35170fd84532bbd3d40cd348" }
revm = { version = "27.0.3", features = ["alloydb", "serde"] }

View File

@@ -3,7 +3,7 @@ use std::collections::{HashMap, HashSet};
use colored::Colorize;
use serde::{Deserialize, Serialize};
use similar::{ChangeTag, TextDiff};
use tycho_common::{dto::ProtocolComponent, Bytes};
use tycho_simulation::{protocol::models::ProtocolComponent, tycho_common::Bytes};
/// Represents a ProtocolComponent with its main attributes
#[derive(Debug, Clone, Deserialize, Serialize)]
@@ -31,14 +31,18 @@ impl ProtocolComponentExpectation {
let mut diffs = Vec::new();
// Compare id (case-insensitive)
if self.id.to_lowercase() != other.id.to_lowercase() {
let diff = self.format_diff("id", &self.id, &other.id, colorize_output);
if self.id.to_lowercase() != other.id.to_string().to_lowercase() {
let diff = self.format_diff("id", &self.id, &other.id.to_string(), colorize_output);
diffs.push(format!("Field 'id' mismatch for {}:\n{}", self.id, diff));
}
// Compare tokens (order-independent)
let self_tokens_set: HashSet<_> = self.tokens.iter().collect();
let other_tokens_set: HashSet<_> = other.tokens.iter().collect();
let other_tokens_set: HashSet<_> = other
.tokens
.iter()
.map(|token| &token.address)
.collect();
if self_tokens_set != other_tokens_set {
let self_tokens = format!("{:?}", self.tokens);
let other_tokens = format!("{:?}", other.tokens);

View File

@@ -9,10 +9,11 @@ use std::str::FromStr;
use alloy::{primitives::Keccak256, sol_types::SolValue};
use miette::{IntoDiagnostic, WrapErr};
use num_bigint::BigUint;
use tycho_common::{dto::Chain, Bytes};
use serde_json::json;
use tycho_simulation::{
evm::protocol::u256_num::biguint_to_u256,
protocol::models::ProtocolComponent,
tycho_common::{dto::Chain, Bytes},
tycho_execution::encoding::{
errors::EncodingError,
evm::{encoder_builders::TychoRouterEncoderBuilder, utils::bytes_to_address},
@@ -22,7 +23,7 @@ use tycho_simulation::{
},
};
use crate::execution::EXECUTORS_JSON;
use crate::execution::EXECUTOR_ADDRESS;
/// Creates a Solution for the given swap parameters.
///
@@ -36,17 +37,17 @@ use crate::execution::EXECUTORS_JSON;
/// # Returns
/// A `Result<Solution, EncodingError>` containing the solution, or an error if creation fails.
pub fn get_solution(
component: ProtocolComponent,
token_in: Bytes,
token_out: Bytes,
amount_in: BigUint,
amount_out: BigUint,
component: &ProtocolComponent,
token_in: &Bytes,
token_out: &Bytes,
amount_in: &BigUint,
amount_out: &BigUint,
) -> miette::Result<Solution> {
let alice_address = Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2")
let user_address = Bytes::from_str("0xf847a638E44186F3287ee9F8cAF73FF4d4B80784")
.into_diagnostic()
.wrap_err("Failed to parse Alice's address for Tycho router encoding")?;
let swap = SwapBuilder::new(component, token_in.clone(), token_out.clone()).build();
let swap = SwapBuilder::new(component.clone(), token_in.clone(), token_out.clone()).build();
let slippage = 0.0025; // 0.25% slippage
let bps = BigUint::from(10_000u32);
@@ -55,11 +56,11 @@ pub fn get_solution(
let min_amount_out = (amount_out * &multiplier) / &bps;
Ok(Solution {
sender: alice_address.clone(),
receiver: alice_address.clone(),
given_token: token_in,
given_amount: amount_in,
checked_token: token_out,
sender: user_address.clone(),
receiver: user_address.clone(),
given_token: token_in.clone(),
given_amount: amount_in.clone(),
checked_token: token_out.clone(),
exact_out: false,
checked_amount: min_amount_out,
swaps: vec![swap],
@@ -83,18 +84,26 @@ pub fn get_solution(
/// A `Result<Transaction, EncodingError>` containing the encoded transaction data for the Tycho
/// router, or an error if encoding fails.
pub fn encode_swap(
component: ProtocolComponent,
token_in: Bytes,
token_out: Bytes,
amount_in: BigUint,
amount_out: BigUint,
component: &ProtocolComponent,
token_in: &Bytes,
token_out: &Bytes,
amount_in: &BigUint,
amount_out: &BigUint,
) -> miette::Result<(Transaction, Solution)> {
let chain: tycho_common::models::Chain = Chain::Ethereum.into();
let protocol_system = component.protocol_system.clone();
let executors_json = json!({
"ethereum": {
(protocol_system):EXECUTOR_ADDRESS
}
})
.to_string();
let chain: tycho_simulation::tycho_common::models::Chain = Chain::Ethereum.into();
let encoder = TychoRouterEncoderBuilder::new()
.chain(chain)
.user_transfer_type(UserTransferType::TransferFrom)
.executors_addresses(EXECUTORS_JSON.to_string())
.executors_addresses(executors_json)
.historical_trade()
.build()
.into_diagnostic()

View File

@@ -7,25 +7,29 @@
use std::{collections::HashMap, str::FromStr, sync::LazyLock};
use alloy::{
primitives::{Address, U256},
rpc::types::{Block, TransactionRequest},
primitives::{keccak256, map::AddressHashMap, Address, FixedBytes, U256},
rpc::types::{state::AccountOverride, Block, TransactionRequest},
};
use miette::{miette, IntoDiagnostic, WrapErr};
use num_bigint::BigUint;
use serde_json::Value;
use tracing::info;
use tycho_common::traits::{AllowanceSlotDetector, BalanceSlotDetector};
use tycho_ethereum::entrypoint_tracer::{
allowance_slot_detector::{AllowanceSlotDetectorConfig, EVMAllowanceSlotDetector},
balance_slot_detector::{BalanceSlotDetectorConfig, EVMBalanceSlotDetector},
};
use tycho_simulation::{
evm::protocol::u256_num::u256_to_biguint, tycho_execution::encoding::models::Solution,
evm::protocol::u256_num::{biguint_to_u256, u256_to_biguint},
tycho_common::{
traits::{AllowanceSlotDetector, BalanceSlotDetector},
Bytes,
},
tycho_ethereum::entrypoint_tracer::{
allowance_slot_detector::{AllowanceSlotDetectorConfig, EVMAllowanceSlotDetector},
balance_slot_detector::{BalanceSlotDetectorConfig, EVMBalanceSlotDetector},
},
tycho_execution::encoding::models::Solution,
};
use crate::rpc::RPCProvider;
const ROUTER_BYTECODE_JSON: &str = include_str!("../../evm/test/router/TychoRouter.runtime.json");
pub const EXECUTORS_JSON: &str = include_str!("../test_executor_addresses.json");
pub const ROUTER_BYTECODE_JSON: &str =
include_str!("../../evm/test/router/TychoRouter.runtime.json");
pub const EXECUTOR_ADDRESS: &str = "0xaE04CA7E9Ed79cBD988f6c536CE11C621166f41B";
// Include all executor bytecode files at compile time
const UNISWAP_V2_BYTECODE_JSON: &str =
@@ -60,62 +64,6 @@ static EXECUTOR_MAPPING: LazyLock<HashMap<&'static str, &'static str>> = LazyLoc
map
});
/// Executor addresses loaded from test_executor_addresses.json at startup
pub static EXECUTOR_ADDRESSES: LazyLock<HashMap<String, Address>> = LazyLock::new(|| {
let json_value: Value = serde_json::from_str(&EXECUTORS_JSON)
.expect("Failed to parse test_executor_addresses.json");
let ethereum_addresses = json_value["ethereum"]
.as_object()
.expect("Missing 'ethereum' key in test_executor_addresses.json");
let mut addresses = HashMap::new();
for (protocol_name, address_value) in ethereum_addresses {
let address_str = address_value
.as_str()
.unwrap_or_else(|| panic!("Invalid address format for protocol '{protocol_name}'"));
let address = Address::from_str(address_str).unwrap_or_else(|_| {
panic!("Invalid address '{address_str}' for protocol '{protocol_name}'")
});
addresses.insert(protocol_name.clone(), address);
}
addresses
});
#[derive(Debug, Clone)]
pub struct StateOverride {
pub code: Option<Vec<u8>>,
pub balance: Option<U256>,
pub state_diff: HashMap<alloy::primitives::Bytes, alloy::primitives::Bytes>,
}
impl StateOverride {
pub fn new() -> Self {
Self { code: None, balance: None, state_diff: HashMap::new() }
}
pub fn with_code(mut self, code: Vec<u8>) -> Self {
self.code = Some(code);
self
}
pub fn with_balance(mut self, balance: U256) -> Self {
self.balance = Some(balance);
self
}
pub fn with_state_diff(
mut self,
slot: alloy::primitives::Bytes,
value: alloy::primitives::Bytes,
) -> Self {
self.state_diff.insert(slot, value);
self
}
}
/// Get executor bytecode JSON based on component ID
fn get_executor_bytecode_json(component_id: &str) -> miette::Result<&'static str> {
for (pattern, executor_json) in EXECUTOR_MAPPING.iter() {
@@ -126,20 +74,9 @@ fn get_executor_bytecode_json(component_id: &str) -> miette::Result<&'static str
Err(miette!("Unknown component type '{}' - no matching executor found", component_id))
}
/// Get executor address for a given component ID
fn get_executor_address(component_id: &str) -> miette::Result<Address> {
if let Some(&address) = EXECUTOR_ADDRESSES.get(component_id) {
return Ok(address);
}
Err(miette!("No executor address found for component type '{}'", component_id))
}
/// Load executor bytecode from embedded constants based on solution component
fn load_executor_bytecode(solution: &Solution) -> miette::Result<Vec<u8>> {
let first_swap = solution.swaps.first().unwrap();
let component_id = &first_swap.component;
let executor_json = get_executor_bytecode_json(&component_id.protocol_system)?;
/// Load executor bytecode from embedded constants based on the protocol system
pub fn load_executor_bytecode(protocol_system: &str) -> miette::Result<Vec<u8>> {
let executor_json = get_executor_bytecode_json(protocol_system)?;
let json_value: serde_json::Value = serde_json::from_str(executor_json)
.into_diagnostic()
@@ -177,93 +114,206 @@ fn calculate_gas_fees(block_header: &Block) -> miette::Result<(U256, U256)> {
Ok((max_fee_per_gas, max_priority_fee_per_gas))
}
/// Set up all state overrides needed for simulation
async fn setup_state_overrides(
/// Calculate storage slot for Solidity mapping.
///
/// The solidity code:
/// keccak256(abi.encodePacked(bytes32(key), bytes32(slot)))
pub fn calculate_executor_storage_slot(key: Address) -> FixedBytes<32> {
// Convert key (20 bytes) to 32-byte left-padded array (uint256)
let mut key_bytes = [0u8; 32];
key_bytes[12..].copy_from_slice(key.as_slice());
// The base of the executor storage slot is 1, since there is only one
// variable that is initialized before it (which is _roles in AccessControl.sol).
// In this case, _roles gets slot 0.
// The slots are given in order to the parent contracts' variables first and foremost.
let slot = U256::from(1);
// Convert U256 slot to 32-byte big-endian array
let slot_bytes = slot.to_be_bytes::<32>();
// Concatenate key_bytes + slot_bytes, then keccak hash
let mut buf = [0u8; 64];
buf[..32].copy_from_slice(&key_bytes);
buf[32..].copy_from_slice(&slot_bytes);
keccak256(buf)
}
/// Sets up state overwrites for the Tycho router and its associated executor.
///
/// This method prepares the router for simulation by:
/// 1. Overriding the router's bytecode with the embedded runtime bytecode
/// 2. Copying executor approval storage from the current block to maintain permissions
/// 3. Overriding the executor's bytecode based on the protocol system
///
/// # Arguments
/// * `router_address` - The address of the Tycho router contract
/// * `protocol_system` - The protocol system identifier (e.g., "uniswap_v2", "vm:balancer_v2")
///
/// # Returns
/// A HashMap containing account overwrites for both the router and executor addresses.
/// The router override includes bytecode and executor approval storage.
/// The executor override includes the appropriate bytecode for the protocol.
///
/// # Errors
/// Returns an error if:
/// - Router bytecode JSON parsing fails
/// - Executor address parsing fails
/// - Storage slot fetching fails
/// - Executor bytecode loading fails
pub async fn setup_router_overwrites(
router_address: Address,
protocol_system: &str,
) -> miette::Result<AddressHashMap<AccountOverride>> {
let json_value: serde_json::Value = serde_json::from_str(ROUTER_BYTECODE_JSON)
.into_diagnostic()
.wrap_err("Failed to parse router JSON")?;
let bytecode_str = json_value["runtimeBytecode"]
.as_str()
.ok_or_else(|| miette::miette!("No runtimeBytecode field found in router JSON"))?;
// Remove 0x prefix if present
let bytecode_hex =
if let Some(stripped) = bytecode_str.strip_prefix("0x") { stripped } else { bytecode_str };
let router_bytecode = hex::decode(bytecode_hex)
.into_diagnostic()
.wrap_err("Failed to decode router bytecode from hex")?;
// Start with the router bytecode override
let mut state_overwrites = AddressHashMap::default();
let mut tycho_router_override = AccountOverride::default().with_code(router_bytecode);
// Find executor address approval storage slot
let executor_address = Address::from_str(EXECUTOR_ADDRESS).into_diagnostic()?;
let storage_slot = calculate_executor_storage_slot(executor_address);
// The executors mapping starts at storage value 1
let storage_value = FixedBytes::<32>::from(U256::ONE);
tycho_router_override =
tycho_router_override.with_state_diff(vec![(storage_slot, storage_value)]);
state_overwrites.insert(router_address, tycho_router_override);
// Add bytecode overwrite for the executor
let executor_bytecode = load_executor_bytecode(protocol_system)?;
state_overwrites
.insert(executor_address, AccountOverride::default().with_code(executor_bytecode.to_vec()));
Ok(state_overwrites)
}
/// Sets up state overwrites for user accounts and tokens required for swap simulation.
///
/// This method prepares the user environment for historical block simulation by:
/// 1. Providing the user with sufficient ETH balance (100 ETH) for gas payments
/// 2. For ETH swaps: Adding the swap amount to the user's ETH balance
/// 3. For ERC20 swaps: Overriding token balance and allowance storage slots to ensure:
/// - User has sufficient tokens for the swap
/// - Router has unlimited allowance to spend user's tokens
///
/// The function uses EVM storage slot detection to find the correct storage locations
/// for token balances and allowances, then applies state overwrites to simulate the
/// required pre-conditions without executing actual token transfers.
///
/// # Arguments
/// * `solution` - The encoded swap solution containing token and amount information
/// * `transaction` - The transaction details for determining router address
/// * `user_address` - The address of the user performing the swap
/// * `rpc_url` - RPC endpoint URL for storage slot detection
/// * `block` - The historical block context for storage queries
///
/// # Returns
/// A HashMap containing account overwrites for:
/// - User account: ETH balance override
/// - Token contract: Balance and allowance storage slot overwrites (for ERC20 swaps)
///
/// # Errors
/// Returns an error if:
/// - Storage slot detection fails for balance or allowance
/// - Token address parsing fails
/// - RPC queries for storage detection fail
async fn setup_user_overwrites(
solution: &Solution,
transaction: &tycho_simulation::tycho_execution::encoding::models::Transaction,
user_address: Address,
executor_bytecode: &[u8],
rpc_url: String,
block: &Block,
) -> miette::Result<HashMap<Address, StateOverride>> {
let mut state_overwrites = HashMap::new();
let token_address = Address::from_slice(&solution.given_token[..20]);
// Extract executor address from the encoded solution's swaps data.
// The solution should only have one swap for the test, so this should be safe.
let executor_address = if let Some(first_swap) = solution.swaps.first() {
get_executor_address(&first_swap.component.protocol_system)?
} else {
return Err(miette!("No swaps in solution - cannot determine executor address"));
};
// Add bytecode overwrite for the executor
state_overwrites
.insert(executor_address, StateOverride::new().with_code(executor_bytecode.to_vec()));
) -> miette::Result<AddressHashMap<AccountOverride>> {
let mut overwrites = AddressHashMap::default();
// Add ETH balance override for the user to ensure they have enough gas funds
state_overwrites.insert(
user_address,
StateOverride::new().with_balance(U256::from_str("100000000000000000000").unwrap()), // 100 ETH
);
let mut eth_balance = U256::from_str("100000000000000000000").unwrap(); // 100 ETH
let detector = EVMBalanceSlotDetector::new(BalanceSlotDetectorConfig {
rpc_url: rpc_url.clone(),
..Default::default()
})
.into_diagnostic()?;
let token_address = Address::from_slice(&solution.given_token[..20]);
// If given token is ETH, add the given amount to the balance
if solution.given_token == Bytes::zero(20) {
eth_balance += biguint_to_u256(&solution.given_amount);
// if the given token is not ETH, do balance and allowance slots overwrites
} else {
let detector = EVMBalanceSlotDetector::new(BalanceSlotDetectorConfig {
rpc_url: rpc_url.clone(),
..Default::default()
})
.into_diagnostic()?;
let results = detector
.detect_balance_slots(
&[solution.given_token.clone()],
(**user_address).into(),
(*block.header.hash).into(),
)
.await;
let results = detector
.detect_balance_slots(
std::slice::from_ref(&solution.given_token),
(**user_address).into(),
(*block.header.hash).into(),
)
.await;
let balance_slot =
if let Some(Ok((_storage_addr, slot))) = results.get(&solution.given_token.clone()) {
slot
} else {
return Err(miette!("Couldn't find balance storage slot for token {token_address}"));
};
let balance_slot =
if let Some(Ok((_storage_addr, slot))) = results.get(&solution.given_token.clone()) {
slot
} else {
return Err(miette!("Couldn't find balance storage slot for token {token_address}"));
};
let detector = EVMAllowanceSlotDetector::new(AllowanceSlotDetectorConfig {
rpc_url,
..Default::default()
})
.into_diagnostic()?;
let detector = EVMAllowanceSlotDetector::new(AllowanceSlotDetectorConfig {
rpc_url,
..Default::default()
})
.into_diagnostic()?;
let results = detector
.detect_allowance_slots(
&[solution.given_token.clone()],
(**user_address).into(),
transaction.to.clone(), // tycho router
(*block.header.hash).into(),
)
.await;
let results = detector
.detect_allowance_slots(
std::slice::from_ref(&solution.given_token),
(**user_address).into(),
transaction.to.clone(), // tycho router
(*block.header.hash).into(),
)
.await;
let allowance_slot =
if let Some(Ok((_storage_addr, slot))) = results.get(&solution.given_token.clone()) {
let allowance_slot = if let Some(Ok((_storage_addr, slot))) =
results.get(&solution.given_token.clone())
{
slot
} else {
return Err(miette!("Couldn't find allowance storage slot for token {token_address}"));
};
state_overwrites.insert(
token_address,
StateOverride::new()
.with_state_diff(
alloy::primitives::Bytes::from(allowance_slot.to_vec()),
alloy::primitives::Bytes::from(U256::MAX.to_be_bytes::<32>()),
)
.with_state_diff(
alloy::primitives::Bytes::from(balance_slot.to_vec()),
alloy::primitives::Bytes::from(U256::MAX.to_be_bytes::<32>()),
),
);
overwrites.insert(
token_address,
AccountOverride::default().with_state_diff(vec![
(
alloy::primitives::B256::from_slice(allowance_slot),
alloy::primitives::B256::from_slice(&U256::MAX.to_be_bytes::<32>()),
),
(
alloy::primitives::B256::from_slice(balance_slot),
alloy::primitives::B256::from_slice(
&biguint_to_u256(&solution.given_amount).to_be_bytes::<32>(),
),
),
]),
);
}
overwrites.insert(user_address, AccountOverride::default().with_balance(eth_balance));
Ok(state_overwrites)
Ok(overwrites)
}
/// Simulate a trade using eth_call for historical blocks
@@ -271,10 +321,11 @@ pub async fn simulate_trade_with_eth_call(
rpc_provider: &RPCProvider,
transaction: &tycho_simulation::tycho_execution::encoding::models::Transaction,
solution: &Solution,
block_number: u64,
block: &Block,
) -> miette::Result<BigUint> {
let executor_bytecode = load_executor_bytecode(solution)?;
let first_swap = solution.swaps.first().unwrap();
let protocol_system = &first_swap.component.protocol_system;
let user_address = Address::from_slice(&solution.sender[..20]);
let (max_fee_per_gas, max_priority_fee_per_gas) = calculate_gas_fees(block)?;
// Convert main transaction to alloy TransactionRequest
@@ -295,28 +346,24 @@ pub async fn simulate_trade_with_eth_call(
);
let tycho_router_address = Address::from_slice(&transaction.to[..20]);
// Copy router storage and code from current block to historical block
let router_override = rpc_provider
.copy_contract_storage_and_code(tycho_router_address, ROUTER_BYTECODE_JSON)
let router_overwrites = setup_router_overwrites(tycho_router_address, protocol_system)
.await
.wrap_err("Failed to create router override")?;
// Set up state overrides including router override
let mut state_overwrites = setup_state_overrides(
let mut user_overwrites = setup_user_overwrites(
solution,
transaction,
user_address,
&executor_bytecode,
rpc_provider.url.to_string(),
block,
)
.await?; // Include executor override for historical blocks
.await?;
// Add the router override
state_overwrites.insert(tycho_router_address, router_override);
// Merge router overwrites with user overwrites
user_overwrites.extend(router_overwrites);
let execution_amount_out = rpc_provider
.simulate_transactions_with_tracing(execution_tx, block_number, state_overwrites)
.simulate_transactions_with_tracing(execution_tx, block.number(), user_overwrites)
.await
.map_err(|e| {
info!("Execution transaction failed with error: {}", e);

View File

@@ -9,9 +9,10 @@ mod tycho_rpc;
mod tycho_runner;
mod utils;
use std::{fmt::Display, path::PathBuf};
use std::{env, fmt::Display, path::PathBuf};
use clap::Parser;
use dotenv::dotenv;
use miette::{miette, IntoDiagnostic, WrapErr};
use tracing::info;
use tracing_subscriber::EnvFilter;
@@ -78,6 +79,9 @@ impl Args {
}
fn main() -> miette::Result<()> {
// Load .env file before setting up logging
dotenv().ok();
tracing_subscriber::fmt()
.with_env_filter(EnvFilter::from_default_env())
.with_target(false)
@@ -90,6 +94,10 @@ fn main() -> miette::Result<()> {
}
info!("{version}");
let rpc_url = env::var("RPC_URL")
.into_diagnostic()
.wrap_err("Missing RPC_URL in environment")?;
let args = Args::parse();
let test_runner = TestRunner::new(
@@ -99,7 +107,8 @@ fn main() -> miette::Result<()> {
args.db_url,
args.vm_simulation_traces,
args.execution_traces,
);
rpc_url,
)?;
test_runner.run_tests()
}

View File

@@ -1,10 +1,10 @@
use std::{collections::HashMap, str::FromStr};
use std::str::FromStr;
use alloy::{
contract::{ContractInstance, Interface},
dyn_abi::DynSolValue,
eips::eip1898::BlockId,
primitives::{address, keccak256, map::AddressHashMap, Address, FixedBytes, U256},
primitives::{address, map::AddressHashMap, Address, U256},
providers::{Provider, ProviderBuilder},
rpc::types::{
state::AccountOverride,
@@ -20,12 +20,9 @@ use alloy::{
use miette::{IntoDiagnostic, WrapErr};
use serde_json::Value;
use tracing::info;
use tycho_common::Bytes;
use tycho_simulation::tycho_common::Bytes;
use crate::{
execution::{StateOverride, EXECUTOR_ADDRESSES},
traces::print_call_trace,
};
use crate::traces::print_call_trace;
const NATIVE_ALIASES: &[Address] = &[
address!("0x0000000000000000000000000000000000000000"),
@@ -101,139 +98,13 @@ impl RPCProvider {
.and_then(|block_opt| block_opt.ok_or_else(|| miette::miette!("Block not found")))
}
/// Helper function to get the contract's storage at the given slot at the latest block.
pub async fn get_storage_at(
&self,
contract_address: Address,
slot: FixedBytes<32>,
) -> miette::Result<FixedBytes<32>> {
let provider = ProviderBuilder::new().connect_http(self.url.clone());
let storage_value = provider
.get_storage_at(contract_address, slot.into())
.await
.into_diagnostic()
.wrap_err("Failed to fetch storage slot")?;
Ok(storage_value.into())
}
pub async fn copy_contract_storage_and_code(
&self,
contract_address: Address,
router_bytecode_json: &str,
) -> miette::Result<StateOverride> {
let json_value: serde_json::Value = serde_json::from_str(router_bytecode_json)
.into_diagnostic()
.wrap_err("Failed to parse router JSON")?;
let bytecode_str = json_value["runtimeBytecode"]
.as_str()
.ok_or_else(|| miette::miette!("No runtimeBytecode field found in router JSON"))?;
// Remove 0x prefix if present
let bytecode_hex = if let Some(stripped) = bytecode_str.strip_prefix("0x") {
stripped
} else {
bytecode_str
};
let router_bytecode = hex::decode(bytecode_hex)
.into_diagnostic()
.wrap_err("Failed to decode router bytecode from hex")?;
// Start with the router bytecode override
let mut state_override = StateOverride::new().with_code(router_bytecode);
for (protocol_name, &executor_address) in EXECUTOR_ADDRESSES.iter() {
let storage_slot = self.calculate_executor_storage_slot(executor_address);
match self
.get_storage_at(contract_address, storage_slot)
.await
{
Ok(value) => {
state_override = state_override.with_state_diff(
alloy::primitives::Bytes::from(storage_slot.to_vec()),
alloy::primitives::Bytes::from(value.to_vec()),
);
}
Err(e) => {
info!(
"Failed to fetch executor approval for {} ({:?}): {}",
protocol_name, executor_address, e
);
}
}
}
Ok(state_override)
}
/// Calculate storage slot for Solidity mapping.
///
/// The solidity code:
/// keccak256(abi.encodePacked(bytes32(key), bytes32(slot)))
pub fn calculate_executor_storage_slot(&self, key: Address) -> FixedBytes<32> {
// Convert key (20 bytes) to 32-byte left-padded array (uint256)
let mut key_bytes = [0u8; 32];
key_bytes[12..].copy_from_slice(key.as_slice());
// The base of the executor storage slot is 1, since there is only one
// variable that is initialized before it (which is _roles in AccessControl.sol).
// In this case, _roles gets slot 0.
// The slots are given in order to the parent contracts' variables first and foremost.
let slot = U256::from(1);
// Convert U256 slot to 32-byte big-endian array
let slot_bytes = slot.to_be_bytes::<32>();
// Concatenate key_bytes + slot_bytes, then keccak hash
let mut buf = [0u8; 64];
buf[..32].copy_from_slice(&key_bytes);
buf[32..].copy_from_slice(&slot_bytes);
keccak256(buf)
}
fn bytes_to_fixed_32(bytes: &[u8]) -> [u8; 32] {
let mut arr = [0u8; 32];
let len = bytes.len().min(32);
// Right-pad by copying to the end of the array
arr[32 - len..].copy_from_slice(&bytes[bytes.len() - len..]);
arr
}
pub async fn simulate_transactions_with_tracing(
&self,
transaction: TransactionRequest,
block_number: u64,
state_overwrites: HashMap<Address, StateOverride>,
state_overwrites: AddressHashMap<AccountOverride>,
) -> miette::Result<U256> {
let provider = ProviderBuilder::new().connect_http(self.url.clone());
// Convert our StateOverride to alloy's state override format
let mut alloy_state_overrides = AddressHashMap::default();
for (address, override_data) in state_overwrites {
let mut account_override = AccountOverride::default();
if let Some(code) = override_data.code {
account_override.code = Some(alloy::primitives::Bytes::from(code));
}
if let Some(balance) = override_data.balance {
account_override.balance = Some(balance);
}
if !override_data.state_diff.is_empty() {
// Convert Bytes to FixedBytes<32> for storage slots
let mut state_diff = HashMap::default();
for (slot, value) in override_data.state_diff {
let slot_bytes = Self::bytes_to_fixed_32(&slot);
let value_bytes = Self::bytes_to_fixed_32(&value);
state_diff.insert(FixedBytes(slot_bytes), FixedBytes(value_bytes));
}
account_override.state_diff = Some(state_diff);
}
alloy_state_overrides.insert(address, account_override);
}
// Configure tracing options - use callTracer for better formatted results
let tracing_options = GethDebugTracingOptions {
@@ -247,10 +118,10 @@ impl RPCProvider {
let trace_options = GethDebugTracingCallOptions {
tracing_options,
state_overrides: if alloy_state_overrides.is_empty() {
state_overrides: if state_overwrites.is_empty() {
None
} else {
Some(alloy_state_overrides)
Some(state_overwrites)
},
block_overrides: None,
};

File diff suppressed because it is too large Load Diff

View File

@@ -5,8 +5,8 @@
use alloy::dyn_abi::{DynSolType, DynSolValue};
use colored::Colorize;
use foundry_evm::traces::identifier::SignaturesIdentifier;
use serde_json::Value;
use tycho_simulation::foundry_evm::traces::identifier::SignaturesIdentifier;
/// Decode method selectors and return function info
pub async fn decode_method_selector_with_info(input: &str) -> Option<(String, Vec<DynSolType>)> {
@@ -84,18 +84,16 @@ pub async fn decode_function_with_params(input: &str) -> Option<String> {
if input.len() > 10 {
let calldata_hex = &input[10..]; // Skip the 4-byte selector
if let Ok(calldata) = hex::decode(calldata_hex) {
if let Ok(decoded_values) =
if let Ok(DynSolValue::Tuple(values)) =
DynSolType::Tuple(param_types.clone()).abi_decode(&calldata)
{
if let DynSolValue::Tuple(values) = decoded_values {
let formatted_params: Vec<String> = values
.iter()
.zip(param_types.iter())
.map(|(value, ty)| format_parameter_value(value, ty))
.collect();
let formatted_params: Vec<String> = values
.iter()
.zip(param_types.iter())
.map(|(value, ty)| format_parameter_value(value, ty))
.collect();
return Some(format!("{}({})", name, formatted_params.join(", ")));
}
return Some(format!("{}({})", name, formatted_params.join(", ")));
}
}
}
@@ -255,11 +253,6 @@ pub async fn print_call_trace(call: &Value, depth: usize) {
.any(|field| call_obj.get(*field).is_some());
let call_failed = has_error || has_revert || has_other_error;
// Debug: if there's any failure, print all fields to help identify the error structure
if call_failed && depth <= 2 {
eprintln!("DEBUG: Failed call at depth {}: {:#?}", depth, call_obj);
}
// Create tree structure prefix
let tree_prefix = if depth == 0 { "".to_string() } else { " ".repeat(depth) + "├─ " };
@@ -295,19 +288,19 @@ pub async fn print_call_trace(call: &Value, depth: usize) {
let mut found_error = false;
if let Some(error) = call_obj.get("error") {
println!("{}{}", result_indent, format!("[Error] {}", error));
println!("{result_indent} [Error] {error}");
found_error = true;
}
if let Some(revert_reason) = call_obj.get("revertReason") {
println!("{}{}", result_indent, format!("[Revert] {}", revert_reason));
println!("{}[Revert] {}", result_indent, revert_reason);
found_error = true;
}
// Check for other possible error fields
for error_field in ["revert", "reverted", "message", "errorMessage", "reason"] {
if let Some(error_val) = call_obj.get(error_field) {
println!("{}{}", result_indent, format!("[{}] {}", error_field, error_val));
println!("{}[{}] {}", result_indent, error_field, error_val);
found_error = true;
}
}
@@ -319,28 +312,27 @@ pub async fn print_call_trace(call: &Value, depth: usize) {
{
if !output.is_empty() && output != "0x" {
// Try to decode revert reason from output if it looks like revert data
if output.starts_with("0x08c379a0") {
if let Some(stripped) = output.strip_prefix("0x08c379a0") {
// Error(string) selector
if let Ok(decoded) = hex::decode(&output[10..]) {
if let Ok(reason) = alloy::dyn_abi::DynSolType::String.abi_decode(&decoded)
if let Ok(decoded) = hex::decode(stripped) {
if let Ok(alloy::dyn_abi::DynSolValue::String(reason_str)) =
alloy::dyn_abi::DynSolType::String.abi_decode(&decoded)
{
if let alloy::dyn_abi::DynSolValue::String(reason_str) = reason {
println!(
"{}{}",
result_indent,
format!("[Revert] {}", reason_str).red()
);
found_error = true;
}
println!(
"{}{}",
result_indent,
format!("[Revert] {}", reason_str).red()
);
found_error = true;
}
}
}
if !found_error {
println!("{}{}", result_indent, format!("[Return] {}", output));
println!("{}[Return] {}", result_indent, output);
}
} else if !found_error {
println!("{}{}", result_indent, "[Return]");
println!("{}[Return]", result_indent);
}
}

View File

@@ -1,14 +1,16 @@
use std::{collections::HashMap, error::Error as StdError, fmt};
use tracing::debug;
use tycho_client::{rpc::RPCClient, HttpRPCClient};
use tycho_common::{
dto::{
Chain, PaginationParams, ProtocolComponent, ProtocolComponentsRequestBody, ResponseAccount,
ResponseProtocolState, ResponseToken, StateRequestBody, VersionParam,
use tycho_simulation::{
tycho_client::{rpc::RPCClient, HttpRPCClient},
tycho_common::{
dto::{
Chain, PaginationParams, ProtocolComponent, ProtocolComponentsRequestBody,
ResponseAccount, ResponseProtocolState, ResponseToken, StateRequestBody, VersionParam,
},
models::token::Token,
Bytes,
},
models::token::Token,
Bytes,
};
/// Custom error type for RPC operations
@@ -33,8 +35,8 @@ impl From<Box<dyn StdError>> for RpcError {
}
}
impl From<tycho_client::RPCError> for RpcError {
fn from(error: tycho_client::RPCError) -> Self {
impl From<tycho_simulation::tycho_client::RPCError> for RpcError {
fn from(error: tycho_simulation::tycho_client::RPCError) -> Self {
RpcError::ClientError(error.to_string())
}
}
@@ -79,7 +81,7 @@ impl TychoClient {
) -> Result<Vec<ResponseProtocolState>, RpcError> {
let chunk_size = 100;
let concurrency = 1;
let version: tycho_common::dto::VersionParam = VersionParam::default();
let version: tycho_simulation::tycho_common::dto::VersionParam = VersionParam::default();
let protocol_states = self
.http_client

View File

@@ -9,15 +9,16 @@ use std::{
use miette::{IntoDiagnostic, WrapErr};
use tracing::{debug, info};
use crate::config::ProtocolComponentWithTestConfig;
pub struct TychoRunner {
db_url: String,
initialized_accounts: Vec<String>,
}
// TODO: Currently Tycho-Indexer cannot be run as a lib. We need to expose the entrypoints to allow
// running it as a lib
pub struct TychoRpcServer {
sender: Sender<bool>,
thread_handle: thread::JoinHandle<()>,
}
impl TychoRunner {
pub fn new(db_url: String, initialized_accounts: Vec<String>) -> Self {
Self { db_url, initialized_accounts }
@@ -93,16 +94,7 @@ impl TychoRunner {
Ok(())
}
pub fn run_with_rpc_server<F, R>(
&self,
func: F,
expected_components: &Vec<ProtocolComponentWithTestConfig>,
start_block: u64,
stop_block: u64,
) -> miette::Result<R>
where
F: FnOnce(&Vec<ProtocolComponentWithTestConfig>, u64, u64) -> R,
{
pub fn start_rpc_server(&self) -> miette::Result<TychoRpcServer> {
let (tx, rx): (Sender<bool>, Receiver<bool>) = mpsc::channel();
let db_url = self.db_url.clone();
@@ -140,18 +132,22 @@ impl TychoRunner {
// Give the RPC server time to start
thread::sleep(Duration::from_secs(3));
// Run the provided function
let result = func(expected_components, start_block, stop_block);
Ok(TychoRpcServer { sender: tx, thread_handle: rpc_thread })
}
tx.send(true)
.expect("Failed to send termination message");
pub fn stop_rpc_server(&self, server: TychoRpcServer) -> miette::Result<()> {
server
.sender
.send(true)
.into_diagnostic()
.wrap_err("Failed to send termination message")?;
// Wait for the RPC thread to finish
if rpc_thread.join().is_err() {
if server.thread_handle.join().is_err() {
eprintln!("Failed to join RPC thread");
}
Ok(result)
Ok(())
}
// Helper method to handle process output in separate threads

View File

@@ -1,14 +0,0 @@
{
"ethereum": {
"uniswap_v2": "0xaE04CA7E9Ed79cBD988f6c536CE11C621166f41B",
"uniswap_v3": "0xbab7124C9662B15C6b9AF0b1f329907dD55a24FC",
"uniswap_v4": "0x2C2EaB81Cf983602153E67b1890164BC4CABC6ed",
"vm:balancer_v2": "0xB5b8dc3F0a1Be99685a0DEd015Af93bFBB55C411",
"vm:balancer_v3": "0xec5cE4bF6FbcB7bB0148652c92a4AEC8c1d474Ec",
"sushiswap_v2": "0x2017ad7035D781C14699C8E44ed62d3083723A18",
"pancakeswap_v2": "0xC9db3FEB380E4fd9af239e2595ECdEcE3b5c34A4",
"pancakeswap_v3": "0x9D32e9F569B22Ae8d8C6f788037C1CD53632A059",
"vm:curve": "0x879F3008D96EBea0fc584aD684c7Df31777F3165",
"vm:maverick_v2": "0xF35e3F5F205769B41508A18787b62A21bC80200B"
}
}

View File

@@ -24,6 +24,7 @@ tests:
coins: "0x5b22307836623137353437346538393039346334346461393862393534656564656163343935323731643066222c22307861306238363939316336323138623336633164313964346132653965623063653336303665623438222c22307864616331376639353864326565353233613232303632303639393435393763313364383331656337225d" # ["0x6b175474e89094c44da98b954eedeac495271d0f","0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48","0xdac17f958d2ee523a2206206994597c13d831ec7"]
creation_tx: "0x20793bbf260912aae189d5d261ff003c9b9166da8191d8f9d63ff1c7722f3ac6"
skip_simulation: false
skip_execution: true # the block is before EIP-1559 where base fee was added. The current testing logic doesn't work without it
# Unique pool (no factory) steth - 0xdc24316b9ae028f1497c275eb9192a3ea0f67022
- name: test_steth
@@ -41,6 +42,7 @@ tests:
coins: "0x5b22307865656565656565656565656565656565656565656565656565656565656565656565656565656565222c22307861653761623936353230646533613138653565313131623565616162303935333132643766653834225d" # ["0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee","0xae7ab96520de3a18e5e111b5eaab095312d7fe84"]
creation_tx: "0xfac67ecbd423a5b915deff06045ec9343568edaec34ae95c43d35f2c018afdaa"
skip_simulation: false
skip_execution: true # the block is before EIP-1559 where base fee was added. The current testing logic doesn't work without it
# Unique pool (no factory) tricrypto2 - 0xd51a44d3fae010294c616388b506acda1bfaae46
- name: test_tricrypto2
@@ -59,6 +61,7 @@ tests:
coins: "0x5b22307864616331376639353864326565353233613232303632303639393435393763313364383331656337222c22307832323630666163356535353432613737336161343466626366656466376331393362633263353939222c22307863303261616133396232323366653864306130653563346632376561643930383363373536636332225d" # ["0xdac17f958d2ee523a2206206994597c13d831ec7","0x2260fac5e5542a773aa44fbcfedf7c193bc2c599","0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"]
creation_tx: "0xdafb6385ed988ce8aacecfe1d97b38ea5e60b1ebce74d2423f71ddd621680138"
skip_simulation: false
skip_execution: true # the block is before EIP-1559 where base fee was added. The current testing logic doesn't work without it
# Unique pool (no factory) susd - 0xa5407eae9ba41422680e2e00537571bcc53efbfd
- name: test_susd
@@ -78,6 +81,7 @@ tests:
coins: "0x5b22307836623137353437346538393039346334346461393862393534656564656163343935323731643066222c22307861306238363939316336323138623336633164313964346132653965623063653336303665623438222c22307864616331376639353864326565353233613232303632303639393435393763313364383331656337222c22307835376162316563323864313239373037303532646634646634313864353861326434366435663531225d" # ["0x6b175474e89094c44da98b954eedeac495271d0f","0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48","0xdac17f958d2ee523a2206206994597c13d831ec7","0x57ab1ec28d129707052df4df418d58a2d46d5f51"]
creation_tx: "0x51aca4a03a395de8855fa2ca59b7febe520c2a223e69c502066162f7c1a95ec2"
skip_simulation: false
skip_execution: true # the block is before EIP-1559 where base fee was added. The current testing logic doesn't work without it
# Unique pool (no factory) fraxusdc - 0xdcef968d416a41cdac0ed8702fac8128a64241a2
- name: test_fraxusdc
@@ -95,6 +99,7 @@ tests:
coins: "0x5b22307838353364393535616365663832326462303538656238353035393131656437376631373562393965222c22307861306238363939316336323138623336633164313964346132653965623063653336303665623438225d" # ["0x853d955acef822db058eb8505911ed77f175b99e","0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"]
creation_tx: "0x1f4254004ce9e19d4eb742ee5a69d30f29085902d976f73e97c44150225ef775"
skip_simulation: false
skip_execution: true # the block is before the Shanghai upgrade and our router needs functionality introduced there
# CryptoSwapNG factory 0x6A8cbed756804B16E05E741eDaBd5cB544AE21bf - PlainPool
- name: test_crypto_swap_ng_factory_plain_pool
@@ -120,6 +125,7 @@ tests:
coins: "0x5b22307834633965646435383532636439303566303836633735396538333833653039626666316536386233222c22307861306238363939316336323138623336633164313964346132653965623063653336303665623438225d" # ["0x4c9edd5852cd905f086c759e8383e09bff1e68b3","0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"]
creation_tx: "0x6f4438aa1785589e2170599053a0cdc740d8987746a4b5ad9614b6ab7bb4e550"
skip_simulation: false
skip_execution: true # the block is before the Cancun upgrade and our router needs functionality introduced there (transient storage)
# CryptoSwapNG factory 0x6A8cbed756804B16E05E741eDaBd5cB544AE21bf - MetaPool
- name: test_crypto_swap_ng_factory_metapool
@@ -143,6 +149,7 @@ tests:
coins: "0x5b22307838363533373733363730353435313665313730313463636465643165376438313465646339636534222c22307861353538386637636466353630383131373130613264383264336339633939373639646231646362225d" # ["0x865377367054516e17014ccded1e7d814edc9ce4","0xa5588f7cdf560811710a2d82d3c9c99769db1dcb"]
creation_tx: "0x3cfeecae1b43086ee5705f89b803e21eb0492d7d5db06c229586db8fc72f5665"
skip_simulation: false
skip_execution: true # the block is before the Cancun upgrade and our router needs functionality introduced there (transient storage)
# # Metapool factory 0xB9fC157394Af804a3578134A6585C0dc9cc990d4 - MetaPool
# - name: test_metapool_factory_metapool
@@ -184,6 +191,8 @@ tests:
coins: "0x5b22307865393633336335326634633862376264656230386334613766653861356331623834616663663637222c22307837376530366339656363663265373937666434363261393262366437363432656638356230613434225d" # ["0xe9633c52f4c8b7bdeb08c4a7fe8a5c1b84afcf67","0x77e06c9eccf2e797fd462a92b6d7642ef85b0a44"]
creation_tx: "0xeb34c90d352f18ffcfe78b7e393e155f0314acf06c54d1ac9996e4ee5a9b4742"
skip_simulation: false
skip_execution: true # the block is before the Cancun upgrade and our router needs functionality introduced there (transient storage)
- id: "0x3f67dc2AdBA4B1beB6A48c30AB3AFb1c1440d35B"
tokens:
- "0xe9633C52f4c8B7BDeb08c4A7fE8a5c1B84AFCf67"
@@ -196,6 +205,7 @@ tests:
coins: "0x5b22307865393633336335326634633862376264656230386334613766653861356331623834616663663637222c22307837376530366339656363663265373937666434363261393262366437363432656638356230613434225d" # ["0xe9633c52f4c8b7bdeb08c4a7fe8a5c1b84afcf67","0x77e06c9eccf2e797fd462a92b6d7642ef85b0a44"]
creation_tx: "0x455559b43afaf429c15c1d807fd7f5dd47be30f6411a854499f719b944f4c024"
skip_simulation: true # Reason: this pool has no liquidity at stop_block
skip_execution: true # the block is before the Cancun upgrade and our router needs functionality introduced there (transient storage)
# CryptoPool factory 0xF18056Bbd320E96A48e3Fbf8bC061322531aac99
- name: test_cryptopool_factory
@@ -217,6 +227,7 @@ tests:
coins: "0x5b22307830346331353462363663623334306633616532343131316363373637653031383465643030636336222c22307834353931646266663632363536653738353961666535653435663666343764333636396662623238225d" # ["0x04c154b66cb340f3ae24111cc767e0184ed00cc6","0x4591dbff62656e7859afe5e45f6f47d3669fbb28"]
creation_tx: "0xa89c09a7e0dfd84f3a294b8df4f33cc4a623e6d52deee357457afe2591ea596f"
skip_simulation: false
skip_execution: true # the block is before the Cancun upgrade and our router needs functionality introduced there (transient storage)
- id: "0x6c9Fe53cC13b125d6476E5Ce2b76983bd5b7A112"
tokens:
- "0x35fA164735182de50811E8e2E824cFb9B6118ac2"
@@ -230,6 +241,7 @@ tests:
coins: "0x5b22307833356661313634373335313832646535303831316538653265383234636662396236313138616332222c22307866393531653333356166623238393335336463323439653832393236313738656163376465643738225d" # ["0x35fa164735182de50811e8e2e824cfb9b6118ac2","0xf951e335afb289353dc249e82926178eac7ded78"]
creation_tx: "0xa5b13d50c56242f7994b8e1339032bb4c6f9ac3af3054d4eae3ce9e32e3c1a50"
skip_simulation: true # Reason: this pool has no liquidity at stop_block
skip_execution: true # the block is before the Cancun upgrade and our router needs functionality introduced there (transient storage)
# CryptoPool factory 0xF18056Bbd320E96A48e3Fbf8bC061322531aac99 - with ETH
- name: test_cryptopool_factory_with_eth
@@ -251,6 +263,7 @@ tests:
coins: "0x5b22307865656565656565656565656565656565656565656565656565656565656565656565656565656565222c22307835353239366636396634306561366432306534373835333363313561366230386236353465373538225d" # ["0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee","0x55296f69f40ea6d20e478533c15a6b08b654e758"]
creation_tx: "0x52f0f76d97e77579eebd32876de99f656930a99131dc4c4f1dec005786c8782b"
skip_simulation: false
skip_execution: true # the block is before the Cancun upgrade and our router needs functionality introduced there (transient storage)
# Tricrypto factory 0x0c0e5f2fF0ff18a3be9b835635039256dC4B4963
- name: test_tricrypto_factory
@@ -273,6 +286,7 @@ tests:
coins: "0x5b22307861306238363939316336323138623336633164313964346132653965623063653336303665623438222c22307832323630666163356535353432613737336161343466626366656466376331393362633263353939222c22307865656565656565656565656565656565656565656565656565656565656565656565656565656565225d" # ["0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48","0x2260fac5e5542a773aa44fbcfedf7c193bc2c599","0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"]
creation_tx: "0x2bd59c19f993b83729fb23498f897a58567c6f0b3ee2f00613ba515a7b19fe23"
skip_simulation: false
skip_execution: true # the block is before the Cancun upgrade and our router needs functionality introduced there (transient storage)
# Twocrypto factory 0x98ee851a00abee0d95d08cf4ca2bdce32aeaaf7f
- name: test_twocrypto_factory
@@ -313,6 +327,7 @@ tests:
coins: "0x5b22307864616331376639353864326565353233613232303632303639393435393763313364383331656337222c22307866393339653061303366623037663539613733333134653733373934626530653537616331623465225d" # ["0xdac17f958d2ee523a2206206994597c13d831ec7","0xf939e0a03fb07f59a73314e73794be0e57ac1b4e"]
creation_tx: "0x40b25773bf8ea673434277d279af40a85b09072072e7004e9048a2ec0f0dd5a0"
skip_simulation: false
skip_execution: true # the block is before the Cancun upgrade and our router needs functionality introduced there (transient storage)
# StableSwap factory 0x4F8846Ae9380B90d2E71D5e3D042dff3E7ebb40d - Metapool
# - name: test_stableswap_factory_meta_pool