1use crate::{
4 fuzz::{invariant::BasicTxDetails, BaseCounterExample},
5 multi_runner::{is_matching_test, TestContract, TestRunnerConfig},
6 progress::{start_fuzz_progress, TestsProgress},
7 result::{SuiteResult, TestResult, TestSetup},
8 MultiContractRunner, TestFilter,
9};
10use alloy_dyn_abi::DynSolValue;
11use alloy_json_abi::Function;
12use alloy_primitives::{address, map::HashMap, Address, Bytes, U256};
13use eyre::Result;
14use foundry_common::{contracts::ContractsByAddress, TestFunctionExt, TestFunctionKind};
15use foundry_compilers::utils::canonicalized;
16use foundry_config::{Config, InvariantConfig};
17use foundry_evm::{
18 constants::CALLER,
19 decode::RevertDecoder,
20 executors::{
21 fuzz::FuzzedExecutor,
22 invariant::{
23 check_sequence, replay_error, replay_run, InvariantExecutor, InvariantFuzzError,
24 },
25 CallResult, EvmError, Executor, ITest, RawCallResult,
26 },
27 fuzz::{
28 fixture_name,
29 invariant::{CallDetails, InvariantContract},
30 CounterExample, FuzzFixtures,
31 },
32 traces::{load_contracts, TraceKind, TraceMode},
33};
34use proptest::test_runner::{
35 FailurePersistence, FileFailurePersistence, RngAlgorithm, TestError, TestRng, TestRunner,
36};
37use rayon::prelude::*;
38use serde::{Deserialize, Serialize};
39use std::{
40 borrow::Cow,
41 cmp::min,
42 collections::BTreeMap,
43 path::{Path, PathBuf},
44 sync::Arc,
45 time::Instant,
46};
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 result.reason = reason;
152 return Ok(result);
153 }
154 }
155
156 let address = self.sender.create(self.executor.get_nonce(self.sender)?);
157 result.address = address;
158
159 self.executor.set_balance(address, self.initial_balance())?;
162
163 let deploy_result = self.executor.deploy(
165 self.sender,
166 self.contract.bytecode.clone(),
167 U256::ZERO,
168 Some(&self.mcr.revert_decoder),
169 );
170
171 result.deployment_failure = deploy_result.is_err();
172
173 if let Ok(dr) = &deploy_result {
174 debug_assert_eq!(dr.address, address);
175 }
176 let (raw, reason) = RawCallResult::from_evm_result(deploy_result.map(Into::into))?;
177 result.extend(raw, TraceKind::Deployment);
178 if reason.is_some() {
179 result.reason = reason;
180 return Ok(result);
181 }
182
183 self.executor.set_balance(self.sender, self.initial_balance())?;
185 self.executor.set_balance(CALLER, self.initial_balance())?;
186 self.executor.set_balance(LIBRARY_DEPLOYER, self.initial_balance())?;
187
188 self.executor.deploy_create2_deployer()?;
189
190 if call_setup {
192 trace!("calling setUp");
193 let res = self.executor.setup(None, address, Some(&self.mcr.revert_decoder));
194 let (raw, reason) = RawCallResult::from_evm_result(res)?;
195 result.extend(raw, TraceKind::Setup);
196 result.reason = reason;
197 }
198
199 result.fuzz_fixtures = self.fuzz_fixtures(address);
200
201 Ok(result)
202 }
203
204 fn initial_balance(&self) -> U256 {
205 self.evm_opts.initial_balance
206 }
207
208 fn apply_contract_inline_config(&mut self) -> Result<()> {
210 if self.inline_config.contains_contract(self.name) {
211 let new_config = Arc::new(self.inline_config(None)?);
212 self.tcfg.to_mut().reconfigure_with(new_config);
213 let prev_tracer = self.executor.inspector_mut().tracer.take();
214 self.tcfg.configure_executor(&mut self.executor);
215 self.executor.inspector_mut().tracer = prev_tracer;
217 }
218 Ok(())
219 }
220
221 fn inline_config(&self, func: Option<&Function>) -> Result<Config> {
223 let function = func.map(|f| f.name.as_str()).unwrap_or("");
224 let config =
225 self.mcr.inline_config.merge(self.name, function, &self.config).extract::<Config>()?;
226 Ok(config)
227 }
228
229 fn fuzz_fixtures(&mut self, address: Address) -> FuzzFixtures {
245 let mut fixtures = HashMap::default();
246 let fixture_functions = self.contract.abi.functions().filter(|func| func.is_fixture());
247 for func in fixture_functions {
248 if func.inputs.is_empty() {
249 if let Ok(CallResult { raw: _, decoded_result }) =
251 self.executor.call(CALLER, address, func, &[], U256::ZERO, None)
252 {
253 fixtures.insert(fixture_name(func.name.clone()), decoded_result);
254 }
255 } else {
256 let mut vals = Vec::new();
259 let mut index = 0;
260 loop {
261 if let Ok(CallResult { raw: _, decoded_result }) = self.executor.call(
262 CALLER,
263 address,
264 func,
265 &[DynSolValue::Uint(U256::from(index), 256)],
266 U256::ZERO,
267 None,
268 ) {
269 vals.push(decoded_result);
270 } else {
271 break;
274 }
275 index += 1;
276 }
277 fixtures.insert(fixture_name(func.name.clone()), DynSolValue::Array(vals));
278 };
279 }
280 FuzzFixtures::new(fixtures)
281 }
282
283 pub fn run_tests(mut self, filter: &dyn TestFilter) -> SuiteResult {
285 let start = Instant::now();
286 let mut warnings = Vec::new();
287
288 let setup_fns: Vec<_> =
290 self.contract.abi.functions().filter(|func| func.name.is_setup()).collect();
291 let call_setup = setup_fns.len() == 1 && setup_fns[0].name == "setUp";
292 for &setup_fn in &setup_fns {
294 if setup_fn.name != "setUp" {
295 warnings.push(format!(
296 "Found invalid setup function \"{}\" did you mean \"setUp()\"?",
297 setup_fn.signature()
298 ));
299 }
300 }
301
302 if setup_fns.len() > 1 {
304 return SuiteResult::new(
305 start.elapsed(),
306 [("setUp()".to_string(), TestResult::fail("multiple setUp functions".to_string()))]
307 .into(),
308 warnings,
309 )
310 }
311
312 let after_invariant_fns: Vec<_> =
314 self.contract.abi.functions().filter(|func| func.name.is_after_invariant()).collect();
315 if after_invariant_fns.len() > 1 {
316 return SuiteResult::new(
318 start.elapsed(),
319 [(
320 "afterInvariant()".to_string(),
321 TestResult::fail("multiple afterInvariant functions".to_string()),
322 )]
323 .into(),
324 warnings,
325 )
326 }
327 let call_after_invariant = after_invariant_fns.first().is_some_and(|after_invariant_fn| {
328 let match_sig = after_invariant_fn.name == "afterInvariant";
329 if !match_sig {
330 warnings.push(format!(
331 "Found invalid afterInvariant function \"{}\" did you mean \"afterInvariant()\"?",
332 after_invariant_fn.signature()
333 ));
334 }
335 match_sig
336 });
337
338 let has_invariants = self.contract.abi.functions().any(|func| func.is_invariant_test());
341
342 let prev_tracer = self.executor.inspector_mut().tracer.take();
343 if prev_tracer.is_some() || has_invariants {
344 self.executor.set_tracing(TraceMode::Call);
345 }
346
347 let setup_time = Instant::now();
348 let setup = self.setup(call_setup);
349 debug!("finished setting up in {:?}", setup_time.elapsed());
350
351 self.executor.inspector_mut().tracer = prev_tracer;
352
353 if setup.reason.is_some() {
354 let fail_msg = if !setup.deployment_failure {
356 "setUp()".to_string()
357 } else {
358 "constructor()".to_string()
359 };
360 return SuiteResult::new(
361 start.elapsed(),
362 [(fail_msg, TestResult::setup_result(setup))].into(),
363 warnings,
364 )
365 }
366
367 let find_timer = Instant::now();
370 let functions = self
371 .contract
372 .abi
373 .functions()
374 .filter(|func| is_matching_test(func, filter))
375 .collect::<Vec<_>>();
376 debug!(
377 "Found {} test functions out of {} in {:?}",
378 functions.len(),
379 self.contract.abi.functions().count(),
380 find_timer.elapsed(),
381 );
382
383 let identified_contracts = has_invariants.then(|| {
384 load_contracts(setup.traces.iter().map(|(_, t)| &t.arena), &self.mcr.known_contracts)
385 });
386
387 let test_fail_instances = functions
388 .iter()
389 .filter_map(|func| {
390 TestFunctionKind::classify(&func.name, !func.inputs.is_empty())
391 .is_any_test_fail()
392 .then_some(func.name.clone())
393 })
394 .collect::<Vec<_>>();
395
396 if !test_fail_instances.is_empty() {
397 let instances = format!(
398 "Found {} instances: {}",
399 test_fail_instances.len(),
400 test_fail_instances.join(", ")
401 );
402 let fail = TestResult::fail("`testFail*` has been removed. Consider changing to test_Revert[If|When]_Condition and expecting a revert".to_string());
403 return SuiteResult::new(start.elapsed(), [(instances, fail)].into(), warnings)
404 }
405
406 let test_results = functions
407 .par_iter()
408 .map(|&func| {
409 let start = Instant::now();
410
411 let _guard = self.tokio_handle.enter();
412
413 let _guard;
414 let current_span = tracing::Span::current();
415 if current_span.is_none() || current_span.id() != self.span.id() {
416 _guard = self.span.enter();
417 }
418
419 let sig = func.signature();
420 let kind = func.test_function_kind();
421
422 let _guard = debug_span!(
423 "test",
424 %kind,
425 name = %if enabled!(tracing::Level::TRACE) { &sig } else { &func.name },
426 )
427 .entered();
428
429 let mut res = FunctionRunner::new(&self, &setup).run(
430 func,
431 kind,
432 call_after_invariant,
433 identified_contracts.as_ref(),
434 );
435 res.duration = start.elapsed();
436
437 (sig, res)
438 })
439 .collect::<BTreeMap<_, _>>();
440
441 let duration = start.elapsed();
442 SuiteResult::new(duration, test_results, warnings)
443 }
444}
445
446struct FunctionRunner<'a> {
448 tcfg: Cow<'a, TestRunnerConfig>,
450 executor: Cow<'a, Executor>,
452 cr: &'a ContractRunner<'a>,
454 address: Address,
456 setup: &'a TestSetup,
458 result: TestResult,
460}
461
462impl<'a> std::ops::Deref for FunctionRunner<'a> {
463 type Target = Cow<'a, TestRunnerConfig>;
464
465 #[inline(always)]
466 fn deref(&self) -> &Self::Target {
467 &self.tcfg
468 }
469}
470
471impl<'a> FunctionRunner<'a> {
472 fn new(cr: &'a ContractRunner<'a>, setup: &'a TestSetup) -> Self {
473 Self {
474 tcfg: match &cr.tcfg {
475 Cow::Borrowed(tcfg) => Cow::Borrowed(tcfg),
476 Cow::Owned(tcfg) => Cow::Owned(tcfg.clone()),
477 },
478 executor: Cow::Borrowed(&cr.executor),
479 cr,
480 address: setup.address,
481 setup,
482 result: TestResult::new(setup),
483 }
484 }
485
486 fn revert_decoder(&self) -> &'a RevertDecoder {
487 &self.cr.mcr.revert_decoder
488 }
489
490 fn apply_function_inline_config(&mut self, func: &Function) -> Result<()> {
492 if self.inline_config.contains_function(self.cr.name, &func.name) {
493 let new_config = Arc::new(self.cr.inline_config(Some(func))?);
494 self.tcfg.to_mut().reconfigure_with(new_config);
495 self.tcfg.configure_executor(self.executor.to_mut());
496 }
497 Ok(())
498 }
499
500 fn run(
501 mut self,
502 func: &Function,
503 kind: TestFunctionKind,
504 call_after_invariant: bool,
505 identified_contracts: Option<&ContractsByAddress>,
506 ) -> TestResult {
507 if let Err(e) = self.apply_function_inline_config(func) {
508 self.result.single_fail(Some(e.to_string()));
509 return self.result;
510 }
511
512 match kind {
513 TestFunctionKind::UnitTest { .. } => self.run_unit_test(func),
514 TestFunctionKind::FuzzTest { .. } => self.run_fuzz_test(func),
515 TestFunctionKind::InvariantTest => {
516 let test_bytecode = &self.cr.contract.bytecode;
517 self.run_invariant_test(
518 func,
519 call_after_invariant,
520 identified_contracts.unwrap(),
521 test_bytecode,
522 )
523 }
524 _ => unreachable!(),
525 }
526 }
527
528 fn run_unit_test(mut self, func: &Function) -> TestResult {
537 if self.prepare_test(func).is_err() {
539 return self.result;
540 }
541
542 let (mut raw_call_result, reason) = match self.executor.call(
544 self.sender,
545 self.address,
546 func,
547 &[],
548 U256::ZERO,
549 Some(self.revert_decoder()),
550 ) {
551 Ok(res) => (res.raw, None),
552 Err(EvmError::Execution(err)) => (err.raw, Some(err.reason)),
553 Err(EvmError::Skip(reason)) => {
554 self.result.single_skip(reason);
555 return self.result;
556 }
557 Err(err) => {
558 self.result.single_fail(Some(err.to_string()));
559 return self.result;
560 }
561 };
562
563 let success =
564 self.executor.is_raw_call_mut_success(self.address, &mut raw_call_result, false);
565 self.result.single_result(success, reason, raw_call_result);
566 self.result
567 }
568
569 fn run_invariant_test(
570 mut self,
571 func: &Function,
572 call_after_invariant: bool,
573 identified_contracts: &ContractsByAddress,
574 test_bytecode: &Bytes,
575 ) -> TestResult {
576 if let Err(EvmError::Skip(reason)) = self.executor.call(
578 self.sender,
579 self.address,
580 func,
581 &[],
582 U256::ZERO,
583 Some(self.revert_decoder()),
584 ) {
585 self.result.invariant_skip(reason);
586 return self.result;
587 };
588
589 let runner = self.invariant_runner();
590 let invariant_config = &self.config.invariant;
591
592 let mut evm = InvariantExecutor::new(
593 self.clone_executor(),
594 runner,
595 invariant_config.clone(),
596 identified_contracts,
597 &self.cr.mcr.known_contracts,
598 );
599 let invariant_contract = InvariantContract {
600 address: self.address,
601 invariant_function: func,
602 call_after_invariant,
603 abi: &self.cr.contract.abi,
604 };
605
606 let (failure_dir, failure_file) = invariant_failure_paths(
607 invariant_config,
608 self.cr.name,
609 &invariant_contract.invariant_function.name,
610 );
611 let show_solidity = invariant_config.show_solidity;
612
613 if let Some(mut call_sequence) =
615 persisted_call_sequence(failure_file.as_path(), test_bytecode)
616 {
617 let txes = call_sequence
619 .iter_mut()
620 .map(|seq| {
621 seq.show_solidity = show_solidity;
622 BasicTxDetails {
623 sender: seq.sender.unwrap_or_default(),
624 call_details: CallDetails {
625 target: seq.addr.unwrap_or_default(),
626 calldata: seq.calldata.clone(),
627 },
628 }
629 })
630 .collect::<Vec<BasicTxDetails>>();
631 if let Ok((success, replayed_entirely)) = check_sequence(
632 self.clone_executor(),
633 &txes,
634 (0..min(txes.len(), invariant_config.depth as usize)).collect(),
635 invariant_contract.address,
636 invariant_contract.invariant_function.selector().to_vec().into(),
637 invariant_config.fail_on_revert,
638 invariant_contract.call_after_invariant,
639 ) {
640 if !success {
641 let _= sh_warn!("\
642 Replayed invariant failure from {:?} file. \
643 Run `forge clean` or remove file to ignore failure and to continue invariant test campaign.",
644 failure_file.as_path()
645 );
646 let _ = replay_run(
649 &invariant_contract,
650 self.clone_executor(),
651 &self.cr.mcr.known_contracts,
652 identified_contracts.clone(),
653 &mut self.result.logs,
654 &mut self.result.traces,
655 &mut self.result.coverage,
656 &mut self.result.deprecated_cheatcodes,
657 &txes,
658 show_solidity,
659 );
660 self.result.invariant_replay_fail(
661 replayed_entirely,
662 &invariant_contract.invariant_function.name,
663 call_sequence,
664 );
665 return self.result;
666 }
667 }
668 }
669
670 let progress =
671 start_fuzz_progress(self.cr.progress, self.cr.name, &func.name, invariant_config.runs);
672 let invariant_result = match evm.invariant_fuzz(
673 invariant_contract.clone(),
674 &self.setup.fuzz_fixtures,
675 &self.setup.deployed_libs,
676 progress.as_ref(),
677 ) {
678 Ok(x) => x,
679 Err(e) => {
680 self.result.invariant_setup_fail(e);
681 return self.result;
682 }
683 };
684 self.result.merge_coverages(invariant_result.coverage);
686
687 let mut counterexample = None;
688 let success = invariant_result.error.is_none();
689 let reason = invariant_result.error.as_ref().and_then(|err| err.revert_reason());
690
691 match invariant_result.error {
692 Some(error) => match error {
694 InvariantFuzzError::BrokenInvariant(case_data) |
695 InvariantFuzzError::Revert(case_data) => {
696 match replay_error(
699 &case_data,
700 &invariant_contract,
701 self.clone_executor(),
702 &self.cr.mcr.known_contracts,
703 identified_contracts.clone(),
704 &mut self.result.logs,
705 &mut self.result.traces,
706 &mut self.result.coverage,
707 &mut self.result.deprecated_cheatcodes,
708 progress.as_ref(),
709 show_solidity,
710 ) {
711 Ok(call_sequence) => {
712 if !call_sequence.is_empty() {
713 if let Err(err) = foundry_common::fs::create_dir_all(failure_dir) {
715 error!(%err, "Failed to create invariant failure dir");
716 } else if let Err(err) = foundry_common::fs::write_json_file(
717 failure_file.as_path(),
718 &InvariantPersistedFailure {
719 call_sequence: call_sequence.clone(),
720 driver_bytecode: Some(test_bytecode.clone()),
721 },
722 ) {
723 error!(%err, "Failed to record call sequence");
724 }
725
726 let original_seq_len =
727 if let TestError::Fail(_, calls) = &case_data.test_error {
728 calls.len()
729 } else {
730 call_sequence.len()
731 };
732
733 counterexample =
734 Some(CounterExample::Sequence(original_seq_len, call_sequence))
735 }
736 }
737 Err(err) => {
738 error!(%err, "Failed to replay invariant error");
739 }
740 };
741 }
742 InvariantFuzzError::MaxAssumeRejects(_) => {}
743 },
744
745 _ => {
748 if let Err(err) = replay_run(
749 &invariant_contract,
750 self.clone_executor(),
751 &self.cr.mcr.known_contracts,
752 identified_contracts.clone(),
753 &mut self.result.logs,
754 &mut self.result.traces,
755 &mut self.result.coverage,
756 &mut self.result.deprecated_cheatcodes,
757 &invariant_result.last_run_inputs,
758 show_solidity,
759 ) {
760 error!(%err, "Failed to replay last invariant run");
761 }
762 }
763 }
764
765 self.result.invariant_result(
766 invariant_result.gas_report_traces,
767 success,
768 reason,
769 counterexample,
770 invariant_result.cases,
771 invariant_result.reverts,
772 invariant_result.metrics,
773 );
774 self.result
775 }
776
777 fn run_fuzz_test(mut self, func: &Function) -> TestResult {
787 if self.prepare_test(func).is_err() {
789 return self.result;
790 }
791
792 let runner = self.fuzz_runner();
793 let fuzz_config = self.config.fuzz.clone();
794
795 let progress =
796 start_fuzz_progress(self.cr.progress, self.cr.name, &func.name, fuzz_config.runs);
797
798 let fuzzed_executor =
800 FuzzedExecutor::new(self.executor.into_owned(), runner, self.tcfg.sender, fuzz_config);
801 let result = fuzzed_executor.fuzz(
802 func,
803 &self.setup.fuzz_fixtures,
804 &self.setup.deployed_libs,
805 self.address,
806 &self.cr.mcr.revert_decoder,
807 progress.as_ref(),
808 );
809 self.result.fuzz_result(result);
810 self.result
811 }
812
813 fn prepare_test(&mut self, func: &Function) -> Result<(), ()> {
823 let address = self.setup.address;
824
825 if self.cr.contract.abi.functions().filter(|func| func.name.is_before_test_setup()).count() ==
827 1
828 {
829 for calldata in self
830 .executor
831 .call_sol_default(
832 address,
833 &ITest::beforeTestSetupCall { testSelector: func.selector() },
834 )
835 .beforeTestCalldata
836 {
837 match self.executor.to_mut().transact_raw(
839 self.tcfg.sender,
840 address,
841 calldata,
842 U256::ZERO,
843 ) {
844 Ok(call_result) => {
845 let reverted = call_result.reverted;
846
847 self.result.extend(call_result);
849
850 if reverted {
852 self.result.single_fail(None);
853 return Err(());
854 }
855 }
856 Err(_) => {
857 self.result.single_fail(None);
858 return Err(());
859 }
860 }
861 }
862 }
863 Ok(())
864 }
865
866 fn fuzz_runner(&self) -> TestRunner {
867 let config = &self.config.fuzz;
868 let failure_persist_path = config
869 .failure_persist_dir
870 .as_ref()
871 .unwrap()
872 .join(config.failure_persist_file.as_ref().unwrap())
873 .into_os_string()
874 .into_string()
875 .unwrap();
876 fuzzer_with_cases(
877 config.seed,
878 config.runs,
879 config.max_test_rejects,
880 Some(Box::new(FileFailurePersistence::Direct(failure_persist_path.leak()))),
881 )
882 }
883
884 fn invariant_runner(&self) -> TestRunner {
885 let config = &self.config.invariant;
886 fuzzer_with_cases(self.config.fuzz.seed, config.runs, config.max_assume_rejects, None)
887 }
888
889 fn clone_executor(&self) -> Executor {
890 self.executor.clone().into_owned()
891 }
892}
893
894fn fuzzer_with_cases(
895 seed: Option<U256>,
896 cases: u32,
897 max_global_rejects: u32,
898 file_failure_persistence: Option<Box<dyn FailurePersistence>>,
899) -> TestRunner {
900 let config = proptest::test_runner::Config {
901 failure_persistence: file_failure_persistence,
902 cases,
903 max_global_rejects,
904 max_shrink_iters: 0,
907 ..Default::default()
908 };
909
910 if let Some(seed) = seed {
911 trace!(target: "forge::test", %seed, "building deterministic fuzzer");
912 let rng = TestRng::from_seed(RngAlgorithm::ChaCha, &seed.to_be_bytes::<32>());
913 TestRunner::new_with_rng(config, rng)
914 } else {
915 trace!(target: "forge::test", "building stochastic fuzzer");
916 TestRunner::new(config)
917 }
918}
919
920#[derive(Serialize, Deserialize)]
922struct InvariantPersistedFailure {
923 call_sequence: Vec<BaseCounterExample>,
925 #[serde(skip_serializing_if = "Option::is_none")]
927 driver_bytecode: Option<Bytes>,
928}
929
930fn persisted_call_sequence(path: &Path, bytecode: &Bytes) -> Option<Vec<BaseCounterExample>> {
933 foundry_common::fs::read_json_file::<InvariantPersistedFailure>(path).ok().and_then(
934 |persisted_failure| {
935 if let Some(persisted_bytecode) = &persisted_failure.driver_bytecode {
936 if !bytecode.eq(persisted_bytecode) {
938 let _= sh_warn!("\
939 Failure from {:?} file was ignored because test contract bytecode has changed.",
940 path
941 );
942 return None;
943 }
944 };
945 Some(persisted_failure.call_sequence)
946 },
947 )
948}
949
950fn invariant_failure_paths(
952 config: &InvariantConfig,
953 contract_name: &str,
954 invariant_name: &str,
955) -> (PathBuf, PathBuf) {
956 let dir = config
957 .failure_persist_dir
958 .clone()
959 .unwrap()
960 .join("failures")
961 .join(contract_name.split(':').next_back().unwrap());
962 let dir = canonicalized(dir);
963 let file = canonicalized(dir.join(invariant_name));
964 (dir, file)
965}