forge/
runner.rs

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