forge/
runner.rs

1//! The Forge test runner.
2
3use crate::{
4    MultiContractRunner, TestFilter,
5    coverage::HitMaps,
6    fuzz::{BaseCounterExample, FuzzTestResult},
7    multi_runner::{TestContract, TestRunnerConfig},
8    progress::{TestsProgress, start_fuzz_progress},
9    result::{SuiteResult, TestResult, TestSetup},
10};
11use alloy_dyn_abi::{DynSolValue, JsonAbiExt};
12use alloy_json_abi::Function;
13use alloy_primitives::{Address, Bytes, U256, address, map::HashMap};
14use eyre::Result;
15use foundry_common::{TestFunctionExt, TestFunctionKind, contracts::ContractsByAddress};
16use foundry_compilers::utils::canonicalized;
17use foundry_config::{Config, FuzzCorpusConfig};
18use foundry_evm::{
19    constants::CALLER,
20    decode::RevertDecoder,
21    executors::{
22        CallResult, EvmError, Executor, ITest, RawCallResult,
23        fuzz::FuzzedExecutor,
24        invariant::{
25            InvariantExecutor, InvariantFuzzError, check_sequence, replay_error, replay_run,
26        },
27    },
28    fuzz::{
29        BasicTxDetails, CallDetails, CounterExample, FuzzFixtures, fixture_name,
30        invariant::InvariantContract,
31    },
32    traces::{TraceKind, TraceMode, load_contracts},
33};
34use itertools::Itertools;
35use proptest::test_runner::{RngAlgorithm, TestError, TestRng, TestRunner};
36use rayon::prelude::*;
37use serde::{Deserialize, Serialize};
38use std::{
39    borrow::Cow,
40    cmp::min,
41    collections::BTreeMap,
42    path::{Path, PathBuf},
43    sync::Arc,
44    time::Instant,
45};
46use tracing::Span;
47
48/// When running tests, we deploy all external libraries present in the project. To avoid additional
49/// libraries affecting nonces of senders used in tests, we are using separate address to
50/// predeploy libraries.
51///
52/// `address(uint160(uint256(keccak256("foundry library deployer"))))`
53pub const LIBRARY_DEPLOYER: Address = address!("0x1F95D37F27EA0dEA9C252FC09D5A6eaA97647353");
54
55/// A type that executes all tests of a contract
56pub struct ContractRunner<'a> {
57    /// The name of the contract.
58    name: &'a str,
59    /// The data of the contract.
60    contract: &'a TestContract,
61    /// The EVM executor.
62    executor: Executor,
63    /// Overall test run progress.
64    progress: Option<&'a TestsProgress>,
65    /// The handle to the tokio runtime.
66    tokio_handle: &'a tokio::runtime::Handle,
67    /// The span of the contract.
68    span: tracing::Span,
69    /// The contract-level configuration.
70    tcfg: Cow<'a, TestRunnerConfig>,
71    /// The parent runner.
72    mcr: &'a MultiContractRunner,
73}
74
75impl<'a> std::ops::Deref for ContractRunner<'a> {
76    type Target = Cow<'a, TestRunnerConfig>;
77
78    #[inline(always)]
79    fn deref(&self) -> &Self::Target {
80        &self.tcfg
81    }
82}
83
84impl<'a> ContractRunner<'a> {
85    pub fn new(
86        name: &'a str,
87        contract: &'a TestContract,
88        executor: Executor,
89        progress: Option<&'a TestsProgress>,
90        tokio_handle: &'a tokio::runtime::Handle,
91        span: Span,
92        mcr: &'a MultiContractRunner,
93    ) -> Self {
94        Self {
95            name,
96            contract,
97            executor,
98            progress,
99            tokio_handle,
100            span,
101            tcfg: Cow::Borrowed(&mcr.tcfg),
102            mcr,
103        }
104    }
105
106    /// Deploys the test contract inside the runner from the sending account, and optionally runs
107    /// the `setUp` function on the test contract.
108    pub fn setup(&mut self, call_setup: bool) -> TestSetup {
109        self._setup(call_setup).unwrap_or_else(|err| {
110            if err.to_string().contains("skipped") {
111                TestSetup::skipped(err.to_string())
112            } else {
113                TestSetup::failed(err.to_string())
114            }
115        })
116    }
117
118    fn _setup(&mut self, call_setup: bool) -> Result<TestSetup> {
119        trace!(call_setup, "setting up");
120
121        self.apply_contract_inline_config()?;
122
123        // We max out their balance so that they can deploy and make calls.
124        self.executor.set_balance(self.sender, U256::MAX)?;
125        self.executor.set_balance(CALLER, U256::MAX)?;
126
127        // We set the nonce of the deployer accounts to 1 to get the same addresses as DappTools.
128        self.executor.set_nonce(self.sender, 1)?;
129
130        // Deploy libraries.
131        self.executor.set_balance(LIBRARY_DEPLOYER, U256::MAX)?;
132
133        let mut result = TestSetup::default();
134        for code in &self.mcr.libs_to_deploy {
135            let deploy_result = self.executor.deploy(
136                LIBRARY_DEPLOYER,
137                code.clone(),
138                U256::ZERO,
139                Some(&self.mcr.revert_decoder),
140            );
141
142            // Record deployed library address.
143            if let Ok(deployed) = &deploy_result {
144                result.deployed_libs.push(deployed.address);
145            }
146
147            let (raw, reason) = RawCallResult::from_evm_result(deploy_result.map(Into::into))?;
148            result.extend(raw, TraceKind::Deployment);
149            if reason.is_some() {
150                result.reason = reason;
151                return Ok(result);
152            }
153        }
154
155        let address = self.sender.create(self.executor.get_nonce(self.sender)?);
156        result.address = address;
157
158        // Set the contracts initial balance before deployment, so it is available during
159        // construction
160        self.executor.set_balance(address, self.initial_balance())?;
161
162        // Deploy the test contract
163        let deploy_result = self.executor.deploy(
164            self.sender,
165            self.contract.bytecode.clone(),
166            U256::ZERO,
167            Some(&self.mcr.revert_decoder),
168        );
169
170        result.deployment_failure = deploy_result.is_err();
171
172        if let Ok(dr) = &deploy_result {
173            debug_assert_eq!(dr.address, address);
174        }
175        let (raw, reason) = RawCallResult::from_evm_result(deploy_result.map(Into::into))?;
176        result.extend(raw, TraceKind::Deployment);
177        if reason.is_some() {
178            result.reason = reason;
179            return Ok(result);
180        }
181
182        // Reset `self.sender`s, `CALLER`s and `LIBRARY_DEPLOYER`'s balance to the initial balance.
183        self.executor.set_balance(self.sender, self.initial_balance())?;
184        self.executor.set_balance(CALLER, self.initial_balance())?;
185        self.executor.set_balance(LIBRARY_DEPLOYER, self.initial_balance())?;
186
187        self.executor.deploy_create2_deployer()?;
188
189        // Optionally call the `setUp` function
190        if call_setup {
191            trace!("calling setUp");
192            let res = self.executor.setup(None, address, Some(&self.mcr.revert_decoder));
193            let (raw, reason) = RawCallResult::from_evm_result(res)?;
194            result.extend(raw, TraceKind::Setup);
195            result.reason = reason;
196        }
197
198        result.fuzz_fixtures = self.fuzz_fixtures(address);
199
200        Ok(result)
201    }
202
203    fn initial_balance(&self) -> U256 {
204        self.evm_opts.initial_balance
205    }
206
207    /// Configures this runner with the inline configuration for the contract.
208    fn apply_contract_inline_config(&mut self) -> Result<()> {
209        if self.inline_config.contains_contract(self.name) {
210            let new_config = Arc::new(self.inline_config(None)?);
211            self.tcfg.to_mut().reconfigure_with(new_config);
212            let prev_tracer = self.executor.inspector_mut().tracer.take();
213            self.tcfg.configure_executor(&mut self.executor);
214            // Don't set tracer here.
215            self.executor.inspector_mut().tracer = prev_tracer;
216        }
217        Ok(())
218    }
219
220    /// Returns the configuration for a contract or function.
221    fn inline_config(&self, func: Option<&Function>) -> Result<Config> {
222        let function = func.map(|f| f.name.as_str()).unwrap_or("");
223        let config =
224            self.mcr.inline_config.merge(self.name, function, &self.config).extract::<Config>()?;
225        Ok(config)
226    }
227
228    /// Collect fixtures from test contract.
229    ///
230    /// Fixtures can be defined:
231    /// - as storage arrays in test contract, prefixed with `fixture`
232    /// - as functions prefixed with `fixture` and followed by parameter name to be fuzzed
233    ///
234    /// Storage array fixtures:
235    /// `uint256[] public fixture_amount = [1, 2, 3];`
236    /// define an array of uint256 values to be used for fuzzing `amount` named parameter in scope
237    /// of the current test.
238    ///
239    /// Function fixtures:
240    /// `function fixture_owner() public returns (address[] memory){}`
241    /// returns an array of addresses to be used for fuzzing `owner` named parameter in scope of the
242    /// current test.
243    fn fuzz_fixtures(&mut self, address: Address) -> FuzzFixtures {
244        let mut fixtures = HashMap::default();
245        let fixture_functions = self.contract.abi.functions().filter(|func| func.is_fixture());
246        for func in fixture_functions {
247            if func.inputs.is_empty() {
248                // Read fixtures declared as functions.
249                if let Ok(CallResult { raw: _, decoded_result }) =
250                    self.executor.call(CALLER, address, func, &[], U256::ZERO, None)
251                {
252                    fixtures.insert(fixture_name(func.name.clone()), decoded_result);
253                }
254            } else {
255                // For reading fixtures from storage arrays we collect values by calling the
256                // function with incremented indexes until there's an error.
257                let mut vals = Vec::new();
258                let mut index = 0;
259                loop {
260                    if let Ok(CallResult { raw: _, decoded_result }) = self.executor.call(
261                        CALLER,
262                        address,
263                        func,
264                        &[DynSolValue::Uint(U256::from(index), 256)],
265                        U256::ZERO,
266                        None,
267                    ) {
268                        vals.push(decoded_result);
269                    } else {
270                        // No result returned for this index, we reached the end of storage
271                        // array or the function is not a valid fixture.
272                        break;
273                    }
274                    index += 1;
275                }
276                fixtures.insert(fixture_name(func.name.clone()), DynSolValue::Array(vals));
277            };
278        }
279        FuzzFixtures::new(fixtures)
280    }
281
282    /// Runs all tests for a contract whose names match the provided regular expression
283    pub fn run_tests(mut self, filter: &dyn TestFilter) -> SuiteResult {
284        let start = Instant::now();
285        let mut warnings = Vec::new();
286
287        // Check if `setUp` function with valid signature declared.
288        let setup_fns: Vec<_> =
289            self.contract.abi.functions().filter(|func| func.name.is_setup()).collect();
290        let call_setup = setup_fns.len() == 1 && setup_fns[0].name == "setUp";
291        // There is a single miss-cased `setUp` function, so we add a warning
292        for &setup_fn in &setup_fns {
293            if setup_fn.name != "setUp" {
294                warnings.push(format!(
295                    "Found invalid setup function \"{}\" did you mean \"setUp()\"?",
296                    setup_fn.signature()
297                ));
298            }
299        }
300
301        // There are multiple setUp function, so we return a single test result for `setUp`
302        if setup_fns.len() > 1 {
303            return SuiteResult::new(
304                start.elapsed(),
305                [("setUp()".to_string(), TestResult::fail("multiple setUp functions".to_string()))]
306                    .into(),
307                warnings,
308            );
309        }
310
311        // Check if `afterInvariant` function with valid signature declared.
312        let after_invariant_fns: Vec<_> =
313            self.contract.abi.functions().filter(|func| func.name.is_after_invariant()).collect();
314        if after_invariant_fns.len() > 1 {
315            // Return a single test result failure if multiple functions declared.
316            return SuiteResult::new(
317                start.elapsed(),
318                [(
319                    "afterInvariant()".to_string(),
320                    TestResult::fail("multiple afterInvariant functions".to_string()),
321                )]
322                .into(),
323                warnings,
324            );
325        }
326        let call_after_invariant = after_invariant_fns.first().is_some_and(|after_invariant_fn| {
327            let match_sig = after_invariant_fn.name == "afterInvariant";
328            if !match_sig {
329                warnings.push(format!(
330                    "Found invalid afterInvariant function \"{}\" did you mean \"afterInvariant()\"?",
331                    after_invariant_fn.signature()
332                ));
333            }
334            match_sig
335        });
336
337        // Invariant testing requires tracing to figure out what contracts were created.
338        // We also want to disable `debug` for setup since we won't be using those traces.
339        let has_invariants = self.contract.abi.functions().any(|func| func.is_invariant_test());
340
341        let prev_tracer = self.executor.inspector_mut().tracer.take();
342        if prev_tracer.is_some() || has_invariants {
343            self.executor.set_tracing(TraceMode::Call);
344        }
345
346        let setup_time = Instant::now();
347        let setup = self.setup(call_setup);
348        debug!("finished setting up in {:?}", setup_time.elapsed());
349
350        self.executor.inspector_mut().tracer = prev_tracer;
351
352        if setup.reason.is_some() {
353            // The setup failed, so we return a single test result for `setUp`
354            let fail_msg = if !setup.deployment_failure {
355                "setUp()".to_string()
356            } else {
357                "constructor()".to_string()
358            };
359            return SuiteResult::new(
360                start.elapsed(),
361                [(fail_msg, TestResult::setup_result(setup))].into(),
362                warnings,
363            );
364        }
365
366        // Filter out functions sequentially since it's very fast and there is no need to do it
367        // in parallel.
368        let find_timer = Instant::now();
369        let functions = self
370            .contract
371            .abi
372            .functions()
373            .filter(|func| filter.matches_test_function(func))
374            .collect::<Vec<_>>();
375        debug!(
376            "Found {} test functions out of {} in {:?}",
377            functions.len(),
378            self.contract.abi.functions().count(),
379            find_timer.elapsed(),
380        );
381
382        let identified_contracts = has_invariants.then(|| {
383            load_contracts(setup.traces.iter().map(|(_, t)| &t.arena), &self.mcr.known_contracts)
384        });
385
386        let test_fail_functions =
387            functions.iter().filter(|func| func.test_function_kind().is_any_test_fail());
388        if test_fail_functions.clone().next().is_some() {
389            let fail = || {
390                TestResult::fail("`testFail*` has been removed. Consider changing to test_Revert[If|When]_Condition and expecting a revert".to_string())
391            };
392            let test_results = test_fail_functions.map(|func| (func.signature(), fail())).collect();
393            return SuiteResult::new(start.elapsed(), test_results, warnings);
394        }
395
396        let fail_fast = &self.tcfg.fail_fast;
397
398        let test_results = functions
399            .par_iter()
400            .map(|&func| {
401                // Early exit if we're running with fail-fast and a test already failed.
402                if fail_fast.should_stop() {
403                    return (func.signature(), TestResult::setup_result(setup.clone()));
404                }
405
406                let start = Instant::now();
407
408                let _guard = self.tokio_handle.enter();
409
410                let _guard;
411                let current_span = tracing::Span::current();
412                if current_span.is_none() || current_span.id() != self.span.id() {
413                    _guard = self.span.enter();
414                }
415
416                let sig = func.signature();
417                let kind = func.test_function_kind();
418
419                let _guard = debug_span!(
420                    "test",
421                    %kind,
422                    name = %if enabled!(tracing::Level::TRACE) { &sig } else { &func.name },
423                )
424                .entered();
425
426                let mut res = FunctionRunner::new(&self, &setup).run(
427                    func,
428                    kind,
429                    call_after_invariant,
430                    identified_contracts.as_ref(),
431                );
432                res.duration = start.elapsed();
433
434                // Set fail fast flag if current test failed.
435                if res.status.is_failure() {
436                    fail_fast.record_fail();
437                }
438
439                (sig, res)
440            })
441            .collect::<BTreeMap<_, _>>();
442
443        let duration = start.elapsed();
444        SuiteResult::new(duration, test_results, warnings)
445    }
446}
447
448/// Executes a single test function, returning a [`TestResult`].
449struct FunctionRunner<'a> {
450    /// The function-level configuration.
451    tcfg: Cow<'a, TestRunnerConfig>,
452    /// The EVM executor.
453    executor: Cow<'a, Executor>,
454    /// The parent runner.
455    cr: &'a ContractRunner<'a>,
456    /// The address of the test contract.
457    address: Address,
458    /// The test setup result.
459    setup: &'a TestSetup,
460    /// The test result. Returned after running the test.
461    result: TestResult,
462}
463
464impl<'a> std::ops::Deref for FunctionRunner<'a> {
465    type Target = Cow<'a, TestRunnerConfig>;
466
467    #[inline(always)]
468    fn deref(&self) -> &Self::Target {
469        &self.tcfg
470    }
471}
472
473impl<'a> FunctionRunner<'a> {
474    fn new(cr: &'a ContractRunner<'a>, setup: &'a TestSetup) -> Self {
475        Self {
476            tcfg: match &cr.tcfg {
477                Cow::Borrowed(tcfg) => Cow::Borrowed(tcfg),
478                Cow::Owned(tcfg) => Cow::Owned(tcfg.clone()),
479            },
480            executor: Cow::Borrowed(&cr.executor),
481            cr,
482            address: setup.address,
483            setup,
484            result: TestResult::new(setup),
485        }
486    }
487
488    fn revert_decoder(&self) -> &'a RevertDecoder {
489        &self.cr.mcr.revert_decoder
490    }
491
492    /// Configures this runner with the inline configuration for the contract.
493    fn apply_function_inline_config(&mut self, func: &Function) -> Result<()> {
494        if self.inline_config.contains_function(self.cr.name, &func.name) {
495            let new_config = Arc::new(self.cr.inline_config(Some(func))?);
496            self.tcfg.to_mut().reconfigure_with(new_config);
497            self.tcfg.configure_executor(self.executor.to_mut());
498        }
499        Ok(())
500    }
501
502    fn run(
503        mut self,
504        func: &Function,
505        kind: TestFunctionKind,
506        call_after_invariant: bool,
507        identified_contracts: Option<&ContractsByAddress>,
508    ) -> TestResult {
509        if let Err(e) = self.apply_function_inline_config(func) {
510            self.result.single_fail(Some(e.to_string()));
511            return self.result;
512        }
513
514        match kind {
515            TestFunctionKind::UnitTest { .. } => self.run_unit_test(func),
516            TestFunctionKind::FuzzTest { .. } => self.run_fuzz_test(func),
517            TestFunctionKind::TableTest => self.run_table_test(func),
518            TestFunctionKind::InvariantTest => {
519                let test_bytecode = &self.cr.contract.bytecode;
520                self.run_invariant_test(
521                    func,
522                    call_after_invariant,
523                    identified_contracts.unwrap(),
524                    test_bytecode,
525                )
526            }
527            _ => unreachable!(),
528        }
529    }
530
531    /// Runs a single unit test.
532    ///
533    /// Applies before test txes (if any), runs current test and returns the `TestResult`.
534    ///
535    /// Before test txes are applied in order and state modifications committed to the EVM database
536    /// (therefore the unit test call will be made on modified state).
537    /// State modifications of before test txes and unit test function call are discarded after
538    /// test ends, similar to `eth_call`.
539    fn run_unit_test(mut self, func: &Function) -> TestResult {
540        // Prepare unit test execution.
541        if self.prepare_test(func).is_err() {
542            return self.result;
543        }
544
545        // Run current unit test.
546        let (mut raw_call_result, reason) = match self.executor.call(
547            self.sender,
548            self.address,
549            func,
550            &[],
551            U256::ZERO,
552            Some(self.revert_decoder()),
553        ) {
554            Ok(res) => (res.raw, None),
555            Err(EvmError::Execution(err)) => (err.raw, Some(err.reason)),
556            Err(EvmError::Skip(reason)) => {
557                self.result.single_skip(reason);
558                return self.result;
559            }
560            Err(err) => {
561                self.result.single_fail(Some(err.to_string()));
562                return self.result;
563            }
564        };
565
566        let success =
567            self.executor.is_raw_call_mut_success(self.address, &mut raw_call_result, false);
568        self.result.single_result(success, reason, raw_call_result);
569        self.result
570    }
571
572    /// Runs a table test.
573    /// The parameters dataset (table) is created from defined parameter fixtures, therefore each
574    /// test table parameter should have the same number of fixtures defined.
575    /// E.g. for table test
576    /// - `table_test(uint256 amount, bool swap)` fixtures are defined as
577    /// - `uint256[] public fixtureAmount = [2, 5]`
578    /// - `bool[] public fixtureSwap = [true, false]` The `table_test` is then called with the pair
579    ///   of args `(2, true)` and `(5, false)`.
580    fn run_table_test(mut self, func: &Function) -> TestResult {
581        // Prepare unit test execution.
582        if self.prepare_test(func).is_err() {
583            return self.result;
584        }
585
586        // Extract and validate fixtures for the first table test parameter.
587        let Some(first_param) = func.inputs.first() else {
588            self.result.single_fail(Some("Table test should have at least one parameter".into()));
589            return self.result;
590        };
591
592        let Some(first_param_fixtures) =
593            &self.setup.fuzz_fixtures.param_fixtures(first_param.name())
594        else {
595            self.result.single_fail(Some("Table test should have fixtures defined".into()));
596            return self.result;
597        };
598
599        if first_param_fixtures.is_empty() {
600            self.result.single_fail(Some("Table test should have at least one fixture".into()));
601            return self.result;
602        }
603
604        let fixtures_len = first_param_fixtures.len();
605        let mut table_fixtures = vec![&first_param_fixtures[..]];
606
607        // Collect fixtures for remaining parameters.
608        for param in &func.inputs[1..] {
609            let param_name = param.name();
610            let Some(fixtures) = &self.setup.fuzz_fixtures.param_fixtures(param.name()) else {
611                self.result.single_fail(Some(format!("No fixture defined for param {param_name}")));
612                return self.result;
613            };
614
615            if fixtures.len() != fixtures_len {
616                self.result.single_fail(Some(format!(
617                    "{} fixtures defined for {param_name} (expected {})",
618                    fixtures.len(),
619                    fixtures_len
620                )));
621                return self.result;
622            }
623
624            table_fixtures.push(&fixtures[..]);
625        }
626
627        let progress = start_fuzz_progress(
628            self.cr.progress,
629            self.cr.name,
630            &func.name,
631            None,
632            fixtures_len as u32,
633        );
634
635        let mut result = FuzzTestResult::default();
636
637        for i in 0..fixtures_len {
638            if self.tcfg.fail_fast.should_stop() {
639                return self.result;
640            }
641
642            // Increment progress bar.
643            if let Some(progress) = progress.as_ref() {
644                progress.inc(1);
645            }
646
647            let args = table_fixtures.iter().map(|row| row[i].clone()).collect_vec();
648            let (mut raw_call_result, reason) = match self.executor.call(
649                self.sender,
650                self.address,
651                func,
652                &args,
653                U256::ZERO,
654                Some(self.revert_decoder()),
655            ) {
656                Ok(res) => (res.raw, None),
657                Err(EvmError::Execution(err)) => (err.raw, Some(err.reason)),
658                Err(EvmError::Skip(reason)) => {
659                    self.result.single_skip(reason);
660                    return self.result;
661                }
662                Err(err) => {
663                    self.result.single_fail(Some(err.to_string()));
664                    return self.result;
665                }
666            };
667
668            result.gas_by_case.push((raw_call_result.gas_used, raw_call_result.stipend));
669            result.logs.extend(raw_call_result.logs.clone());
670            result.labels.extend(raw_call_result.labels.clone());
671            HitMaps::merge_opt(&mut result.line_coverage, raw_call_result.line_coverage.clone());
672
673            let is_success =
674                self.executor.is_raw_call_mut_success(self.address, &mut raw_call_result, false);
675            // Record counterexample if test fails.
676            if !is_success {
677                result.counterexample =
678                    Some(CounterExample::Single(BaseCounterExample::from_fuzz_call(
679                        Bytes::from(func.abi_encode_input(&args).unwrap()),
680                        args,
681                        raw_call_result.traces.clone(),
682                    )));
683                result.reason = reason;
684                result.traces = raw_call_result.traces;
685                self.result.table_result(result);
686                return self.result;
687            }
688
689            // If it's the last iteration and all other runs succeeded, then use last call result
690            // for logs and traces.
691            if i == fixtures_len - 1 {
692                result.success = true;
693                result.traces = raw_call_result.traces;
694                self.result.table_result(result);
695                return self.result;
696            }
697        }
698
699        self.result
700    }
701
702    fn run_invariant_test(
703        mut self,
704        func: &Function,
705        call_after_invariant: bool,
706        identified_contracts: &ContractsByAddress,
707        test_bytecode: &Bytes,
708    ) -> TestResult {
709        // First, run the test normally to see if it needs to be skipped.
710        if let Err(EvmError::Skip(reason)) = self.executor.call(
711            self.sender,
712            self.address,
713            func,
714            &[],
715            U256::ZERO,
716            Some(self.revert_decoder()),
717        ) {
718            self.result.invariant_skip(reason);
719            return self.result;
720        };
721
722        let runner = self.invariant_runner();
723        let invariant_config = &self.config.invariant;
724
725        let mut executor = self.clone_executor();
726        // Enable edge coverage if running with coverage guided fuzzing or with edge coverage
727        // metrics (useful for benchmarking the fuzzer).
728        executor
729            .inspector_mut()
730            .collect_edge_coverage(invariant_config.corpus.collect_edge_coverage());
731        let mut config = invariant_config.clone();
732        let (failure_dir, failure_file) = test_paths(
733            &mut config.corpus,
734            invariant_config.failure_persist_dir.clone().unwrap(),
735            self.cr.name,
736            &func.name,
737        );
738
739        let mut evm = InvariantExecutor::new(
740            executor,
741            runner,
742            config,
743            identified_contracts,
744            &self.cr.mcr.known_contracts,
745        );
746        let invariant_contract = InvariantContract {
747            address: self.address,
748            invariant_function: func,
749            call_after_invariant,
750            abi: &self.cr.contract.abi,
751        };
752        let show_solidity = invariant_config.show_solidity;
753
754        // Try to replay recorded failure if any.
755        if let Some(mut call_sequence) =
756            persisted_call_sequence(failure_file.as_path(), test_bytecode)
757        {
758            // Create calls from failed sequence and check if invariant still broken.
759            let txes = call_sequence
760                .iter_mut()
761                .map(|seq| {
762                    seq.show_solidity = show_solidity;
763                    BasicTxDetails {
764                        sender: seq.sender.unwrap_or_default(),
765                        call_details: CallDetails {
766                            target: seq.addr.unwrap_or_default(),
767                            calldata: seq.calldata.clone(),
768                        },
769                    }
770                })
771                .collect::<Vec<BasicTxDetails>>();
772            if let Ok((success, replayed_entirely)) = check_sequence(
773                self.clone_executor(),
774                &txes,
775                (0..min(txes.len(), invariant_config.depth as usize)).collect(),
776                invariant_contract.address,
777                invariant_contract.invariant_function.selector().to_vec().into(),
778                invariant_config.fail_on_revert,
779                invariant_contract.call_after_invariant,
780            ) && !success
781            {
782                let _ = sh_warn!(
783                    "\
784                            Replayed invariant failure from {:?} file. \
785                            Run `forge clean` or remove file to ignore failure and to continue invariant test campaign.",
786                    failure_file.as_path()
787                );
788                // If sequence still fails then replay error to collect traces and
789                // exit without executing new runs.
790                let _ = replay_run(
791                    &invariant_contract,
792                    self.clone_executor(),
793                    &self.cr.mcr.known_contracts,
794                    identified_contracts.clone(),
795                    &mut self.result.logs,
796                    &mut self.result.traces,
797                    &mut self.result.line_coverage,
798                    &mut self.result.deprecated_cheatcodes,
799                    &txes,
800                    show_solidity,
801                );
802                self.result.invariant_replay_fail(
803                    replayed_entirely,
804                    &invariant_contract.invariant_function.name,
805                    call_sequence,
806                );
807                return self.result;
808            }
809        }
810
811        let progress = start_fuzz_progress(
812            self.cr.progress,
813            self.cr.name,
814            &func.name,
815            invariant_config.timeout,
816            invariant_config.runs,
817        );
818        let invariant_result = match evm.invariant_fuzz(
819            invariant_contract.clone(),
820            &self.setup.fuzz_fixtures,
821            &self.setup.deployed_libs,
822            progress.as_ref(),
823            &self.tcfg.fail_fast,
824        ) {
825            Ok(x) => x,
826            Err(e) => {
827                self.result.invariant_setup_fail(e);
828                return self.result;
829            }
830        };
831        // Merge coverage collected during invariant run with test setup coverage.
832        self.result.merge_coverages(invariant_result.line_coverage);
833
834        let mut counterexample = None;
835        let success = invariant_result.error.is_none();
836        let reason = invariant_result.error.as_ref().and_then(|err| err.revert_reason());
837
838        match invariant_result.error {
839            // If invariants were broken, replay the error to collect logs and traces
840            Some(error) => match error {
841                InvariantFuzzError::BrokenInvariant(case_data)
842                | InvariantFuzzError::Revert(case_data) => {
843                    // Replay error to create counterexample and to collect logs, traces and
844                    // coverage.
845                    match replay_error(
846                        &case_data,
847                        &invariant_contract,
848                        self.clone_executor(),
849                        &self.cr.mcr.known_contracts,
850                        identified_contracts.clone(),
851                        &mut self.result.logs,
852                        &mut self.result.traces,
853                        &mut self.result.line_coverage,
854                        &mut self.result.deprecated_cheatcodes,
855                        progress.as_ref(),
856                        show_solidity,
857                    ) {
858                        Ok(call_sequence) => {
859                            if !call_sequence.is_empty() {
860                                // Persist error in invariant failure dir.
861                                if let Err(err) = foundry_common::fs::create_dir_all(failure_dir) {
862                                    error!(%err, "Failed to create invariant failure dir");
863                                } else if let Err(err) = foundry_common::fs::write_json_file(
864                                    failure_file.as_path(),
865                                    &InvariantPersistedFailure {
866                                        call_sequence: call_sequence.clone(),
867                                        driver_bytecode: Some(test_bytecode.clone()),
868                                    },
869                                ) {
870                                    error!(%err, "Failed to record call sequence");
871                                }
872
873                                let original_seq_len =
874                                    if let TestError::Fail(_, calls) = &case_data.test_error {
875                                        calls.len()
876                                    } else {
877                                        call_sequence.len()
878                                    };
879
880                                counterexample =
881                                    Some(CounterExample::Sequence(original_seq_len, call_sequence))
882                            }
883                        }
884                        Err(err) => {
885                            error!(%err, "Failed to replay invariant error");
886                        }
887                    };
888                }
889                InvariantFuzzError::MaxAssumeRejects(_) => {}
890            },
891
892            // If invariants ran successfully, replay the last run to collect logs and
893            // traces.
894            _ => {
895                if let Err(err) = replay_run(
896                    &invariant_contract,
897                    self.clone_executor(),
898                    &self.cr.mcr.known_contracts,
899                    identified_contracts.clone(),
900                    &mut self.result.logs,
901                    &mut self.result.traces,
902                    &mut self.result.line_coverage,
903                    &mut self.result.deprecated_cheatcodes,
904                    &invariant_result.last_run_inputs,
905                    show_solidity,
906                ) {
907                    error!(%err, "Failed to replay last invariant run");
908                }
909            }
910        }
911
912        self.result.invariant_result(
913            invariant_result.gas_report_traces,
914            success,
915            reason,
916            counterexample,
917            invariant_result.cases,
918            invariant_result.reverts,
919            invariant_result.metrics,
920            invariant_result.failed_corpus_replays,
921        );
922        self.result
923    }
924
925    /// Runs a fuzzed test.
926    ///
927    /// Applies the before test txes (if any), fuzzes the current function and returns the
928    /// `TestResult`.
929    ///
930    /// Before test txes are applied in order and state modifications committed to the EVM database
931    /// (therefore the fuzz test will use the modified state).
932    /// State modifications of before test txes and fuzz test are discarded after test ends,
933    /// similar to `eth_call`.
934    fn run_fuzz_test(mut self, func: &Function) -> TestResult {
935        // Prepare fuzz test execution.
936        if self.prepare_test(func).is_err() {
937            return self.result;
938        }
939
940        let runner = self.fuzz_runner();
941        let mut fuzz_config = self.config.fuzz.clone();
942        let (failure_dir, failure_file) = test_paths(
943            &mut fuzz_config.corpus,
944            fuzz_config.failure_persist_dir.clone().unwrap(),
945            self.cr.name,
946            &func.name,
947        );
948
949        let progress = start_fuzz_progress(
950            self.cr.progress,
951            self.cr.name,
952            &func.name,
953            fuzz_config.timeout,
954            fuzz_config.runs,
955        );
956
957        let mut executor = self.executor.into_owned();
958        // Enable edge coverage if running with coverage guided fuzzing or with edge coverage
959        // metrics (useful for benchmarking the fuzzer).
960        executor.inspector_mut().collect_edge_coverage(fuzz_config.corpus.collect_edge_coverage());
961        // Load persisted counterexample, if any.
962        let persisted_failure =
963            foundry_common::fs::read_json_file::<BaseCounterExample>(failure_file.as_path()).ok();
964        // Run fuzz test.
965        let mut fuzzed_executor =
966            FuzzedExecutor::new(executor, runner, self.tcfg.sender, fuzz_config, persisted_failure);
967        let result = match fuzzed_executor.fuzz(
968            func,
969            &self.setup.fuzz_fixtures,
970            &self.setup.deployed_libs,
971            self.address,
972            &self.cr.mcr.revert_decoder,
973            progress.as_ref(),
974            &self.tcfg.fail_fast,
975        ) {
976            Ok(x) => x,
977            Err(e) => {
978                self.result.fuzz_setup_fail(e);
979                return self.result;
980            }
981        };
982
983        // Record counterexample.
984        if let Some(CounterExample::Single(counterexample)) = &result.counterexample {
985            if let Err(err) = foundry_common::fs::create_dir_all(failure_dir) {
986                error!(%err, "Failed to create fuzz failure dir");
987            } else if let Err(err) =
988                foundry_common::fs::write_json_file(failure_file.as_path(), counterexample)
989            {
990                error!(%err, "Failed to record call sequence");
991            }
992        }
993
994        self.result.fuzz_result(result);
995        self.result
996    }
997
998    /// Prepares single unit test and fuzz test execution:
999    /// - set up the test result and executor
1000    /// - check if before test txes are configured and apply them in order
1001    ///
1002    /// Before test txes are arrays of arbitrary calldata obtained by calling the `beforeTest`
1003    /// function with test selector as a parameter.
1004    ///
1005    /// Unit tests within same contract (or even current test) are valid options for before test tx
1006    /// configuration. Test execution stops if any of before test txes fails.
1007    fn prepare_test(&mut self, func: &Function) -> Result<(), ()> {
1008        let address = self.setup.address;
1009
1010        // Apply before test configured functions (if any).
1011        if self.cr.contract.abi.functions().filter(|func| func.name.is_before_test_setup()).count()
1012            == 1
1013        {
1014            for calldata in self.executor.call_sol_default(
1015                address,
1016                &ITest::beforeTestSetupCall { testSelector: func.selector() },
1017            ) {
1018                // Apply before test configured calldata.
1019                match self.executor.to_mut().transact_raw(
1020                    self.tcfg.sender,
1021                    address,
1022                    calldata,
1023                    U256::ZERO,
1024                ) {
1025                    Ok(call_result) => {
1026                        let reverted = call_result.reverted;
1027
1028                        // Merge tx result traces in unit test result.
1029                        self.result.extend(call_result);
1030
1031                        // To continue unit test execution the call should not revert.
1032                        if reverted {
1033                            self.result.single_fail(None);
1034                            return Err(());
1035                        }
1036                    }
1037                    Err(_) => {
1038                        self.result.single_fail(None);
1039                        return Err(());
1040                    }
1041                }
1042            }
1043        }
1044        Ok(())
1045    }
1046
1047    fn fuzz_runner(&self) -> TestRunner {
1048        let config = &self.config.fuzz;
1049        fuzzer_with_cases(config.seed, config.runs, config.max_test_rejects)
1050    }
1051
1052    fn invariant_runner(&self) -> TestRunner {
1053        let config = &self.config.invariant;
1054        fuzzer_with_cases(self.config.fuzz.seed, config.runs, config.max_assume_rejects)
1055    }
1056
1057    fn clone_executor(&self) -> Executor {
1058        self.executor.clone().into_owned()
1059    }
1060}
1061
1062fn fuzzer_with_cases(seed: Option<U256>, cases: u32, max_global_rejects: u32) -> TestRunner {
1063    let config = proptest::test_runner::Config {
1064        cases,
1065        max_global_rejects,
1066        // Disable proptest shrink: for fuzz tests we provide single counterexample,
1067        // for invariant tests we shrink outside proptest.
1068        max_shrink_iters: 0,
1069        ..Default::default()
1070    };
1071
1072    if let Some(seed) = seed {
1073        trace!(target: "forge::test", %seed, "building deterministic fuzzer");
1074        let rng = TestRng::from_seed(RngAlgorithm::ChaCha, &seed.to_be_bytes::<32>());
1075        TestRunner::new_with_rng(config, rng)
1076    } else {
1077        trace!(target: "forge::test", "building stochastic fuzzer");
1078        TestRunner::new(config)
1079    }
1080}
1081
1082/// Holds data about a persisted invariant failure.
1083#[derive(Serialize, Deserialize)]
1084struct InvariantPersistedFailure {
1085    /// Recorded counterexample.
1086    call_sequence: Vec<BaseCounterExample>,
1087    /// Bytecode of the test contract that generated the counterexample.
1088    #[serde(skip_serializing_if = "Option::is_none")]
1089    driver_bytecode: Option<Bytes>,
1090}
1091
1092/// Helper function to load failed call sequence from file.
1093/// Ignores failure if generated with different test contract than the current one.
1094fn persisted_call_sequence(path: &Path, bytecode: &Bytes) -> Option<Vec<BaseCounterExample>> {
1095    foundry_common::fs::read_json_file::<InvariantPersistedFailure>(path).ok().and_then(
1096        |persisted_failure| {
1097            if let Some(persisted_bytecode) = &persisted_failure.driver_bytecode {
1098                // Ignore persisted sequence if test bytecode doesn't match.
1099                if !bytecode.eq(persisted_bytecode) {
1100                    let _= sh_warn!("\
1101                            Failure from {:?} file was ignored because test contract bytecode has changed.",
1102                        path
1103                    );
1104                    return None;
1105                }
1106            };
1107            Some(persisted_failure.call_sequence)
1108        },
1109    )
1110}
1111
1112/// Helper function to set test corpus dir and to compose persisted failure paths.
1113fn test_paths(
1114    corpus_config: &mut FuzzCorpusConfig,
1115    persist_dir: PathBuf,
1116    contract_name: &str,
1117    test_name: &str,
1118) -> (PathBuf, PathBuf) {
1119    let contract = contract_name.split(':').next_back().unwrap();
1120    // Update config with corpus dir for current test.
1121    corpus_config.with_test(contract, test_name);
1122
1123    let failures_dir = canonicalized(persist_dir.join("failures").join(contract));
1124    let failure_file = canonicalized(failures_dir.join(test_name));
1125    (failures_dir, failure_file)
1126}