forge/
runner.rs

1//! The Forge test runner.
2
3use crate::{
4    MultiContractRunner, TestFilter,
5    coverage::HitMaps,
6    fuzz::{BaseCounterExample, FuzzTestResult},
7    multi_runner::{TestContract, TestRunnerConfig},
8    progress::{TestsProgress, start_fuzz_progress},
9    result::{SuiteResult, TestResult, TestSetup},
10};
11use alloy_dyn_abi::{DynSolValue, JsonAbiExt};
12use alloy_json_abi::Function;
13use alloy_primitives::{Address, Bytes, U256, address, map::HashMap};
14use eyre::Result;
15use foundry_common::{TestFunctionExt, TestFunctionKind, contracts::ContractsByAddress};
16use foundry_compilers::utils::canonicalized;
17use foundry_config::{Config, FuzzCorpusConfig};
18use foundry_evm::{
19    constants::CALLER,
20    decode::RevertDecoder,
21    executors::{
22        CallResult, EvmError, Executor, ITest, RawCallResult,
23        fuzz::FuzzedExecutor,
24        invariant::{
25            InvariantExecutor, InvariantFuzzError, check_sequence, replay_error, replay_run,
26        },
27    },
28    fuzz::{
29        BasicTxDetails, CallDetails, CounterExample, FuzzFixtures, fixture_name,
30        invariant::InvariantContract,
31    },
32    traces::{TraceKind, TraceMode, load_contracts},
33};
34use itertools::Itertools;
35use proptest::test_runner::{RngAlgorithm, TestError, TestRng, TestRunner};
36use rayon::prelude::*;
37use serde::{Deserialize, Serialize};
38use std::{
39    borrow::Cow,
40    cmp::min,
41    collections::BTreeMap,
42    path::{Path, PathBuf},
43    sync::Arc,
44    time::Instant,
45};
46use 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_exit();
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                // Set fail fast flag if current test failed.
446                if res.status.is_failure() {
447                    early_exit.record_exit();
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 = InvariantContract {
758            address: self.address,
759            invariant_function: func,
760            call_after_invariant,
761            abi: &self.cr.contract.abi,
762        };
763        let show_solidity = invariant_config.show_solidity;
764
765        // Try to replay recorded failure if any.
766        if let Some(mut call_sequence) =
767            persisted_call_sequence(failure_file.as_path(), test_bytecode)
768        {
769            // Create calls from failed sequence and check if invariant still broken.
770            let txes = call_sequence
771                .iter_mut()
772                .map(|seq| {
773                    seq.show_solidity = show_solidity;
774                    BasicTxDetails {
775                        sender: seq.sender.unwrap_or_default(),
776                        call_details: CallDetails {
777                            target: seq.addr.unwrap_or_default(),
778                            calldata: seq.calldata.clone(),
779                        },
780                    }
781                })
782                .collect::<Vec<BasicTxDetails>>();
783            if let Ok((success, replayed_entirely)) = check_sequence(
784                self.clone_executor(),
785                &txes,
786                (0..min(txes.len(), invariant_config.depth as usize)).collect(),
787                invariant_contract.address,
788                invariant_contract.invariant_function.selector().to_vec().into(),
789                invariant_config.fail_on_revert,
790                invariant_contract.call_after_invariant,
791            ) && !success
792            {
793                let _ = sh_warn!(
794                    "\
795                            Replayed invariant failure from {:?} file. \
796                            Run `forge clean` or remove file to ignore failure and to continue invariant test campaign.",
797                    failure_file.as_path()
798                );
799                // If sequence still fails then replay error to collect traces and
800                // exit without executing new runs.
801                let _ = replay_run(
802                    &invariant_contract,
803                    self.clone_executor(),
804                    &self.cr.mcr.known_contracts,
805                    identified_contracts.clone(),
806                    &mut self.result.logs,
807                    &mut self.result.traces,
808                    &mut self.result.line_coverage,
809                    &mut self.result.deprecated_cheatcodes,
810                    &txes,
811                    show_solidity,
812                );
813                self.result.invariant_replay_fail(
814                    replayed_entirely,
815                    &invariant_contract.invariant_function.name,
816                    call_sequence,
817                );
818                return self.result;
819            }
820        }
821
822        let progress = start_fuzz_progress(
823            self.cr.progress,
824            self.cr.name,
825            &func.name,
826            invariant_config.timeout,
827            invariant_config.runs,
828        );
829        let invariant_result = match evm.invariant_fuzz(
830            invariant_contract.clone(),
831            &self.setup.fuzz_fixtures,
832            &self.setup.deployed_libs,
833            progress.as_ref(),
834            &self.tcfg.early_exit,
835        ) {
836            Ok(x) => x,
837            Err(e) => {
838                self.result.invariant_setup_fail(e);
839                return self.result;
840            }
841        };
842        // Merge coverage collected during invariant run with test setup coverage.
843        self.result.merge_coverages(invariant_result.line_coverage);
844
845        let mut counterexample = None;
846        let success = invariant_result.error.is_none();
847        let reason = invariant_result.error.as_ref().and_then(|err| err.revert_reason());
848
849        match invariant_result.error {
850            // If invariants were broken, replay the error to collect logs and traces
851            Some(error) => match error {
852                InvariantFuzzError::BrokenInvariant(case_data)
853                | InvariantFuzzError::Revert(case_data) => {
854                    // Replay error to create counterexample and to collect logs, traces and
855                    // coverage.
856                    match replay_error(
857                        &case_data,
858                        &invariant_contract,
859                        self.clone_executor(),
860                        &self.cr.mcr.known_contracts,
861                        identified_contracts.clone(),
862                        &mut self.result.logs,
863                        &mut self.result.traces,
864                        &mut self.result.line_coverage,
865                        &mut self.result.deprecated_cheatcodes,
866                        progress.as_ref(),
867                        show_solidity,
868                        &self.tcfg.early_exit,
869                    ) {
870                        Ok(call_sequence) => {
871                            if !call_sequence.is_empty() {
872                                // Persist error in invariant failure dir.
873                                if let Err(err) = foundry_common::fs::create_dir_all(failure_dir) {
874                                    error!(%err, "Failed to create invariant failure dir");
875                                } else if let Err(err) = foundry_common::fs::write_json_file(
876                                    failure_file.as_path(),
877                                    &InvariantPersistedFailure {
878                                        call_sequence: call_sequence.clone(),
879                                        driver_bytecode: Some(test_bytecode.clone()),
880                                    },
881                                ) {
882                                    error!(%err, "Failed to record call sequence");
883                                }
884
885                                let original_seq_len =
886                                    if let TestError::Fail(_, calls) = &case_data.test_error {
887                                        calls.len()
888                                    } else {
889                                        call_sequence.len()
890                                    };
891
892                                counterexample =
893                                    Some(CounterExample::Sequence(original_seq_len, call_sequence))
894                            }
895                        }
896                        Err(err) => {
897                            error!(%err, "Failed to replay invariant error");
898                        }
899                    };
900                }
901                InvariantFuzzError::MaxAssumeRejects(_) => {}
902            },
903
904            // If invariants ran successfully, replay the last run to collect logs and
905            // traces.
906            _ => {
907                if let Err(err) = replay_run(
908                    &invariant_contract,
909                    self.clone_executor(),
910                    &self.cr.mcr.known_contracts,
911                    identified_contracts.clone(),
912                    &mut self.result.logs,
913                    &mut self.result.traces,
914                    &mut self.result.line_coverage,
915                    &mut self.result.deprecated_cheatcodes,
916                    &invariant_result.last_run_inputs,
917                    show_solidity,
918                ) {
919                    error!(%err, "Failed to replay last invariant run");
920                }
921            }
922        }
923
924        self.result.invariant_result(
925            invariant_result.gas_report_traces,
926            success,
927            reason,
928            counterexample,
929            invariant_result.cases,
930            invariant_result.reverts,
931            invariant_result.metrics,
932            invariant_result.failed_corpus_replays,
933        );
934        self.result
935    }
936
937    /// Runs a fuzzed test.
938    ///
939    /// Applies the before test txes (if any), fuzzes the current function and returns the
940    /// `TestResult`.
941    ///
942    /// Before test txes are applied in order and state modifications committed to the EVM database
943    /// (therefore the fuzz test will use the modified state).
944    /// State modifications of before test txes and fuzz test are discarded after test ends,
945    /// similar to `eth_call`.
946    fn run_fuzz_test(mut self, func: &Function) -> TestResult {
947        // Prepare fuzz test execution.
948        if self.prepare_test(func).is_err() {
949            return self.result;
950        }
951
952        let runner = self.fuzz_runner();
953        let mut fuzz_config = self.config.fuzz.clone();
954        let (failure_dir, failure_file) = test_paths(
955            &mut fuzz_config.corpus,
956            fuzz_config.failure_persist_dir.clone().unwrap(),
957            self.cr.name,
958            &func.name,
959        );
960
961        let progress = start_fuzz_progress(
962            self.cr.progress,
963            self.cr.name,
964            &func.name,
965            fuzz_config.timeout,
966            fuzz_config.runs,
967        );
968
969        let mut executor = self.executor.into_owned();
970        // Enable edge coverage if running with coverage guided fuzzing or with edge coverage
971        // metrics (useful for benchmarking the fuzzer).
972        executor.inspector_mut().collect_edge_coverage(fuzz_config.corpus.collect_edge_coverage());
973        // Load persisted counterexample, if any.
974        let persisted_failure =
975            foundry_common::fs::read_json_file::<BaseCounterExample>(failure_file.as_path()).ok();
976        // Run fuzz test.
977        let mut fuzzed_executor =
978            FuzzedExecutor::new(executor, runner, self.tcfg.sender, fuzz_config, persisted_failure);
979        let result = match fuzzed_executor.fuzz(
980            func,
981            &self.setup.fuzz_fixtures,
982            &self.setup.deployed_libs,
983            self.address,
984            &self.cr.mcr.revert_decoder,
985            progress.as_ref(),
986            &self.tcfg.early_exit,
987        ) {
988            Ok(x) => x,
989            Err(e) => {
990                self.result.fuzz_setup_fail(e);
991                return self.result;
992            }
993        };
994
995        // Record counterexample.
996        if let Some(CounterExample::Single(counterexample)) = &result.counterexample {
997            if let Err(err) = foundry_common::fs::create_dir_all(failure_dir) {
998                error!(%err, "Failed to create fuzz failure dir");
999            } else if let Err(err) =
1000                foundry_common::fs::write_json_file(failure_file.as_path(), counterexample)
1001            {
1002                error!(%err, "Failed to record call sequence");
1003            }
1004        }
1005
1006        self.result.fuzz_result(result);
1007        self.result
1008    }
1009
1010    /// Prepares single unit test and fuzz test execution:
1011    /// - set up the test result and executor
1012    /// - check if before test txes are configured and apply them in order
1013    ///
1014    /// Before test txes are arrays of arbitrary calldata obtained by calling the `beforeTest`
1015    /// function with test selector as a parameter.
1016    ///
1017    /// Unit tests within same contract (or even current test) are valid options for before test tx
1018    /// configuration. Test execution stops if any of before test txes fails.
1019    fn prepare_test(&mut self, func: &Function) -> Result<(), ()> {
1020        let address = self.setup.address;
1021
1022        // Apply before test configured functions (if any).
1023        if self.cr.contract.abi.functions().any(|func| func.name.is_before_test_setup()) {
1024            for calldata in self.executor.call_sol_default(
1025                address,
1026                &ITest::beforeTestSetupCall { testSelector: func.selector() },
1027            ) {
1028                debug!(?calldata, spec=%self.executor.spec_id(), "applying before_test_setup");
1029                // Apply before test configured calldata.
1030                match self.executor.to_mut().transact_raw(
1031                    self.tcfg.sender,
1032                    address,
1033                    calldata,
1034                    U256::ZERO,
1035                ) {
1036                    Ok(call_result) => {
1037                        let reverted = call_result.reverted;
1038
1039                        // Merge tx result traces in unit test result.
1040                        self.result.extend(call_result);
1041
1042                        // To continue unit test execution the call should not revert.
1043                        if reverted {
1044                            self.result.single_fail(None);
1045                            return Err(());
1046                        }
1047                    }
1048                    Err(_) => {
1049                        self.result.single_fail(None);
1050                        return Err(());
1051                    }
1052                }
1053            }
1054        }
1055        Ok(())
1056    }
1057
1058    fn fuzz_runner(&self) -> TestRunner {
1059        let config = &self.config.fuzz;
1060        fuzzer_with_cases(config.seed, config.runs, config.max_test_rejects)
1061    }
1062
1063    fn invariant_runner(&self) -> TestRunner {
1064        let config = &self.config.invariant;
1065        fuzzer_with_cases(self.config.fuzz.seed, config.runs, config.max_assume_rejects)
1066    }
1067
1068    fn clone_executor(&self) -> Executor {
1069        self.executor.clone().into_owned()
1070    }
1071}
1072
1073fn fuzzer_with_cases(seed: Option<U256>, cases: u32, max_global_rejects: u32) -> TestRunner {
1074    let config = proptest::test_runner::Config {
1075        cases,
1076        max_global_rejects,
1077        // Disable proptest shrink: for fuzz tests we provide single counterexample,
1078        // for invariant tests we shrink outside proptest.
1079        max_shrink_iters: 0,
1080        ..Default::default()
1081    };
1082
1083    if let Some(seed) = seed {
1084        trace!(target: "forge::test", %seed, "building deterministic fuzzer");
1085        let rng = TestRng::from_seed(RngAlgorithm::ChaCha, &seed.to_be_bytes::<32>());
1086        TestRunner::new_with_rng(config, rng)
1087    } else {
1088        trace!(target: "forge::test", "building stochastic fuzzer");
1089        TestRunner::new(config)
1090    }
1091}
1092
1093/// Holds data about a persisted invariant failure.
1094#[derive(Serialize, Deserialize)]
1095struct InvariantPersistedFailure {
1096    /// Recorded counterexample.
1097    call_sequence: Vec<BaseCounterExample>,
1098    /// Bytecode of the test contract that generated the counterexample.
1099    #[serde(skip_serializing_if = "Option::is_none")]
1100    driver_bytecode: Option<Bytes>,
1101}
1102
1103/// Helper function to load failed call sequence from file.
1104/// Ignores failure if generated with different test contract than the current one.
1105fn persisted_call_sequence(path: &Path, bytecode: &Bytes) -> Option<Vec<BaseCounterExample>> {
1106    foundry_common::fs::read_json_file::<InvariantPersistedFailure>(path).ok().and_then(
1107        |persisted_failure| {
1108            if let Some(persisted_bytecode) = &persisted_failure.driver_bytecode {
1109                // Ignore persisted sequence if test bytecode doesn't match.
1110                if !bytecode.eq(persisted_bytecode) {
1111                    let _= sh_warn!("\
1112                            Failure from {:?} file was ignored because test contract bytecode has changed.",
1113                        path
1114                    );
1115                    return None;
1116                }
1117            };
1118            Some(persisted_failure.call_sequence)
1119        },
1120    )
1121}
1122
1123/// Helper function to set test corpus dir and to compose persisted failure paths.
1124fn test_paths(
1125    corpus_config: &mut FuzzCorpusConfig,
1126    persist_dir: PathBuf,
1127    contract_name: &str,
1128    test_name: &str,
1129) -> (PathBuf, PathBuf) {
1130    let contract = contract_name.split(':').next_back().unwrap();
1131    // Update config with corpus dir for current test.
1132    corpus_config.with_test(contract, test_name);
1133
1134    let failures_dir = canonicalized(persist_dir.join("failures").join(contract));
1135    let failure_file = canonicalized(failures_dir.join(test_name));
1136    (failures_dir, failure_file)
1137}