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:
460
protocol-testing/Cargo.lock
generated
460
protocol-testing/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -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"] }
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user