diff --git a/crates/config/src/fuzz.rs b/crates/config/src/fuzz.rs index 0d85aa43b83d1..ccb8cf45b632b 100644 --- a/crates/config/src/fuzz.rs +++ b/crates/config/src/fuzz.rs @@ -1,6 +1,7 @@ //! Configuration for fuzz testing. use alloy_primitives::U256; +use foundry_compilers::utils::canonicalized; use serde::{Deserialize, Serialize}; use std::path::PathBuf; @@ -112,9 +113,9 @@ pub struct FuzzCorpusConfig { } impl FuzzCorpusConfig { - pub fn with_test_name(&mut self, test_name: &String) { + pub fn with_test(&mut self, contract: &str, test: &str) { if let Some(corpus_dir) = &self.corpus_dir { - self.corpus_dir = Some(corpus_dir.join(test_name)); + self.corpus_dir = Some(canonicalized(corpus_dir.join(contract).join(test))); } } diff --git a/crates/evm/evm/src/executors/corpus.rs b/crates/evm/evm/src/executors/corpus.rs index 7a4a18be8811e..0f2a0505d9d1f 100644 --- a/crates/evm/evm/src/executors/corpus.rs +++ b/crates/evm/evm/src/executors/corpus.rs @@ -151,16 +151,12 @@ pub(crate) struct CorpusManager { impl CorpusManager { pub fn new( - config: &FuzzCorpusConfig, - func_name: &String, + config: FuzzCorpusConfig, tx_generator: BoxedStrategy, executor: &Executor, fuzzed_function: Option<&Function>, fuzzed_contracts: Option<&FuzzRunIdentifiedContracts>, ) -> eyre::Result { - let mut config = config.clone(); - config.with_test_name(func_name); - let mutation_generator = prop_oneof![ Just(MutationType::Splice), Just(MutationType::Repeat), diff --git a/crates/evm/evm/src/executors/fuzz/mod.rs b/crates/evm/evm/src/executors/fuzz/mod.rs index 4efbe692e4ab7..9d2a56557d9b8 100644 --- a/crates/evm/evm/src/executors/fuzz/mod.rs +++ b/crates/evm/evm/src/executors/fuzz/mod.rs @@ -119,8 +119,7 @@ impl FuzzedExecutor { let max_traces_to_collect = std::cmp::max(1, self.config.gas_report_samples) as usize; let mut corpus_manager = CorpusManager::new( - &self.config.corpus, - &func.name, + self.config.corpus.clone(), strategy.boxed(), &self.executor, Some(func), diff --git a/crates/evm/evm/src/executors/invariant/mod.rs b/crates/evm/evm/src/executors/invariant/mod.rs index e8000c87d8531..0a411f1f7ba29 100644 --- a/crates/evm/evm/src/executors/invariant/mod.rs +++ b/crates/evm/evm/src/executors/invariant/mod.rs @@ -622,8 +622,7 @@ impl<'a> InvariantExecutor<'a> { } let corpus_manager = CorpusManager::new( - &self.config.corpus, - &invariant_contract.invariant_function.name, + self.config.corpus.clone(), strategy.boxed(), &self.executor, None, diff --git a/crates/forge/src/runner.rs b/crates/forge/src/runner.rs index b65d458e8c6ae..afe6071e5e950 100644 --- a/crates/forge/src/runner.rs +++ b/crates/forge/src/runner.rs @@ -13,7 +13,7 @@ use alloy_primitives::{Address, Bytes, U256, address, map::HashMap}; use eyre::Result; use foundry_common::{TestFunctionExt, TestFunctionKind, contracts::ContractsByAddress}; use foundry_compilers::utils::canonicalized; -use foundry_config::Config; +use foundry_config::{Config, FuzzCorpusConfig}; use foundry_evm::{ constants::CALLER, decode::RevertDecoder, @@ -725,11 +725,18 @@ impl<'a> FunctionRunner<'a> { executor .inspector_mut() .collect_edge_coverage(invariant_config.corpus.collect_edge_coverage()); + let mut config = invariant_config.clone(); + let (failure_dir, failure_file) = test_paths( + &mut config.corpus, + invariant_config.failure_persist_dir.clone().unwrap(), + self.cr.name, + &func.name, + ); let mut evm = InvariantExecutor::new( executor, runner, - invariant_config.clone(), + config, identified_contracts, &self.cr.mcr.known_contracts, ); @@ -739,12 +746,6 @@ impl<'a> FunctionRunner<'a> { call_after_invariant, abi: &self.cr.contract.abi, }; - - let (failure_dir, failure_file) = test_failure_paths( - invariant_config.failure_persist_dir.clone().unwrap(), - self.cr.name, - &invariant_contract.invariant_function.name, - ); let show_solidity = invariant_config.show_solidity; // Try to replay recorded failure if any. @@ -934,7 +935,13 @@ impl<'a> FunctionRunner<'a> { } let runner = self.fuzz_runner(); - let fuzz_config = self.config.fuzz.clone(); + let mut fuzz_config = self.config.fuzz.clone(); + let (failure_dir, failure_file) = test_paths( + &mut fuzz_config.corpus, + fuzz_config.failure_persist_dir.clone().unwrap(), + self.cr.name, + &func.name, + ); let progress = start_fuzz_progress( self.cr.progress, @@ -944,12 +951,6 @@ impl<'a> FunctionRunner<'a> { fuzz_config.runs, ); - let (failure_dir, failure_file) = test_failure_paths( - fuzz_config.failure_persist_dir.clone().unwrap(), - self.cr.name, - &func.name, - ); - let mut executor = self.executor.into_owned(); // Enable edge coverage if running with coverage guided fuzzing or with edge coverage // metrics (useful for benchmarking the fuzzer). @@ -1105,14 +1106,18 @@ fn persisted_call_sequence(path: &Path, bytecode: &Bytes) -> Option (PathBuf, PathBuf) { - let dir = persist_dir.join("failures").join(contract_name.split(':').next_back().unwrap()); - let dir = canonicalized(dir); - let file = canonicalized(dir.join(invariant_name)); - (dir, file) + let contract = contract_name.split(':').next_back().unwrap(); + // Update config with corpus dir for current test. + corpus_config.with_test(contract, test_name); + + let failures_dir = canonicalized(persist_dir.join("failures").join(contract)); + let failure_file = canonicalized(failures_dir.join(test_name)); + (failures_dir, failure_file) } diff --git a/crates/forge/tests/it/invariant.rs b/crates/forge/tests/it/invariant.rs index c9d3140771b13..92672402f566e 100644 --- a/crates/forge/tests/it/invariant.rs +++ b/crates/forge/tests/it/invariant.rs @@ -1547,3 +1547,83 @@ Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) "#] ]); }); + +// +forgetest_init!(corpus_dir, |prj, cmd| { + prj.update_config(|config| { + config.invariant.runs = 10; + config.invariant.depth = 10; + config.invariant.corpus.corpus_dir = Some("invariant_corpus".into()); + + config.fuzz.runs = 10; + config.fuzz.corpus.corpus_dir = Some("fuzz_corpus".into()); + }); + prj.add_test( + "CounterTests.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; +import {Counter} from "../src/Counter.sol"; + +contract Counter1Test is Test { + Counter public counter; + + function setUp() public { + counter = new Counter(); + counter.setNumber(0); + } + + function testFuzz_SetNumber(uint256 x) public { + counter.setNumber(x); + assertEq(counter.number(), x); + } + + function invariant_counter_called() public view { + } +} + +contract Counter2Test is Test { + Counter public counter; + + function setUp() public { + counter = new Counter(); + counter.setNumber(0); + } + + function testFuzz_SetNumber(uint256 x) public { + counter.setNumber(x); + assertEq(counter.number(), x); + } + + function invariant_counter_called() public view { + } +} + "#, + ); + + cmd.args(["test"]).assert_success().stdout_eq(str![[r#" +... +Ran 3 test suites [ELAPSED]: 6 tests passed, 0 failed, 0 skipped (6 total tests) + +"#]]); + + assert!( + prj.root() + .join("invariant_corpus") + .join("Counter1Test") + .join("invariant_counter_called") + .exists() + ); + assert!( + prj.root() + .join("invariant_corpus") + .join("Counter2Test") + .join("invariant_counter_called") + .exists() + ); + assert!( + prj.root().join("fuzz_corpus").join("Counter1Test").join("testFuzz_SetNumber").exists() + ); + assert!( + prj.root().join("fuzz_corpus").join("Counter2Test").join("testFuzz_SetNumber").exists() + ); +});