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,
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 if let Some(mut call_sequence) =
767 persisted_call_sequence(failure_file.as_path(), test_bytecode)
768 {
769 let txes = call_sequence
771 .iter_mut()
772 .map(|seq| {
773 seq.show_solidity = show_solidity;
774 BasicTxDetails {
775 sender: seq.sender.unwrap_or_default(),
776 call_details: CallDetails {
777 target: seq.addr.unwrap_or_default(),
778 calldata: seq.calldata.clone(),
779 },
780 }
781 })
782 .collect::<Vec<BasicTxDetails>>();
783 if let Ok((success, replayed_entirely)) = check_sequence(
784 self.clone_executor(),
785 &txes,
786 (0..min(txes.len(), invariant_config.depth as usize)).collect(),
787 invariant_contract.address,
788 invariant_contract.invariant_function.selector().to_vec().into(),
789 invariant_config.fail_on_revert,
790 invariant_contract.call_after_invariant,
791 ) && !success
792 {
793 let _ = sh_warn!(
794 "\
795 Replayed invariant failure from {:?} file. \
796 Run `forge clean` or remove file to ignore failure and to continue invariant test campaign.",
797 failure_file.as_path()
798 );
799 let _ = replay_run(
802 &invariant_contract,
803 self.clone_executor(),
804 &self.cr.mcr.known_contracts,
805 identified_contracts.clone(),
806 &mut self.result.logs,
807 &mut self.result.traces,
808 &mut self.result.line_coverage,
809 &mut self.result.deprecated_cheatcodes,
810 &txes,
811 show_solidity,
812 );
813 self.result.invariant_replay_fail(
814 replayed_entirely,
815 &invariant_contract.invariant_function.name,
816 call_sequence,
817 );
818 return self.result;
819 }
820 }
821
822 let progress = start_fuzz_progress(
823 self.cr.progress,
824 self.cr.name,
825 &func.name,
826 invariant_config.timeout,
827 invariant_config.runs,
828 );
829 let invariant_result = match evm.invariant_fuzz(
830 invariant_contract.clone(),
831 &self.setup.fuzz_fixtures,
832 &self.setup.deployed_libs,
833 progress.as_ref(),
834 &self.tcfg.early_exit,
835 ) {
836 Ok(x) => x,
837 Err(e) => {
838 self.result.invariant_setup_fail(e);
839 return self.result;
840 }
841 };
842 self.result.merge_coverages(invariant_result.line_coverage);
844
845 let mut counterexample = None;
846 let success = invariant_result.error.is_none();
847 let reason = invariant_result.error.as_ref().and_then(|err| err.revert_reason());
848
849 match invariant_result.error {
850 Some(error) => match error {
852 InvariantFuzzError::BrokenInvariant(case_data)
853 | InvariantFuzzError::Revert(case_data) => {
854 match replay_error(
857 &case_data,
858 &invariant_contract,
859 self.clone_executor(),
860 &self.cr.mcr.known_contracts,
861 identified_contracts.clone(),
862 &mut self.result.logs,
863 &mut self.result.traces,
864 &mut self.result.line_coverage,
865 &mut self.result.deprecated_cheatcodes,
866 progress.as_ref(),
867 show_solidity,
868 &self.tcfg.early_exit,
869 ) {
870 Ok(call_sequence) => {
871 if !call_sequence.is_empty() {
872 if let Err(err) = foundry_common::fs::create_dir_all(failure_dir) {
874 error!(%err, "Failed to create invariant failure dir");
875 } else if let Err(err) = foundry_common::fs::write_json_file(
876 failure_file.as_path(),
877 &InvariantPersistedFailure {
878 call_sequence: call_sequence.clone(),
879 driver_bytecode: Some(test_bytecode.clone()),
880 },
881 ) {
882 error!(%err, "Failed to record call sequence");
883 }
884
885 let original_seq_len =
886 if let TestError::Fail(_, calls) = &case_data.test_error {
887 calls.len()
888 } else {
889 call_sequence.len()
890 };
891
892 counterexample =
893 Some(CounterExample::Sequence(original_seq_len, call_sequence))
894 }
895 }
896 Err(err) => {
897 error!(%err, "Failed to replay invariant error");
898 }
899 };
900 }
901 InvariantFuzzError::MaxAssumeRejects(_) => {}
902 },
903
904 _ => {
907 if let Err(err) = replay_run(
908 &invariant_contract,
909 self.clone_executor(),
910 &self.cr.mcr.known_contracts,
911 identified_contracts.clone(),
912 &mut self.result.logs,
913 &mut self.result.traces,
914 &mut self.result.line_coverage,
915 &mut self.result.deprecated_cheatcodes,
916 &invariant_result.last_run_inputs,
917 show_solidity,
918 ) {
919 error!(%err, "Failed to replay last invariant run");
920 }
921 }
922 }
923
924 self.result.invariant_result(
925 invariant_result.gas_report_traces,
926 success,
927 reason,
928 counterexample,
929 invariant_result.cases,
930 invariant_result.reverts,
931 invariant_result.metrics,
932 invariant_result.failed_corpus_replays,
933 );
934 self.result
935 }
936
937 fn run_fuzz_test(mut self, func: &Function) -> TestResult {
947 if self.prepare_test(func).is_err() {
949 return self.result;
950 }
951
952 let runner = self.fuzz_runner();
953 let mut fuzz_config = self.config.fuzz.clone();
954 let (failure_dir, failure_file) = test_paths(
955 &mut fuzz_config.corpus,
956 fuzz_config.failure_persist_dir.clone().unwrap(),
957 self.cr.name,
958 &func.name,
959 );
960
961 let progress = start_fuzz_progress(
962 self.cr.progress,
963 self.cr.name,
964 &func.name,
965 fuzz_config.timeout,
966 fuzz_config.runs,
967 );
968
969 let mut executor = self.executor.into_owned();
970 executor.inspector_mut().collect_edge_coverage(fuzz_config.corpus.collect_edge_coverage());
973 let persisted_failure =
975 foundry_common::fs::read_json_file::<BaseCounterExample>(failure_file.as_path()).ok();
976 let mut fuzzed_executor =
978 FuzzedExecutor::new(executor, runner, self.tcfg.sender, fuzz_config, persisted_failure);
979 let result = match fuzzed_executor.fuzz(
980 func,
981 &self.setup.fuzz_fixtures,
982 &self.setup.deployed_libs,
983 self.address,
984 &self.cr.mcr.revert_decoder,
985 progress.as_ref(),
986 &self.tcfg.early_exit,
987 ) {
988 Ok(x) => x,
989 Err(e) => {
990 self.result.fuzz_setup_fail(e);
991 return self.result;
992 }
993 };
994
995 if let Some(CounterExample::Single(counterexample)) = &result.counterexample {
997 if let Err(err) = foundry_common::fs::create_dir_all(failure_dir) {
998 error!(%err, "Failed to create fuzz failure dir");
999 } else if let Err(err) =
1000 foundry_common::fs::write_json_file(failure_file.as_path(), counterexample)
1001 {
1002 error!(%err, "Failed to record call sequence");
1003 }
1004 }
1005
1006 self.result.fuzz_result(result);
1007 self.result
1008 }
1009
1010 fn prepare_test(&mut self, func: &Function) -> Result<(), ()> {
1020 let address = self.setup.address;
1021
1022 if self.cr.contract.abi.functions().any(|func| func.name.is_before_test_setup()) {
1024 for calldata in self.executor.call_sol_default(
1025 address,
1026 &ITest::beforeTestSetupCall { testSelector: func.selector() },
1027 ) {
1028 debug!(?calldata, spec=%self.executor.spec_id(), "applying before_test_setup");
1029 match self.executor.to_mut().transact_raw(
1031 self.tcfg.sender,
1032 address,
1033 calldata,
1034 U256::ZERO,
1035 ) {
1036 Ok(call_result) => {
1037 let reverted = call_result.reverted;
1038
1039 self.result.extend(call_result);
1041
1042 if reverted {
1044 self.result.single_fail(None);
1045 return Err(());
1046 }
1047 }
1048 Err(_) => {
1049 self.result.single_fail(None);
1050 return Err(());
1051 }
1052 }
1053 }
1054 }
1055 Ok(())
1056 }
1057
1058 fn fuzz_runner(&self) -> TestRunner {
1059 let config = &self.config.fuzz;
1060 fuzzer_with_cases(config.seed, config.runs, config.max_test_rejects)
1061 }
1062
1063 fn invariant_runner(&self) -> TestRunner {
1064 let config = &self.config.invariant;
1065 fuzzer_with_cases(self.config.fuzz.seed, config.runs, config.max_assume_rejects)
1066 }
1067
1068 fn clone_executor(&self) -> Executor {
1069 self.executor.clone().into_owned()
1070 }
1071}
1072
1073fn fuzzer_with_cases(seed: Option<U256>, cases: u32, max_global_rejects: u32) -> TestRunner {
1074 let config = proptest::test_runner::Config {
1075 cases,
1076 max_global_rejects,
1077 max_shrink_iters: 0,
1080 ..Default::default()
1081 };
1082
1083 if let Some(seed) = seed {
1084 trace!(target: "forge::test", %seed, "building deterministic fuzzer");
1085 let rng = TestRng::from_seed(RngAlgorithm::ChaCha, &seed.to_be_bytes::<32>());
1086 TestRunner::new_with_rng(config, rng)
1087 } else {
1088 trace!(target: "forge::test", "building stochastic fuzzer");
1089 TestRunner::new(config)
1090 }
1091}
1092
1093#[derive(Serialize, Deserialize)]
1095struct InvariantPersistedFailure {
1096 call_sequence: Vec<BaseCounterExample>,
1098 #[serde(skip_serializing_if = "Option::is_none")]
1100 driver_bytecode: Option<Bytes>,
1101}
1102
1103fn persisted_call_sequence(path: &Path, bytecode: &Bytes) -> Option<Vec<BaseCounterExample>> {
1106 foundry_common::fs::read_json_file::<InvariantPersistedFailure>(path).ok().and_then(
1107 |persisted_failure| {
1108 if let Some(persisted_bytecode) = &persisted_failure.driver_bytecode {
1109 if !bytecode.eq(persisted_bytecode) {
1111 let _= sh_warn!("\
1112 Failure from {:?} file was ignored because test contract bytecode has changed.",
1113 path
1114 );
1115 return None;
1116 }
1117 };
1118 Some(persisted_failure.call_sequence)
1119 },
1120 )
1121}
1122
1123fn test_paths(
1125 corpus_config: &mut FuzzCorpusConfig,
1126 persist_dir: PathBuf,
1127 contract_name: &str,
1128 test_name: &str,
1129) -> (PathBuf, PathBuf) {
1130 let contract = contract_name.split(':').next_back().unwrap();
1131 corpus_config.with_test(contract, test_name);
1133
1134 let failures_dir = canonicalized(persist_dir.join("failures").join(contract));
1135 let failure_file = canonicalized(failures_dir.join(test_name));
1136 (failures_dir, failure_file)
1137}