From e48d6b9891ca93eae2fe58840fdc1e4abb8cd4db Mon Sep 17 00:00:00 2001 From: steven Date: Thu, 20 Aug 2026 22:54:45 -0600 Subject: [PATCH 1/5] fix(forge): isolate overloaded fuzz paths --- .changelog/overloaded-fuzz-test-paths.md | 5 + crates/forge/src/runner.rs | 40 +++++++- crates/forge/tests/cli/test_cmd/fuzz.rs | 120 +++++++++++++++++++++++ 3 files changed, 160 insertions(+), 5 deletions(-) create mode 100644 .changelog/overloaded-fuzz-test-paths.md diff --git a/.changelog/overloaded-fuzz-test-paths.md b/.changelog/overloaded-fuzz-test-paths.md new file mode 100644 index 0000000000000..327df80506638 --- /dev/null +++ b/.changelog/overloaded-fuzz-test-paths.md @@ -0,0 +1,5 @@ +--- +forge: patch +--- + +Persist and replay failures for overloaded fuzz tests independently. diff --git a/crates/forge/src/runner.rs b/crates/forge/src/runner.rs index 0663d5a322be8..91b6c2ba581ee 100644 --- a/crates/forge/src/runner.rs +++ b/crates/forge/src/runner.rs @@ -2183,11 +2183,12 @@ impl<'a, FEN: FoundryEvmNetwork> FunctionRunner<'a, FEN> { } let mut fuzz_config = self.config.fuzz.clone(); + let test_name = fuzz_test_path_name(&self.cr.contract.abi, func); let _ = test_paths( &mut fuzz_config.corpus, fuzz_config.failure_persist_dir.clone().unwrap(), self.cr.name, - &func.name, + &test_name, ); let limit = self.config.symbolic.corpus_seed_limit; let mut metadata = SymbolicCorpusSeedMetadata { @@ -3591,6 +3592,7 @@ impl<'a, FEN: FoundryEvmNetwork> FunctionRunner<'a, FEN> { let invariant_address = self.address; return self.run_showmap( func, + &func.name, corpus_dir, &showmap, ShowmapReplayTarget { @@ -4888,11 +4890,12 @@ impl<'a, FEN: FoundryEvmNetwork> FunctionRunner<'a, FEN> { let runner = self.fuzz_runner(); let mut fuzz_config = self.config.fuzz.clone(); + let test_name = fuzz_test_path_name(&self.cr.contract.abi, func); let (failure_dir, failure_file) = test_paths( &mut fuzz_config.corpus, fuzz_config.failure_persist_dir.clone().unwrap(), self.cr.name, - &func.name, + &test_name, ); let fuzz_input = self.cr.mcr.tcfg.fuzz_input.as_ref(); let is_explicit_target = fuzz_input @@ -4911,12 +4914,13 @@ impl<'a, FEN: FoundryEvmNetwork> FunctionRunner<'a, FEN> { .corpus_dir .clone() .map(|corpus_dir| { - narrow_generated_fuzz_corpus_root(corpus_dir, self.cr.name, &func.name) + narrow_generated_fuzz_corpus_root(corpus_dir, self.cr.name, &test_name) }) .or_else(|| fuzz_config.corpus.corpus_dir.clone()); let fuzzed_address = self.address; return self.run_showmap( func, + &test_name, corpus_dir, &showmap, ShowmapReplayTarget { @@ -4991,7 +4995,23 @@ impl<'a, FEN: FoundryEvmNetwork> FunctionRunner<'a, FEN> { let persisted_failure = if is_explicit_target { fuzz_input.map(|input| input.failure.as_ref().clone()) } else { - foundry_common::fs::read_json_file::(failure_file.as_path()).ok() + foundry_common::fs::read_json_file::(failure_file.as_path()) + .ok() + .or_else(|| { + if test_name.as_ref() == func.name { + return None; + } + let legacy_file = canonicalized(failure_dir.join(&func.name)); + let failure = foundry_common::fs::read_json_file::( + legacy_file.as_path(), + ) + .ok()?; + failure + .calldata + .get(..4) + .is_some_and(|selector| func.selector() == selector) + .then_some(failure) + }) }; if self.cr.mcr.tcfg.fuzz_failure_replay { let Some(failure) = persisted_failure.as_ref() else { @@ -5151,6 +5171,7 @@ impl<'a, FEN: FoundryEvmNetwork> FunctionRunner<'a, FEN> { fn run_showmap( mut self, func: &Function, + test_name: &str, corpus_dir: Option, showmap: &crate::multi_runner::ShowmapConfig, target: ShowmapReplayTarget<'_>, @@ -5178,7 +5199,7 @@ impl<'a, FEN: FoundryEvmNetwork> FunctionRunner<'a, FEN> { // (which `File::create_new` would reject). Distinct anchors sharing one // corpus simply produce equivalent, separately-named approach dirs. let safe_id = self.cr.name.replace(['/', '\\', ':'], "_"); - let safe_fn = func.name.replace(['/', '\\', ':', '(', ')', ',', ' '], "_"); + let safe_fn = test_name.replace(['/', '\\', ':', '(', ')', ',', ' '], "_"); let approach = format!("{}__{safe_id}__{safe_fn}", showmap.approach); let opts = ShowmapOpts { out_dir: showmap.out_dir.clone(), @@ -5560,6 +5581,15 @@ fn frontier_filter_display(values: &[T]) -> String { if values.is_empty() { "any".to_string() } else { values.iter().format(", ").to_string() } } +/// Returns a stable path component that distinguishes overloaded fuzz tests. +fn fuzz_test_path_name<'a>(abi: &JsonAbi, func: &'a Function) -> Cow<'a, str> { + if abi.functions.get(&func.name).is_some_and(|functions| functions.len() > 1) { + Cow::Owned(format!("{}-{}", func.name, hex::encode(func.selector()))) + } else { + Cow::Borrowed(&func.name) + } +} + /// Helper function to set test corpus dir and to compose persisted failure paths. fn test_paths( corpus_config: &mut FuzzCorpusConfig, diff --git a/crates/forge/tests/cli/test_cmd/fuzz.rs b/crates/forge/tests/cli/test_cmd/fuzz.rs index ab1c8ff8b0f05..af2681d921639 100644 --- a/crates/forge/tests/cli/test_cmd/fuzz.rs +++ b/crates/forge/tests/cli/test_cmd/fuzz.rs @@ -316,6 +316,126 @@ Ran 1 test suite [ELAPSED]: 0 tests passed, 0 failed, 2 skipped (2 total tests) ]]); }); +forgetest_init!(overloaded_fuzz_tests_use_distinct_paths, |prj, cmd| { + let corpus_root = prj.root().join("overloaded-corpus"); + prj.update_config(|config| { + config.fuzz.runs = 1; + config.fuzz.seed = Some(U256::from(1)); + config.fuzz.corpus.corpus_dir = Some(corpus_root.clone()); + }); + prj.add_test( + "OverloadedFuzz.t.sol", + r#" +contract OverloadedFuzzTest { + function testFuzz_collision(address) external pure { + revert("ADDRESS_BUG"); + } + + function testFuzz_collision(uint256) external pure { + revert("UINT_BUG"); + } +} + "#, + ); + + cmd.args(["test", "--mc", "OverloadedFuzzTest", "-j1"]).assert_failure(); + + let failure_root = prj.root().join("cache/fuzz/failures/OverloadedFuzzTest"); + let address_name = format!( + "testFuzz_collision-{}", + hex::encode(&keccak256("testFuzz_collision(address)")[..4]) + ); + let uint_name = format!( + "testFuzz_collision-{}", + hex::encode(&keccak256("testFuzz_collision(uint256)")[..4]) + ); + let address_failure = failure_root.join(&address_name); + let uint_failure = failure_root.join(&uint_name); + assert!(address_failure.exists()); + assert!(uint_failure.exists()); + assert!(!failure_root.join("testFuzz_collision").exists()); + assert!(corpus_root.join("OverloadedFuzzTest").join(&address_name).exists()); + assert!(corpus_root.join("OverloadedFuzzTest").join(&uint_name).exists()); + + let showmap_root = prj.root().join("overloaded-showmap"); + cmd.forge_fuse() + .args([ + "test", + "--mc", + "OverloadedFuzzTest", + "--mt", + "testFuzz_collision", + "--showmap-out", + "overloaded-showmap", + "--showmap-trial", + "overloaded", + "-j1", + ]) + .assert_success(); + let showmap_prefix = "replay__test_OverloadedFuzz.t.sol_OverloadedFuzzTest__"; + assert!(showmap_root.join(format!("{showmap_prefix}{address_name}/overloaded.txt")).exists()); + assert!(showmap_root.join(format!("{showmap_prefix}{uint_name}/overloaded.txt")).exists()); + + let address_replay = cmd + .forge_fuse() + .args([ + "fuzz", + "replay", + "--mc", + "OverloadedFuzzTest", + "--mt", + r"^testFuzz_collision\(address\)$", + ]) + .assert_failure(); + let stdout = String::from_utf8_lossy(&address_replay.get_output().stdout); + assert!(stdout.contains("[FAIL: ADDRESS_BUG; counterexample:"), "{stdout}"); + + let uint_replay = cmd + .forge_fuse() + .args([ + "fuzz", + "replay", + "--mc", + "OverloadedFuzzTest", + "--mt", + r"^testFuzz_collision\(uint256\)$", + ]) + .assert_failure(); + let stdout = String::from_utf8_lossy(&uint_replay.get_output().stdout); + assert!(stdout.contains("[FAIL: UINT_BUG; counterexample:"), "{stdout}"); + + std::fs::rename(&address_failure, failure_root.join("testFuzz_collision")).unwrap(); + std::fs::remove_file(&uint_failure).unwrap(); + + let mismatched_legacy_replay = cmd + .forge_fuse() + .args([ + "fuzz", + "replay", + "--mc", + "OverloadedFuzzTest", + "--mt", + r"^testFuzz_collision\(uint256\)$", + ]) + .assert_success(); + let stdout = String::from_utf8_lossy(&mismatched_legacy_replay.get_output().stdout); + assert!(stdout.contains("no persisted fuzz failure found"), "{stdout}"); + + let legacy_replay = cmd + .forge_fuse() + .args([ + "fuzz", + "replay", + "--mc", + "OverloadedFuzzTest", + "--mt", + r"^testFuzz_collision\(address\)$", + ]) + .assert_failure(); + let stdout = String::from_utf8_lossy(&legacy_replay.get_output().stdout); + assert!(stdout.contains("[FAIL: ADDRESS_BUG; counterexample:"), "{stdout}"); +}); + forgetest_init!(forge_fuzz_replays_explicit_failure_file, |prj, cmd| { prj.add_test( "ForgeExplicitFuzzReplay.t.sol", From 4b514eac9e1ccfc19d7afc98f7b14deeb5263f40 Mon Sep 17 00:00:00 2001 From: steven Date: Mon, 24 Aug 2026 08:43:35 -0600 Subject: [PATCH 2/5] fix(forge): preserve fuzz paths after overload removal --- crates/forge/src/runner.rs | 35 +++++++++++++++++++++---- crates/forge/tests/cli/test_cmd/fuzz.rs | 35 +++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 5 deletions(-) diff --git a/crates/forge/src/runner.rs b/crates/forge/src/runner.rs index 91b6c2ba581ee..289eab89ff4e8 100644 --- a/crates/forge/src/runner.rs +++ b/crates/forge/src/runner.rs @@ -2183,7 +2183,8 @@ impl<'a, FEN: FoundryEvmNetwork> FunctionRunner<'a, FEN> { } let mut fuzz_config = self.config.fuzz.clone(); - let test_name = fuzz_test_path_name(&self.cr.contract.abi, func); + let test_name = + fuzz_test_path_name(&self.cr.contract.abi, func, &fuzz_config, self.cr.name); let _ = test_paths( &mut fuzz_config.corpus, fuzz_config.failure_persist_dir.clone().unwrap(), @@ -4890,7 +4891,8 @@ impl<'a, FEN: FoundryEvmNetwork> FunctionRunner<'a, FEN> { let runner = self.fuzz_runner(); let mut fuzz_config = self.config.fuzz.clone(); - let test_name = fuzz_test_path_name(&self.cr.contract.abi, func); + let test_name = + fuzz_test_path_name(&self.cr.contract.abi, func, &fuzz_config, self.cr.name); let (failure_dir, failure_file) = test_paths( &mut fuzz_config.corpus, fuzz_config.failure_persist_dir.clone().unwrap(), @@ -5582,9 +5584,32 @@ fn frontier_filter_display(values: &[T]) -> String { } /// Returns a stable path component that distinguishes overloaded fuzz tests. -fn fuzz_test_path_name<'a>(abi: &JsonAbi, func: &'a Function) -> Cow<'a, str> { - if abi.functions.get(&func.name).is_some_and(|functions| functions.len() > 1) { - Cow::Owned(format!("{}-{}", func.name, hex::encode(func.selector()))) +fn fuzz_test_path_name<'a>( + abi: &JsonAbi, + func: &'a Function, + config: &FuzzConfig, + contract_name: &str, +) -> Cow<'a, str> { + let test_name = format!("{}-{}", func.name, hex::encode(func.selector())); + let overloaded = abi.functions.get(&func.name).is_some_and(|functions| functions.len() > 1); + let contract = contract_name.split(':').next_back().unwrap(); + let has_qualified_artifact = config + .failure_persist_dir + .as_ref() + .is_some_and(|dir| dir.join("failures").join(contract).join(&test_name).exists()) + || config + .corpus + .corpus_dir + .as_ref() + .is_some_and(|dir| dir.join(contract).join(&test_name).exists()) + || config + .corpus + .frontier_dir + .as_ref() + .is_some_and(|dir| dir.join(contract).join(&test_name).exists()); + + if overloaded || has_qualified_artifact { + Cow::Owned(test_name) } else { Cow::Borrowed(&func.name) } diff --git a/crates/forge/tests/cli/test_cmd/fuzz.rs b/crates/forge/tests/cli/test_cmd/fuzz.rs index af2681d921639..8fa33f78e6768 100644 --- a/crates/forge/tests/cli/test_cmd/fuzz.rs +++ b/crates/forge/tests/cli/test_cmd/fuzz.rs @@ -404,6 +404,41 @@ contract OverloadedFuzzTest { let stdout = String::from_utf8_lossy(&uint_replay.get_output().stdout); assert!(stdout.contains("[FAIL: UINT_BUG; counterexample:"), "{stdout}"); + prj.add_test( + "OverloadedFuzz.t.sol", + r#" +contract OverloadedFuzzTest { + function testFuzz_collision(address) external pure { + revert("ADDRESS_BUG"); + } +} + "#, + ); + let address_replay = cmd + .forge_fuse() + .args(["fuzz", "replay", "--mc", "OverloadedFuzzTest", "--mt", "testFuzz_collision"]) + .assert_failure(); + let stdout = String::from_utf8_lossy(&address_replay.get_output().stdout); + assert!(stdout.contains("[FAIL: ADDRESS_BUG; counterexample:"), "{stdout}"); + assert!(address_failure.exists()); + assert!(!failure_root.join("testFuzz_collision").exists()); + assert!(corpus_root.join("OverloadedFuzzTest").join(&address_name).exists()); + assert!(!corpus_root.join("OverloadedFuzzTest/testFuzz_collision").exists()); + + prj.add_test( + "OverloadedFuzz.t.sol", + r#" +contract OverloadedFuzzTest { + function testFuzz_collision(address) external pure { + revert("ADDRESS_BUG"); + } + + function testFuzz_collision(uint256) external pure { + revert("UINT_BUG"); + } +} + "#, + ); std::fs::rename(&address_failure, failure_root.join("testFuzz_collision")).unwrap(); std::fs::remove_file(&uint_failure).unwrap(); From 20cfd5c94bdaeff4b2c9f7bc1811211d9cefda0b Mon Sep 17 00:00:00 2001 From: steven Date: Tue, 25 Aug 2026 10:36:59 -0600 Subject: [PATCH 3/5] fix(forge): reuse legacy overloaded fuzz corpus --- crates/evm/evm/src/executors/corpus.rs | 6 ++- crates/evm/evm/src/executors/fuzz/mod.rs | 15 +++++- crates/evm/evm/src/executors/invariant/mod.rs | 1 + crates/forge/src/runner.rs | 51 +++++++++++++++++-- crates/forge/tests/cli/test_cmd/fuzz.rs | 39 +++++++++++++- 5 files changed, 104 insertions(+), 8 deletions(-) diff --git a/crates/evm/evm/src/executors/corpus.rs b/crates/evm/evm/src/executors/corpus.rs index e7ce4e3846ce8..2d0613ad6f2ac 100644 --- a/crates/evm/evm/src/executors/corpus.rs +++ b/crates/evm/evm/src/executors/corpus.rs @@ -434,6 +434,7 @@ impl WorkerCorpusSeed { pub(crate) fn load_from_disk( config: &FuzzCorpusConfig, + replay_root: Option<&Path>, executor: Option<&Executor>, target: ReplayTarget<'_>, ) -> Result { @@ -441,7 +442,7 @@ impl WorkerCorpusSeed { let Some(corpus_dir) = &config.corpus_dir else { return Ok(seed); }; - let replay_dirs = canonical_replay_dirs(corpus_dir); + let replay_dirs = canonical_replay_dirs(replay_root.unwrap_or(corpus_dir)); seed.replay_dirs = Some(replay_dirs.clone()); // Seed in-memory corpus with the persisted optimization best sequence so the mutation @@ -864,12 +865,13 @@ impl WorkerCorpus { id: usize, config: FuzzCorpusConfig, sequence_generator: SequenceGenerator, + replay_root: Option<&Path>, // Only required by master worker (id = 0) to replay existing corpus. executor: Option<&Executor>, target: ReplayTarget<'_>, ) -> Result { let seed = if id == 0 { - WorkerCorpusSeed::load_from_disk(&config, executor, target)? + WorkerCorpusSeed::load_from_disk(&config, replay_root, executor, target)? } else { WorkerCorpusSeed::empty(&config).with_optimization_state(&config) }; diff --git a/crates/evm/evm/src/executors/fuzz/mod.rs b/crates/evm/evm/src/executors/fuzz/mod.rs index 4c36a01c99ccf..45476dc30f97a 100644 --- a/crates/evm/evm/src/executors/fuzz/mod.rs +++ b/crates/evm/evm/src/executors/fuzz/mod.rs @@ -34,6 +34,7 @@ use proptest::test_runner::{RngAlgorithm, TestCaseError, TestRng, TestRunner}; use rayon::iter::{IntoParallelIterator, ParallelIterator}; use serde_json::json; use std::{ + path::PathBuf, sync::{ Arc, OnceLock, atomic::{AtomicU32, Ordering}, @@ -199,6 +200,8 @@ pub struct FuzzedExecutor { config: FuzzConfig, /// The persisted counterexample to be replayed, if any. persisted_failure: Option, + /// An existing corpus to replay before persisting into the configured corpus directory. + corpus_replay_dir: Option, /// The number of parallel workers. num_workers: usize, } @@ -211,6 +214,7 @@ impl FuzzedExecutor { sender: Address, config: FuzzConfig, persisted_failure: Option, + corpus_replay_dir: Option, ) -> Self { let run_limit = if config.run.is_some() { 1 } else { config.runs }; let max_workers = if run_limit == 0 { @@ -221,7 +225,15 @@ impl FuzzedExecutor { Ord::max(1, run_limit / MIN_RUNS_PER_WORKER) }; let num_workers = Ord::min(rayon::current_num_threads(), max_workers as usize); - Self { executor_f: executor, runner, sender, config, persisted_failure, num_workers } + Self { + executor_f: executor, + runner, + sender, + config, + persisted_failure, + corpus_replay_dir, + num_workers, + } } /// Fuzzes the provided function, assuming it is available at the contract at `address` @@ -680,6 +692,7 @@ impl FuzzedExecutor { worker_id, self.config.corpus.clone(), generator, + self.corpus_replay_dir.as_deref(), // Master worker replays the persisted corpus using the executor (worker_id == 0).then_some(&self.executor_f), replay_target, diff --git a/crates/evm/evm/src/executors/invariant/mod.rs b/crates/evm/evm/src/executors/invariant/mod.rs index 1c91446d796d0..ec933a47a1d01 100644 --- a/crates/evm/evm/src/executors/invariant/mod.rs +++ b/crates/evm/evm/src/executors/invariant/mod.rs @@ -928,6 +928,7 @@ impl<'a, FEN: FoundryEvmNetwork> InvariantExecutor<'a, FEN> { let dynamic = self.dynamic_target_ctx(); let corpus_seed = WorkerCorpusSeed::load_from_disk( &self.config.corpus, + None, Some(&corpus_replay_executor), ReplayTarget { stateless: None, diff --git a/crates/forge/src/runner.rs b/crates/forge/src/runner.rs index 3db30504cb2ff..121809aee5bb1 100644 --- a/crates/forge/src/runner.rs +++ b/crates/forge/src/runner.rs @@ -2187,21 +2187,28 @@ impl<'a, FEN: FoundryEvmNetwork> FunctionRunner<'a, FEN> { let mut fuzz_config = self.config.fuzz.clone(); let test_name = fuzz_test_path_name(&self.cr.contract.abi, func, &fuzz_config, self.cr.name); + let legacy_corpus_dir = legacy_fuzz_corpus_dir( + fuzz_config.corpus.corpus_dir.as_deref(), + self.cr.name, + func, + &test_name, + ); let _ = test_paths( &mut fuzz_config.corpus, fuzz_config.failure_persist_dir.clone().unwrap(), self.cr.name, &test_name, ); + let corpus_dir = legacy_corpus_dir.or_else(|| fuzz_config.corpus.corpus_dir.clone()); let limit = self.config.symbolic.corpus_seed_limit; let mut metadata = SymbolicCorpusSeedMetadata { - corpus_dir: fuzz_config.corpus.corpus_dir.clone(), + corpus_dir: corpus_dir.clone(), limit, loaded: 0, skipped: 0, used: Vec::new(), }; - let Some(corpus_dir) = fuzz_config.corpus.corpus_dir.clone() else { + let Some(corpus_dir) = corpus_dir else { let _ = sh_warn!( "`--symbolic-use-fuzz-corpus` requires `--fuzz-corpus-dir` or `fuzz.corpus_dir`; \ running without imported corpus seeds" @@ -4895,6 +4902,12 @@ impl<'a, FEN: FoundryEvmNetwork> FunctionRunner<'a, FEN> { let mut fuzz_config = self.config.fuzz.clone(); let test_name = fuzz_test_path_name(&self.cr.contract.abi, func, &fuzz_config, self.cr.name); + let legacy_corpus_dir = legacy_fuzz_corpus_dir( + fuzz_config.corpus.corpus_dir.as_deref(), + self.cr.name, + func, + &test_name, + ); let (failure_dir, failure_file) = test_paths( &mut fuzz_config.corpus, fuzz_config.failure_persist_dir.clone().unwrap(), @@ -4920,6 +4933,7 @@ impl<'a, FEN: FoundryEvmNetwork> FunctionRunner<'a, FEN> { .map(|corpus_dir| { narrow_generated_fuzz_corpus_root(corpus_dir, self.cr.name, &test_name) }) + .or(legacy_corpus_dir.clone()) .or_else(|| fuzz_config.corpus.corpus_dir.clone()); let fuzzed_address = self.address; return self.run_showmap( @@ -5068,8 +5082,14 @@ impl<'a, FEN: FoundryEvmNetwork> FunctionRunner<'a, FEN> { .inspector_mut() .collect_sancov_trace_cmp(fuzz_config.corpus.collect_sancov_trace_cmp()); // Run fuzz test. - let mut fuzzed_executor = - FuzzedExecutor::new(executor, runner, self.tcfg.sender, fuzz_config, persisted_failure); + let mut fuzzed_executor = FuzzedExecutor::new( + executor, + runner, + self.tcfg.sender, + fuzz_config, + persisted_failure, + legacy_corpus_dir, + ); if self.cr.mcr.tcfg.fuzz_failure_replay { let result = match fuzzed_executor.replay_persisted_failure( func, @@ -5617,6 +5637,29 @@ fn fuzz_test_path_name<'a>( } } +/// Returns the legacy unqualified corpus when the qualified corpus has no entries. +fn legacy_fuzz_corpus_dir( + root: Option<&Path>, + contract_name: &str, + func: &Function, + test_name: &str, +) -> Option { + if test_name == func.name { + return None; + } + let contract = contract_name.split(':').next_back().unwrap(); + let root = root?; + let qualified = root.join(contract).join(test_name); + if canonical_replay_dirs(&qualified).iter().any(|dir| read_corpus_dir(dir).next().is_some()) { + return None; + } + let legacy = root.join(contract).join(&func.name); + canonical_replay_dirs(&legacy) + .iter() + .any(|dir| read_corpus_dir(dir).next().is_some()) + .then(|| canonicalized(legacy)) +} + /// Helper function to set test corpus dir and to compose persisted failure paths. fn test_paths( corpus_config: &mut FuzzCorpusConfig, diff --git a/crates/forge/tests/cli/test_cmd/fuzz.rs b/crates/forge/tests/cli/test_cmd/fuzz.rs index 8fa33f78e6768..fd3f23cc80da0 100644 --- a/crates/forge/tests/cli/test_cmd/fuzz.rs +++ b/crates/forge/tests/cli/test_cmd/fuzz.rs @@ -357,6 +357,43 @@ contract OverloadedFuzzTest { assert!(corpus_root.join("OverloadedFuzzTest").join(&address_name).exists()); assert!(corpus_root.join("OverloadedFuzzTest").join(&uint_name).exists()); + let address_corpus = corpus_root.join("OverloadedFuzzTest").join(&address_name); + let legacy_corpus = corpus_root.join("OverloadedFuzzTest/testFuzz_collision"); + std::fs::rename(&address_corpus, &legacy_corpus).unwrap(); + + let legacy_showmap_root = prj.root().join("legacy-showmap"); + cmd.forge_fuse() + .args([ + "test", + "--mc", + "OverloadedFuzzTest", + "--mt", + r"^testFuzz_collision\(address\)$", + "--showmap-out", + "legacy-showmap", + "--showmap-trial", + "legacy", + "-j1", + ]) + .assert_success(); + let showmap_prefix = "replay__test_OverloadedFuzz.t.sol_OverloadedFuzzTest__"; + assert!( + legacy_showmap_root.join(format!("{showmap_prefix}{address_name}/legacy.txt")).exists() + ); + + cmd.forge_fuse() + .args([ + "test", + "--mc", + "OverloadedFuzzTest", + "--mt", + r"^testFuzz_collision\(address\)$", + "-j1", + ]) + .assert_failure(); + assert!(address_corpus.exists()); + assert!(legacy_corpus.exists()); + let showmap_root = prj.root().join("overloaded-showmap"); cmd.forge_fuse() .args([ @@ -372,9 +409,9 @@ contract OverloadedFuzzTest { "-j1", ]) .assert_success(); - let showmap_prefix = "replay__test_OverloadedFuzz.t.sol_OverloadedFuzzTest__"; assert!(showmap_root.join(format!("{showmap_prefix}{address_name}/overloaded.txt")).exists()); assert!(showmap_root.join(format!("{showmap_prefix}{uint_name}/overloaded.txt")).exists()); + std::fs::remove_dir_all(&legacy_corpus).unwrap(); let address_replay = cmd .forge_fuse() From 4e4480936651f0182e664129147d9b520c3f638f Mon Sep 17 00:00:00 2001 From: steven Date: Tue, 25 Aug 2026 10:53:40 -0600 Subject: [PATCH 4/5] clippy --- crates/forge/src/runner.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/forge/src/runner.rs b/crates/forge/src/runner.rs index 121809aee5bb1..220aaf8ca0519 100644 --- a/crates/forge/src/runner.rs +++ b/crates/forge/src/runner.rs @@ -4933,7 +4933,7 @@ impl<'a, FEN: FoundryEvmNetwork> FunctionRunner<'a, FEN> { .map(|corpus_dir| { narrow_generated_fuzz_corpus_root(corpus_dir, self.cr.name, &test_name) }) - .or(legacy_corpus_dir.clone()) + .or(legacy_corpus_dir) .or_else(|| fuzz_config.corpus.corpus_dir.clone()); let fuzzed_address = self.address; return self.run_showmap( From 150a95306cc4bfa082fb650dee760bff3c4fa8d6 Mon Sep 17 00:00:00 2001 From: steven Date: Tue, 25 Aug 2026 11:17:48 -0600 Subject: [PATCH 5/5] clippy --- crates/evm/evm/src/executors/corpus.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/evm/evm/src/executors/corpus.rs b/crates/evm/evm/src/executors/corpus.rs index 2d0613ad6f2ac..54db2213bf76c 100644 --- a/crates/evm/evm/src/executors/corpus.rs +++ b/crates/evm/evm/src/executors/corpus.rs @@ -2323,6 +2323,7 @@ mod tests { let seed = WorkerCorpusSeed::load_from_disk::( &config, None, + None, ReplayTarget { stateless: None, fuzzed_contracts: None, dynamic: None }, ) .unwrap(); @@ -2555,6 +2556,7 @@ mod tests { config, generator, None, + None, ReplayTarget { stateless: None, fuzzed_contracts: None, dynamic: None }, ) .unwrap();