From cef5bd1de8bba94eda2875ab9042a92773d6f458 Mon Sep 17 00:00:00 2001 From: "Odysseas.eth" Date: Fri, 19 Dec 2025 23:27:44 -0500 Subject: [PATCH 1/3] chore: fix svm dep --- Cargo.lock | 70 ++++++++++++++++++++++++++++++++++++++++++++---------- Cargo.toml | 3 +++ 2 files changed, 61 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f8476b64ff417..2f787dd538ab5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3983,6 +3983,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d" dependencies = [ "crc32fast", + "libz-rs-sys", "miniz_oxide", ] @@ -4084,7 +4085,6 @@ dependencies = [ "solar-sema", "soldeer-commands", "strum 0.27.1", - "svm-rs", "tempfile", "thiserror 2.0.12", "tokio", @@ -4502,7 +4502,7 @@ dependencies = [ "sha2 0.10.9", "solar-parse", "solar-sema", - "svm-rs", + "svm-rs 0.5.16", "svm-rs-builds", "tempfile", "thiserror 2.0.12", @@ -4574,7 +4574,7 @@ dependencies = [ "semver 1.0.26", "serde", "serde_json", - "svm-rs", + "svm-rs 0.5.16", "tempfile", "thiserror 2.0.12", "tokio", @@ -6116,6 +6116,15 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "libz-rs-sys" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15413ef615ad868d4d65dce091cb233b229419c7c0c4bcaa746c0901c49ff39c" +dependencies = [ + "zlib-rs", +] + [[package]] name = "linux-raw-sys" version = "0.4.15" @@ -9151,7 +9160,7 @@ dependencies = [ "solar-config", "solar-data-structures", "solar-macros", - "thiserror 2.0.12", + "thiserror 1.0.69", "tracing", "unicode-width 0.2.0", ] @@ -9262,7 +9271,7 @@ dependencies = [ "tokio", "toml_edit", "uuid 1.17.0", - "zip", + "zip 2.4.2", "zip-extract", ] @@ -9496,21 +9505,38 @@ dependencies = [ "serde_json", "sha2 0.10.9", "tempfile", - "thiserror 2.0.12", + "thiserror 1.0.69", "url", - "zip", + "zip 2.4.2", +] + +[[package]] +name = "svm-rs" +version = "0.5.22" +source = "git+https://github.com/alloy-rs/svm-rs?tag=v0.5.22#99fe67b6af86784ad1df18b9a1775cb627cd6b6d" +dependencies = [ + "const-hex", + "dirs", + "reqwest", + "semver 1.0.26", + "serde", + "serde_json", + "sha2 0.10.9", + "tempfile", + "thiserror 1.0.69", + "url", + "zip 4.6.1", ] [[package]] name = "svm-rs-builds" -version = "0.5.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ed5035d2abae3cd98c201b116ff22a82861589c060f3e4b687ce951cf381a6e" +version = "0.5.22" +source = "git+https://github.com/alloy-rs/svm-rs?tag=v0.5.22#99fe67b6af86784ad1df18b9a1775cb627cd6b6d" dependencies = [ "const-hex", "semver 1.0.26", "serde_json", - "svm-rs", + "svm-rs 0.5.22", ] [[package]] @@ -11279,6 +11305,20 @@ dependencies = [ "zstd 0.13.3", ] +[[package]] +name = "zip" +version = "4.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caa8cd6af31c3b31c6631b8f483848b91589021b28fffe50adada48d4f4d2ed1" +dependencies = [ + "arbitrary", + "crc32fast", + "flate2", + "indexmap 2.9.0", + "memchr", + "zopfli", +] + [[package]] name = "zip-extract" version = "0.2.1" @@ -11287,9 +11327,15 @@ checksum = "25a8c9e90f27d1435088a7b540b6cc8ae6ee525d992a695f16012d2f365b3d3c" dependencies = [ "log", "thiserror 1.0.69", - "zip", + "zip 2.4.2", ] +[[package]] +name = "zlib-rs" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51f936044d677be1a1168fae1d03b583a285a5dd9d8cbf7b24c23aa1fc775235" + [[package]] name = "zopfli" version = "0.8.2" diff --git a/Cargo.toml b/Cargo.toml index e2fbc4a9f9bd4..7b1fc8b795957 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -362,6 +362,9 @@ idna_adapter = "=1.1.0" zip-extract = "=0.2.1" [patch.crates-io] +# Fix for duplicate SOLC_VERSION_0_8_31 definition in svm-rs-builds 0.5.16 +svm-rs-builds = { git = "https://github.com/alloy-rs/svm-rs", tag = "v0.5.22" } + ## alloy-core # alloy-dyn-abi = { path = "../../alloy-rs/core/crates/dyn-abi" } # alloy-json-abi = { path = "../../alloy-rs/core/crates/json-abi" } From e7678edd688d8ca3b7c0e16ea5ac7aad524ac205 Mon Sep 17 00:00:00 2001 From: "Odysseas.eth" Date: Fri, 19 Dec 2025 23:28:11 -0500 Subject: [PATCH 2/3] chore: nits --- crates/cheatcodes/src/credible.rs | 90 ++++++++++++++++--------------- crates/forge/Cargo.toml | 3 -- 2 files changed, 48 insertions(+), 45 deletions(-) diff --git a/crates/cheatcodes/src/credible.rs b/crates/cheatcodes/src/credible.rs index 76ace0b4f0dd2..ca7a1c20156a1 100644 --- a/crates/cheatcodes/src/credible.rs +++ b/crates/cheatcodes/src/credible.rs @@ -114,7 +114,12 @@ pub fn execute_assertion( let state = ecx.journaled_state.state.clone(); let chain_id = ecx.cfg.chain_id; - let nonce = ecx.db().basic(tx_attributes.caller).unwrap_or_default().unwrap_or_default().nonce; + let nonce = ecx + .db() + .basic(tx_attributes.caller) + .ok() + .flatten() + .map_or(0, |info| info.nonce); // Setup assertion database let db = ThreadSafeDb::new(ecx.db()); @@ -124,22 +129,23 @@ pub fn execute_assertion( let store = AssertionStore::new_ephemeral().expect("Failed to create assertion store"); + if assertion.create_data.is_empty() { + bail!("Assertion bytecode is empty"); + } + let mut assertion_state = AssertionState::new_active(assertion.create_data.clone().into(), &config) .expect("Failed to create assertion state"); - let mut trigger_types_to_remove = Vec::new(); - // Filter triggers for one fn selector - for (trigger_type, fn_selectors) in assertion_state.trigger_recorder.triggers.iter_mut() { + // Filter triggers to only keep those matching our fn_selector + assertion_state.trigger_recorder.triggers.retain(|_, fn_selectors| { if fn_selectors.contains(&assertion.fn_selector) { *fn_selectors = HashSet::from_iter([assertion.fn_selector]); + true } else { - trigger_types_to_remove.push(trigger_type.clone()); + false } - } - for trigger_type in trigger_types_to_remove { - assertion_state.trigger_recorder.triggers.remove(&trigger_type); - } + }); store.insert(assertion.adopter, assertion_state).expect("Failed to store assertions"); let tx_env = TxEnv { @@ -167,18 +173,19 @@ pub fn execute_assertion( // TODO: Remove this once we have a proper way to handle this. let mut ext_db = revm::database::WrapDatabaseRef(fork_db.clone()); - // Store assertions + // Execute assertion validation let tx_validation = assertion_executor - .validate_transaction_ext_db(block.clone(), tx_env.clone(), &mut fork_db, &mut ext_db) + .validate_transaction_ext_db(block, tx_env, &mut fork_db, &mut ext_db) .map_err(|e| format!("Assertion Executor Error: {e:#?}"))?; let mut inspector = executor.get_inspector(cheats); // if transaction execution reverted, log the revert reason if !tx_validation.result_and_state.result.is_success() { inspector.console_log(&format!( - "Transaction reverted: {}", + "Mock Transaction Revert Reason: {}", decode_invalidated_assertion(&tx_validation.result_and_state.result).reason() )); + bail!("Mock Transaction Reverted"); } // else get information about the assertion execution @@ -192,11 +199,17 @@ pub fn execute_assertion( // test evm in this case. ecx.journaled_state.inner.checkpoint(); + // Drop inspector first to release the borrow on cheats std::mem::drop(inspector); if let Some(expected) = &mut cheats.expected_revert { expected.max_depth = max(ecx.journaled_state.depth(), expected.max_depth); } - bail!("Expected 1 assertion to be executed, but {total_assertions_ran} were executed."); + // Get a new inspector for logging + let mut inspector = executor.get_inspector(cheats); + inspector.console_log(&format!( + "Expected 1 assertion fn to be executed, but {total_assertions_ran} were executed." + )); + bail!("Assertion Fn number mismatch"); } //Expect is safe because we validate above that 1 assertion was ran. @@ -217,10 +230,9 @@ pub fn execute_assertion( } } - let assertion_gas_message = format!( - "Transaction gas cost: {tx_gas_used}\n Assertion gas cost: {total_assertion_gas}\n " - ); - inspector.console_log(&assertion_gas_message); + inspector.console_log(&format!( + "Transaction gas cost: {tx_gas_used}\n Assertion gas cost: {total_assertion_gas}" + )); // Drop the inspector to avoid borrow checker issues std::mem::drop(inspector); @@ -235,39 +247,33 @@ pub fn execute_assertion( } let mut inspector = executor.get_inspector(cheats); - match &assertion_fn_result.result { - AssertionFunctionExecutionResult::AssertionContractDeployFailure(result) => { - inspector.console_log(&format!( - "Assertion contract deploy failed: {}", - decode_invalidated_assertion(&result).reason() - )); - let output = result.output().unwrap_or_default(); - return Err(crate::Error::from(output.clone())); + let (msg, result) = match &assertion_fn_result.result { + AssertionFunctionExecutionResult::AssertionContractDeployFailure(r) => { + ("Assertion contract deploy failed", r) } - AssertionFunctionExecutionResult::AssertionExecutionResult(result) => { - inspector.console_log(&format!( - "Assertion function reverted: {}", - decode_invalidated_assertion(&result).reason() - )); - let output = result.output().unwrap_or_default(); - return Err(crate::Error::from(output.clone())); + AssertionFunctionExecutionResult::AssertionExecutionResult(r) => { + ("Assertion function reverted", r) } - } + }; + inspector.console_log(&format!( + "{msg}: {}", + decode_invalidated_assertion(result).reason() + )); + return Err(crate::Error::from(result.output().unwrap_or_default().clone())); } Ok(()) } -fn decode_invalidated_assertion(execution_result: &ExecutionResult) -> Revert { - let result = execution_result; +fn decode_invalidated_assertion(result: &ExecutionResult) -> Revert { match result { - ExecutionResult::Success{..} => Revert { - reason: "Tried to decode invalidated assertion, but result was success. This is a bug in phoundry. Please report to the Phylax team.".to_string(), - }, - ExecutionResult::Revert{output, ..} => { - Revert::abi_decode(output) - .unwrap_or(Revert::new(("Unknown Revert Reason".to_string(),))) + ExecutionResult::Success { .. } => Revert { + reason: "Tried to decode invalidated assertion, but result was success. \ + This is a bug in phoundry. Please report to the Phylax team." + .to_string(), }, - ExecutionResult::Halt{reason, ..} => Revert { + ExecutionResult::Revert { output, .. } => Revert::abi_decode(output) + .unwrap_or_else(|_| Revert::new(("Unknown Revert Reason".to_string(),))), + ExecutionResult::Halt { reason, .. } => Revert { reason: format!("Halt reason: {reason:#?}"), }, } diff --git a/crates/forge/Cargo.toml b/crates/forge/Cargo.toml index ee5727837862c..f5dfb8266031d 100644 --- a/crates/forge/Cargo.toml +++ b/crates/forge/Cargo.toml @@ -112,9 +112,6 @@ mockall = "0.13" globset = "0.4" paste = "1.0" similar-asserts.workspace = true -svm = { package = "svm-rs", version = "0.5", default-features = false, features = [ - "rustls", -] } tempfile.workspace = true alloy-signer-local.workspace = true From bbaedfe118ed048a10facd80ce54026c83374181 Mon Sep 17 00:00:00 2001 From: "Odysseas.eth" Date: Fri, 19 Dec 2025 23:32:24 -0500 Subject: [PATCH 3/3] fix: robust evm revert reasons --- crates/cheatcodes/src/credible.rs | 69 +++++++++++++++++++++---------- 1 file changed, 47 insertions(+), 22 deletions(-) diff --git a/crates/cheatcodes/src/credible.rs b/crates/cheatcodes/src/credible.rs index ca7a1c20156a1..671e938418d4c 100644 --- a/crates/cheatcodes/src/credible.rs +++ b/crates/cheatcodes/src/credible.rs @@ -1,6 +1,6 @@ use crate::{inspector::Ecx, Cheatcode, Cheatcodes, CheatcodesExecutor, CheatsCtxt, Result, Vm::*}; use alloy_primitives::{Bytes, FixedBytes, TxKind}; -use alloy_sol_types::{Revert, SolError}; +use foundry_evm_core::decode::RevertDecoder; use assertion_executor::{ db::{fork_db::ForkDb, DatabaseCommit, DatabaseRef}, primitives::{ @@ -183,7 +183,7 @@ pub fn execute_assertion( if !tx_validation.result_and_state.result.is_success() { inspector.console_log(&format!( "Mock Transaction Revert Reason: {}", - decode_invalidated_assertion(&tx_validation.result_and_state.result).reason() + decode_invalidated_assertion(&tx_validation.result_and_state.result) )); bail!("Mock Transaction Reverted"); } @@ -257,37 +257,44 @@ pub fn execute_assertion( }; inspector.console_log(&format!( "{msg}: {}", - decode_invalidated_assertion(result).reason() + decode_invalidated_assertion(result) )); return Err(crate::Error::from(result.output().unwrap_or_default().clone())); } Ok(()) } -fn decode_invalidated_assertion(result: &ExecutionResult) -> Revert { +/// Decodes revert data from an assertion execution result. +/// Uses foundry's RevertDecoder to handle all revert types: +/// - Error(string) from revert()/require() +/// - Panic(uint256) from assert()/overflow/etc +/// - Custom errors +/// - Raw bytes as fallback +fn decode_invalidated_assertion(result: &ExecutionResult) -> String { match result { - ExecutionResult::Success { .. } => Revert { - reason: "Tried to decode invalidated assertion, but result was success. \ - This is a bug in phoundry. Please report to the Phylax team." - .to_string(), - }, - ExecutionResult::Revert { output, .. } => Revert::abi_decode(output) - .unwrap_or_else(|_| Revert::new(("Unknown Revert Reason".to_string(),))), - ExecutionResult::Halt { reason, .. } => Revert { - reason: format!("Halt reason: {reason:#?}"), - }, + ExecutionResult::Success { .. } => { + "Tried to decode invalidated assertion, but result was success. \ + This is a bug in phoundry. Please report to the Phylax team." + .to_string() + } + ExecutionResult::Revert { output, .. } => { + RevertDecoder::default().decode(output, None) + } + ExecutionResult::Halt { reason, .. } => { + format!("Halt reason: {reason:#?}") + } } } #[cfg(test)] mod tests { use super::*; + use alloy_sol_types::{Revert, SolError}; use assertion_executor::primitives::HaltReason; use revm::context::result::{Output, SuccessReason}; #[test] fn test_decode_revert_error_success() { - // Test case 1: When result is success let result = ExecutionResult::Success { gas_used: 0, gas_refunded: 0, @@ -295,8 +302,8 @@ mod tests { output: Output::Call(Bytes::new()), reason: SuccessReason::Return, }; - let revert = decode_invalidated_assertion(&result); - assert_eq!(revert.reason(), "Tried to decode invalidated assertion, but result was success. This is a bug in phoundry. Please report to the Phylax team."); + let decoded = decode_invalidated_assertion(&result); + assert!(decoded.contains("bug in phoundry")); } #[test] @@ -304,16 +311,34 @@ mod tests { let revert_reason = "Something is a bit fky wuky"; let revert_output = Revert::new((revert_reason.to_string(),)).abi_encode(); let result = ExecutionResult::Revert { output: revert_output.into(), gas_used: 0 }; - let revert = decode_invalidated_assertion(&result); - assert_eq!(revert.reason(), revert_reason); + let decoded = decode_invalidated_assertion(&result); + assert_eq!(decoded, revert_reason); + } + + #[test] + fn test_decode_revert_panic() { + // Panic(uint256) with code 0x01 (assertion failed) + let panic_output = alloy_sol_types::Panic::from(0x01).abi_encode(); + let result = ExecutionResult::Revert { output: panic_output.into(), gas_used: 0 }; + let decoded = decode_invalidated_assertion(&result); + assert!(decoded.contains("Panic") || decoded.contains("panic")); + } + + #[test] + fn test_decode_revert_raw_bytes() { + // Raw bytes that don't match any known format + let raw_bytes = vec![0xde, 0xad, 0xbe, 0xef]; + let result = ExecutionResult::Revert { output: raw_bytes.into(), gas_used: 0 }; + let decoded = decode_invalidated_assertion(&result); + // Should contain hex representation + assert!(decoded.contains("deadbeef") || decoded.contains("custom error")); } #[test] fn test_decode_revert_error_halt() { let halt_reason = HaltReason::CallTooDeep; let result = ExecutionResult::Halt { reason: halt_reason, gas_used: 0 }; - - let revert = decode_invalidated_assertion(&result); - assert_eq!(revert.reason(), "Halt reason: CallTooDeep"); + let decoded = decode_invalidated_assertion(&result); + assert_eq!(decoded, "Halt reason: CallTooDeep"); } }