1use crate::{
4 MultiContractRunner, TestFilter,
5 fuzz::{BaseCounterExample, invariant::BasicTxDetails},
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, InvariantConfig};
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 CounterExample, FuzzFixtures, fixture_name,
29 invariant::{CallDetails, InvariantContract},
30 },
31 traces::{TraceKind, TraceMode, load_contracts},
32};
33use itertools::Itertools;
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 let nonce = self.executor.get_nonce(self.sender)?;
156 let address = self.sender.create(nonce);
157 result.address = address;
158 self.executor.backend_mut().set_test_contract(address);
162 self.executor.set_balance(address, self.initial_balance())?;
166
167 let deploy_result = self.executor.deploy(
169 self.sender,
170 self.contract.bytecode.clone(),
171 U256::ZERO,
172 Some(&self.mcr.revert_decoder),
173 );
174
175 result.deployment_failure = deploy_result.is_err();
176
177 if let Ok(dr) = &deploy_result {
178 debug_assert_eq!(dr.address, address);
179 }
180 let (raw, reason) = RawCallResult::from_evm_result(deploy_result.map(Into::into))?;
181 result.extend(raw, TraceKind::Deployment);
182 if reason.is_some() {
183 result.reason = reason;
184 return Ok(result);
185 }
186
187 self.executor.set_balance(self.sender, self.initial_balance())?;
189 self.executor.set_balance(CALLER, self.initial_balance())?;
190 self.executor.set_balance(LIBRARY_DEPLOYER, self.initial_balance())?;
191
192 self.executor.deploy_create2_deployer()?;
193
194 if let Some(cheatcodes) = &mut self.executor.inspector_mut().cheatcodes {
195 debug!("test contract deployed");
196 cheatcodes.strategy.runner.base_contract_deployed(cheatcodes.strategy.context.as_mut());
197 }
198
199 if call_setup {
201 trace!("calling setUp");
202 let res = self.executor.setup(None, address, Some(&self.mcr.revert_decoder));
203 let (raw, reason) = RawCallResult::from_evm_result(res)?;
204 result.extend(raw, TraceKind::Setup);
205 result.reason = reason;
206 }
207
208 result.fuzz_fixtures = self.fuzz_fixtures(address);
209
210 Ok(result)
211 }
212
213 fn initial_balance(&self) -> U256 {
214 self.evm_opts.initial_balance
215 }
216
217 fn apply_contract_inline_config(&mut self) -> Result<()> {
219 if self.inline_config.contains_contract(self.name) {
220 let new_config = Arc::new(self.inline_config(None)?);
221 self.tcfg.to_mut().reconfigure_with(new_config);
222 let prev_tracer = self.executor.inspector_mut().tracer.take();
223 self.tcfg.configure_executor(&mut self.executor);
224 self.executor.inspector_mut().tracer = prev_tracer;
226 }
227 Ok(())
228 }
229
230 fn inline_config(&self, func: Option<&Function>) -> Result<Config> {
232 let function = func.map(|f| f.name.as_str()).unwrap_or("");
233 let config =
234 self.mcr.inline_config.merge(self.name, function, &self.config).extract::<Config>()?;
235 Ok(config)
236 }
237
238 fn fuzz_fixtures(&mut self, address: Address) -> FuzzFixtures {
254 let mut fixtures = HashMap::default();
255 let fixture_functions = self.contract.abi.functions().filter(|func| func.is_fixture());
256 for func in fixture_functions {
257 if func.inputs.is_empty() {
258 if let Ok(CallResult { raw: _, decoded_result }) =
260 self.executor.call(CALLER, address, func, &[], U256::ZERO, None)
261 {
262 fixtures.insert(fixture_name(func.name.clone()), decoded_result);
263 }
264 } else {
265 let mut vals = Vec::new();
268 let mut index = 0;
269 loop {
270 if let Ok(CallResult { raw: _, decoded_result }) = self.executor.call(
271 CALLER,
272 address,
273 func,
274 &[DynSolValue::Uint(U256::from(index), 256)],
275 U256::ZERO,
276 None,
277 ) {
278 vals.push(decoded_result);
279 } else {
280 break;
283 }
284 index += 1;
285 }
286 fixtures.insert(fixture_name(func.name.clone()), DynSolValue::Array(vals));
287 };
288 }
289 FuzzFixtures::new(fixtures)
290 }
291
292 pub fn run_tests(mut self, filter: &dyn TestFilter) -> SuiteResult {
294 let start = Instant::now();
295 let mut warnings = Vec::new();
296
297 let setup_fns: Vec<_> =
299 self.contract.abi.functions().filter(|func| func.name.is_setup()).collect();
300 let call_setup = setup_fns.len() == 1 && setup_fns[0].name == "setUp";
301 for &setup_fn in &setup_fns {
303 if setup_fn.name != "setUp" {
304 warnings.push(format!(
305 "Found invalid setup function \"{}\" did you mean \"setUp()\"?",
306 setup_fn.signature()
307 ));
308 }
309 }
310
311 if setup_fns.len() > 1 {
313 return SuiteResult::new(
314 start.elapsed(),
315 [("setUp()".to_string(), TestResult::fail("multiple setUp functions".to_string()))]
316 .into(),
317 warnings,
318 );
319 }
320
321 let after_invariant_fns: Vec<_> =
323 self.contract.abi.functions().filter(|func| func.name.is_after_invariant()).collect();
324 if after_invariant_fns.len() > 1 {
325 return SuiteResult::new(
327 start.elapsed(),
328 [(
329 "afterInvariant()".to_string(),
330 TestResult::fail("multiple afterInvariant functions".to_string()),
331 )]
332 .into(),
333 warnings,
334 );
335 }
336 let call_after_invariant = after_invariant_fns.first().is_some_and(|after_invariant_fn| {
337 let match_sig = after_invariant_fn.name == "afterInvariant";
338 if !match_sig {
339 warnings.push(format!(
340 "Found invalid afterInvariant function \"{}\" did you mean \"afterInvariant()\"?",
341 after_invariant_fn.signature()
342 ));
343 }
344 match_sig
345 });
346
347 let has_invariants = self.contract.abi.functions().any(|func| func.is_invariant_test());
350
351 let prev_tracer = self.executor.inspector_mut().tracer.take();
352 if prev_tracer.is_some() || has_invariants {
353 self.executor.set_tracing(TraceMode::Call);
354 }
355
356 let setup_time = Instant::now();
357 let setup = self.setup(call_setup);
358 debug!("finished setting up in {:?}", setup_time.elapsed());
359
360 self.executor.inspector_mut().tracer = prev_tracer;
361
362 if setup.reason.is_some() {
363 let fail_msg = if !setup.deployment_failure {
365 "setUp()".to_string()
366 } else {
367 "constructor()".to_string()
368 };
369 return SuiteResult::new(
370 start.elapsed(),
371 [(fail_msg, TestResult::setup_result(setup))].into(),
372 warnings,
373 );
374 }
375
376 let find_timer = Instant::now();
379 let functions = self
380 .contract
381 .abi
382 .functions()
383 .filter(|func| is_matching_test(func, filter))
384 .collect::<Vec<_>>();
385 debug!(
386 "Found {} test functions out of {} in {:?}",
387 functions.len(),
388 self.contract.abi.functions().count(),
389 find_timer.elapsed(),
390 );
391
392 let identified_contracts = has_invariants.then(|| {
393 load_contracts(setup.traces.iter().map(|(_, t)| &t.arena), &self.mcr.known_contracts)
394 });
395
396 let test_fail_instances = functions
397 .iter()
398 .filter_map(|func| {
399 TestFunctionKind::classify(&func.name, !func.inputs.is_empty())
400 .is_any_test_fail()
401 .then_some(func.name.clone())
402 })
403 .collect::<Vec<_>>();
404
405 if !test_fail_instances.is_empty() {
406 let instances = format!(
407 "Found {} instances: {}",
408 test_fail_instances.len(),
409 test_fail_instances.join(", ")
410 );
411 let fail = TestResult::fail("`testFail*` has been removed. Consider changing to test_Revert[If|When]_Condition and expecting a revert".to_string());
412 return SuiteResult::new(start.elapsed(), [(instances, fail)].into(), warnings);
413 }
414 let f = |func: &Function| {
415 let f = || {
416 let start = Instant::now();
417
418 let _guard = self.tokio_handle.enter();
419
420 let _guard;
421 let current_span = tracing::Span::current();
422 if current_span.is_none() || current_span.id() != self.span.id() {
423 _guard = self.span.enter();
424 }
425
426 let sig = func.signature();
427 let kind = func.test_function_kind();
428
429 let _guard = debug_span!(
430 "test",
431 %kind,
432 name = %if enabled!(tracing::Level::TRACE) { &sig } else { &func.name },
433 )
434 .entered();
435
436 let mut res = FunctionRunner::new(&self, &setup).run(
437 func,
438 kind,
439 call_after_invariant,
440 identified_contracts.as_ref(),
441 );
442 res.duration = start.elapsed();
443
444 (sig, res)
445 };
446 f()
447 };
448
449 let test_results = functions.into_par_iter().map(f).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 match kind {
522 TestFunctionKind::UnitTest { .. } => self.run_unit_test(func),
523 TestFunctionKind::FuzzTest { .. } => self.run_fuzz_test(func),
524 TestFunctionKind::TableTest => self.run_table_test(func),
525 TestFunctionKind::InvariantTest => {
526 let test_bytecode = &self.cr.contract.bytecode;
527 self.run_invariant_test(
528 func,
529 call_after_invariant,
530 identified_contracts.unwrap(),
531 test_bytecode,
532 )
533 }
534 _ => unreachable!(),
535 }
536 }
537
538 fn run_unit_test(mut self, func: &Function) -> TestResult {
547 self.executor.strategy.runner.start_transaction(self.executor.strategy.context.as_ref());
549 if self.prepare_test(func).is_err() {
550 self.executor
551 .strategy
552 .runner
553 .rollback_transaction(self.executor.strategy.context.as_ref());
554 return self.result;
555 }
556
557 let (mut raw_call_result, reason) = match self.executor.call(
559 self.sender,
560 self.address,
561 func,
562 &[],
563 U256::ZERO,
564 Some(self.revert_decoder()),
565 ) {
566 Ok(res) => (res.raw, None),
567 Err(EvmError::Execution(err)) => (err.raw, Some(err.reason)),
568 Err(EvmError::Skip(reason)) => {
569 self.result.single_skip(reason);
570 self.executor
571 .strategy
572 .runner
573 .rollback_transaction(self.executor.strategy.context.as_ref());
574 return self.result;
575 }
576 Err(err) => {
577 self.result.single_fail(Some(err.to_string()));
578 self.executor
579 .strategy
580 .runner
581 .rollback_transaction(self.executor.strategy.context.as_ref());
582 return self.result;
583 }
584 };
585
586 let success =
587 self.executor.is_raw_call_mut_success(self.address, &mut raw_call_result, false);
588 self.result.single_result(success, reason, raw_call_result);
589 self.executor.strategy.runner.rollback_transaction(self.executor.strategy.context.as_ref());
590
591 self.result
592 }
593
594 fn run_table_test(mut self, func: &Function) -> TestResult {
603 if self.prepare_test(func).is_err() {
604 return self.result;
605 }
606
607 let Some(first_param) = func.inputs.first() else {
609 self.result.single_fail(Some("Table test should have at least one parameter".into()));
610 return self.result;
611 };
612
613 let Some(first_param_fixtures) =
614 &self.setup.fuzz_fixtures.param_fixtures(first_param.name())
615 else {
616 self.result.single_fail(Some("Table test should have fixtures defined".into()));
617 return self.result;
618 };
619
620 if first_param_fixtures.is_empty() {
621 self.result.single_fail(Some("Table test should have at least one fixture".into()));
622 return self.result;
623 }
624
625 let fixtures_len = first_param_fixtures.len();
626 let mut table_fixtures = vec![&first_param_fixtures[..]];
627
628 for param in &func.inputs[1..] {
630 let param_name = param.name();
631 let Some(fixtures) = &self.setup.fuzz_fixtures.param_fixtures(param.name()) else {
632 self.result.single_fail(Some(format!("No fixture defined for param {param_name}")));
633 return self.result;
634 };
635
636 if fixtures.len() != fixtures_len {
637 self.result.single_fail(Some(format!(
638 "{} fixtures defined for {param_name} (expected {})",
639 fixtures.len(),
640 fixtures_len
641 )));
642 return self.result;
643 }
644
645 table_fixtures.push(&fixtures[..]);
646 }
647
648 let progress = start_fuzz_progress(
649 self.cr.progress,
650 self.cr.name,
651 &func.name,
652 None,
653 fixtures_len as u32,
654 );
655
656 for i in 0..fixtures_len {
657 if let Some(progress) = progress.as_ref() {
659 progress.inc(1);
660 }
661
662 let args = table_fixtures.iter().map(|row| row[i].clone()).collect_vec();
663 self.executor
664 .strategy
665 .runner
666 .start_transaction(self.executor.strategy.context.as_ref());
667
668 let (mut raw_call_result, reason) = match self.executor.call(
669 self.sender,
670 self.address,
671 func,
672 &args,
673 U256::ZERO,
674 Some(self.revert_decoder()),
675 ) {
676 Ok(res) => (res.raw, None),
677 Err(EvmError::Execution(err)) => (err.raw, Some(err.reason)),
678 Err(EvmError::Skip(reason)) => {
679 self.result.single_skip(reason);
680 self.executor
681 .strategy
682 .runner
683 .rollback_transaction(self.executor.strategy.context.as_ref());
684 return self.result;
685 }
686 Err(err) => {
687 self.result.single_fail(Some(err.to_string()));
688 self.executor
689 .strategy
690 .runner
691 .rollback_transaction(self.executor.strategy.context.as_ref());
692 return self.result;
693 }
694 };
695 self.executor
696 .strategy
697 .runner
698 .rollback_transaction(self.executor.strategy.context.as_ref());
699
700 let is_success =
701 self.executor.is_raw_call_mut_success(self.address, &mut raw_call_result, false);
702 if !is_success {
704 self.result.counterexample =
705 Some(CounterExample::Single(BaseCounterExample::from_fuzz_call(
706 Bytes::from(func.abi_encode_input(&args).unwrap()),
707 args,
708 raw_call_result.traces.clone(),
709 )));
710 self.result.single_result(false, reason, raw_call_result);
711 return self.result;
712 }
713
714 if i == fixtures_len - 1 {
717 self.result.single_result(true, None, raw_call_result);
718 return self.result;
719 }
720 }
721
722 self.result
723 }
724
725 fn run_invariant_test(
726 mut self,
727 func: &Function,
728 call_after_invariant: bool,
729 identified_contracts: &ContractsByAddress,
730 test_bytecode: &Bytes,
731 ) -> TestResult {
732 if let Err(EvmError::Skip(reason)) = self.executor.call(
734 self.sender,
735 self.address,
736 func,
737 &[],
738 U256::ZERO,
739 Some(self.revert_decoder()),
740 ) {
741 self.result.invariant_skip(reason);
742 return self.result;
743 };
744
745 let runner = self.invariant_runner();
746 let invariant_config = &self.config.invariant;
747
748 let mut executor = self.clone_executor();
749 executor.inspector_mut().collect_edge_coverage(
752 invariant_config.corpus_dir.is_some() || invariant_config.show_edge_coverage,
753 );
754
755 let mut evm = InvariantExecutor::new(
756 executor,
757 runner,
758 invariant_config.clone(),
759 identified_contracts,
760 &self.cr.mcr.known_contracts,
761 );
762 let invariant_contract = InvariantContract {
763 address: self.address,
764 invariant_function: func,
765 call_after_invariant,
766 abi: &self.cr.contract.abi,
767 };
768
769 let (failure_dir, failure_file) = invariant_failure_paths(
770 invariant_config,
771 self.cr.name,
772 &invariant_contract.invariant_function.name,
773 );
774 let show_solidity = invariant_config.show_solidity;
775
776 if let Some(mut call_sequence) =
778 persisted_call_sequence(failure_file.as_path(), test_bytecode)
779 {
780 let txes = call_sequence
782 .iter_mut()
783 .map(|seq| {
784 seq.show_solidity = show_solidity;
785 BasicTxDetails {
786 sender: seq.sender.unwrap_or_default(),
787 call_details: CallDetails {
788 target: seq.addr.unwrap_or_default(),
789 calldata: seq.calldata.clone(),
790 },
791 }
792 })
793 .collect::<Vec<BasicTxDetails>>();
794 if let Ok((success, replayed_entirely)) = check_sequence(
795 self.clone_executor(),
796 &txes,
797 (0..min(txes.len(), invariant_config.depth as usize)).collect(),
798 invariant_contract.address,
799 invariant_contract.invariant_function.selector().to_vec().into(),
800 invariant_config.fail_on_revert,
801 invariant_contract.call_after_invariant,
802 ) && !success
803 {
804 let _ = sh_warn!(
805 "\
806 Replayed invariant failure from {:?} file. \
807 Run `forge clean` or remove file to ignore failure and to continue invariant test campaign.",
808 failure_file.as_path()
809 );
810 let _ = replay_run(
813 &invariant_contract,
814 self.clone_executor(),
815 &self.cr.mcr.known_contracts,
816 identified_contracts.clone(),
817 &mut self.result.logs,
818 &mut self.result.traces,
819 &mut self.result.line_coverage,
820 &mut self.result.deprecated_cheatcodes,
821 &txes,
822 show_solidity,
823 );
824 self.result.invariant_replay_fail(
825 replayed_entirely,
826 &invariant_contract.invariant_function.name,
827 call_sequence,
828 );
829 return self.result;
830 }
831 }
832
833 let progress = start_fuzz_progress(
834 self.cr.progress,
835 self.cr.name,
836 &func.name,
837 invariant_config.timeout,
838 invariant_config.runs,
839 );
840 let invariant_result = match evm.invariant_fuzz(
841 invariant_contract.clone(),
842 &self.setup.fuzz_fixtures,
843 &self.setup.deployed_libs,
844 progress.as_ref(),
845 ) {
846 Ok(x) => x,
847 Err(e) => {
848 self.result.invariant_setup_fail(e);
849 return self.result;
850 }
851 };
852 self.result.merge_coverages(invariant_result.line_coverage);
854
855 let mut counterexample = None;
856 let success = invariant_result.error.is_none();
857 let reason = invariant_result.error.as_ref().and_then(|err| err.revert_reason());
858
859 match invariant_result.error {
860 Some(error) => match error {
862 InvariantFuzzError::BrokenInvariant(case_data)
863 | InvariantFuzzError::Revert(case_data) => {
864 match replay_error(
867 &case_data,
868 &invariant_contract,
869 self.clone_executor(),
870 &self.cr.mcr.known_contracts,
871 identified_contracts.clone(),
872 &mut self.result.logs,
873 &mut self.result.traces,
874 &mut self.result.line_coverage,
875 &mut self.result.deprecated_cheatcodes,
876 progress.as_ref(),
877 show_solidity,
878 ) {
879 Ok(call_sequence) => {
880 if !call_sequence.is_empty() {
881 if let Err(err) = foundry_common::fs::create_dir_all(failure_dir) {
883 error!(%err, "Failed to create invariant failure dir");
884 } else if let Err(err) = foundry_common::fs::write_json_file(
885 failure_file.as_path(),
886 &InvariantPersistedFailure {
887 call_sequence: call_sequence.clone(),
888 driver_bytecode: Some(test_bytecode.clone()),
889 },
890 ) {
891 error!(%err, "Failed to record call sequence");
892 }
893
894 let original_seq_len =
895 if let TestError::Fail(_, calls) = &case_data.test_error {
896 calls.len()
897 } else {
898 call_sequence.len()
899 };
900
901 counterexample =
902 Some(CounterExample::Sequence(original_seq_len, call_sequence))
903 }
904 }
905 Err(err) => {
906 error!(%err, "Failed to replay invariant error");
907 }
908 };
909 }
910 InvariantFuzzError::MaxAssumeRejects(_) => {}
911 },
912
913 _ => {
916 if let Err(err) = replay_run(
917 &invariant_contract,
918 self.clone_executor(),
919 &self.cr.mcr.known_contracts,
920 identified_contracts.clone(),
921 &mut self.result.logs,
922 &mut self.result.traces,
923 &mut self.result.line_coverage,
924 &mut self.result.deprecated_cheatcodes,
925 &invariant_result.last_run_inputs,
926 show_solidity,
927 ) {
928 error!(%err, "Failed to replay last invariant run");
929 }
930 }
931 }
932
933 self.result.invariant_result(
934 invariant_result.gas_report_traces,
935 success,
936 reason,
937 counterexample,
938 invariant_result.cases,
939 invariant_result.reverts,
940 invariant_result.metrics,
941 invariant_result.failed_corpus_replays,
942 );
943 self.result
944 }
945
946 fn run_fuzz_test(mut self, func: &Function) -> TestResult {
956 let binding = self.executor.clone().into_owned();
957 self.executor = Cow::Owned(binding);
958 if self.prepare_test(func).is_err() {
960 return self.result;
961 }
962
963 let runner = self.fuzz_runner();
964 let fuzz_config = self.config.fuzz.clone();
965
966 let progress = start_fuzz_progress(
967 self.cr.progress,
968 self.cr.name,
969 &func.name,
970 fuzz_config.timeout,
971 fuzz_config.runs,
972 );
973
974 let fuzzed_executor =
976 FuzzedExecutor::new(self.executor.into_owned(), runner, self.tcfg.sender, fuzz_config);
977 let result = fuzzed_executor.fuzz(
978 func,
979 &self.setup.fuzz_fixtures,
980 &self.setup.deployed_libs,
981 self.address,
982 &self.cr.mcr.revert_decoder,
983 progress.as_ref(),
984 );
985 self.result.fuzz_result(result);
986
987 self.result
988 }
989
990 fn prepare_test(&mut self, func: &Function) -> Result<(), ()> {
1000 let address = self.setup.address;
1001
1002 if self.cr.contract.abi.functions().filter(|func| func.name.is_before_test_setup()).count()
1004 == 1
1005 {
1006 for calldata in self.executor.call_sol_default(
1007 address,
1008 &ITest::beforeTestSetupCall { testSelector: func.selector() },
1009 ) {
1010 match self.executor.to_mut().transact_raw(
1012 self.tcfg.sender,
1013 address,
1014 calldata,
1015 U256::ZERO,
1016 ) {
1017 Ok(call_result) => {
1018 let reverted = call_result.reverted;
1019
1020 self.result.extend(call_result);
1022
1023 if reverted {
1025 self.result.single_fail(None);
1026 return Err(());
1027 }
1028 }
1029 Err(_) => {
1030 self.result.single_fail(None);
1031 return Err(());
1032 }
1033 }
1034 }
1035 }
1036 Ok(())
1037 }
1038
1039 fn fuzz_runner(&self) -> TestRunner {
1040 let config = &self.config.fuzz;
1041 let failure_persist_path = config
1042 .failure_persist_dir
1043 .as_ref()
1044 .unwrap()
1045 .join(config.failure_persist_file.as_ref().unwrap())
1046 .into_os_string()
1047 .into_string()
1048 .unwrap();
1049 fuzzer_with_cases(
1050 config.seed,
1051 config.runs,
1052 config.max_test_rejects,
1053 Some(Box::new(FileFailurePersistence::Direct(failure_persist_path.leak()))),
1054 )
1055 }
1056
1057 fn invariant_runner(&self) -> TestRunner {
1058 let config = &self.config.invariant;
1059 fuzzer_with_cases(self.config.fuzz.seed, config.runs, config.max_assume_rejects, None)
1060 }
1061
1062 fn clone_executor(&self) -> Executor {
1063 self.executor.clone().into_owned()
1064 }
1065}
1066
1067fn fuzzer_with_cases(
1068 seed: Option<U256>,
1069 cases: u32,
1070 max_global_rejects: u32,
1071 file_failure_persistence: Option<Box<dyn FailurePersistence>>,
1072) -> TestRunner {
1073 let config = proptest::test_runner::Config {
1074 failure_persistence: file_failure_persistence,
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 invariant_failure_paths(
1125 config: &InvariantConfig,
1126 contract_name: &str,
1127 invariant_name: &str,
1128) -> (PathBuf, PathBuf) {
1129 let dir = config
1130 .failure_persist_dir
1131 .clone()
1132 .unwrap()
1133 .join("failures")
1134 .join(contract_name.split(':').next_back().unwrap());
1135 let dir = canonicalized(dir);
1136 let file = canonicalized(dir.join(invariant_name));
1137 (dir, file)
1138}