Skip to main content

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