forge/
runner.rs

1//! The Forge test runner.
2
3use crate::{
4    fuzz::{invariant::BasicTxDetails, BaseCounterExample},
5    multi_runner::{is_matching_test, TestContract, TestRunnerConfig},
6    progress::{start_fuzz_progress, TestsProgress},
7    result::{SuiteResult, TestResult, TestSetup},
8    MultiContractRunner, TestFilter,
9};
10use alloy_dyn_abi::DynSolValue;
11use alloy_json_abi::Function;
12use alloy_primitives::{address, map::HashMap, Address, Bytes, U256};
13use eyre::Result;
14use foundry_common::{contracts::ContractsByAddress, TestFunctionExt, TestFunctionKind};
15use foundry_compilers::utils::canonicalized;
16use foundry_config::{Config, InvariantConfig};
17use foundry_evm::{
18    constants::CALLER,
19    decode::RevertDecoder,
20    executors::{
21        fuzz::FuzzedExecutor,
22        invariant::{
23            check_sequence, replay_error, replay_run, InvariantExecutor, InvariantFuzzError,
24        },
25        CallResult, EvmError, Executor, ITest, RawCallResult,
26    },
27    fuzz::{
28        fixture_name,
29        invariant::{CallDetails, InvariantContract},
30        CounterExample, FuzzFixtures,
31    },
32    traces::{load_contracts, TraceKind, TraceMode},
33};
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::InvariantTest => {
516                let test_bytecode = &self.cr.contract.bytecode;
517                self.run_invariant_test(
518                    func,
519                    call_after_invariant,
520                    identified_contracts.unwrap(),
521                    test_bytecode,
522                )
523            }
524            _ => unreachable!(),
525        }
526    }
527
528    /// Runs a single unit test.
529    ///
530    /// Applies before test txes (if any), runs current test and returns the `TestResult`.
531    ///
532    /// Before test txes are applied in order and state modifications committed to the EVM database
533    /// (therefore the unit test call will be made on modified state).
534    /// State modifications of before test txes and unit test function call are discarded after
535    /// test ends, similar to `eth_call`.
536    fn run_unit_test(mut self, func: &Function) -> TestResult {
537        // Prepare unit test execution.
538        if self.prepare_test(func).is_err() {
539            return self.result;
540        }
541
542        // Run current unit test.
543        let (mut raw_call_result, reason) = match self.executor.call(
544            self.sender,
545            self.address,
546            func,
547            &[],
548            U256::ZERO,
549            Some(self.revert_decoder()),
550        ) {
551            Ok(res) => (res.raw, None),
552            Err(EvmError::Execution(err)) => (err.raw, Some(err.reason)),
553            Err(EvmError::Skip(reason)) => {
554                self.result.single_skip(reason);
555                return self.result;
556            }
557            Err(err) => {
558                self.result.single_fail(Some(err.to_string()));
559                return self.result;
560            }
561        };
562
563        let success =
564            self.executor.is_raw_call_mut_success(self.address, &mut raw_call_result, false);
565        self.result.single_result(success, reason, raw_call_result);
566        self.result
567    }
568
569    fn run_invariant_test(
570        mut self,
571        func: &Function,
572        call_after_invariant: bool,
573        identified_contracts: &ContractsByAddress,
574        test_bytecode: &Bytes,
575    ) -> TestResult {
576        // First, run the test normally to see if it needs to be skipped.
577        if let Err(EvmError::Skip(reason)) = self.executor.call(
578            self.sender,
579            self.address,
580            func,
581            &[],
582            U256::ZERO,
583            Some(self.revert_decoder()),
584        ) {
585            self.result.invariant_skip(reason);
586            return self.result;
587        };
588
589        let runner = self.invariant_runner();
590        let invariant_config = &self.config.invariant;
591
592        let mut evm = InvariantExecutor::new(
593            self.clone_executor(),
594            runner,
595            invariant_config.clone(),
596            identified_contracts,
597            &self.cr.mcr.known_contracts,
598        );
599        let invariant_contract = InvariantContract {
600            address: self.address,
601            invariant_function: func,
602            call_after_invariant,
603            abi: &self.cr.contract.abi,
604        };
605
606        let (failure_dir, failure_file) = invariant_failure_paths(
607            invariant_config,
608            self.cr.name,
609            &invariant_contract.invariant_function.name,
610        );
611        let show_solidity = invariant_config.show_solidity;
612
613        // Try to replay recorded failure if any.
614        if let Some(mut call_sequence) =
615            persisted_call_sequence(failure_file.as_path(), test_bytecode)
616        {
617            // Create calls from failed sequence and check if invariant still broken.
618            let txes = call_sequence
619                .iter_mut()
620                .map(|seq| {
621                    seq.show_solidity = show_solidity;
622                    BasicTxDetails {
623                        sender: seq.sender.unwrap_or_default(),
624                        call_details: CallDetails {
625                            target: seq.addr.unwrap_or_default(),
626                            calldata: seq.calldata.clone(),
627                        },
628                    }
629                })
630                .collect::<Vec<BasicTxDetails>>();
631            if let Ok((success, replayed_entirely)) = check_sequence(
632                self.clone_executor(),
633                &txes,
634                (0..min(txes.len(), invariant_config.depth as usize)).collect(),
635                invariant_contract.address,
636                invariant_contract.invariant_function.selector().to_vec().into(),
637                invariant_config.fail_on_revert,
638                invariant_contract.call_after_invariant,
639            ) {
640                if !success {
641                    let _= sh_warn!("\
642                            Replayed invariant failure from {:?} file. \
643                            Run `forge clean` or remove file to ignore failure and to continue invariant test campaign.",
644                        failure_file.as_path()
645                    );
646                    // If sequence still fails then replay error to collect traces and
647                    // exit without executing new runs.
648                    let _ = replay_run(
649                        &invariant_contract,
650                        self.clone_executor(),
651                        &self.cr.mcr.known_contracts,
652                        identified_contracts.clone(),
653                        &mut self.result.logs,
654                        &mut self.result.traces,
655                        &mut self.result.coverage,
656                        &mut self.result.deprecated_cheatcodes,
657                        &txes,
658                        show_solidity,
659                    );
660                    self.result.invariant_replay_fail(
661                        replayed_entirely,
662                        &invariant_contract.invariant_function.name,
663                        call_sequence,
664                    );
665                    return self.result;
666                }
667            }
668        }
669
670        let progress =
671            start_fuzz_progress(self.cr.progress, self.cr.name, &func.name, invariant_config.runs);
672        let invariant_result = match evm.invariant_fuzz(
673            invariant_contract.clone(),
674            &self.setup.fuzz_fixtures,
675            &self.setup.deployed_libs,
676            progress.as_ref(),
677        ) {
678            Ok(x) => x,
679            Err(e) => {
680                self.result.invariant_setup_fail(e);
681                return self.result;
682            }
683        };
684        // Merge coverage collected during invariant run with test setup coverage.
685        self.result.merge_coverages(invariant_result.coverage);
686
687        let mut counterexample = None;
688        let success = invariant_result.error.is_none();
689        let reason = invariant_result.error.as_ref().and_then(|err| err.revert_reason());
690
691        match invariant_result.error {
692            // If invariants were broken, replay the error to collect logs and traces
693            Some(error) => match error {
694                InvariantFuzzError::BrokenInvariant(case_data) |
695                InvariantFuzzError::Revert(case_data) => {
696                    // Replay error to create counterexample and to collect logs, traces and
697                    // coverage.
698                    match replay_error(
699                        &case_data,
700                        &invariant_contract,
701                        self.clone_executor(),
702                        &self.cr.mcr.known_contracts,
703                        identified_contracts.clone(),
704                        &mut self.result.logs,
705                        &mut self.result.traces,
706                        &mut self.result.coverage,
707                        &mut self.result.deprecated_cheatcodes,
708                        progress.as_ref(),
709                        show_solidity,
710                    ) {
711                        Ok(call_sequence) => {
712                            if !call_sequence.is_empty() {
713                                // Persist error in invariant failure dir.
714                                if let Err(err) = foundry_common::fs::create_dir_all(failure_dir) {
715                                    error!(%err, "Failed to create invariant failure dir");
716                                } else if let Err(err) = foundry_common::fs::write_json_file(
717                                    failure_file.as_path(),
718                                    &InvariantPersistedFailure {
719                                        call_sequence: call_sequence.clone(),
720                                        driver_bytecode: Some(test_bytecode.clone()),
721                                    },
722                                ) {
723                                    error!(%err, "Failed to record call sequence");
724                                }
725
726                                let original_seq_len =
727                                    if let TestError::Fail(_, calls) = &case_data.test_error {
728                                        calls.len()
729                                    } else {
730                                        call_sequence.len()
731                                    };
732
733                                counterexample =
734                                    Some(CounterExample::Sequence(original_seq_len, call_sequence))
735                            }
736                        }
737                        Err(err) => {
738                            error!(%err, "Failed to replay invariant error");
739                        }
740                    };
741                }
742                InvariantFuzzError::MaxAssumeRejects(_) => {}
743            },
744
745            // If invariants ran successfully, replay the last run to collect logs and
746            // traces.
747            _ => {
748                if let Err(err) = replay_run(
749                    &invariant_contract,
750                    self.clone_executor(),
751                    &self.cr.mcr.known_contracts,
752                    identified_contracts.clone(),
753                    &mut self.result.logs,
754                    &mut self.result.traces,
755                    &mut self.result.coverage,
756                    &mut self.result.deprecated_cheatcodes,
757                    &invariant_result.last_run_inputs,
758                    show_solidity,
759                ) {
760                    error!(%err, "Failed to replay last invariant run");
761                }
762            }
763        }
764
765        self.result.invariant_result(
766            invariant_result.gas_report_traces,
767            success,
768            reason,
769            counterexample,
770            invariant_result.cases,
771            invariant_result.reverts,
772            invariant_result.metrics,
773        );
774        self.result
775    }
776
777    /// Runs a fuzzed test.
778    ///
779    /// Applies the before test txes (if any), fuzzes the current function and returns the
780    /// `TestResult`.
781    ///
782    /// Before test txes are applied in order and state modifications committed to the EVM database
783    /// (therefore the fuzz test will use the modified state).
784    /// State modifications of before test txes and fuzz test are discarded after test ends,
785    /// similar to `eth_call`.
786    fn run_fuzz_test(mut self, func: &Function) -> TestResult {
787        // Prepare fuzz test execution.
788        if self.prepare_test(func).is_err() {
789            return self.result;
790        }
791
792        let runner = self.fuzz_runner();
793        let fuzz_config = self.config.fuzz.clone();
794
795        let progress =
796            start_fuzz_progress(self.cr.progress, self.cr.name, &func.name, fuzz_config.runs);
797
798        // Run fuzz test.
799        let fuzzed_executor =
800            FuzzedExecutor::new(self.executor.into_owned(), runner, self.tcfg.sender, fuzz_config);
801        let result = fuzzed_executor.fuzz(
802            func,
803            &self.setup.fuzz_fixtures,
804            &self.setup.deployed_libs,
805            self.address,
806            &self.cr.mcr.revert_decoder,
807            progress.as_ref(),
808        );
809        self.result.fuzz_result(result);
810        self.result
811    }
812
813    /// Prepares single unit test and fuzz test execution:
814    /// - set up the test result and executor
815    /// - check if before test txes are configured and apply them in order
816    ///
817    /// Before test txes are arrays of arbitrary calldata obtained by calling the `beforeTest`
818    /// function with test selector as a parameter.
819    ///
820    /// Unit tests within same contract (or even current test) are valid options for before test tx
821    /// configuration. Test execution stops if any of before test txes fails.
822    fn prepare_test(&mut self, func: &Function) -> Result<(), ()> {
823        let address = self.setup.address;
824
825        // Apply before test configured functions (if any).
826        if self.cr.contract.abi.functions().filter(|func| func.name.is_before_test_setup()).count() ==
827            1
828        {
829            for calldata in self
830                .executor
831                .call_sol_default(
832                    address,
833                    &ITest::beforeTestSetupCall { testSelector: func.selector() },
834                )
835                .beforeTestCalldata
836            {
837                // Apply before test configured calldata.
838                match self.executor.to_mut().transact_raw(
839                    self.tcfg.sender,
840                    address,
841                    calldata,
842                    U256::ZERO,
843                ) {
844                    Ok(call_result) => {
845                        let reverted = call_result.reverted;
846
847                        // Merge tx result traces in unit test result.
848                        self.result.extend(call_result);
849
850                        // To continue unit test execution the call should not revert.
851                        if reverted {
852                            self.result.single_fail(None);
853                            return Err(());
854                        }
855                    }
856                    Err(_) => {
857                        self.result.single_fail(None);
858                        return Err(());
859                    }
860                }
861            }
862        }
863        Ok(())
864    }
865
866    fn fuzz_runner(&self) -> TestRunner {
867        let config = &self.config.fuzz;
868        let failure_persist_path = config
869            .failure_persist_dir
870            .as_ref()
871            .unwrap()
872            .join(config.failure_persist_file.as_ref().unwrap())
873            .into_os_string()
874            .into_string()
875            .unwrap();
876        fuzzer_with_cases(
877            config.seed,
878            config.runs,
879            config.max_test_rejects,
880            Some(Box::new(FileFailurePersistence::Direct(failure_persist_path.leak()))),
881        )
882    }
883
884    fn invariant_runner(&self) -> TestRunner {
885        let config = &self.config.invariant;
886        fuzzer_with_cases(self.config.fuzz.seed, config.runs, config.max_assume_rejects, None)
887    }
888
889    fn clone_executor(&self) -> Executor {
890        self.executor.clone().into_owned()
891    }
892}
893
894fn fuzzer_with_cases(
895    seed: Option<U256>,
896    cases: u32,
897    max_global_rejects: u32,
898    file_failure_persistence: Option<Box<dyn FailurePersistence>>,
899) -> TestRunner {
900    let config = proptest::test_runner::Config {
901        failure_persistence: file_failure_persistence,
902        cases,
903        max_global_rejects,
904        // Disable proptest shrink: for fuzz tests we provide single counterexample,
905        // for invariant tests we shrink outside proptest.
906        max_shrink_iters: 0,
907        ..Default::default()
908    };
909
910    if let Some(seed) = seed {
911        trace!(target: "forge::test", %seed, "building deterministic fuzzer");
912        let rng = TestRng::from_seed(RngAlgorithm::ChaCha, &seed.to_be_bytes::<32>());
913        TestRunner::new_with_rng(config, rng)
914    } else {
915        trace!(target: "forge::test", "building stochastic fuzzer");
916        TestRunner::new(config)
917    }
918}
919
920/// Holds data about a persisted invariant failure.
921#[derive(Serialize, Deserialize)]
922struct InvariantPersistedFailure {
923    /// Recorded counterexample.
924    call_sequence: Vec<BaseCounterExample>,
925    /// Bytecode of the test contract that generated the counterexample.
926    #[serde(skip_serializing_if = "Option::is_none")]
927    driver_bytecode: Option<Bytes>,
928}
929
930/// Helper function to load failed call sequence from file.
931/// Ignores failure if generated with different test contract than the current one.
932fn persisted_call_sequence(path: &Path, bytecode: &Bytes) -> Option<Vec<BaseCounterExample>> {
933    foundry_common::fs::read_json_file::<InvariantPersistedFailure>(path).ok().and_then(
934        |persisted_failure| {
935            if let Some(persisted_bytecode) = &persisted_failure.driver_bytecode {
936                // Ignore persisted sequence if test bytecode doesn't match.
937                if !bytecode.eq(persisted_bytecode) {
938                    let _= sh_warn!("\
939                            Failure from {:?} file was ignored because test contract bytecode has changed.",
940                        path
941                    );
942                    return None;
943                }
944            };
945            Some(persisted_failure.call_sequence)
946        },
947    )
948}
949
950/// Helper functions to return canonicalized invariant failure paths.
951fn invariant_failure_paths(
952    config: &InvariantConfig,
953    contract_name: &str,
954    invariant_name: &str,
955) -> (PathBuf, PathBuf) {
956    let dir = config
957        .failure_persist_dir
958        .clone()
959        .unwrap()
960        .join("failures")
961        .join(contract_name.split(':').next_back().unwrap());
962    let dir = canonicalized(dir);
963    let file = canonicalized(dir.join(invariant_name));
964    (dir, file)
965}