forge/
runner.rs

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