1use crate::{
4 MultiContractRunner, TestFilter,
5 fuzz::BaseCounterExample,
6 multi_runner::{TestContract, TestRunnerConfig, is_matching_test},
7 progress::{TestsProgress, start_fuzz_progress},
8 result::{SuiteResult, TestResult, TestSetup},
9};
10use alloy_dyn_abi::{DynSolValue, JsonAbiExt};
11use alloy_json_abi::Function;
12use alloy_primitives::{Address, Bytes, U256, address, map::HashMap};
13use eyre::Result;
14use foundry_common::{TestFunctionExt, TestFunctionKind, contracts::ContractsByAddress};
15use foundry_compilers::utils::canonicalized;
16use foundry_config::Config;
17use foundry_evm::{
18 constants::CALLER,
19 decode::RevertDecoder,
20 executors::{
21 CallResult, EvmError, Executor, ITest, RawCallResult,
22 fuzz::FuzzedExecutor,
23 invariant::{
24 InvariantExecutor, InvariantFuzzError, check_sequence, replay_error, replay_run,
25 },
26 },
27 fuzz::{
28 BasicTxDetails, CallDetails, CounterExample, FuzzFixtures, fixture_name,
29 invariant::InvariantContract,
30 },
31 traces::{TraceKind, TraceMode, load_contracts},
32};
33use itertools::Itertools;
34use proptest::test_runner::{RngAlgorithm, TestError, TestRng, TestRunner};
35use rayon::prelude::*;
36use serde::{Deserialize, Serialize};
37use std::{
38 borrow::Cow,
39 cmp::min,
40 collections::BTreeMap,
41 path::{Path, PathBuf},
42 sync::Arc,
43 time::Instant,
44};
45use tracing::Span;
46
47pub const LIBRARY_DEPLOYER: Address = address!("0x1F95D37F27EA0dEA9C252FC09D5A6eaA97647353");
53
54pub struct ContractRunner<'a> {
56 name: &'a str,
58 contract: &'a TestContract,
60 executor: Executor,
62 progress: Option<&'a TestsProgress>,
64 tokio_handle: &'a tokio::runtime::Handle,
66 span: tracing::Span,
68 tcfg: Cow<'a, TestRunnerConfig>,
70 mcr: &'a MultiContractRunner,
72}
73
74impl<'a> std::ops::Deref for ContractRunner<'a> {
75 type Target = Cow<'a, TestRunnerConfig>;
76
77 #[inline(always)]
78 fn deref(&self) -> &Self::Target {
79 &self.tcfg
80 }
81}
82
83impl<'a> ContractRunner<'a> {
84 pub fn new(
85 name: &'a str,
86 contract: &'a TestContract,
87 executor: Executor,
88 progress: Option<&'a TestsProgress>,
89 tokio_handle: &'a tokio::runtime::Handle,
90 span: Span,
91 mcr: &'a MultiContractRunner,
92 ) -> Self {
93 Self {
94 name,
95 contract,
96 executor,
97 progress,
98 tokio_handle,
99 span,
100 tcfg: Cow::Borrowed(&mcr.tcfg),
101 mcr,
102 }
103 }
104
105 pub fn setup(&mut self, call_setup: bool) -> TestSetup {
108 self._setup(call_setup).unwrap_or_else(|err| {
109 if err.to_string().contains("skipped") {
110 TestSetup::skipped(err.to_string())
111 } else {
112 TestSetup::failed(err.to_string())
113 }
114 })
115 }
116
117 fn _setup(&mut self, call_setup: bool) -> Result<TestSetup> {
118 trace!(call_setup, "setting up");
119
120 self.apply_contract_inline_config()?;
121
122 self.executor.set_balance(self.sender, U256::MAX)?;
124 self.executor.set_balance(CALLER, U256::MAX)?;
125
126 self.executor.set_nonce(self.sender, 1)?;
128
129 self.executor.set_balance(LIBRARY_DEPLOYER, U256::MAX)?;
131
132 let mut result = TestSetup::default();
133 for code in &self.mcr.libs_to_deploy {
134 let deploy_result = self.executor.deploy(
135 LIBRARY_DEPLOYER,
136 code.clone(),
137 U256::ZERO,
138 Some(&self.mcr.revert_decoder),
139 );
140
141 if let Ok(deployed) = &deploy_result {
143 result.deployed_libs.push(deployed.address);
144 }
145
146 let (raw, reason) = RawCallResult::from_evm_result(deploy_result.map(Into::into))?;
147 result.extend(raw, TraceKind::Deployment);
148 if reason.is_some() {
149 result.reason = reason;
150 return Ok(result);
151 }
152 }
153
154 let address = self.sender.create(self.executor.get_nonce(self.sender)?);
155 result.address = address;
156
157 self.executor.set_balance(address, self.initial_balance())?;
160
161 let deploy_result = self.executor.deploy(
163 self.sender,
164 self.contract.bytecode.clone(),
165 U256::ZERO,
166 Some(&self.mcr.revert_decoder),
167 );
168
169 result.deployment_failure = deploy_result.is_err();
170
171 if let Ok(dr) = &deploy_result {
172 debug_assert_eq!(dr.address, address);
173 }
174 let (raw, reason) = RawCallResult::from_evm_result(deploy_result.map(Into::into))?;
175 result.extend(raw, TraceKind::Deployment);
176 if reason.is_some() {
177 result.reason = reason;
178 return Ok(result);
179 }
180
181 self.executor.set_balance(self.sender, self.initial_balance())?;
183 self.executor.set_balance(CALLER, self.initial_balance())?;
184 self.executor.set_balance(LIBRARY_DEPLOYER, self.initial_balance())?;
185
186 self.executor.deploy_create2_deployer()?;
187
188 if call_setup {
190 trace!("calling setUp");
191 let res = self.executor.setup(None, address, Some(&self.mcr.revert_decoder));
192 let (raw, reason) = RawCallResult::from_evm_result(res)?;
193 result.extend(raw, TraceKind::Setup);
194 result.reason = reason;
195 }
196
197 result.fuzz_fixtures = self.fuzz_fixtures(address);
198
199 Ok(result)
200 }
201
202 fn initial_balance(&self) -> U256 {
203 self.evm_opts.initial_balance
204 }
205
206 fn apply_contract_inline_config(&mut self) -> Result<()> {
208 if self.inline_config.contains_contract(self.name) {
209 let new_config = Arc::new(self.inline_config(None)?);
210 self.tcfg.to_mut().reconfigure_with(new_config);
211 let prev_tracer = self.executor.inspector_mut().tracer.take();
212 self.tcfg.configure_executor(&mut self.executor);
213 self.executor.inspector_mut().tracer = prev_tracer;
215 }
216 Ok(())
217 }
218
219 fn inline_config(&self, func: Option<&Function>) -> Result<Config> {
221 let function = func.map(|f| f.name.as_str()).unwrap_or("");
222 let config =
223 self.mcr.inline_config.merge(self.name, function, &self.config).extract::<Config>()?;
224 Ok(config)
225 }
226
227 fn fuzz_fixtures(&mut self, address: Address) -> FuzzFixtures {
243 let mut fixtures = HashMap::default();
244 let fixture_functions = self.contract.abi.functions().filter(|func| func.is_fixture());
245 for func in fixture_functions {
246 if func.inputs.is_empty() {
247 if let Ok(CallResult { raw: _, decoded_result }) =
249 self.executor.call(CALLER, address, func, &[], U256::ZERO, None)
250 {
251 fixtures.insert(fixture_name(func.name.clone()), decoded_result);
252 }
253 } else {
254 let mut vals = Vec::new();
257 let mut index = 0;
258 loop {
259 if let Ok(CallResult { raw: _, decoded_result }) = self.executor.call(
260 CALLER,
261 address,
262 func,
263 &[DynSolValue::Uint(U256::from(index), 256)],
264 U256::ZERO,
265 None,
266 ) {
267 vals.push(decoded_result);
268 } else {
269 break;
272 }
273 index += 1;
274 }
275 fixtures.insert(fixture_name(func.name.clone()), DynSolValue::Array(vals));
276 };
277 }
278 FuzzFixtures::new(fixtures)
279 }
280
281 pub fn run_tests(mut self, filter: &dyn TestFilter) -> SuiteResult {
283 let start = Instant::now();
284 let mut warnings = Vec::new();
285
286 let setup_fns: Vec<_> =
288 self.contract.abi.functions().filter(|func| func.name.is_setup()).collect();
289 let call_setup = setup_fns.len() == 1 && setup_fns[0].name == "setUp";
290 for &setup_fn in &setup_fns {
292 if setup_fn.name != "setUp" {
293 warnings.push(format!(
294 "Found invalid setup function \"{}\" did you mean \"setUp()\"?",
295 setup_fn.signature()
296 ));
297 }
298 }
299
300 if setup_fns.len() > 1 {
302 return SuiteResult::new(
303 start.elapsed(),
304 [("setUp()".to_string(), TestResult::fail("multiple setUp functions".to_string()))]
305 .into(),
306 warnings,
307 );
308 }
309
310 let after_invariant_fns: Vec<_> =
312 self.contract.abi.functions().filter(|func| func.name.is_after_invariant()).collect();
313 if after_invariant_fns.len() > 1 {
314 return SuiteResult::new(
316 start.elapsed(),
317 [(
318 "afterInvariant()".to_string(),
319 TestResult::fail("multiple afterInvariant functions".to_string()),
320 )]
321 .into(),
322 warnings,
323 );
324 }
325 let call_after_invariant = after_invariant_fns.first().is_some_and(|after_invariant_fn| {
326 let match_sig = after_invariant_fn.name == "afterInvariant";
327 if !match_sig {
328 warnings.push(format!(
329 "Found invalid afterInvariant function \"{}\" did you mean \"afterInvariant()\"?",
330 after_invariant_fn.signature()
331 ));
332 }
333 match_sig
334 });
335
336 let has_invariants = self.contract.abi.functions().any(|func| func.is_invariant_test());
339
340 let prev_tracer = self.executor.inspector_mut().tracer.take();
341 if prev_tracer.is_some() || has_invariants {
342 self.executor.set_tracing(TraceMode::Call);
343 }
344
345 let setup_time = Instant::now();
346 let setup = self.setup(call_setup);
347 debug!("finished setting up in {:?}", setup_time.elapsed());
348
349 self.executor.inspector_mut().tracer = prev_tracer;
350
351 if setup.reason.is_some() {
352 let fail_msg = if !setup.deployment_failure {
354 "setUp()".to_string()
355 } else {
356 "constructor()".to_string()
357 };
358 return SuiteResult::new(
359 start.elapsed(),
360 [(fail_msg, TestResult::setup_result(setup))].into(),
361 warnings,
362 );
363 }
364
365 let find_timer = Instant::now();
368 let functions = self
369 .contract
370 .abi
371 .functions()
372 .filter(|func| is_matching_test(func, filter))
373 .collect::<Vec<_>>();
374 debug!(
375 "Found {} test functions out of {} in {:?}",
376 functions.len(),
377 self.contract.abi.functions().count(),
378 find_timer.elapsed(),
379 );
380
381 let identified_contracts = has_invariants.then(|| {
382 load_contracts(setup.traces.iter().map(|(_, t)| &t.arena), &self.mcr.known_contracts)
383 });
384
385 let test_fail_instances = functions
386 .iter()
387 .filter_map(|func| {
388 TestFunctionKind::classify(&func.name, !func.inputs.is_empty())
389 .is_any_test_fail()
390 .then_some(func.name.clone())
391 })
392 .collect::<Vec<_>>();
393
394 if !test_fail_instances.is_empty() {
395 let instances = format!(
396 "Found {} instances: {}",
397 test_fail_instances.len(),
398 test_fail_instances.join(", ")
399 );
400 let fail = TestResult::fail("`testFail*` has been removed. Consider changing to test_Revert[If|When]_Condition and expecting a revert".to_string());
401 return SuiteResult::new(start.elapsed(), [(instances, fail)].into(), warnings);
402 }
403
404 let fail_fast = &self.tcfg.fail_fast;
405
406 let test_results = functions
407 .par_iter()
408 .map(|&func| {
409 if fail_fast.should_stop() {
411 return (func.signature(), TestResult::setup_result(setup.clone()));
412 }
413
414 let start = Instant::now();
415
416 let _guard = self.tokio_handle.enter();
417
418 let _guard;
419 let current_span = tracing::Span::current();
420 if current_span.is_none() || current_span.id() != self.span.id() {
421 _guard = self.span.enter();
422 }
423
424 let sig = func.signature();
425 let kind = func.test_function_kind();
426
427 let _guard = debug_span!(
428 "test",
429 %kind,
430 name = %if enabled!(tracing::Level::TRACE) { &sig } else { &func.name },
431 )
432 .entered();
433
434 let mut res = FunctionRunner::new(&self, &setup).run(
435 func,
436 kind,
437 call_after_invariant,
438 identified_contracts.as_ref(),
439 );
440 res.duration = start.elapsed();
441
442 if res.status.is_failure() {
444 fail_fast.record_fail();
445 }
446
447 (sig, res)
448 })
449 .collect::<BTreeMap<_, _>>();
450
451 let duration = start.elapsed();
452 SuiteResult::new(duration, test_results, warnings)
453 }
454}
455
456struct FunctionRunner<'a> {
458 tcfg: Cow<'a, TestRunnerConfig>,
460 executor: Cow<'a, Executor>,
462 cr: &'a ContractRunner<'a>,
464 address: Address,
466 setup: &'a TestSetup,
468 result: TestResult,
470}
471
472impl<'a> std::ops::Deref for FunctionRunner<'a> {
473 type Target = Cow<'a, TestRunnerConfig>;
474
475 #[inline(always)]
476 fn deref(&self) -> &Self::Target {
477 &self.tcfg
478 }
479}
480
481impl<'a> FunctionRunner<'a> {
482 fn new(cr: &'a ContractRunner<'a>, setup: &'a TestSetup) -> Self {
483 Self {
484 tcfg: match &cr.tcfg {
485 Cow::Borrowed(tcfg) => Cow::Borrowed(tcfg),
486 Cow::Owned(tcfg) => Cow::Owned(tcfg.clone()),
487 },
488 executor: Cow::Borrowed(&cr.executor),
489 cr,
490 address: setup.address,
491 setup,
492 result: TestResult::new(setup),
493 }
494 }
495
496 fn revert_decoder(&self) -> &'a RevertDecoder {
497 &self.cr.mcr.revert_decoder
498 }
499
500 fn apply_function_inline_config(&mut self, func: &Function) -> Result<()> {
502 if self.inline_config.contains_function(self.cr.name, &func.name) {
503 let new_config = Arc::new(self.cr.inline_config(Some(func))?);
504 self.tcfg.to_mut().reconfigure_with(new_config);
505 self.tcfg.configure_executor(self.executor.to_mut());
506 }
507 Ok(())
508 }
509
510 fn run(
511 mut self,
512 func: &Function,
513 kind: TestFunctionKind,
514 call_after_invariant: bool,
515 identified_contracts: Option<&ContractsByAddress>,
516 ) -> TestResult {
517 if let Err(e) = self.apply_function_inline_config(func) {
518 self.result.single_fail(Some(e.to_string()));
519 return self.result;
520 }
521
522 match kind {
523 TestFunctionKind::UnitTest { .. } => self.run_unit_test(func),
524 TestFunctionKind::FuzzTest { .. } => self.run_fuzz_test(func),
525 TestFunctionKind::TableTest => self.run_table_test(func),
526 TestFunctionKind::InvariantTest => {
527 let test_bytecode = &self.cr.contract.bytecode;
528 self.run_invariant_test(
529 func,
530 call_after_invariant,
531 identified_contracts.unwrap(),
532 test_bytecode,
533 )
534 }
535 _ => unreachable!(),
536 }
537 }
538
539 fn run_unit_test(mut self, func: &Function) -> TestResult {
548 if self.prepare_test(func).is_err() {
550 return self.result;
551 }
552
553 let (mut raw_call_result, reason) = match self.executor.call(
555 self.sender,
556 self.address,
557 func,
558 &[],
559 U256::ZERO,
560 Some(self.revert_decoder()),
561 ) {
562 Ok(res) => (res.raw, None),
563 Err(EvmError::Execution(err)) => (err.raw, Some(err.reason)),
564 Err(EvmError::Skip(reason)) => {
565 self.result.single_skip(reason);
566 return self.result;
567 }
568 Err(err) => {
569 self.result.single_fail(Some(err.to_string()));
570 return self.result;
571 }
572 };
573
574 let success =
575 self.executor.is_raw_call_mut_success(self.address, &mut raw_call_result, false);
576 self.result.single_result(success, reason, raw_call_result);
577 self.result
578 }
579
580 fn run_table_test(mut self, func: &Function) -> TestResult {
589 if self.prepare_test(func).is_err() {
591 return self.result;
592 }
593
594 let Some(first_param) = func.inputs.first() else {
596 self.result.single_fail(Some("Table test should have at least one parameter".into()));
597 return self.result;
598 };
599
600 let Some(first_param_fixtures) =
601 &self.setup.fuzz_fixtures.param_fixtures(first_param.name())
602 else {
603 self.result.single_fail(Some("Table test should have fixtures defined".into()));
604 return self.result;
605 };
606
607 if first_param_fixtures.is_empty() {
608 self.result.single_fail(Some("Table test should have at least one fixture".into()));
609 return self.result;
610 }
611
612 let fixtures_len = first_param_fixtures.len();
613 let mut table_fixtures = vec![&first_param_fixtures[..]];
614
615 for param in &func.inputs[1..] {
617 let param_name = param.name();
618 let Some(fixtures) = &self.setup.fuzz_fixtures.param_fixtures(param.name()) else {
619 self.result.single_fail(Some(format!("No fixture defined for param {param_name}")));
620 return self.result;
621 };
622
623 if fixtures.len() != fixtures_len {
624 self.result.single_fail(Some(format!(
625 "{} fixtures defined for {param_name} (expected {})",
626 fixtures.len(),
627 fixtures_len
628 )));
629 return self.result;
630 }
631
632 table_fixtures.push(&fixtures[..]);
633 }
634
635 let progress = start_fuzz_progress(
636 self.cr.progress,
637 self.cr.name,
638 &func.name,
639 None,
640 fixtures_len as u32,
641 );
642
643 for i in 0..fixtures_len {
644 if self.tcfg.fail_fast.should_stop() {
645 return self.result;
646 }
647
648 if let Some(progress) = progress.as_ref() {
650 progress.inc(1);
651 }
652
653 let args = table_fixtures.iter().map(|row| row[i].clone()).collect_vec();
654 let (mut raw_call_result, reason) = match self.executor.call(
655 self.sender,
656 self.address,
657 func,
658 &args,
659 U256::ZERO,
660 Some(self.revert_decoder()),
661 ) {
662 Ok(res) => (res.raw, None),
663 Err(EvmError::Execution(err)) => (err.raw, Some(err.reason)),
664 Err(EvmError::Skip(reason)) => {
665 self.result.single_skip(reason);
666 return self.result;
667 }
668 Err(err) => {
669 self.result.single_fail(Some(err.to_string()));
670 return self.result;
671 }
672 };
673
674 let is_success =
675 self.executor.is_raw_call_mut_success(self.address, &mut raw_call_result, false);
676 if !is_success {
678 self.result.counterexample =
679 Some(CounterExample::Single(BaseCounterExample::from_fuzz_call(
680 Bytes::from(func.abi_encode_input(&args).unwrap()),
681 args,
682 raw_call_result.traces.clone(),
683 )));
684 self.result.single_result(false, reason, raw_call_result);
685 return self.result;
686 }
687
688 if i == fixtures_len - 1 {
691 self.result.single_result(true, None, raw_call_result);
692 return self.result;
693 }
694 }
695
696 self.result
697 }
698
699 fn run_invariant_test(
700 mut self,
701 func: &Function,
702 call_after_invariant: bool,
703 identified_contracts: &ContractsByAddress,
704 test_bytecode: &Bytes,
705 ) -> TestResult {
706 if let Err(EvmError::Skip(reason)) = self.executor.call(
708 self.sender,
709 self.address,
710 func,
711 &[],
712 U256::ZERO,
713 Some(self.revert_decoder()),
714 ) {
715 self.result.invariant_skip(reason);
716 return self.result;
717 };
718
719 let runner = self.invariant_runner();
720 let invariant_config = &self.config.invariant;
721
722 let mut executor = self.clone_executor();
723 executor
726 .inspector_mut()
727 .collect_edge_coverage(invariant_config.corpus.collect_edge_coverage());
728
729 let mut evm = InvariantExecutor::new(
730 executor,
731 runner,
732 invariant_config.clone(),
733 identified_contracts,
734 &self.cr.mcr.known_contracts,
735 );
736 let invariant_contract = InvariantContract {
737 address: self.address,
738 invariant_function: func,
739 call_after_invariant,
740 abi: &self.cr.contract.abi,
741 };
742
743 let (failure_dir, failure_file) = test_failure_paths(
744 invariant_config.failure_persist_dir.clone().unwrap(),
745 self.cr.name,
746 &invariant_contract.invariant_function.name,
747 );
748 let show_solidity = invariant_config.show_solidity;
749
750 if let Some(mut call_sequence) =
752 persisted_call_sequence(failure_file.as_path(), test_bytecode)
753 {
754 let txes = call_sequence
756 .iter_mut()
757 .map(|seq| {
758 seq.show_solidity = show_solidity;
759 BasicTxDetails {
760 sender: seq.sender.unwrap_or_default(),
761 call_details: CallDetails {
762 target: seq.addr.unwrap_or_default(),
763 calldata: seq.calldata.clone(),
764 },
765 }
766 })
767 .collect::<Vec<BasicTxDetails>>();
768 if let Ok((success, replayed_entirely)) = check_sequence(
769 self.clone_executor(),
770 &txes,
771 (0..min(txes.len(), invariant_config.depth as usize)).collect(),
772 invariant_contract.address,
773 invariant_contract.invariant_function.selector().to_vec().into(),
774 invariant_config.fail_on_revert,
775 invariant_contract.call_after_invariant,
776 ) && !success
777 {
778 let _ = sh_warn!(
779 "\
780 Replayed invariant failure from {:?} file. \
781 Run `forge clean` or remove file to ignore failure and to continue invariant test campaign.",
782 failure_file.as_path()
783 );
784 let _ = replay_run(
787 &invariant_contract,
788 self.clone_executor(),
789 &self.cr.mcr.known_contracts,
790 identified_contracts.clone(),
791 &mut self.result.logs,
792 &mut self.result.traces,
793 &mut self.result.line_coverage,
794 &mut self.result.deprecated_cheatcodes,
795 &txes,
796 show_solidity,
797 );
798 self.result.invariant_replay_fail(
799 replayed_entirely,
800 &invariant_contract.invariant_function.name,
801 call_sequence,
802 );
803 return self.result;
804 }
805 }
806
807 let progress = start_fuzz_progress(
808 self.cr.progress,
809 self.cr.name,
810 &func.name,
811 invariant_config.timeout,
812 invariant_config.runs,
813 );
814 let invariant_result = match evm.invariant_fuzz(
815 invariant_contract.clone(),
816 &self.setup.fuzz_fixtures,
817 &self.setup.deployed_libs,
818 progress.as_ref(),
819 &self.tcfg.fail_fast,
820 ) {
821 Ok(x) => x,
822 Err(e) => {
823 self.result.invariant_setup_fail(e);
824 return self.result;
825 }
826 };
827 self.result.merge_coverages(invariant_result.line_coverage);
829
830 let mut counterexample = None;
831 let success = invariant_result.error.is_none();
832 let reason = invariant_result.error.as_ref().and_then(|err| err.revert_reason());
833
834 match invariant_result.error {
835 Some(error) => match error {
837 InvariantFuzzError::BrokenInvariant(case_data)
838 | InvariantFuzzError::Revert(case_data) => {
839 match replay_error(
842 &case_data,
843 &invariant_contract,
844 self.clone_executor(),
845 &self.cr.mcr.known_contracts,
846 identified_contracts.clone(),
847 &mut self.result.logs,
848 &mut self.result.traces,
849 &mut self.result.line_coverage,
850 &mut self.result.deprecated_cheatcodes,
851 progress.as_ref(),
852 show_solidity,
853 ) {
854 Ok(call_sequence) => {
855 if !call_sequence.is_empty() {
856 if let Err(err) = foundry_common::fs::create_dir_all(failure_dir) {
858 error!(%err, "Failed to create invariant failure dir");
859 } else if let Err(err) = foundry_common::fs::write_json_file(
860 failure_file.as_path(),
861 &InvariantPersistedFailure {
862 call_sequence: call_sequence.clone(),
863 driver_bytecode: Some(test_bytecode.clone()),
864 },
865 ) {
866 error!(%err, "Failed to record call sequence");
867 }
868
869 let original_seq_len =
870 if let TestError::Fail(_, calls) = &case_data.test_error {
871 calls.len()
872 } else {
873 call_sequence.len()
874 };
875
876 counterexample =
877 Some(CounterExample::Sequence(original_seq_len, call_sequence))
878 }
879 }
880 Err(err) => {
881 error!(%err, "Failed to replay invariant error");
882 }
883 };
884 }
885 InvariantFuzzError::MaxAssumeRejects(_) => {}
886 },
887
888 _ => {
891 if let Err(err) = replay_run(
892 &invariant_contract,
893 self.clone_executor(),
894 &self.cr.mcr.known_contracts,
895 identified_contracts.clone(),
896 &mut self.result.logs,
897 &mut self.result.traces,
898 &mut self.result.line_coverage,
899 &mut self.result.deprecated_cheatcodes,
900 &invariant_result.last_run_inputs,
901 show_solidity,
902 ) {
903 error!(%err, "Failed to replay last invariant run");
904 }
905 }
906 }
907
908 self.result.invariant_result(
909 invariant_result.gas_report_traces,
910 success,
911 reason,
912 counterexample,
913 invariant_result.cases,
914 invariant_result.reverts,
915 invariant_result.metrics,
916 invariant_result.failed_corpus_replays,
917 );
918 self.result
919 }
920
921 fn run_fuzz_test(mut self, func: &Function) -> TestResult {
931 if self.prepare_test(func).is_err() {
933 return self.result;
934 }
935
936 let runner = self.fuzz_runner();
937 let fuzz_config = self.config.fuzz.clone();
938
939 let progress = start_fuzz_progress(
940 self.cr.progress,
941 self.cr.name,
942 &func.name,
943 fuzz_config.timeout,
944 fuzz_config.runs,
945 );
946
947 let (failure_dir, failure_file) = test_failure_paths(
948 fuzz_config.failure_persist_dir.clone().unwrap(),
949 self.cr.name,
950 &func.name,
951 );
952
953 let mut executor = self.executor.into_owned();
954 executor.inspector_mut().collect_edge_coverage(fuzz_config.corpus.collect_edge_coverage());
957 let persisted_failure =
959 foundry_common::fs::read_json_file::<BaseCounterExample>(failure_file.as_path()).ok();
960 let mut fuzzed_executor =
962 FuzzedExecutor::new(executor, runner, self.tcfg.sender, fuzz_config, persisted_failure);
963 let result = match fuzzed_executor.fuzz(
964 func,
965 &self.setup.fuzz_fixtures,
966 &self.setup.deployed_libs,
967 self.address,
968 &self.cr.mcr.revert_decoder,
969 progress.as_ref(),
970 &self.tcfg.fail_fast,
971 ) {
972 Ok(x) => x,
973 Err(e) => {
974 self.result.fuzz_setup_fail(e);
975 return self.result;
976 }
977 };
978
979 if let Some(CounterExample::Single(counterexample)) = &result.counterexample {
981 if let Err(err) = foundry_common::fs::create_dir_all(failure_dir) {
982 error!(%err, "Failed to create fuzz failure dir");
983 } else if let Err(err) =
984 foundry_common::fs::write_json_file(failure_file.as_path(), counterexample)
985 {
986 error!(%err, "Failed to record call sequence");
987 }
988 }
989
990 self.result.fuzz_result(result);
991 self.result
992 }
993
994 fn prepare_test(&mut self, func: &Function) -> Result<(), ()> {
1004 let address = self.setup.address;
1005
1006 if self.cr.contract.abi.functions().filter(|func| func.name.is_before_test_setup()).count()
1008 == 1
1009 {
1010 for calldata in self.executor.call_sol_default(
1011 address,
1012 &ITest::beforeTestSetupCall { testSelector: func.selector() },
1013 ) {
1014 match self.executor.to_mut().transact_raw(
1016 self.tcfg.sender,
1017 address,
1018 calldata,
1019 U256::ZERO,
1020 ) {
1021 Ok(call_result) => {
1022 let reverted = call_result.reverted;
1023
1024 self.result.extend(call_result);
1026
1027 if reverted {
1029 self.result.single_fail(None);
1030 return Err(());
1031 }
1032 }
1033 Err(_) => {
1034 self.result.single_fail(None);
1035 return Err(());
1036 }
1037 }
1038 }
1039 }
1040 Ok(())
1041 }
1042
1043 fn fuzz_runner(&self) -> TestRunner {
1044 let config = &self.config.fuzz;
1045 fuzzer_with_cases(config.seed, config.runs, config.max_test_rejects)
1046 }
1047
1048 fn invariant_runner(&self) -> TestRunner {
1049 let config = &self.config.invariant;
1050 fuzzer_with_cases(self.config.fuzz.seed, config.runs, config.max_assume_rejects)
1051 }
1052
1053 fn clone_executor(&self) -> Executor {
1054 self.executor.clone().into_owned()
1055 }
1056}
1057
1058fn fuzzer_with_cases(seed: Option<U256>, cases: u32, max_global_rejects: u32) -> TestRunner {
1059 let config = proptest::test_runner::Config {
1060 cases,
1061 max_global_rejects,
1062 max_shrink_iters: 0,
1065 ..Default::default()
1066 };
1067
1068 if let Some(seed) = seed {
1069 trace!(target: "forge::test", %seed, "building deterministic fuzzer");
1070 let rng = TestRng::from_seed(RngAlgorithm::ChaCha, &seed.to_be_bytes::<32>());
1071 TestRunner::new_with_rng(config, rng)
1072 } else {
1073 trace!(target: "forge::test", "building stochastic fuzzer");
1074 TestRunner::new(config)
1075 }
1076}
1077
1078#[derive(Serialize, Deserialize)]
1080struct InvariantPersistedFailure {
1081 call_sequence: Vec<BaseCounterExample>,
1083 #[serde(skip_serializing_if = "Option::is_none")]
1085 driver_bytecode: Option<Bytes>,
1086}
1087
1088fn persisted_call_sequence(path: &Path, bytecode: &Bytes) -> Option<Vec<BaseCounterExample>> {
1091 foundry_common::fs::read_json_file::<InvariantPersistedFailure>(path).ok().and_then(
1092 |persisted_failure| {
1093 if let Some(persisted_bytecode) = &persisted_failure.driver_bytecode {
1094 if !bytecode.eq(persisted_bytecode) {
1096 let _= sh_warn!("\
1097 Failure from {:?} file was ignored because test contract bytecode has changed.",
1098 path
1099 );
1100 return None;
1101 }
1102 };
1103 Some(persisted_failure.call_sequence)
1104 },
1105 )
1106}
1107
1108fn test_failure_paths(
1110 persist_dir: PathBuf,
1111 contract_name: &str,
1112 invariant_name: &str,
1113) -> (PathBuf, PathBuf) {
1114 let dir = persist_dir.join("failures").join(contract_name.split(':').next_back().unwrap());
1115 let dir = canonicalized(dir);
1116 let file = canonicalized(dir.join(invariant_name));
1117 (dir, file)
1118}