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 decode::RevertDecoder,
21 executors::{
22 CallResult, EvmError, Executor, ITest, RawCallResult,
23 fuzz::FuzzedExecutor,
24 invariant::{
25 InvariantExecutor, InvariantFuzzError, check_sequence, replay_error, replay_run,
26 },
27 },
28 fuzz::{
29 BasicTxDetails, CallDetails, CounterExample, FuzzFixtures, fixture_name,
30 invariant::InvariantContract, strategies::EvmFuzzState,
31 },
32 traces::{TraceKind, TraceMode, load_contracts},
33};
34use itertools::Itertools;
35use proptest::test_runner::{RngAlgorithm, TestError, TestRng, TestRunner};
36use rayon::prelude::*;
37use serde::{Deserialize, Serialize};
38use std::{
39 borrow::Cow,
40 cmp::min,
41 collections::BTreeMap,
42 path::{Path, PathBuf},
43 sync::Arc,
44 time::Instant,
45};
46use tokio::signal;
47use tracing::Span;
48
49pub const LIBRARY_DEPLOYER: Address = address!("0x1F95D37F27EA0dEA9C252FC09D5A6eaA97647353");
55
56pub struct ContractRunner<'a> {
58 name: &'a str,
60 contract: &'a TestContract,
62 executor: Executor,
64 progress: Option<&'a TestsProgress>,
66 tokio_handle: &'a tokio::runtime::Handle,
68 span: tracing::Span,
70 tcfg: Cow<'a, TestRunnerConfig>,
72 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 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 self.executor.set_balance(self.sender, U256::MAX)?;
126 self.executor.set_balance(CALLER, U256::MAX)?;
127
128 self.executor.set_nonce(self.sender, 1)?;
130
131 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 if let Ok(deployed) = &deploy_result {
145 result.deployed_libs.push(deployed.address);
146 }
147
148 let (raw, reason) = RawCallResult::from_evm_result(deploy_result.map(Into::into))?;
149 result.extend(raw, TraceKind::Deployment);
150 if reason.is_some() {
151 debug!(?reason, "deployment of library failed");
152 result.reason = reason;
153 return Ok(result);
154 }
155 }
156
157 let address = self.sender.create(self.executor.get_nonce(self.sender)?);
158 result.address = address;
159
160 self.executor.set_balance(address, self.initial_balance())?;
163
164 let deploy_result = self.executor.deploy(
166 self.sender,
167 self.contract.bytecode.clone(),
168 U256::ZERO,
169 Some(&self.mcr.revert_decoder),
170 );
171
172 result.deployment_failure = deploy_result.is_err();
173
174 if let Ok(dr) = &deploy_result {
175 debug_assert_eq!(dr.address, address);
176 }
177 let (raw, reason) = RawCallResult::from_evm_result(deploy_result.map(Into::into))?;
178 result.extend(raw, TraceKind::Deployment);
179 if reason.is_some() {
180 debug!(?reason, "deployment of test contract failed");
181 result.reason = reason;
182 return Ok(result);
183 }
184
185 self.executor.set_balance(self.sender, self.initial_balance())?;
187 self.executor.set_balance(CALLER, self.initial_balance())?;
188 self.executor.set_balance(LIBRARY_DEPLOYER, self.initial_balance())?;
189
190 self.executor.deploy_create2_deployer()?;
191
192 if call_setup {
194 trace!("calling setUp");
195 let res = self.executor.setup(None, address, Some(&self.mcr.revert_decoder));
196 let (raw, reason) = RawCallResult::from_evm_result(res)?;
197 result.extend(raw, TraceKind::Setup);
198 result.reason = reason;
199 }
200
201 result.fuzz_fixtures = self.fuzz_fixtures(address);
202
203 Ok(result)
204 }
205
206 fn initial_balance(&self) -> U256 {
207 self.evm_opts.initial_balance
208 }
209
210 fn apply_contract_inline_config(&mut self) -> Result<()> {
212 if self.inline_config.contains_contract(self.name) {
213 let new_config = Arc::new(self.inline_config(None)?);
214 self.tcfg.to_mut().reconfigure_with(new_config);
215 let prev_tracer = self.executor.inspector_mut().tracer.take();
216 self.tcfg.configure_executor(&mut self.executor);
217 self.executor.inspector_mut().tracer = prev_tracer;
219 }
220 Ok(())
221 }
222
223 fn inline_config(&self, func: Option<&Function>) -> Result<Config> {
225 let function = func.map(|f| f.name.as_str()).unwrap_or("");
226 let config =
227 self.mcr.inline_config.merge(self.name, function, &self.config).extract::<Config>()?;
228 Ok(config)
229 }
230
231 fn fuzz_fixtures(&mut self, address: Address) -> FuzzFixtures {
247 let mut fixtures = HashMap::default();
248 let fixture_functions = self.contract.abi.functions().filter(|func| func.is_fixture());
249 for func in fixture_functions {
250 if func.inputs.is_empty() {
251 if let Ok(CallResult { raw: _, decoded_result }) =
253 self.executor.call(CALLER, address, func, &[], U256::ZERO, None)
254 {
255 fixtures.insert(fixture_name(func.name.clone()), decoded_result);
256 }
257 } else {
258 let mut vals = Vec::new();
261 let mut index = 0;
262 loop {
263 if let Ok(CallResult { raw: _, decoded_result }) = self.executor.call(
264 CALLER,
265 address,
266 func,
267 &[DynSolValue::Uint(U256::from(index), 256)],
268 U256::ZERO,
269 None,
270 ) {
271 vals.push(decoded_result);
272 } else {
273 break;
276 }
277 index += 1;
278 }
279 fixtures.insert(fixture_name(func.name.clone()), DynSolValue::Array(vals));
280 };
281 }
282 FuzzFixtures::new(fixtures)
283 }
284
285 pub fn run_tests(mut self, filter: &dyn TestFilter) -> SuiteResult {
287 let start = Instant::now();
288 let mut warnings = Vec::new();
289
290 let setup_fns: Vec<_> =
292 self.contract.abi.functions().filter(|func| func.name.is_setup()).collect();
293 let call_setup = setup_fns.len() == 1 && setup_fns[0].name == "setUp";
294 for &setup_fn in &setup_fns {
296 if setup_fn.name != "setUp" {
297 warnings.push(format!(
298 "Found invalid setup function \"{}\" did you mean \"setUp()\"?",
299 setup_fn.signature()
300 ));
301 }
302 }
303
304 if setup_fns.len() > 1 {
306 return SuiteResult::new(
307 start.elapsed(),
308 [("setUp()".to_string(), TestResult::fail("multiple setUp functions".to_string()))]
309 .into(),
310 warnings,
311 );
312 }
313
314 let after_invariant_fns: Vec<_> =
316 self.contract.abi.functions().filter(|func| func.name.is_after_invariant()).collect();
317 if after_invariant_fns.len() > 1 {
318 return SuiteResult::new(
320 start.elapsed(),
321 [(
322 "afterInvariant()".to_string(),
323 TestResult::fail("multiple afterInvariant functions".to_string()),
324 )]
325 .into(),
326 warnings,
327 );
328 }
329 let call_after_invariant = after_invariant_fns.first().is_some_and(|after_invariant_fn| {
330 let match_sig = after_invariant_fn.name == "afterInvariant";
331 if !match_sig {
332 warnings.push(format!(
333 "Found invalid afterInvariant function \"{}\" did you mean \"afterInvariant()\"?",
334 after_invariant_fn.signature()
335 ));
336 }
337 match_sig
338 });
339
340 let has_invariants = self.contract.abi.functions().any(|func| func.is_invariant_test());
343
344 let prev_tracer = self.executor.inspector_mut().tracer.take();
345 if prev_tracer.is_some() || has_invariants {
346 self.executor.set_tracing(TraceMode::Call);
347 }
348
349 let setup_time = Instant::now();
350 let setup = self.setup(call_setup);
351 debug!("finished setting up in {:?}", setup_time.elapsed());
352
353 self.executor.inspector_mut().tracer = prev_tracer;
354
355 if setup.reason.is_some() {
356 let fail_msg = if !setup.deployment_failure {
358 "setUp()".to_string()
359 } else {
360 "constructor()".to_string()
361 };
362 return SuiteResult::new(
363 start.elapsed(),
364 [(fail_msg, TestResult::setup_result(setup))].into(),
365 warnings,
366 );
367 }
368
369 let find_timer = Instant::now();
372 let functions = self
373 .contract
374 .abi
375 .functions()
376 .filter(|func| filter.matches_test_function(func))
377 .collect::<Vec<_>>();
378 debug!(
379 "Found {} test functions out of {} in {:?}",
380 functions.len(),
381 self.contract.abi.functions().count(),
382 find_timer.elapsed(),
383 );
384
385 let identified_contracts = has_invariants.then(|| {
386 load_contracts(setup.traces.iter().map(|(_, t)| &t.arena), &self.mcr.known_contracts)
387 });
388
389 let test_fail_functions =
390 functions.iter().filter(|func| func.test_function_kind().is_any_test_fail());
391 if test_fail_functions.clone().next().is_some() {
392 let fail = || {
393 TestResult::fail("`testFail*` has been removed. Consider changing to test_Revert[If|When]_Condition and expecting a revert".to_string())
394 };
395 let test_results = test_fail_functions.map(|func| (func.signature(), fail())).collect();
396 return SuiteResult::new(start.elapsed(), test_results, warnings);
397 }
398
399 let early_exit = &self.tcfg.early_exit;
400
401 if self.progress.is_some() {
402 let interrupt = early_exit.clone();
403 self.tokio_handle.spawn(async move {
404 signal::ctrl_c().await.expect("Failed to listen for Ctrl+C");
405 interrupt.record_exit();
406 });
407 }
408
409 let test_results = functions
410 .par_iter()
411 .filter_map(|&func| {
412 if early_exit.should_stop() {
414 return None;
415 }
416
417 let start = Instant::now();
418
419 let _guard = self.tokio_handle.enter();
420
421 let _guard;
422 let current_span = tracing::Span::current();
423 if current_span.is_none() || current_span.id() != self.span.id() {
424 _guard = self.span.enter();
425 }
426
427 let sig = func.signature();
428 let kind = func.test_function_kind();
429
430 let _guard = debug_span!(
431 "test",
432 %kind,
433 name = %if enabled!(tracing::Level::TRACE) { &sig } else { &func.name },
434 )
435 .entered();
436
437 let mut res = FunctionRunner::new(&self, &setup).run(
438 func,
439 kind,
440 call_after_invariant,
441 identified_contracts.as_ref(),
442 );
443 res.duration = start.elapsed();
444
445 if res.status.is_failure() {
447 early_exit.record_exit();
448 }
449
450 Some((sig, res))
451 })
452 .collect::<BTreeMap<_, _>>();
453
454 let duration = start.elapsed();
455 SuiteResult::new(duration, test_results, warnings)
456 }
457}
458
459struct FunctionRunner<'a> {
461 tcfg: Cow<'a, TestRunnerConfig>,
463 executor: Cow<'a, Executor>,
465 cr: &'a ContractRunner<'a>,
467 address: Address,
469 setup: &'a TestSetup,
471 result: TestResult,
473}
474
475impl<'a> std::ops::Deref for FunctionRunner<'a> {
476 type Target = Cow<'a, TestRunnerConfig>;
477
478 #[inline(always)]
479 fn deref(&self) -> &Self::Target {
480 &self.tcfg
481 }
482}
483
484impl<'a> FunctionRunner<'a> {
485 fn new(cr: &'a ContractRunner<'a>, setup: &'a TestSetup) -> Self {
486 Self {
487 tcfg: match &cr.tcfg {
488 Cow::Borrowed(tcfg) => Cow::Borrowed(tcfg),
489 Cow::Owned(tcfg) => Cow::Owned(tcfg.clone()),
490 },
491 executor: Cow::Borrowed(&cr.executor),
492 cr,
493 address: setup.address,
494 setup,
495 result: TestResult::new(setup),
496 }
497 }
498
499 fn revert_decoder(&self) -> &'a RevertDecoder {
500 &self.cr.mcr.revert_decoder
501 }
502
503 fn apply_function_inline_config(&mut self, func: &Function) -> Result<()> {
505 if self.inline_config.contains_function(self.cr.name, &func.name) {
506 let new_config = Arc::new(self.cr.inline_config(Some(func))?);
507 self.tcfg.to_mut().reconfigure_with(new_config);
508 self.tcfg.configure_executor(self.executor.to_mut());
509 }
510 Ok(())
511 }
512
513 fn run(
514 mut self,
515 func: &Function,
516 kind: TestFunctionKind,
517 call_after_invariant: bool,
518 identified_contracts: Option<&ContractsByAddress>,
519 ) -> TestResult {
520 if let Err(e) = self.apply_function_inline_config(func) {
521 self.result.single_fail(Some(e.to_string()));
522 return self.result;
523 }
524
525 match kind {
526 TestFunctionKind::UnitTest { .. } => self.run_unit_test(func),
527 TestFunctionKind::FuzzTest { .. } => self.run_fuzz_test(func),
528 TestFunctionKind::TableTest => self.run_table_test(func),
529 TestFunctionKind::InvariantTest => {
530 let test_bytecode = &self.cr.contract.bytecode;
531 self.run_invariant_test(
532 func,
533 call_after_invariant,
534 identified_contracts.unwrap(),
535 test_bytecode,
536 )
537 }
538 _ => unreachable!(),
539 }
540 }
541
542 fn run_unit_test(mut self, func: &Function) -> TestResult {
551 if self.prepare_test(func).is_err() {
553 return self.result;
554 }
555
556 let (mut raw_call_result, reason) = match self.executor.call(
558 self.sender,
559 self.address,
560 func,
561 &[],
562 U256::ZERO,
563 Some(self.revert_decoder()),
564 ) {
565 Ok(res) => (res.raw, None),
566 Err(EvmError::Execution(err)) => (err.raw, Some(err.reason)),
567 Err(EvmError::Skip(reason)) => {
568 self.result.single_skip(reason);
569 return self.result;
570 }
571 Err(err) => {
572 self.result.single_fail(Some(err.to_string()));
573 return self.result;
574 }
575 };
576
577 let success =
578 self.executor.is_raw_call_mut_success(self.address, &mut raw_call_result, false);
579 self.result.single_result(success, reason, raw_call_result);
580 self.result
581 }
582
583 fn run_table_test(mut self, func: &Function) -> TestResult {
592 if self.prepare_test(func).is_err() {
594 return self.result;
595 }
596
597 let Some(first_param) = func.inputs.first() else {
599 self.result.single_fail(Some("Table test should have at least one parameter".into()));
600 return self.result;
601 };
602
603 let Some(first_param_fixtures) =
604 &self.setup.fuzz_fixtures.param_fixtures(first_param.name())
605 else {
606 self.result.single_fail(Some("Table test should have fixtures defined".into()));
607 return self.result;
608 };
609
610 if first_param_fixtures.is_empty() {
611 self.result.single_fail(Some("Table test should have at least one fixture".into()));
612 return self.result;
613 }
614
615 let fixtures_len = first_param_fixtures.len();
616 let mut table_fixtures = vec![&first_param_fixtures[..]];
617
618 for param in &func.inputs[1..] {
620 let param_name = param.name();
621 let Some(fixtures) = &self.setup.fuzz_fixtures.param_fixtures(param.name()) else {
622 self.result.single_fail(Some(format!("No fixture defined for param {param_name}")));
623 return self.result;
624 };
625
626 if fixtures.len() != fixtures_len {
627 self.result.single_fail(Some(format!(
628 "{} fixtures defined for {param_name} (expected {})",
629 fixtures.len(),
630 fixtures_len
631 )));
632 return self.result;
633 }
634
635 table_fixtures.push(&fixtures[..]);
636 }
637
638 let progress = start_fuzz_progress(
639 self.cr.progress,
640 self.cr.name,
641 &func.name,
642 None,
643 fixtures_len as u32,
644 );
645
646 let mut result = FuzzTestResult::default();
647
648 for i in 0..fixtures_len {
649 if self.tcfg.early_exit.should_stop() {
650 return self.result;
651 }
652
653 if let Some(progress) = progress.as_ref() {
655 progress.inc(1);
656 }
657
658 let args = table_fixtures.iter().map(|row| row[i].clone()).collect_vec();
659 let (mut raw_call_result, reason) = match self.executor.call(
660 self.sender,
661 self.address,
662 func,
663 &args,
664 U256::ZERO,
665 Some(self.revert_decoder()),
666 ) {
667 Ok(res) => (res.raw, None),
668 Err(EvmError::Execution(err)) => (err.raw, Some(err.reason)),
669 Err(EvmError::Skip(reason)) => {
670 self.result.single_skip(reason);
671 return self.result;
672 }
673 Err(err) => {
674 self.result.single_fail(Some(err.to_string()));
675 return self.result;
676 }
677 };
678
679 result.gas_by_case.push((raw_call_result.gas_used, raw_call_result.stipend));
680 result.logs.extend(raw_call_result.logs.clone());
681 result.labels.extend(raw_call_result.labels.clone());
682 HitMaps::merge_opt(&mut result.line_coverage, raw_call_result.line_coverage.clone());
683
684 let is_success =
685 self.executor.is_raw_call_mut_success(self.address, &mut raw_call_result, false);
686 if !is_success {
688 result.counterexample =
689 Some(CounterExample::Single(BaseCounterExample::from_fuzz_call(
690 Bytes::from(func.abi_encode_input(&args).unwrap()),
691 args,
692 raw_call_result.traces.clone(),
693 )));
694 result.reason = reason;
695 result.traces = raw_call_result.traces;
696 self.result.table_result(result);
697 return self.result;
698 }
699
700 if i == fixtures_len - 1 {
703 result.success = true;
704 result.traces = raw_call_result.traces;
705 self.result.table_result(result);
706 return self.result;
707 }
708 }
709
710 self.result
711 }
712
713 fn run_invariant_test(
714 mut self,
715 func: &Function,
716 call_after_invariant: bool,
717 identified_contracts: &ContractsByAddress,
718 test_bytecode: &Bytes,
719 ) -> TestResult {
720 if let Err(EvmError::Skip(reason)) = self.executor.call(
722 self.sender,
723 self.address,
724 func,
725 &[],
726 U256::ZERO,
727 Some(self.revert_decoder()),
728 ) {
729 self.result.invariant_skip(reason);
730 return self.result;
731 };
732
733 let runner = self.invariant_runner();
734 let invariant_config = &self.config.invariant;
735
736 let mut executor = self.clone_executor();
737 executor
740 .inspector_mut()
741 .collect_edge_coverage(invariant_config.corpus.collect_edge_coverage());
742 let mut config = invariant_config.clone();
743 let (failure_dir, failure_file) = test_paths(
744 &mut config.corpus,
745 invariant_config.failure_persist_dir.clone().unwrap(),
746 self.cr.name,
747 &func.name,
748 );
749
750 let mut evm = InvariantExecutor::new(
751 executor,
752 runner,
753 config,
754 identified_contracts,
755 &self.cr.mcr.known_contracts,
756 );
757 let invariant_contract = InvariantContract {
758 address: self.address,
759 invariant_function: func,
760 call_after_invariant,
761 abi: &self.cr.contract.abi,
762 };
763 let show_solidity = invariant_config.show_solidity;
764
765 let progress = start_fuzz_progress(
766 self.cr.progress,
767 self.cr.name,
768 &func.name,
769 invariant_config.timeout,
770 invariant_config.runs,
771 );
772
773 if let Some(mut call_sequence) =
775 persisted_call_sequence(failure_file.as_path(), test_bytecode)
776 {
777 let txes = call_sequence
779 .iter_mut()
780 .map(|seq| {
781 seq.show_solidity = show_solidity;
782 BasicTxDetails {
783 warp: seq.warp,
784 roll: seq.roll,
785 sender: seq.sender.unwrap_or_default(),
786 call_details: CallDetails {
787 target: seq.addr.unwrap_or_default(),
788 calldata: seq.calldata.clone(),
789 },
790 }
791 })
792 .collect::<Vec<BasicTxDetails>>();
793 if let Ok((success, replayed_entirely)) = check_sequence(
794 self.clone_executor(),
795 &txes,
796 (0..min(txes.len(), invariant_config.depth as usize)).collect(),
797 invariant_contract.address,
798 invariant_contract.invariant_function.selector().to_vec().into(),
799 invariant_config.fail_on_revert,
800 invariant_contract.call_after_invariant,
801 ) && !success
802 {
803 let warn = format!(
804 "Replayed invariant failure from {:?} file. \nRun `forge clean` or remove file to ignore failure and to continue invariant test campaign.",
805 failure_file.as_path()
806 );
807
808 if let Some(ref progress) = progress {
809 progress.set_prefix(format!("{}\n{warn}\n", &func.name));
810 } else {
811 let _ = sh_warn!("{warn}");
812 }
813
814 match replay_error(
817 evm.config(),
818 self.clone_executor(),
819 &txes,
820 None,
821 &invariant_contract,
822 &self.cr.mcr.known_contracts,
823 identified_contracts.clone(),
824 &mut self.result.logs,
825 &mut self.result.traces,
826 &mut self.result.line_coverage,
827 &mut self.result.deprecated_cheatcodes,
828 progress.as_ref(),
829 &self.tcfg.early_exit,
830 ) {
831 Ok(replayed_call_sequence) => {
832 if !replayed_call_sequence.is_empty() {
833 call_sequence = replayed_call_sequence;
834 record_invariant_failure(
836 failure_dir.as_path(),
837 failure_file.as_path(),
838 &call_sequence,
839 test_bytecode,
840 );
841 }
842 }
843 Err(err) => {
844 error!(%err, "Failed to replay invariant error");
845 }
846 }
847
848 self.result.invariant_replay_fail(
849 replayed_entirely,
850 &invariant_contract.invariant_function.name,
851 call_sequence,
852 );
853 return self.result;
854 }
855 }
856
857 let invariant_result = match evm.invariant_fuzz(
858 invariant_contract.clone(),
859 &self.setup.fuzz_fixtures,
860 self.build_fuzz_state(true),
861 progress.as_ref(),
862 &self.tcfg.early_exit,
863 ) {
864 Ok(x) => x,
865 Err(e) => {
866 self.result.invariant_setup_fail(e);
867 return self.result;
868 }
869 };
870 self.result.merge_coverages(invariant_result.line_coverage);
872
873 let mut counterexample = None;
874 let success = invariant_result.error.is_none();
875 let reason = invariant_result.error.as_ref().and_then(|err| err.revert_reason());
876
877 match invariant_result.error {
878 Some(error) => match error {
880 InvariantFuzzError::BrokenInvariant(case_data)
881 | InvariantFuzzError::Revert(case_data) => {
882 match case_data.test_error {
885 TestError::Abort(_) => {}
886 TestError::Fail(_, ref calls) => {
887 match replay_error(
888 evm.config(),
889 self.clone_executor(),
890 calls,
891 Some(case_data.inner_sequence),
892 &invariant_contract,
893 &self.cr.mcr.known_contracts,
894 identified_contracts.clone(),
895 &mut self.result.logs,
896 &mut self.result.traces,
897 &mut self.result.line_coverage,
898 &mut self.result.deprecated_cheatcodes,
899 progress.as_ref(),
900 &self.tcfg.early_exit,
901 ) {
902 Ok(call_sequence) => {
903 if !call_sequence.is_empty() {
904 record_invariant_failure(
906 failure_dir.as_path(),
907 failure_file.as_path(),
908 &call_sequence,
909 test_bytecode,
910 );
911
912 let original_seq_len = if let TestError::Fail(_, calls) =
913 &case_data.test_error
914 {
915 calls.len()
916 } else {
917 call_sequence.len()
918 };
919
920 counterexample = Some(CounterExample::Sequence(
921 original_seq_len,
922 call_sequence,
923 ))
924 }
925 }
926 Err(err) => {
927 error!(%err, "Failed to replay invariant error");
928 }
929 }
930 }
931 };
932 }
933 InvariantFuzzError::MaxAssumeRejects(_) => {}
934 },
935
936 _ => {
939 if let Err(err) = replay_run(
940 &invariant_contract,
941 self.clone_executor(),
942 &self.cr.mcr.known_contracts,
943 identified_contracts.clone(),
944 &mut self.result.logs,
945 &mut self.result.traces,
946 &mut self.result.line_coverage,
947 &mut self.result.deprecated_cheatcodes,
948 &invariant_result.last_run_inputs,
949 show_solidity,
950 ) {
951 error!(%err, "Failed to replay last invariant run");
952 }
953 }
954 }
955
956 self.result.invariant_result(
957 invariant_result.gas_report_traces,
958 success,
959 reason,
960 counterexample,
961 invariant_result.cases,
962 invariant_result.reverts,
963 invariant_result.metrics,
964 invariant_result.failed_corpus_replays,
965 );
966 self.result
967 }
968
969 fn run_fuzz_test(mut self, func: &Function) -> TestResult {
979 if self.prepare_test(func).is_err() {
981 return self.result;
982 }
983
984 let runner = self.fuzz_runner();
985 let mut fuzz_config = self.config.fuzz.clone();
986 let (failure_dir, failure_file) = test_paths(
987 &mut fuzz_config.corpus,
988 fuzz_config.failure_persist_dir.clone().unwrap(),
989 self.cr.name,
990 &func.name,
991 );
992
993 let progress = start_fuzz_progress(
994 self.cr.progress,
995 self.cr.name,
996 &func.name,
997 fuzz_config.timeout,
998 fuzz_config.runs,
999 );
1000
1001 let state = self.build_fuzz_state(false);
1002 let mut executor = self.executor.into_owned();
1003 executor.inspector_mut().collect_edge_coverage(fuzz_config.corpus.collect_edge_coverage());
1006 let persisted_failure =
1008 foundry_common::fs::read_json_file::<BaseCounterExample>(failure_file.as_path()).ok();
1009 let mut fuzzed_executor =
1011 FuzzedExecutor::new(executor, runner, self.tcfg.sender, fuzz_config, persisted_failure);
1012 let result = match fuzzed_executor.fuzz(
1013 func,
1014 &self.setup.fuzz_fixtures,
1015 state,
1016 self.address,
1017 &self.cr.mcr.revert_decoder,
1018 progress.as_ref(),
1019 &self.tcfg.early_exit,
1020 ) {
1021 Ok(x) => x,
1022 Err(e) => {
1023 self.result.fuzz_setup_fail(e);
1024 return self.result;
1025 }
1026 };
1027
1028 if let Some(CounterExample::Single(counterexample)) = &result.counterexample {
1030 if let Err(err) = foundry_common::fs::create_dir_all(failure_dir) {
1031 error!(%err, "Failed to create fuzz failure dir");
1032 } else if let Err(err) =
1033 foundry_common::fs::write_json_file(failure_file.as_path(), counterexample)
1034 {
1035 error!(%err, "Failed to record call sequence");
1036 }
1037 }
1038
1039 self.result.fuzz_result(result);
1040 self.result
1041 }
1042
1043 fn prepare_test(&mut self, func: &Function) -> Result<(), ()> {
1053 let address = self.setup.address;
1054
1055 if self.cr.contract.abi.functions().any(|func| func.name.is_before_test_setup()) {
1057 for calldata in self.executor.call_sol_default(
1058 address,
1059 &ITest::beforeTestSetupCall { testSelector: func.selector() },
1060 ) {
1061 debug!(?calldata, spec=%self.executor.spec_id(), "applying before_test_setup");
1062 match self.executor.to_mut().transact_raw(
1064 self.tcfg.sender,
1065 address,
1066 calldata,
1067 U256::ZERO,
1068 ) {
1069 Ok(call_result) => {
1070 let reverted = call_result.reverted;
1071
1072 self.result.extend(call_result);
1074
1075 if reverted {
1077 self.result.single_fail(None);
1078 return Err(());
1079 }
1080 }
1081 Err(_) => {
1082 self.result.single_fail(None);
1083 return Err(());
1084 }
1085 }
1086 }
1087 }
1088 Ok(())
1089 }
1090
1091 fn fuzz_runner(&self) -> TestRunner {
1092 let config = &self.config.fuzz;
1093 fuzzer_with_cases(config.seed, config.runs, config.max_test_rejects)
1094 }
1095
1096 fn invariant_runner(&self) -> TestRunner {
1097 let config = &self.config.invariant;
1098 fuzzer_with_cases(self.config.fuzz.seed, config.runs, config.max_assume_rejects)
1099 }
1100
1101 fn clone_executor(&self) -> Executor {
1102 self.executor.clone().into_owned()
1103 }
1104
1105 fn build_fuzz_state(&self, invariant: bool) -> EvmFuzzState {
1106 let config =
1107 if invariant { self.config.invariant.dictionary } else { self.config.fuzz.dictionary };
1108 if let Some(db) = self.executor.backend().active_fork_db() {
1109 EvmFuzzState::new(
1110 &self.setup.deployed_libs,
1111 db,
1112 config,
1113 Some(&self.cr.mcr.fuzz_literals),
1114 )
1115 } else {
1116 let db = self.executor.backend().mem_db();
1117 EvmFuzzState::new(
1118 &self.setup.deployed_libs,
1119 db,
1120 config,
1121 Some(&self.cr.mcr.fuzz_literals),
1122 )
1123 }
1124 }
1125}
1126
1127fn fuzzer_with_cases(seed: Option<U256>, cases: u32, max_global_rejects: u32) -> TestRunner {
1128 let config = proptest::test_runner::Config {
1129 cases,
1130 max_global_rejects,
1131 max_shrink_iters: 0,
1134 ..Default::default()
1135 };
1136
1137 if let Some(seed) = seed {
1138 trace!(target: "forge::test", %seed, "building deterministic fuzzer");
1139 let rng = TestRng::from_seed(RngAlgorithm::ChaCha, &seed.to_be_bytes::<32>());
1140 TestRunner::new_with_rng(config, rng)
1141 } else {
1142 trace!(target: "forge::test", "building stochastic fuzzer");
1143 TestRunner::new(config)
1144 }
1145}
1146
1147#[derive(Serialize, Deserialize)]
1149struct InvariantPersistedFailure {
1150 call_sequence: Vec<BaseCounterExample>,
1152 #[serde(skip_serializing_if = "Option::is_none")]
1154 driver_bytecode: Option<Bytes>,
1155}
1156
1157fn persisted_call_sequence(path: &Path, bytecode: &Bytes) -> Option<Vec<BaseCounterExample>> {
1160 foundry_common::fs::read_json_file::<InvariantPersistedFailure>(path).ok().and_then(
1161 |persisted_failure| {
1162 if let Some(persisted_bytecode) = &persisted_failure.driver_bytecode {
1163 if !bytecode.eq(persisted_bytecode) {
1165 let _= sh_warn!("\
1166 Failure from {:?} file was ignored because test contract bytecode has changed.",
1167 path
1168 );
1169 return None;
1170 }
1171 };
1172 Some(persisted_failure.call_sequence)
1173 },
1174 )
1175}
1176
1177fn test_paths(
1179 corpus_config: &mut FuzzCorpusConfig,
1180 persist_dir: PathBuf,
1181 contract_name: &str,
1182 test_name: &str,
1183) -> (PathBuf, PathBuf) {
1184 let contract = contract_name.split(':').next_back().unwrap();
1185 corpus_config.with_test(contract, test_name);
1187
1188 let failures_dir = canonicalized(persist_dir.join("failures").join(contract));
1189 let failure_file = canonicalized(failures_dir.join(test_name));
1190 (failures_dir, failure_file)
1191}
1192
1193fn record_invariant_failure(
1195 failure_dir: &Path,
1196 failure_file: &Path,
1197 call_sequence: &[BaseCounterExample],
1198 test_bytecode: &Bytes,
1199) {
1200 if let Err(err) = foundry_common::fs::create_dir_all(failure_dir) {
1201 error!(%err, "Failed to create invariant failure dir");
1202 return;
1203 }
1204
1205 if let Err(err) = foundry_common::fs::write_json_file(
1206 failure_file,
1207 &InvariantPersistedFailure {
1208 call_sequence: call_sequence.to_owned(),
1209 driver_bytecode: Some(test_bytecode.clone()),
1210 },
1211 ) {
1212 error!(%err, "Failed to record call sequence");
1213 }
1214}