1use crate::{
4 MultiContractRunner, TestFilter,
5 coverage::HitMaps,
6 fuzz::{BaseCounterExample, FuzzTestResult},
7 multi_runner::{TestContract, TestRunnerConfig},
8 progress::{TestsProgress, start_fuzz_progress},
9 result::{SuiteResult, TestResult, TestSetup},
10};
11use alloy_dyn_abi::{DynSolValue, JsonAbiExt};
12use alloy_json_abi::Function;
13use alloy_primitives::{Address, Bytes, U256, address, map::HashMap};
14use eyre::Result;
15use foundry_common::{TestFunctionExt, TestFunctionKind, contracts::ContractsByAddress};
16use foundry_compilers::utils::canonicalized;
17use foundry_config::{Config, FuzzCorpusConfig};
18use foundry_evm::{
19 constants::CALLER,
20 core::evm::FoundryEvmNetwork,
21 decode::RevertDecoder,
22 executors::{
23 CallResult, EvmError, Executor, ITest, RawCallResult,
24 fuzz::FuzzedExecutor,
25 invariant::{
26 CheckSequenceOptions, InvariantExecutor, InvariantFuzzError, check_sequence,
27 replay_error, replay_run,
28 },
29 },
30 fuzz::{
31 BasicTxDetails, CallDetails, CounterExample, FuzzFixtures, fixture_name,
32 invariant::{InvariantContract, InvariantSettings},
33 strategies::EvmFuzzState,
34 },
35 revm::primitives::hardfork::SpecId,
36 traces::{TraceKind, TraceMode, load_contracts},
37};
38use itertools::Itertools;
39use proptest::test_runner::{RngAlgorithm, TestError, TestRng, TestRunner};
40use rayon::prelude::*;
41use serde::{Deserialize, Serialize};
42use std::{
43 borrow::Cow,
44 cmp::min,
45 collections::BTreeMap,
46 ops::Deref,
47 path::{Path, PathBuf},
48 sync::Arc,
49 time::Instant,
50};
51use tokio::signal;
52use tracing::Span;
53
54pub const LIBRARY_DEPLOYER: Address = address!("0x1F95D37F27EA0dEA9C252FC09D5A6eaA97647353");
60
61pub struct ContractRunner<'a, FEN: FoundryEvmNetwork> {
63 name: &'a str,
65 contract: &'a TestContract,
67 executor: Executor<FEN>,
69 progress: Option<&'a TestsProgress>,
71 tokio_handle: &'a tokio::runtime::Handle,
73 span: tracing::Span,
75 tcfg: Cow<'a, TestRunnerConfig<FEN>>,
77 mcr: &'a MultiContractRunner<FEN>,
79}
80
81impl<'a, FEN: FoundryEvmNetwork> Deref for ContractRunner<'a, FEN> {
82 type Target = Cow<'a, TestRunnerConfig<FEN>>;
83
84 #[inline(always)]
85 fn deref(&self) -> &Self::Target {
86 &self.tcfg
87 }
88}
89
90impl<'a, FEN: FoundryEvmNetwork> ContractRunner<'a, FEN> {
91 pub const fn new(
92 name: &'a str,
93 contract: &'a TestContract,
94 executor: Executor<FEN>,
95 progress: Option<&'a TestsProgress>,
96 tokio_handle: &'a tokio::runtime::Handle,
97 span: Span,
98 mcr: &'a MultiContractRunner<FEN>,
99 ) -> Self {
100 Self {
101 name,
102 contract,
103 executor,
104 progress,
105 tokio_handle,
106 span,
107 tcfg: Cow::Borrowed(&mcr.tcfg),
108 mcr,
109 }
110 }
111
112 pub fn setup(&mut self, call_setup: bool) -> TestSetup {
115 self._setup(call_setup).unwrap_or_else(|err| {
116 if err.to_string().contains("skipped") {
117 TestSetup::skipped(err.to_string())
118 } else {
119 TestSetup::failed(err.to_string())
120 }
121 })
122 }
123
124 fn _setup(&mut self, call_setup: bool) -> Result<TestSetup> {
125 trace!(call_setup, "setting up");
126
127 self.apply_contract_inline_config()?;
128
129 self.executor.set_balance(self.sender, U256::MAX)?;
131 self.executor.set_balance(CALLER, U256::MAX)?;
132
133 self.executor.set_nonce(self.sender, 1)?;
135
136 self.executor.set_balance(LIBRARY_DEPLOYER, U256::MAX)?;
138
139 let mut result = TestSetup::default();
140 for code in &self.mcr.libs_to_deploy {
141 let deploy_result = self.executor.deploy(
142 LIBRARY_DEPLOYER,
143 code.clone(),
144 U256::ZERO,
145 Some(&self.mcr.revert_decoder),
146 );
147
148 if let Ok(deployed) = &deploy_result {
150 result.deployed_libs.push(deployed.address);
151 }
152
153 let (raw, reason) = RawCallResult::from_evm_result(deploy_result.map(Into::into))?;
154 result.extend(raw, TraceKind::Deployment);
155 if reason.is_some() {
156 debug!(?reason, "deployment of library failed");
157 result.reason = reason;
158 return Ok(result);
159 }
160 }
161
162 let address = self.sender.create(self.executor.get_nonce(self.sender)?);
163 result.address = address;
164
165 self.executor.set_balance(address, self.initial_balance())?;
168
169 let deploy_result = self.executor.deploy(
171 self.sender,
172 self.contract.bytecode.clone(),
173 U256::ZERO,
174 Some(&self.mcr.revert_decoder),
175 );
176
177 result.deployment_failure = deploy_result.is_err();
178
179 if let Ok(dr) = &deploy_result {
180 debug_assert_eq!(dr.address, address);
181 }
182 let (raw, reason) = RawCallResult::from_evm_result(deploy_result.map(Into::into))?;
183 result.extend(raw, TraceKind::Deployment);
184 if reason.is_some() {
185 debug!(?reason, "deployment of test contract failed");
186 result.reason = reason;
187 return Ok(result);
188 }
189
190 self.executor.set_balance(self.sender, self.initial_balance())?;
192 self.executor.set_balance(CALLER, self.initial_balance())?;
193 self.executor.set_balance(LIBRARY_DEPLOYER, self.initial_balance())?;
194
195 self.executor.deploy_create2_deployer()?;
196
197 if call_setup {
199 trace!("calling setUp");
200 let res = self.executor.setup(None, address, Some(&self.mcr.revert_decoder));
201 let (raw, reason) = RawCallResult::from_evm_result(res)?;
202 result.extend(raw, TraceKind::Setup);
203 result.reason = reason;
204 }
205
206 result.fuzz_fixtures = self.fuzz_fixtures(address);
207
208 Ok(result)
209 }
210
211 fn initial_balance(&self) -> U256 {
212 self.evm_opts.initial_balance
213 }
214
215 fn apply_contract_inline_config(&mut self) -> Result<()> {
217 if self.inline_config.contains_contract(self.name) {
218 let new_config = Arc::new(self.inline_config(None)?);
219 self.tcfg.to_mut().reconfigure_with(new_config);
220 let prev_tracer = self.executor.inspector_mut().tracer.take();
221 self.tcfg.configure_executor(&mut self.executor);
222 self.executor.inspector_mut().tracer = prev_tracer;
224 }
225 Ok(())
226 }
227
228 fn inline_config(&self, func: Option<&Function>) -> Result<Config> {
230 let function = func.map(|f| f.name.as_str()).unwrap_or("");
231 let config =
232 self.mcr.inline_config.merge(self.name, function, &self.config).extract::<Config>()?;
233 Ok(config)
234 }
235
236 fn fuzz_fixtures(&mut self, address: Address) -> FuzzFixtures {
252 let mut fixtures = HashMap::default();
253 let fixture_functions = self.contract.abi.functions().filter(|func| func.is_fixture());
254 for func in fixture_functions {
255 if func.inputs.is_empty() {
256 if let Ok(CallResult { raw: _, decoded_result }) =
258 self.executor.call(CALLER, address, func, &[], U256::ZERO, None)
259 {
260 fixtures.insert(fixture_name(func.name.clone()), decoded_result);
261 }
262 } else {
263 let mut vals = Vec::new();
266 let mut index = 0;
267 loop {
268 if let Ok(CallResult { raw: _, decoded_result }) = self.executor.call(
269 CALLER,
270 address,
271 func,
272 &[DynSolValue::Uint(U256::from(index), 256)],
273 U256::ZERO,
274 None,
275 ) {
276 vals.push(decoded_result);
277 } else {
278 break;
281 }
282 index += 1;
283 }
284 fixtures.insert(fixture_name(func.name.clone()), DynSolValue::Array(vals));
285 };
286 }
287 FuzzFixtures::new(fixtures)
288 }
289
290 pub fn run_tests(mut self, filter: &dyn TestFilter) -> SuiteResult {
292 let start = Instant::now();
293 let mut warnings = Vec::new();
294
295 let setup_fns: Vec<_> =
297 self.contract.abi.functions().filter(|func| func.name.is_setup()).collect();
298 let call_setup = setup_fns.len() == 1 && setup_fns[0].name == "setUp";
299 for &setup_fn in &setup_fns {
301 if setup_fn.name != "setUp" {
302 warnings.push(format!(
303 "Found invalid setup function \"{}\" did you mean \"setUp()\"?",
304 setup_fn.signature()
305 ));
306 }
307 }
308
309 if setup_fns.len() > 1 {
311 return SuiteResult::new(
312 start.elapsed(),
313 [("setUp()".to_string(), TestResult::fail("multiple setUp functions".to_string()))]
314 .into(),
315 warnings,
316 );
317 }
318
319 let after_invariant_fns: Vec<_> =
321 self.contract.abi.functions().filter(|func| func.name.is_after_invariant()).collect();
322 if after_invariant_fns.len() > 1 {
323 return SuiteResult::new(
325 start.elapsed(),
326 [(
327 "afterInvariant()".to_string(),
328 TestResult::fail("multiple afterInvariant functions".to_string()),
329 )]
330 .into(),
331 warnings,
332 );
333 }
334 let call_after_invariant = after_invariant_fns.first().is_some_and(|after_invariant_fn| {
335 let match_sig = after_invariant_fn.name == "afterInvariant";
336 if !match_sig {
337 warnings.push(format!(
338 "Found invalid afterInvariant function \"{}\" did you mean \"afterInvariant()\"?",
339 after_invariant_fn.signature()
340 ));
341 }
342 match_sig
343 });
344
345 let has_invariants = self.contract.abi.functions().any(|func| func.is_invariant_test());
348
349 let prev_tracer = self.executor.inspector_mut().tracer.take();
350 if prev_tracer.is_some() || has_invariants {
351 self.executor.set_tracing(TraceMode::Call);
352 }
353
354 let setup_time = Instant::now();
355 let setup = self.setup(call_setup);
356 debug!("finished setting up in {:?}", setup_time.elapsed());
357
358 self.executor.inspector_mut().tracer = prev_tracer;
359
360 if setup.reason.is_some() {
361 let fail_msg = if setup.deployment_failure {
363 "constructor()".to_string()
364 } else {
365 "setUp()".to_string()
366 };
367 return SuiteResult::new(
368 start.elapsed(),
369 [(fail_msg, TestResult::setup_result(setup))].into(),
370 warnings,
371 );
372 }
373
374 let find_timer = Instant::now();
377 let functions = self
378 .contract
379 .abi
380 .functions()
381 .filter(|func| filter.matches_test_function(func))
382 .collect::<Vec<_>>();
383 debug!(
384 "Found {} test functions out of {} in {:?}",
385 functions.len(),
386 self.contract.abi.functions().count(),
387 find_timer.elapsed(),
388 );
389
390 let identified_contracts = has_invariants.then(|| {
391 load_contracts(setup.traces.iter().map(|(_, t)| &t.arena), &self.mcr.known_contracts)
392 });
393
394 let test_fail_functions =
395 functions.iter().filter(|func| func.test_function_kind().is_any_test_fail());
396 if test_fail_functions.clone().next().is_some() {
397 let fail = || {
398 TestResult::fail("`testFail*` has been removed. Consider changing to test_Revert[If|When]_Condition and expecting a revert".to_string())
399 };
400 let test_results = test_fail_functions.map(|func| (func.signature(), fail())).collect();
401 return SuiteResult::new(start.elapsed(), test_results, warnings);
402 }
403
404 let early_exit = &self.tcfg.early_exit;
405
406 if self.progress.is_some() {
407 let interrupt = early_exit.clone();
408 self.tokio_handle.spawn(async move {
409 signal::ctrl_c().await.expect("Failed to listen for Ctrl+C");
410 interrupt.record_ctrl_c();
411 });
412 }
413
414 let test_results = functions
415 .par_iter()
416 .filter_map(|&func| {
417 if early_exit.should_stop() {
419 return None;
420 }
421
422 let start = Instant::now();
423
424 let _guard = self.tokio_handle.enter();
425
426 let _guard;
427 let current_span = tracing::Span::current();
428 if current_span.is_none() || current_span.id() != self.span.id() {
429 _guard = self.span.enter();
430 }
431
432 let sig = func.signature();
433 let kind = func.test_function_kind();
434
435 let _guard = debug_span!(
436 "test",
437 %kind,
438 name = %if enabled!(tracing::Level::TRACE) { &sig } else { &func.name },
439 )
440 .entered();
441
442 let mut res = FunctionRunner::new(&self, &setup).run(
443 func,
444 kind,
445 call_after_invariant,
446 identified_contracts.as_ref(),
447 );
448 res.duration = start.elapsed();
449
450 if res.status.is_failure() {
452 early_exit.record_failure();
453 }
454
455 Some((sig, res))
456 })
457 .collect::<BTreeMap<_, _>>();
458
459 let duration = start.elapsed();
460 SuiteResult::new(duration, test_results, warnings)
461 }
462}
463
464struct FunctionRunner<'a, FEN: FoundryEvmNetwork> {
466 tcfg: Cow<'a, TestRunnerConfig<FEN>>,
468 executor: Cow<'a, Executor<FEN>>,
470 cr: &'a ContractRunner<'a, FEN>,
472 address: Address,
474 setup: &'a TestSetup,
476 result: TestResult,
478}
479
480impl<'a, FEN: FoundryEvmNetwork> Deref for FunctionRunner<'a, FEN> {
481 type Target = Cow<'a, TestRunnerConfig<FEN>>;
482
483 #[inline(always)]
484 fn deref(&self) -> &Self::Target {
485 &self.tcfg
486 }
487}
488
489impl<'a, FEN: FoundryEvmNetwork> FunctionRunner<'a, FEN> {
490 fn new(cr: &'a ContractRunner<'a, FEN>, setup: &'a TestSetup) -> Self {
491 Self {
492 tcfg: match &cr.tcfg {
493 Cow::Borrowed(tcfg) => Cow::Borrowed(tcfg),
494 Cow::Owned(tcfg) => Cow::Owned(tcfg.clone()),
495 },
496 executor: Cow::Borrowed(&cr.executor),
497 cr,
498 address: setup.address,
499 setup,
500 result: TestResult::new(setup),
501 }
502 }
503
504 const fn revert_decoder(&self) -> &'a RevertDecoder {
505 &self.cr.mcr.revert_decoder
506 }
507
508 fn apply_function_inline_config(&mut self, func: &Function) -> Result<()> {
510 if self.inline_config.contains_function(self.cr.name, &func.name) {
511 let new_config = Arc::new(self.cr.inline_config(Some(func))?);
512 self.tcfg.to_mut().reconfigure_with(new_config);
513 self.tcfg.configure_executor(self.executor.to_mut());
514 }
515 Ok(())
516 }
517
518 fn run(
519 mut self,
520 func: &Function,
521 kind: TestFunctionKind,
522 call_after_invariant: bool,
523 identified_contracts: Option<&ContractsByAddress>,
524 ) -> TestResult {
525 if let Err(e) = self.apply_function_inline_config(func) {
526 self.result.single_fail(Some(e.to_string()));
527 return self.result;
528 }
529
530 match kind {
531 TestFunctionKind::UnitTest { .. } => self.run_unit_test(func),
532 TestFunctionKind::FuzzTest { .. } => self.run_fuzz_test(func),
533 TestFunctionKind::TableTest => self.run_table_test(func),
534 TestFunctionKind::InvariantTest => {
535 self.run_invariant_test(func, call_after_invariant, identified_contracts.unwrap())
536 }
537 _ => unreachable!(),
538 }
539 }
540
541 fn run_unit_test(mut self, func: &Function) -> TestResult {
550 if self.prepare_test(func).is_err() {
552 return self.result;
553 }
554
555 let (mut raw_call_result, reason) = match self.executor.call(
557 self.sender,
558 self.address,
559 func,
560 &[],
561 U256::ZERO,
562 Some(self.revert_decoder()),
563 ) {
564 Ok(res) => (res.raw, None),
565 Err(EvmError::Execution(err)) => (err.raw, Some(err.reason)),
566 Err(EvmError::Skip(reason)) => {
567 self.result.single_skip(reason);
568 return self.result;
569 }
570 Err(err) => {
571 self.result.single_fail(Some(err.to_string()));
572 return self.result;
573 }
574 };
575
576 let success =
577 self.executor.is_raw_call_mut_success(self.address, &mut raw_call_result, false);
578 self.result.single_result(success, reason, raw_call_result);
579 self.result
580 }
581
582 fn run_table_test(mut self, func: &Function) -> TestResult {
591 if self.prepare_test(func).is_err() {
593 return self.result;
594 }
595
596 let Some(first_param) = func.inputs.first() else {
598 self.result.single_fail(Some("Table test should have at least one parameter".into()));
599 return self.result;
600 };
601
602 let Some(first_param_fixtures) =
603 &self.setup.fuzz_fixtures.param_fixtures(first_param.name())
604 else {
605 self.result.single_fail(Some("Table test should have fixtures defined".into()));
606 return self.result;
607 };
608
609 if first_param_fixtures.is_empty() {
610 self.result.single_fail(Some("Table test should have at least one fixture".into()));
611 return self.result;
612 }
613
614 let fixtures_len = first_param_fixtures.len();
615 let mut table_fixtures = vec![&first_param_fixtures[..]];
616
617 for param in &func.inputs[1..] {
619 let param_name = param.name();
620 let Some(fixtures) = &self.setup.fuzz_fixtures.param_fixtures(param.name()) else {
621 self.result.single_fail(Some(format!("No fixture defined for param {param_name}")));
622 return self.result;
623 };
624
625 if fixtures.len() != fixtures_len {
626 self.result.single_fail(Some(format!(
627 "{} fixtures defined for {param_name} (expected {})",
628 fixtures.len(),
629 fixtures_len
630 )));
631 return self.result;
632 }
633
634 table_fixtures.push(&fixtures[..]);
635 }
636
637 let progress = start_fuzz_progress(
638 self.cr.progress,
639 self.cr.name,
640 &func.name,
641 None,
642 fixtures_len as u32,
643 );
644
645 let mut result = FuzzTestResult::default();
646
647 for i in 0..fixtures_len {
648 if self.tcfg.early_exit.should_stop() {
649 return self.result;
650 }
651
652 if let Some(progress) = progress.as_ref() {
654 progress.inc(1);
655 }
656
657 let args = table_fixtures.iter().map(|row| row[i].clone()).collect_vec();
658 let (mut raw_call_result, reason) = match self.executor.call(
659 self.sender,
660 self.address,
661 func,
662 &args,
663 U256::ZERO,
664 Some(self.revert_decoder()),
665 ) {
666 Ok(res) => (res.raw, None),
667 Err(EvmError::Execution(err)) => (err.raw, Some(err.reason)),
668 Err(EvmError::Skip(reason)) => {
669 self.result.single_skip(reason);
670 return self.result;
671 }
672 Err(err) => {
673 self.result.single_fail(Some(err.to_string()));
674 return self.result;
675 }
676 };
677
678 result.gas_by_case.push((raw_call_result.gas_used, raw_call_result.stipend));
679 result.logs.extend(raw_call_result.logs.clone());
680 result.labels.extend(raw_call_result.labels.clone());
681 HitMaps::merge_opt(&mut result.line_coverage, raw_call_result.line_coverage.clone());
682
683 let is_success =
684 self.executor.is_raw_call_mut_success(self.address, &mut raw_call_result, false);
685 if !is_success {
687 result.counterexample =
688 Some(CounterExample::Single(BaseCounterExample::from_fuzz_call(
689 Bytes::from(func.abi_encode_input(&args).unwrap()),
690 args,
691 raw_call_result.traces.clone(),
692 )));
693 result.reason = reason;
694 result.traces = raw_call_result.traces;
695 self.result.table_result(result);
696 return self.result;
697 }
698
699 if i == fixtures_len - 1 {
702 result.success = true;
703 result.traces = raw_call_result.traces;
704 self.result.table_result(result);
705 return self.result;
706 }
707 }
708
709 self.result
710 }
711
712 fn run_invariant_test(
713 mut self,
714 func: &Function,
715 call_after_invariant: bool,
716 identified_contracts: &ContractsByAddress,
717 ) -> TestResult {
718 if let Err(EvmError::Skip(reason)) = self.executor.call(
720 self.sender,
721 self.address,
722 func,
723 &[],
724 U256::ZERO,
725 Some(self.revert_decoder()),
726 ) {
727 self.result.invariant_skip(reason);
728 return self.result;
729 };
730
731 let runner = self.invariant_runner();
732 let invariant_config = &self.config.invariant;
733
734 let mut executor = self.clone_executor();
735 executor
738 .inspector_mut()
739 .collect_edge_coverage(invariant_config.corpus.collect_edge_coverage());
740 let mut config = invariant_config.clone();
741 let (failure_dir, failure_file) = test_paths(
742 &mut config.corpus,
743 invariant_config.failure_persist_dir.clone().unwrap(),
744 self.cr.name,
745 &func.name,
746 );
747
748 let mut evm = InvariantExecutor::new(
749 executor,
750 runner,
751 config,
752 identified_contracts,
753 &self.cr.mcr.known_contracts,
754 );
755 let invariant_contract =
756 InvariantContract::new(self.address, func, call_after_invariant, &self.cr.contract.abi);
757 let show_solidity = invariant_config.show_solidity;
758
759 let current_settings = match evm.compute_settings(self.address) {
761 Ok(s) => s,
762 Err(e) => {
763 self.result.invariant_setup_fail(e);
764 return self.result;
765 }
766 };
767
768 let progress = start_fuzz_progress(
769 self.cr.progress,
770 self.cr.name,
771 &func.name,
772 invariant_config.timeout,
773 invariant_config.runs,
774 );
775
776 if let Some(mut call_sequence) =
778 persisted_call_sequence(failure_file.as_path(), ¤t_settings)
779 {
780 let txes = call_sequence
782 .iter_mut()
783 .map(|seq| {
784 seq.show_solidity = show_solidity;
785 BasicTxDetails {
786 warp: seq.warp,
787 roll: seq.roll,
788 sender: seq.sender.unwrap_or_default(),
789 call_details: CallDetails {
790 target: seq.addr.unwrap_or_default(),
791 calldata: seq.calldata.clone(),
792 },
793 }
794 })
795 .collect::<Vec<BasicTxDetails>>();
796 if let Ok((success, replayed_entirely, replay_reason)) = check_sequence(
797 self.clone_executor(),
798 &txes,
799 (0..min(txes.len(), invariant_config.depth as usize)).collect(),
800 invariant_contract.address,
801 invariant_contract.invariant_function.selector().to_vec().into(),
802 CheckSequenceOptions {
803 accumulate_warp_roll: invariant_config.has_delay(),
804 fail_on_revert: invariant_config.fail_on_revert,
805 call_after_invariant: invariant_contract.call_after_invariant,
806 rd: Some(self.revert_decoder()),
807 },
808 ) && !success
809 {
810 let warn = format!(
811 "Replayed invariant failure from {:?} file. \nRun `forge clean` or remove file to ignore failure and to continue invariant test campaign.",
812 failure_file.as_path()
813 );
814
815 if let Some(ref progress) = progress {
816 progress.set_prefix(format!("{}\n{warn}\n", &func.name));
817 } else {
818 let _ = sh_warn!("{warn}");
819 }
820
821 match replay_error(
824 evm.config(),
825 self.clone_executor(),
826 &txes,
827 None,
828 None, &invariant_contract,
830 &self.cr.mcr.known_contracts,
831 identified_contracts.clone(),
832 &mut self.result.logs,
833 &mut self.result.traces,
834 &mut self.result.line_coverage,
835 &mut self.result.deprecated_cheatcodes,
836 progress.as_ref(),
837 &self.tcfg.early_exit,
838 ) {
839 Ok(replayed_call_sequence) if !replayed_call_sequence.is_empty() => {
840 call_sequence = replayed_call_sequence;
841 record_invariant_failure(
843 failure_dir.as_path(),
844 failure_file.as_path(),
845 &call_sequence,
846 ¤t_settings,
847 );
848 }
849 Ok(_) => {}
850 Err(err) => {
851 error!(%err, "Failed to replay invariant error");
852 }
853 }
854
855 self.result.invariant_replay_fail(
856 replayed_entirely,
857 &invariant_contract.invariant_function.name,
858 replay_reason,
859 call_sequence,
860 );
861 return self.result;
862 }
863 }
864
865 let invariant_result = match evm.invariant_fuzz(
866 invariant_contract.clone(),
867 &self.setup.fuzz_fixtures,
868 self.build_fuzz_state(true),
869 progress.as_ref(),
870 &self.tcfg.early_exit,
871 ) {
872 Ok(x) => x,
873 Err(e) => {
874 self.result.invariant_setup_fail(e);
875 return self.result;
876 }
877 };
878 self.result.merge_coverages(invariant_result.line_coverage);
880
881 let mut counterexample = None;
882 let success = invariant_result.error.is_none();
883 let reason = invariant_result.error.as_ref().and_then(|err| err.revert_reason());
884
885 match invariant_result.error {
886 Some(error) => match error {
888 InvariantFuzzError::BrokenInvariant(case_data)
889 | InvariantFuzzError::Revert(case_data) => {
890 match case_data.test_error {
893 TestError::Abort(_) => {}
894 TestError::Fail(_, ref calls) => {
895 match replay_error(
896 evm.config(),
897 self.clone_executor(),
898 calls,
899 Some(case_data.inner_sequence),
900 None, &invariant_contract,
902 &self.cr.mcr.known_contracts,
903 identified_contracts.clone(),
904 &mut self.result.logs,
905 &mut self.result.traces,
906 &mut self.result.line_coverage,
907 &mut self.result.deprecated_cheatcodes,
908 progress.as_ref(),
909 &self.tcfg.early_exit,
910 ) {
911 Ok(call_sequence) if !call_sequence.is_empty() => {
912 record_invariant_failure(
914 failure_dir.as_path(),
915 failure_file.as_path(),
916 &call_sequence,
917 ¤t_settings,
918 );
919
920 let original_seq_len =
921 if let TestError::Fail(_, calls) = &case_data.test_error {
922 calls.len()
923 } else {
924 call_sequence.len()
925 };
926
927 counterexample = Some(CounterExample::Sequence(
928 original_seq_len,
929 call_sequence,
930 ))
931 }
932 Ok(_) => {}
933 Err(err) => {
934 error!(%err, "Failed to replay invariant error");
935 }
936 }
937 }
938 };
939 }
940 InvariantFuzzError::MaxAssumeRejects(_) => {}
941 },
942
943 _ => {
945 if let Some(best_value) = invariant_result.optimization_best_value {
946 match replay_error(
948 evm.config(),
949 self.clone_executor(),
950 &invariant_result.optimization_best_sequence,
951 None,
952 Some(best_value),
953 &invariant_contract,
954 &self.cr.mcr.known_contracts,
955 identified_contracts.clone(),
956 &mut self.result.logs,
957 &mut self.result.traces,
958 &mut self.result.line_coverage,
959 &mut self.result.deprecated_cheatcodes,
960 progress.as_ref(),
961 &self.tcfg.early_exit,
962 ) {
963 Ok(best_sequence) if !best_sequence.is_empty() => {
964 counterexample = Some(CounterExample::Sequence(
965 invariant_result.optimization_best_sequence.len(),
966 best_sequence,
967 ));
968 }
969 Err(err) => {
970 error!(%err, "Failed to replay optimization best sequence");
971 }
972 _ => {}
973 }
974 } else {
975 if let Err(err) = replay_run(
977 &invariant_contract,
978 self.clone_executor(),
979 &self.cr.mcr.known_contracts,
980 identified_contracts.clone(),
981 &mut self.result.logs,
982 &mut self.result.traces,
983 &mut self.result.line_coverage,
984 &mut self.result.deprecated_cheatcodes,
985 &invariant_result.last_run_inputs,
986 show_solidity,
987 ) {
988 error!(%err, "Failed to replay last invariant run");
989 }
990 }
991 }
992 }
993
994 self.result.invariant_result(
995 invariant_result.gas_report_traces,
996 success,
997 reason,
998 counterexample,
999 invariant_result.cases,
1000 invariant_result.reverts,
1001 invariant_result.metrics,
1002 invariant_result.failed_corpus_replays,
1003 invariant_result.optimization_best_value,
1004 );
1005 self.result
1006 }
1007
1008 fn run_fuzz_test(mut self, func: &Function) -> TestResult {
1018 if self.prepare_test(func).is_err() {
1020 return self.result;
1021 }
1022
1023 let runner = self.fuzz_runner();
1024 let mut fuzz_config = self.config.fuzz.clone();
1025 let (failure_dir, failure_file) = test_paths(
1026 &mut fuzz_config.corpus,
1027 fuzz_config.failure_persist_dir.clone().unwrap(),
1028 self.cr.name,
1029 &func.name,
1030 );
1031
1032 let progress = start_fuzz_progress(
1033 self.cr.progress,
1034 self.cr.name,
1035 &func.name,
1036 fuzz_config.timeout,
1037 fuzz_config.runs,
1038 );
1039
1040 let state = self.build_fuzz_state(false);
1041 let mut executor = self.executor.into_owned();
1042 executor.inspector_mut().collect_edge_coverage(fuzz_config.corpus.collect_edge_coverage());
1045 let persisted_failure =
1047 foundry_common::fs::read_json_file::<BaseCounterExample>(failure_file.as_path()).ok();
1048 let mut fuzzed_executor =
1050 FuzzedExecutor::new(executor, runner, self.tcfg.sender, fuzz_config, persisted_failure);
1051 let result = match fuzzed_executor.fuzz(
1052 func,
1053 &self.setup.fuzz_fixtures,
1054 state,
1055 self.address,
1056 &self.cr.mcr.revert_decoder,
1057 progress.as_ref(),
1058 &self.tcfg.early_exit,
1059 self.cr.tokio_handle,
1060 ) {
1061 Ok(x) => x,
1062 Err(e) => {
1063 self.result.fuzz_setup_fail(e);
1064 return self.result;
1065 }
1066 };
1067
1068 if let Some(CounterExample::Single(counterexample)) = &result.counterexample {
1070 if let Err(err) = foundry_common::fs::create_dir_all(failure_dir) {
1071 error!(%err, "Failed to create fuzz failure dir");
1072 } else if let Err(err) =
1073 foundry_common::fs::write_json_file(failure_file.as_path(), counterexample)
1074 {
1075 error!(%err, "Failed to record call sequence");
1076 }
1077 }
1078
1079 self.result.fuzz_result(result);
1080 self.result
1081 }
1082
1083 fn prepare_test(&mut self, func: &Function) -> Result<(), ()> {
1093 let address = self.setup.address;
1094
1095 if self.cr.contract.abi.functions().any(|func| func.name.is_before_test_setup()) {
1097 for calldata in self.executor.call_sol_default(
1098 address,
1099 &ITest::beforeTestSetupCall { testSelector: func.selector() },
1100 ) {
1101 let spec_id: SpecId = self.executor.spec_id().into();
1102 debug!(?calldata, spec=%spec_id, "applying before_test_setup");
1103 match self.executor.to_mut().transact_raw(
1105 self.tcfg.sender,
1106 address,
1107 calldata,
1108 U256::ZERO,
1109 ) {
1110 Ok(call_result) => {
1111 let reverted = call_result.reverted;
1112
1113 self.result.extend(call_result);
1115
1116 if reverted {
1118 self.result.single_fail(None);
1119 return Err(());
1120 }
1121 }
1122 Err(_) => {
1123 self.result.single_fail(None);
1124 return Err(());
1125 }
1126 }
1127 }
1128 }
1129 Ok(())
1130 }
1131
1132 fn fuzz_runner(&self) -> TestRunner {
1133 let config = &self.config.fuzz;
1134 fuzzer_with_cases(config.seed, config.runs, config.max_test_rejects)
1135 }
1136
1137 fn invariant_runner(&self) -> TestRunner {
1138 let config = &self.config.invariant;
1139 fuzzer_with_cases(self.config.fuzz.seed, config.runs, config.max_assume_rejects)
1140 }
1141
1142 fn clone_executor(&self) -> Executor<FEN> {
1143 self.executor.clone().into_owned()
1144 }
1145
1146 fn build_fuzz_state(&self, invariant: bool) -> EvmFuzzState {
1147 let config =
1148 if invariant { self.config.invariant.dictionary } else { self.config.fuzz.dictionary };
1149 if let Some(db) = self.executor.backend().active_fork_db() {
1150 EvmFuzzState::new(
1151 &self.setup.deployed_libs,
1152 db,
1153 config,
1154 Some(&self.cr.mcr.fuzz_literals),
1155 )
1156 } else {
1157 let db = self.executor.backend().mem_db();
1158 EvmFuzzState::new(
1159 &self.setup.deployed_libs,
1160 db,
1161 config,
1162 Some(&self.cr.mcr.fuzz_literals),
1163 )
1164 }
1165 }
1166}
1167
1168fn fuzzer_with_cases(seed: Option<U256>, cases: u32, max_global_rejects: u32) -> TestRunner {
1169 let config = proptest::test_runner::Config {
1170 cases,
1171 max_global_rejects,
1172 max_shrink_iters: 0,
1175 ..Default::default()
1176 };
1177
1178 if let Some(seed) = seed {
1179 trace!(target: "forge::test", %seed, "building deterministic fuzzer");
1180 let rng = TestRng::from_seed(RngAlgorithm::ChaCha, &seed.to_be_bytes::<32>());
1181 TestRunner::new_with_rng(config, rng)
1182 } else {
1183 trace!(target: "forge::test", "building stochastic fuzzer");
1184 TestRunner::new(config)
1185 }
1186}
1187
1188#[derive(Serialize, Deserialize)]
1190struct InvariantPersistedFailure {
1191 call_sequence: Vec<BaseCounterExample>,
1193 settings: InvariantSettings,
1196}
1197
1198fn persisted_call_sequence(
1201 path: &Path,
1202 current_settings: &InvariantSettings,
1203) -> Option<Vec<BaseCounterExample>> {
1204 foundry_common::fs::read_json_file::<InvariantPersistedFailure>(path).ok().and_then(
1205 |persisted_failure| {
1206 if let Some(diff) = persisted_failure.settings.diff(current_settings) {
1207 let _ = sh_warn!(
1208 "Failure from {:?} file was ignored because invariant test settings have changed: {}",
1209 path,
1210 diff
1211 );
1212 return None;
1213 }
1214 Some(persisted_failure.call_sequence)
1215 },
1216 )
1217}
1218
1219fn test_paths(
1221 corpus_config: &mut FuzzCorpusConfig,
1222 persist_dir: PathBuf,
1223 contract_name: &str,
1224 test_name: &str,
1225) -> (PathBuf, PathBuf) {
1226 let contract = contract_name.split(':').next_back().unwrap();
1227 corpus_config.with_test(contract, test_name);
1229
1230 let failures_dir = canonicalized(persist_dir.join("failures").join(contract));
1231 let failure_file = canonicalized(failures_dir.join(test_name));
1232 (failures_dir, failure_file)
1233}
1234
1235fn record_invariant_failure(
1237 failure_dir: &Path,
1238 failure_file: &Path,
1239 call_sequence: &[BaseCounterExample],
1240 settings: &InvariantSettings,
1241) {
1242 if let Err(err) = foundry_common::fs::create_dir_all(failure_dir) {
1243 error!(%err, "Failed to create invariant failure dir");
1244 return;
1245 }
1246
1247 if let Err(err) = foundry_common::fs::write_json_file(
1248 failure_file,
1249 &InvariantPersistedFailure {
1250 call_sequence: call_sequence.to_owned(),
1251 settings: settings.clone(),
1252 },
1253 ) {
1254 error!(%err, "Failed to record call sequence");
1255 }
1256}