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