diff --git a/protocol-testing/Cargo.lock b/protocol-testing/Cargo.lock index 24e27f2..3cdc38d 100644 --- a/protocol-testing/Cargo.lock +++ b/protocol-testing/Cargo.lock @@ -596,7 +596,7 @@ dependencies = [ "alloy-serde", "alloy-sol-types", "arbitrary", - "itertools 0.13.0", + "itertools 0.14.0", "serde", "serde_json", "serde_with", @@ -909,7 +909,7 @@ checksum = "710e8eae58854cdc1790fcb56cca04d712a17be849eeb81da2a724bf4bae2bc4" dependencies = [ "anstyle", "memchr", - "unicode-width", + "unicode-width 0.2.0", ] [[package]] @@ -1752,7 +1752,7 @@ dependencies = [ "strsim", "terminal_size", "unicase", - "unicode-width", + "unicode-width 0.2.0", ] [[package]] @@ -1870,7 +1870,7 @@ checksum = "4a65ebfec4fb190b6f90e944a817d60499ee0744e582530e2c9900a22e591d9a" dependencies = [ "crossterm", "unicode-segmentation", - "unicode-width", + "unicode-width 0.2.0", ] [[package]] @@ -1882,7 +1882,7 @@ dependencies = [ "encode_unicode", "libc", "once_cell", - "unicode-width", + "unicode-width 0.2.0", "windows-sys 0.59.0", ] @@ -2382,7 +2382,7 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -2919,7 +2919,7 @@ dependencies = [ "foundry-compilers-core", "futures-util", "home", - "itertools 0.13.0", + "itertools 0.14.0", "path-slash", "rayon", "semver 1.0.26", @@ -4020,7 +4020,7 @@ dependencies = [ "console", "number_prefix", "portable-atomic", - "unicode-width", + "unicode-width 0.2.0", "web-time", ] @@ -4493,6 +4493,28 @@ dependencies = [ "thiserror 1.0.69", ] +[[package]] +name = "miette" +version = "7.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f98efec8807c63c752b5bd61f862c165c115b0a35685bdcfd9238c7aeb592b7" +dependencies = [ + "cfg-if", + "miette-derive", + "unicode-width 0.1.14", +] + +[[package]] +name = "miette-derive" +version = "7.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db5b29714e950dbb20d5e6f74f9dcec4edbcc1067bb7f8ed198c097b8c1a818b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.99", +] + [[package]] name = "mime" version = "0.3.17" @@ -5339,7 +5361,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d" dependencies = [ "anyhow", - "itertools 0.13.0", + "itertools 0.14.0", "proc-macro2", "quote", "syn 2.0.99", @@ -5376,6 +5398,7 @@ dependencies = [ "dotenv", "figment", "hex", + "miette", "postgres", "serde", "serde_json", @@ -6723,7 +6746,7 @@ dependencies = [ "derive_more 2.0.1", "dunce", "inturn", - "itertools 0.13.0", + "itertools 0.14.0", "itoa", "match_cfg", "normalize-path", @@ -6734,7 +6757,7 @@ dependencies = [ "solar-macros", "thiserror 2.0.12", "tracing", - "unicode-width", + "unicode-width 0.2.0", ] [[package]] @@ -6757,7 +6780,7 @@ dependencies = [ "alloy-primitives", "bitflags 2.9.4", "bumpalo", - "itertools 0.13.0", + "itertools 0.14.0", "memchr", "num-bigint", "num-rational", @@ -7122,7 +7145,7 @@ dependencies = [ "getrandom 0.3.1", "once_cell", "rustix 1.0.1", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -7891,6 +7914,12 @@ version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + [[package]] name = "unicode-width" version = "0.2.0" diff --git a/protocol-testing/Cargo.toml b/protocol-testing/Cargo.toml index 23a0c30..786e93a 100644 --- a/protocol-testing/Cargo.toml +++ b/protocol-testing/Cargo.toml @@ -4,6 +4,7 @@ version = "0.1.0" edition = "2021" [dependencies] +miette = "7.6.0" # Logging & Tracing tracing = "0.1.37" # Tycho dependencies diff --git a/protocol-testing/src/main.rs b/protocol-testing/src/main.rs index 89da115..76a4dda 100644 --- a/protocol-testing/src/main.rs +++ b/protocol-testing/src/main.rs @@ -30,7 +30,7 @@ struct Args { vm_traces: bool, } -fn main() { +fn main() -> miette::Result<()> { tracing_subscriber::fmt() .with_env_filter(EnvFilter::from_default_env()) .with_target(false) @@ -40,5 +40,5 @@ fn main() { let test_runner = TestRunner::new(args.package, args.tycho_logs, args.db_url, args.vm_traces); - test_runner.run_tests(); + test_runner.run_tests() } diff --git a/protocol-testing/src/test_runner.rs b/protocol-testing/src/test_runner.rs index 36537e3..dd79b57 100644 --- a/protocol-testing/src/test_runner.rs +++ b/protocol-testing/src/test_runner.rs @@ -1,17 +1,19 @@ use std::{collections::HashMap, env, path::PathBuf, str::FromStr}; -use alloy::{primitives::U256}; + +use alloy::primitives::U256; use figment::{ providers::{Format, Yaml}, Figment, }; +use miette::{miette, IntoDiagnostic, WrapErr}; use postgres::{Client, Error, NoTls}; use tokio::runtime::Runtime; use tracing::{debug, info}; use tycho_client::feed::BlockHeader; use tycho_common::{ dto::{Chain, ProtocolComponent, ResponseAccount, ResponseProtocolState}, + models::token::Token, Bytes, - models::token::Token }; use tycho_simulation::{ evm::{ @@ -47,7 +49,7 @@ impl TestRunner { Self { package, tycho_logs, db_url, vm_traces, substreams_path } } - pub fn run_tests(&self) { + pub fn run_tests(&self) -> miette::Result<()> { info!("Running tests..."); let config_yaml_path = self .substreams_path @@ -56,20 +58,21 @@ impl TestRunner { info!("Config YAML: {}", config_yaml_path.display()); let figment = Figment::new().merge(Yaml::file(&config_yaml_path)); - match figment.extract::() { - Ok(config) => { - info!("Loaded test configuration:"); - info!("Protocol types: {:?}", config.protocol_type_names); - info!("Found {} tests to run", config.tests.len()); + let config = figment + .extract::() + .into_diagnostic() + .wrap_err("Failed to load test configuration:")?; + info!("Loaded test configuration:"); + info!("Protocol types: {:?}", config.protocol_type_names); + info!("Found {} tests to run", config.tests.len()); - for test in &config.tests { - self.run_test(test, &config, config.skip_balance_check); - } - } - Err(e) => { - eprintln!("Failed to load test configuration: {}", e); + for test in &config.tests { + if let Err(e) = self.run_test(test, &config, config.skip_balance_check) { + eprintln!("Test '{}' failed: {e}", test.name); } } + + Ok(()) } fn run_test( @@ -77,10 +80,11 @@ impl TestRunner { test: &IntegrationTest, config: &IntegrationTestsConfig, skip_balance_check: bool, - ) { + ) -> miette::Result<()> { info!("Running test: {}", test.name); self.empty_database() - .expect("Failed to empty the database"); + .into_diagnostic() + .wrap_err("Failed to empty the database")?; let substreams_yaml_path = self .substreams_path @@ -98,7 +102,7 @@ impl TestRunner { ); let spkg_path = - build_spkg(&substreams_yaml_path, test.start_block).expect("Failed to build spkg"); + build_spkg(&substreams_yaml_path, test.start_block).wrap_err("Failed to build spkg")?; let tycho_runner = TychoRunner::new(self.db_url.clone(), initialized_accounts, self.tycho_logs); @@ -110,15 +114,17 @@ impl TestRunner { test.stop_block, &config.protocol_type_names, ) - .expect("Failed to run Tycho"); + .wrap_err("Failed to run Tycho")?; - tycho_runner.run_with_rpc_server( + let _ = tycho_runner.run_with_rpc_server( validate_state, &test.expected_components, test.start_block, test.stop_block, skip_balance_check, - ); + )?; + + Ok(()) } fn empty_database(&self) -> Result<(), Error> { @@ -144,12 +150,13 @@ fn validate_state( start_block: u64, stop_block: u64, skip_balance_check: bool, -) { +) -> miette::Result<()> { let rt = Runtime::new().unwrap(); // Create Tycho client for the RPC server - let tycho_client = - TychoClient::new("http://localhost:4242").expect("Failed to create Tycho client"); + let tycho_client = TychoClient::new("http://localhost:4242") + .into_diagnostic() + .wrap_err("Failed to create Tycho client")?; let chain = Chain::Ethereum; let protocol_system = "test_protocol"; @@ -158,7 +165,8 @@ fn validate_state( // module, in order to simplify debugging let protocol_components = rt .block_on(tycho_client.get_protocol_components(protocol_system, chain)) - .expect("Failed to get protocol components"); + .into_diagnostic() + .wrap_err("Failed to get protocol components")?; let expected_ids = expected_components .iter() @@ -167,11 +175,13 @@ fn validate_state( let protocol_states = rt .block_on(tycho_client.get_protocol_state(protocol_system, expected_ids, chain)) - .expect("Failed to get protocol state"); + .into_diagnostic() + .wrap_err("Failed to get protocol state")?; let vm_storages = rt .block_on(tycho_client.get_contract_state(protocol_system, chain)) - .expect("Failed to get contract state"); + .into_diagnostic() + .wrap_err("Failed to get contract state")?; // Create a map of component IDs to components for easy lookup let components_by_id: HashMap = protocol_components @@ -210,7 +220,11 @@ fn validate_state( .compare(component, true); match diff { Some(diff) => { - panic!("Component {} does not match the expected state:\n{}", component_id, diff); + return Err(miette!( + "Component {} does not match the expected state:\n{}", + component_id, + diff + )); } None => { info!("Component {} matches the expected state", component_id); @@ -222,7 +236,9 @@ fn validate_state( // Step 2: Validate Token Balances // In this step, we validate that the token balances of the components match the values // on-chain, extracted by querying the token balances using a node. - let rpc_url = env::var("RPC_URL").expect("Missing ETH_RPC_URL in environment"); + let rpc_url = env::var("RPC_URL") + .into_diagnostic() + .wrap_err("Missing ETH_RPC_URL in environment")?; let rpc_provider = RPCProvider::new(rpc_url.to_string()); for (id, component) in components_by_id.iter() { @@ -280,9 +296,10 @@ fn validate_state( let component_id = &id.clone(); let state = protocol_states_by_id .get(component_id) - .expect("Failed to get state for component") + .wrap_err("Failed to get state for component")? .clone(); - let component_with_state = ComponentWithState { state, component, component_tvl: None, entrypoints: vec![] }; // TODO + let component_with_state = + ComponentWithState { state, component, component_tvl: None, entrypoints: vec![] }; // TODO states.insert(component_id.clone(), component_with_state); } let vm_storage: HashMap = vm_storages @@ -311,7 +328,8 @@ fn validate_state( let all_tokens = rt .block_on(tycho_client.get_tokens(Chain::Ethereum, None, None)) - .expect("Failed to get tokens"); + .into_diagnostic() + .wrap_err("Failed to get tokens")?; info!("Loaded {} tokens", all_tokens.len()); rt.block_on(decoder.set_tokens(all_tokens)); @@ -322,7 +340,8 @@ fn validate_state( let block_msg = rt .block_on(decoder.decode(message)) - .expect("Failed to decode message"); + .into_diagnostic() + .wrap_err("Failed to decode message")?; for (id, comp) in block_msg.new_pairs.iter() { pairs @@ -333,8 +352,8 @@ fn validate_state( // This is where we get blocked. Currently, Tycho Simulation expects the runtime to be // prebuild and accessible from TychoSim - we should allow passing it when parsing the block - // TODO: Since we don't have balances on the VM State, we could try to use Limits, otherwise ask the user - // to specify a set of values on the YAML file. + // TODO: Since we don't have balances on the VM State, we could try to use Limits, otherwise ask + // the user to specify a set of values on the YAML file. for (id, state) in block_msg.states.iter() { if let Some(tokens) = pairs.get(id) { let formatted_token_str = format!("{:}/{:}", &tokens[0].symbol, &tokens[1].symbol); @@ -358,4 +377,5 @@ fn validate_state( // e)) .ok(); } } + Ok(()) } diff --git a/protocol-testing/src/tycho_runner.rs b/protocol-testing/src/tycho_runner.rs index cbd3c2d..1baeb3a 100644 --- a/protocol-testing/src/tycho_runner.rs +++ b/protocol-testing/src/tycho_runner.rs @@ -7,6 +7,7 @@ use std::{ }; use dotenv::dotenv; +use miette::{miette, IntoDiagnostic, WrapErr}; use tracing::debug; use crate::config::ProtocolComponentWithTestConfig; @@ -30,7 +31,7 @@ impl TychoRunner { start_block: u64, end_block: u64, protocol_type_names: &[String], - ) -> Result<(), Box> { + ) -> miette::Result<()> { // Expects a .env present in the same folder as package root (where Cargo.toml is) dotenv().ok(); @@ -67,28 +68,21 @@ impl TychoRunner { cmd.stdout(Stdio::piped()) .stderr(Stdio::piped()); - let mut process = match cmd.spawn() { - Ok(p) => p, - Err(e) => { - println!("Error running Tycho indexer: {}", e); - return Err(e.into()); - } - }; + let mut process = cmd + .spawn() + .into_diagnostic() + .wrap_err("Error running Tycho indexer")?; if self.with_binary_logs { Self::handle_process_output(&mut process); } - match process.wait() { - Ok(status) => { - if !status.success() { - return Err(format!("Process exited with non-zero status: {}", status).into()); - } - } - Err(e) => { - println!("Error waiting for Tycho indexer: {}", e); - return Err(e.into()); - } + let status = process + .wait() + .into_diagnostic() + .wrap_err("Failed to wait on Tycho indexer process")?; + if !status.success() { + return Err(miette!("Process exited with non-zero status: {status}")); } Ok(()) @@ -101,7 +95,7 @@ impl TychoRunner { start_block: u64, stop_block: u64, skip_balance_check: bool, - ) -> R + ) -> miette::Result where F: FnOnce(&Vec, u64, u64, bool) -> R, { @@ -152,7 +146,7 @@ impl TychoRunner { eprintln!("Failed to join RPC thread"); } - result + Ok(result) } // Helper method to handle process output in separate threads diff --git a/protocol-testing/src/utils.rs b/protocol-testing/src/utils.rs index a9ba2a0..4810928 100644 --- a/protocol-testing/src/utils.rs +++ b/protocol-testing/src/utils.rs @@ -1,5 +1,4 @@ use std::{ - error::Error, fs, path::{Path, PathBuf}, process::Command, @@ -10,16 +9,17 @@ use figment::{ value::Value, Figment, }; +use miette::IntoDiagnostic; use tracing::error; /// Build a Substreams package with modifications to the YAML file. -pub fn build_spkg(yaml_file_path: &PathBuf, initial_block: u64) -> Result> { +pub fn build_spkg(yaml_file_path: &PathBuf, initial_block: u64) -> miette::Result { // Create a backup file of the unmodified Substreams protocol YAML config file. let backup_file_path = yaml_file_path.with_extension("backup"); - fs::copy(yaml_file_path, &backup_file_path)?; + fs::copy(yaml_file_path, &backup_file_path).into_diagnostic()?; let figment = Figment::new().merge(Yaml::file(yaml_file_path)); - let mut data: Value = figment.extract()?; + let mut data: Value = figment.extract().into_diagnostic()?; // Apply the modification function to update the YAML files modify_initial_block(&mut data, initial_block); @@ -52,8 +52,8 @@ pub fn build_spkg(yaml_file_path: &PathBuf, initial_block: u64) -> Result Result { - error!("Error running substreams pack command: {}. \ + error!( + "Error running substreams pack command: {}. \ Ensure that the wasm target was built and that substreams CLI\ - is installed and exported on PATH", e); + is installed and exported on PATH", + e + ); } } // Restore the original YAML from backup - fs::copy(&backup_file_path, yaml_file_path)?; - fs::remove_file(&backup_file_path)?; + fs::copy(&backup_file_path, yaml_file_path).into_diagnostic()?; + fs::remove_file(&backup_file_path).into_diagnostic()?; Ok(spkg_name) }