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_ctrl_c();
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_failure();
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 self.cr.tokio_handle,
1021 ) {
1022 Ok(x) => x,
1023 Err(e) => {
1024 self.result.fuzz_setup_fail(e);
1025 return self.result;
1026 }
1027 };
1028
1029 if let Some(CounterExample::Single(counterexample)) = &result.counterexample {
1031 if let Err(err) = foundry_common::fs::create_dir_all(failure_dir) {
1032 error!(%err, "Failed to create fuzz failure dir");
1033 } else if let Err(err) =
1034 foundry_common::fs::write_json_file(failure_file.as_path(), counterexample)
1035 {
1036 error!(%err, "Failed to record call sequence");
1037 }
1038 }
1039
1040 self.result.fuzz_result(result);
1041 self.result
1042 }
1043
1044 fn prepare_test(&mut self, func: &Function) -> Result<(), ()> {
1054 let address = self.setup.address;
1055
1056 if self.cr.contract.abi.functions().any(|func| func.name.is_before_test_setup()) {
1058 for calldata in self.executor.call_sol_default(
1059 address,
1060 &ITest::beforeTestSetupCall { testSelector: func.selector() },
1061 ) {
1062 debug!(?calldata, spec=%self.executor.spec_id(), "applying before_test_setup");
1063 match self.executor.to_mut().transact_raw(
1065 self.tcfg.sender,
1066 address,
1067 calldata,
1068 U256::ZERO,
1069 ) {
1070 Ok(call_result) => {
1071 let reverted = call_result.reverted;
1072
1073 self.result.extend(call_result);
1075
1076 if reverted {
1078 self.result.single_fail(None);
1079 return Err(());
1080 }
1081 }
1082 Err(_) => {
1083 self.result.single_fail(None);
1084 return Err(());
1085 }
1086 }
1087 }
1088 }
1089 Ok(())
1090 }
1091
1092 fn fuzz_runner(&self) -> TestRunner {
1093 let config = &self.config.fuzz;
1094 fuzzer_with_cases(config.seed, config.runs, config.max_test_rejects)
1095 }
1096
1097 fn invariant_runner(&self) -> TestRunner {
1098 let config = &self.config.invariant;
1099 fuzzer_with_cases(self.config.fuzz.seed, config.runs, config.max_assume_rejects)
1100 }
1101
1102 fn clone_executor(&self) -> Executor {
1103 self.executor.clone().into_owned()
1104 }
1105
1106 fn build_fuzz_state(&self, invariant: bool) -> EvmFuzzState {
1107 let config =
1108 if invariant { self.config.invariant.dictionary } else { self.config.fuzz.dictionary };
1109 if let Some(db) = self.executor.backend().active_fork_db() {
1110 EvmFuzzState::new(
1111 &self.setup.deployed_libs,
1112 db,
1113 config,
1114 Some(&self.cr.mcr.fuzz_literals),
1115 )
1116 } else {
1117 let db = self.executor.backend().mem_db();
1118 EvmFuzzState::new(
1119 &self.setup.deployed_libs,
1120 db,
1121 config,
1122 Some(&self.cr.mcr.fuzz_literals),
1123 )
1124 }
1125 }
1126}
1127
1128fn fuzzer_with_cases(seed: Option<U256>, cases: u32, max_global_rejects: u32) -> TestRunner {
1129 let config = proptest::test_runner::Config {
1130 cases,
1131 max_global_rejects,
1132 max_shrink_iters: 0,
1135 ..Default::default()
1136 };
1137
1138 if let Some(seed) = seed {
1139 trace!(target: "forge::test", %seed, "building deterministic fuzzer");
1140 let rng = TestRng::from_seed(RngAlgorithm::ChaCha, &seed.to_be_bytes::<32>());
1141 TestRunner::new_with_rng(config, rng)
1142 } else {
1143 trace!(target: "forge::test", "building stochastic fuzzer");
1144 TestRunner::new(config)
1145 }
1146}
1147
1148#[derive(Serialize, Deserialize)]
1150struct InvariantPersistedFailure {
1151 call_sequence: Vec<BaseCounterExample>,
1153 #[serde(skip_serializing_if = "Option::is_none")]
1155 driver_bytecode: Option<Bytes>,
1156}
1157
1158fn persisted_call_sequence(path: &Path, bytecode: &Bytes) -> Option<Vec<BaseCounterExample>> {
1161 foundry_common::fs::read_json_file::<InvariantPersistedFailure>(path).ok().and_then(
1162 |persisted_failure| {
1163 if let Some(persisted_bytecode) = &persisted_failure.driver_bytecode {
1164 if !bytecode.eq(persisted_bytecode) {
1166 let _= sh_warn!("\
1167 Failure from {:?} file was ignored because test contract bytecode has changed.",
1168 path
1169 );
1170 return None;
1171 }
1172 };
1173 Some(persisted_failure.call_sequence)
1174 },
1175 )
1176}
1177
1178fn test_paths(
1180 corpus_config: &mut FuzzCorpusConfig,
1181 persist_dir: PathBuf,
1182 contract_name: &str,
1183 test_name: &str,
1184) -> (PathBuf, PathBuf) {
1185 let contract = contract_name.split(':').next_back().unwrap();
1186 corpus_config.with_test(contract, test_name);
1188
1189 let failures_dir = canonicalized(persist_dir.join("failures").join(contract));
1190 let failure_file = canonicalized(failures_dir.join(test_name));
1191 (failures_dir, failure_file)
1192}
1193
1194fn record_invariant_failure(
1196 failure_dir: &Path,
1197 failure_file: &Path,
1198 call_sequence: &[BaseCounterExample],
1199 test_bytecode: &Bytes,
1200) {
1201 if let Err(err) = foundry_common::fs::create_dir_all(failure_dir) {
1202 error!(%err, "Failed to create invariant failure dir");
1203 return;
1204 }
1205
1206 if let Err(err) = foundry_common::fs::write_json_file(
1207 failure_file,
1208 &InvariantPersistedFailure {
1209 call_sequence: call_sequence.to_owned(),
1210 driver_bytecode: Some(test_bytecode.clone()),
1211 },
1212 ) {
1213 error!(%err, "Failed to record call sequence");
1214 }
1215}