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, JsonAbiExt};
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 itertools::Itertools;
35use proptest::test_runner::{
36 FailurePersistence, FileFailurePersistence, RngAlgorithm, TestError, TestRng, TestRunner,
37};
38use rayon::prelude::*;
39use serde::{Deserialize, Serialize};
40use std::{
41 borrow::Cow,
42 cmp::min,
43 collections::BTreeMap,
44 path::{Path, PathBuf},
45 sync::Arc,
46 time::Instant,
47};
48use tracing::Span;
49
50pub const LIBRARY_DEPLOYER: Address = address!("0x1F95D37F27EA0dEA9C252FC09D5A6eaA97647353");
56
57pub struct ContractRunner<'a> {
59 name: &'a str,
61 contract: &'a TestContract,
63 executor: Executor,
65 progress: Option<&'a TestsProgress>,
67 tokio_handle: &'a tokio::runtime::Handle,
69 span: tracing::Span,
71 tcfg: Cow<'a, TestRunnerConfig>,
73 mcr: &'a MultiContractRunner,
75}
76
77impl<'a> std::ops::Deref for ContractRunner<'a> {
78 type Target = Cow<'a, TestRunnerConfig>;
79
80 #[inline(always)]
81 fn deref(&self) -> &Self::Target {
82 &self.tcfg
83 }
84}
85
86impl<'a> ContractRunner<'a> {
87 pub fn new(
88 name: &'a str,
89 contract: &'a TestContract,
90 executor: Executor,
91 progress: Option<&'a TestsProgress>,
92 tokio_handle: &'a tokio::runtime::Handle,
93 span: Span,
94 mcr: &'a MultiContractRunner,
95 ) -> Self {
96 Self {
97 name,
98 contract,
99 executor,
100 progress,
101 tokio_handle,
102 span,
103 tcfg: Cow::Borrowed(&mcr.tcfg),
104 mcr,
105 }
106 }
107
108 pub fn setup(&mut self, call_setup: bool) -> TestSetup {
111 self._setup(call_setup).unwrap_or_else(|err| {
112 if err.to_string().contains("skipped") {
113 TestSetup::skipped(err.to_string())
114 } else {
115 TestSetup::failed(err.to_string())
116 }
117 })
118 }
119
120 fn _setup(&mut self, call_setup: bool) -> Result<TestSetup> {
121 trace!(call_setup, "setting up");
122
123 self.apply_contract_inline_config()?;
124
125 self.executor.set_balance(self.sender, U256::MAX)?;
127 self.executor.set_balance(CALLER, U256::MAX)?;
128
129 self.executor.set_nonce(self.sender, 1)?;
131
132 self.executor.set_balance(LIBRARY_DEPLOYER, U256::MAX)?;
134
135 let mut result = TestSetup::default();
136 for code in &self.mcr.libs_to_deploy {
137 let deploy_result = self.executor.deploy(
138 LIBRARY_DEPLOYER,
139 code.clone(),
140 U256::ZERO,
141 Some(&self.mcr.revert_decoder),
142 );
143
144 if let Ok(deployed) = &deploy_result {
146 result.deployed_libs.push(deployed.address);
147 }
148
149 let (raw, reason) = RawCallResult::from_evm_result(deploy_result.map(Into::into))?;
150 result.extend(raw, TraceKind::Deployment);
151 if reason.is_some() {
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 result.reason = reason;
181 return Ok(result);
182 }
183
184 self.executor.set_balance(self.sender, self.initial_balance())?;
186 self.executor.set_balance(CALLER, self.initial_balance())?;
187 self.executor.set_balance(LIBRARY_DEPLOYER, self.initial_balance())?;
188
189 self.executor.deploy_create2_deployer()?;
190
191 if call_setup {
193 trace!("calling setUp");
194 let res = self.executor.setup(None, address, Some(&self.mcr.revert_decoder));
195 let (raw, reason) = RawCallResult::from_evm_result(res)?;
196 result.extend(raw, TraceKind::Setup);
197 result.reason = reason;
198 }
199
200 result.fuzz_fixtures = self.fuzz_fixtures(address);
201
202 Ok(result)
203 }
204
205 fn initial_balance(&self) -> U256 {
206 self.evm_opts.initial_balance
207 }
208
209 fn apply_contract_inline_config(&mut self) -> Result<()> {
211 if self.inline_config.contains_contract(self.name) {
212 let new_config = Arc::new(self.inline_config(None)?);
213 self.tcfg.to_mut().reconfigure_with(new_config);
214 let prev_tracer = self.executor.inspector_mut().tracer.take();
215 self.tcfg.configure_executor(&mut self.executor);
216 self.executor.inspector_mut().tracer = prev_tracer;
218 }
219 Ok(())
220 }
221
222 fn inline_config(&self, func: Option<&Function>) -> Result<Config> {
224 let function = func.map(|f| f.name.as_str()).unwrap_or("");
225 let config =
226 self.mcr.inline_config.merge(self.name, function, &self.config).extract::<Config>()?;
227 Ok(config)
228 }
229
230 fn fuzz_fixtures(&mut self, address: Address) -> FuzzFixtures {
246 let mut fixtures = HashMap::default();
247 let fixture_functions = self.contract.abi.functions().filter(|func| func.is_fixture());
248 for func in fixture_functions {
249 if func.inputs.is_empty() {
250 if let Ok(CallResult { raw: _, decoded_result }) =
252 self.executor.call(CALLER, address, func, &[], U256::ZERO, None)
253 {
254 fixtures.insert(fixture_name(func.name.clone()), decoded_result);
255 }
256 } else {
257 let mut vals = Vec::new();
260 let mut index = 0;
261 loop {
262 if let Ok(CallResult { raw: _, decoded_result }) = self.executor.call(
263 CALLER,
264 address,
265 func,
266 &[DynSolValue::Uint(U256::from(index), 256)],
267 U256::ZERO,
268 None,
269 ) {
270 vals.push(decoded_result);
271 } else {
272 break;
275 }
276 index += 1;
277 }
278 fixtures.insert(fixture_name(func.name.clone()), DynSolValue::Array(vals));
279 };
280 }
281 FuzzFixtures::new(fixtures)
282 }
283
284 pub fn run_tests(mut self, filter: &dyn TestFilter) -> SuiteResult {
286 let start = Instant::now();
287 let mut warnings = Vec::new();
288
289 let setup_fns: Vec<_> =
291 self.contract.abi.functions().filter(|func| func.name.is_setup()).collect();
292 let call_setup = setup_fns.len() == 1 && setup_fns[0].name == "setUp";
293 for &setup_fn in &setup_fns {
295 if setup_fn.name != "setUp" {
296 warnings.push(format!(
297 "Found invalid setup function \"{}\" did you mean \"setUp()\"?",
298 setup_fn.signature()
299 ));
300 }
301 }
302
303 if setup_fns.len() > 1 {
305 return SuiteResult::new(
306 start.elapsed(),
307 [("setUp()".to_string(), TestResult::fail("multiple setUp functions".to_string()))]
308 .into(),
309 warnings,
310 )
311 }
312
313 let after_invariant_fns: Vec<_> =
315 self.contract.abi.functions().filter(|func| func.name.is_after_invariant()).collect();
316 if after_invariant_fns.len() > 1 {
317 return SuiteResult::new(
319 start.elapsed(),
320 [(
321 "afterInvariant()".to_string(),
322 TestResult::fail("multiple afterInvariant functions".to_string()),
323 )]
324 .into(),
325 warnings,
326 )
327 }
328 let call_after_invariant = after_invariant_fns.first().is_some_and(|after_invariant_fn| {
329 let match_sig = after_invariant_fn.name == "afterInvariant";
330 if !match_sig {
331 warnings.push(format!(
332 "Found invalid afterInvariant function \"{}\" did you mean \"afterInvariant()\"?",
333 after_invariant_fn.signature()
334 ));
335 }
336 match_sig
337 });
338
339 let has_invariants = self.contract.abi.functions().any(|func| func.is_invariant_test());
342
343 let prev_tracer = self.executor.inspector_mut().tracer.take();
344 if prev_tracer.is_some() || has_invariants {
345 self.executor.set_tracing(TraceMode::Call);
346 }
347
348 let setup_time = Instant::now();
349 let setup = self.setup(call_setup);
350 debug!("finished setting up in {:?}", setup_time.elapsed());
351
352 self.executor.inspector_mut().tracer = prev_tracer;
353
354 if setup.reason.is_some() {
355 let fail_msg = if !setup.deployment_failure {
357 "setUp()".to_string()
358 } else {
359 "constructor()".to_string()
360 };
361 return SuiteResult::new(
362 start.elapsed(),
363 [(fail_msg, TestResult::setup_result(setup))].into(),
364 warnings,
365 )
366 }
367
368 let find_timer = Instant::now();
371 let functions = self
372 .contract
373 .abi
374 .functions()
375 .filter(|func| is_matching_test(func, filter))
376 .collect::<Vec<_>>();
377 debug!(
378 "Found {} test functions out of {} in {:?}",
379 functions.len(),
380 self.contract.abi.functions().count(),
381 find_timer.elapsed(),
382 );
383
384 let identified_contracts = has_invariants.then(|| {
385 load_contracts(setup.traces.iter().map(|(_, t)| &t.arena), &self.mcr.known_contracts)
386 });
387
388 let test_fail_instances = functions
389 .iter()
390 .filter_map(|func| {
391 TestFunctionKind::classify(&func.name, !func.inputs.is_empty())
392 .is_any_test_fail()
393 .then_some(func.name.clone())
394 })
395 .collect::<Vec<_>>();
396
397 if !test_fail_instances.is_empty() {
398 let instances = format!(
399 "Found {} instances: {}",
400 test_fail_instances.len(),
401 test_fail_instances.join(", ")
402 );
403 let fail = TestResult::fail("`testFail*` has been removed. Consider changing to test_Revert[If|When]_Condition and expecting a revert".to_string());
404 return SuiteResult::new(start.elapsed(), [(instances, fail)].into(), warnings)
405 }
406
407 let test_results = functions
408 .par_iter()
409 .map(|&func| {
410 let start = Instant::now();
411
412 let _guard = self.tokio_handle.enter();
413
414 let _guard;
415 let current_span = tracing::Span::current();
416 if current_span.is_none() || current_span.id() != self.span.id() {
417 _guard = self.span.enter();
418 }
419
420 let sig = func.signature();
421 let kind = func.test_function_kind();
422
423 let _guard = debug_span!(
424 "test",
425 %kind,
426 name = %if enabled!(tracing::Level::TRACE) { &sig } else { &func.name },
427 )
428 .entered();
429
430 let mut res = FunctionRunner::new(&self, &setup).run(
431 func,
432 kind,
433 call_after_invariant,
434 identified_contracts.as_ref(),
435 );
436 res.duration = start.elapsed();
437
438 (sig, res)
439 })
440 .collect::<BTreeMap<_, _>>();
441
442 let duration = start.elapsed();
443 SuiteResult::new(duration, test_results, warnings)
444 }
445}
446
447struct FunctionRunner<'a> {
449 tcfg: Cow<'a, TestRunnerConfig>,
451 executor: Cow<'a, Executor>,
453 cr: &'a ContractRunner<'a>,
455 address: Address,
457 setup: &'a TestSetup,
459 result: TestResult,
461}
462
463impl<'a> std::ops::Deref for FunctionRunner<'a> {
464 type Target = Cow<'a, TestRunnerConfig>;
465
466 #[inline(always)]
467 fn deref(&self) -> &Self::Target {
468 &self.tcfg
469 }
470}
471
472impl<'a> FunctionRunner<'a> {
473 fn new(cr: &'a ContractRunner<'a>, setup: &'a TestSetup) -> Self {
474 Self {
475 tcfg: match &cr.tcfg {
476 Cow::Borrowed(tcfg) => Cow::Borrowed(tcfg),
477 Cow::Owned(tcfg) => Cow::Owned(tcfg.clone()),
478 },
479 executor: Cow::Borrowed(&cr.executor),
480 cr,
481 address: setup.address,
482 setup,
483 result: TestResult::new(setup),
484 }
485 }
486
487 fn revert_decoder(&self) -> &'a RevertDecoder {
488 &self.cr.mcr.revert_decoder
489 }
490
491 fn apply_function_inline_config(&mut self, func: &Function) -> Result<()> {
493 if self.inline_config.contains_function(self.cr.name, &func.name) {
494 let new_config = Arc::new(self.cr.inline_config(Some(func))?);
495 self.tcfg.to_mut().reconfigure_with(new_config);
496 self.tcfg.configure_executor(self.executor.to_mut());
497 }
498 Ok(())
499 }
500
501 fn run(
502 mut self,
503 func: &Function,
504 kind: TestFunctionKind,
505 call_after_invariant: bool,
506 identified_contracts: Option<&ContractsByAddress>,
507 ) -> TestResult {
508 if let Err(e) = self.apply_function_inline_config(func) {
509 self.result.single_fail(Some(e.to_string()));
510 return self.result;
511 }
512
513 match kind {
514 TestFunctionKind::UnitTest { .. } => self.run_unit_test(func),
515 TestFunctionKind::FuzzTest { .. } => self.run_fuzz_test(func),
516 TestFunctionKind::TableTest => self.run_table_test(func),
517 TestFunctionKind::InvariantTest => {
518 let test_bytecode = &self.cr.contract.bytecode;
519 self.run_invariant_test(
520 func,
521 call_after_invariant,
522 identified_contracts.unwrap(),
523 test_bytecode,
524 )
525 }
526 _ => unreachable!(),
527 }
528 }
529
530 fn run_unit_test(mut self, func: &Function) -> TestResult {
539 if self.prepare_test(func).is_err() {
541 return self.result;
542 }
543
544 let (mut raw_call_result, reason) = match self.executor.call(
546 self.sender,
547 self.address,
548 func,
549 &[],
550 U256::ZERO,
551 Some(self.revert_decoder()),
552 ) {
553 Ok(res) => (res.raw, None),
554 Err(EvmError::Execution(err)) => (err.raw, Some(err.reason)),
555 Err(EvmError::Skip(reason)) => {
556 self.result.single_skip(reason);
557 return self.result;
558 }
559 Err(err) => {
560 self.result.single_fail(Some(err.to_string()));
561 return self.result;
562 }
563 };
564
565 let success =
566 self.executor.is_raw_call_mut_success(self.address, &mut raw_call_result, false);
567 self.result.single_result(success, reason, raw_call_result);
568 self.result
569 }
570
571 fn run_table_test(mut self, func: &Function) -> TestResult {
580 if self.prepare_test(func).is_err() {
582 return self.result;
583 }
584
585 let Some(first_param) = func.inputs.first() else {
587 self.result.single_fail(Some("Table test should have at least one parameter".into()));
588 return self.result;
589 };
590
591 let Some(first_param_fixtures) =
592 &self.setup.fuzz_fixtures.param_fixtures(first_param.name())
593 else {
594 self.result.single_fail(Some("Table test should have fixtures defined".into()));
595 return self.result;
596 };
597
598 if first_param_fixtures.is_empty() {
599 self.result.single_fail(Some("Table test should have at least one fixture".into()));
600 return self.result;
601 }
602
603 let fixtures_len = first_param_fixtures.len();
604 let mut table_fixtures = vec![&first_param_fixtures[..]];
605
606 for param in &func.inputs[1..] {
608 let param_name = param.name();
609 let Some(fixtures) = &self.setup.fuzz_fixtures.param_fixtures(param.name()) else {
610 self.result.single_fail(Some(format!("No fixture defined for param {param_name}")));
611 return self.result;
612 };
613
614 if fixtures.len() != fixtures_len {
615 self.result.single_fail(Some(format!(
616 "{} fixtures defined for {param_name} (expected {})",
617 fixtures.len(),
618 fixtures_len
619 )));
620 return self.result;
621 }
622
623 table_fixtures.push(&fixtures[..]);
624 }
625
626 let progress =
627 start_fuzz_progress(self.cr.progress, self.cr.name, &func.name, fixtures_len as u32);
628
629 for i in 0..fixtures_len {
630 if let Some(progress) = progress.as_ref() {
632 progress.inc(1);
633 }
634
635 let args = table_fixtures.iter().map(|row| row[i].clone()).collect_vec();
636 let (mut raw_call_result, reason) = match self.executor.call(
637 self.sender,
638 self.address,
639 func,
640 &args,
641 U256::ZERO,
642 Some(self.revert_decoder()),
643 ) {
644 Ok(res) => (res.raw, None),
645 Err(EvmError::Execution(err)) => (err.raw, Some(err.reason)),
646 Err(EvmError::Skip(reason)) => {
647 self.result.single_skip(reason);
648 return self.result;
649 }
650 Err(err) => {
651 self.result.single_fail(Some(err.to_string()));
652 return self.result;
653 }
654 };
655
656 let is_success =
657 self.executor.is_raw_call_mut_success(self.address, &mut raw_call_result, false);
658 if !is_success {
660 self.result.counterexample =
661 Some(CounterExample::Single(BaseCounterExample::from_fuzz_call(
662 Bytes::from(func.abi_encode_input(&args).unwrap()),
663 args,
664 raw_call_result.traces.clone(),
665 )));
666 self.result.single_result(false, reason, raw_call_result);
667 return self.result;
668 }
669
670 if i == fixtures_len - 1 {
673 self.result.single_result(true, None, raw_call_result);
674 return self.result;
675 }
676 }
677
678 self.result
679 }
680
681 fn run_invariant_test(
682 mut self,
683 func: &Function,
684 call_after_invariant: bool,
685 identified_contracts: &ContractsByAddress,
686 test_bytecode: &Bytes,
687 ) -> TestResult {
688 if let Err(EvmError::Skip(reason)) = self.executor.call(
690 self.sender,
691 self.address,
692 func,
693 &[],
694 U256::ZERO,
695 Some(self.revert_decoder()),
696 ) {
697 self.result.invariant_skip(reason);
698 return self.result;
699 };
700
701 let runner = self.invariant_runner();
702 let invariant_config = &self.config.invariant;
703
704 let mut evm = InvariantExecutor::new(
705 self.clone_executor(),
706 runner,
707 invariant_config.clone(),
708 identified_contracts,
709 &self.cr.mcr.known_contracts,
710 );
711 let invariant_contract = InvariantContract {
712 address: self.address,
713 invariant_function: func,
714 call_after_invariant,
715 abi: &self.cr.contract.abi,
716 };
717
718 let (failure_dir, failure_file) = invariant_failure_paths(
719 invariant_config,
720 self.cr.name,
721 &invariant_contract.invariant_function.name,
722 );
723 let show_solidity = invariant_config.show_solidity;
724
725 if let Some(mut call_sequence) =
727 persisted_call_sequence(failure_file.as_path(), test_bytecode)
728 {
729 let txes = call_sequence
731 .iter_mut()
732 .map(|seq| {
733 seq.show_solidity = show_solidity;
734 BasicTxDetails {
735 sender: seq.sender.unwrap_or_default(),
736 call_details: CallDetails {
737 target: seq.addr.unwrap_or_default(),
738 calldata: seq.calldata.clone(),
739 },
740 }
741 })
742 .collect::<Vec<BasicTxDetails>>();
743 if let Ok((success, replayed_entirely)) = check_sequence(
744 self.clone_executor(),
745 &txes,
746 (0..min(txes.len(), invariant_config.depth as usize)).collect(),
747 invariant_contract.address,
748 invariant_contract.invariant_function.selector().to_vec().into(),
749 invariant_config.fail_on_revert,
750 invariant_contract.call_after_invariant,
751 ) {
752 if !success {
753 let _= sh_warn!("\
754 Replayed invariant failure from {:?} file. \
755 Run `forge clean` or remove file to ignore failure and to continue invariant test campaign.",
756 failure_file.as_path()
757 );
758 let _ = replay_run(
761 &invariant_contract,
762 self.clone_executor(),
763 &self.cr.mcr.known_contracts,
764 identified_contracts.clone(),
765 &mut self.result.logs,
766 &mut self.result.traces,
767 &mut self.result.coverage,
768 &mut self.result.deprecated_cheatcodes,
769 &txes,
770 show_solidity,
771 );
772 self.result.invariant_replay_fail(
773 replayed_entirely,
774 &invariant_contract.invariant_function.name,
775 call_sequence,
776 );
777 return self.result;
778 }
779 }
780 }
781
782 let progress =
783 start_fuzz_progress(self.cr.progress, self.cr.name, &func.name, invariant_config.runs);
784 let invariant_result = match evm.invariant_fuzz(
785 invariant_contract.clone(),
786 &self.setup.fuzz_fixtures,
787 &self.setup.deployed_libs,
788 progress.as_ref(),
789 ) {
790 Ok(x) => x,
791 Err(e) => {
792 self.result.invariant_setup_fail(e);
793 return self.result;
794 }
795 };
796 self.result.merge_coverages(invariant_result.coverage);
798
799 let mut counterexample = None;
800 let success = invariant_result.error.is_none();
801 let reason = invariant_result.error.as_ref().and_then(|err| err.revert_reason());
802
803 match invariant_result.error {
804 Some(error) => match error {
806 InvariantFuzzError::BrokenInvariant(case_data) |
807 InvariantFuzzError::Revert(case_data) => {
808 match replay_error(
811 &case_data,
812 &invariant_contract,
813 self.clone_executor(),
814 &self.cr.mcr.known_contracts,
815 identified_contracts.clone(),
816 &mut self.result.logs,
817 &mut self.result.traces,
818 &mut self.result.coverage,
819 &mut self.result.deprecated_cheatcodes,
820 progress.as_ref(),
821 show_solidity,
822 ) {
823 Ok(call_sequence) => {
824 if !call_sequence.is_empty() {
825 if let Err(err) = foundry_common::fs::create_dir_all(failure_dir) {
827 error!(%err, "Failed to create invariant failure dir");
828 } else if let Err(err) = foundry_common::fs::write_json_file(
829 failure_file.as_path(),
830 &InvariantPersistedFailure {
831 call_sequence: call_sequence.clone(),
832 driver_bytecode: Some(test_bytecode.clone()),
833 },
834 ) {
835 error!(%err, "Failed to record call sequence");
836 }
837
838 let original_seq_len =
839 if let TestError::Fail(_, calls) = &case_data.test_error {
840 calls.len()
841 } else {
842 call_sequence.len()
843 };
844
845 counterexample =
846 Some(CounterExample::Sequence(original_seq_len, call_sequence))
847 }
848 }
849 Err(err) => {
850 error!(%err, "Failed to replay invariant error");
851 }
852 };
853 }
854 InvariantFuzzError::MaxAssumeRejects(_) => {}
855 },
856
857 _ => {
860 if let Err(err) = replay_run(
861 &invariant_contract,
862 self.clone_executor(),
863 &self.cr.mcr.known_contracts,
864 identified_contracts.clone(),
865 &mut self.result.logs,
866 &mut self.result.traces,
867 &mut self.result.coverage,
868 &mut self.result.deprecated_cheatcodes,
869 &invariant_result.last_run_inputs,
870 show_solidity,
871 ) {
872 error!(%err, "Failed to replay last invariant run");
873 }
874 }
875 }
876
877 self.result.invariant_result(
878 invariant_result.gas_report_traces,
879 success,
880 reason,
881 counterexample,
882 invariant_result.cases,
883 invariant_result.reverts,
884 invariant_result.metrics,
885 );
886 self.result
887 }
888
889 fn run_fuzz_test(mut self, func: &Function) -> TestResult {
899 if self.prepare_test(func).is_err() {
901 return self.result;
902 }
903
904 let runner = self.fuzz_runner();
905 let fuzz_config = self.config.fuzz.clone();
906
907 let progress =
908 start_fuzz_progress(self.cr.progress, self.cr.name, &func.name, fuzz_config.runs);
909
910 let fuzzed_executor =
912 FuzzedExecutor::new(self.executor.into_owned(), runner, self.tcfg.sender, fuzz_config);
913 let result = fuzzed_executor.fuzz(
914 func,
915 &self.setup.fuzz_fixtures,
916 &self.setup.deployed_libs,
917 self.address,
918 &self.cr.mcr.revert_decoder,
919 progress.as_ref(),
920 );
921 self.result.fuzz_result(result);
922 self.result
923 }
924
925 fn prepare_test(&mut self, func: &Function) -> Result<(), ()> {
935 let address = self.setup.address;
936
937 if self.cr.contract.abi.functions().filter(|func| func.name.is_before_test_setup()).count() ==
939 1
940 {
941 for calldata in self.executor.call_sol_default(
942 address,
943 &ITest::beforeTestSetupCall { testSelector: func.selector() },
944 ) {
945 match self.executor.to_mut().transact_raw(
947 self.tcfg.sender,
948 address,
949 calldata,
950 U256::ZERO,
951 ) {
952 Ok(call_result) => {
953 let reverted = call_result.reverted;
954
955 self.result.extend(call_result);
957
958 if reverted {
960 self.result.single_fail(None);
961 return Err(());
962 }
963 }
964 Err(_) => {
965 self.result.single_fail(None);
966 return Err(());
967 }
968 }
969 }
970 }
971 Ok(())
972 }
973
974 fn fuzz_runner(&self) -> TestRunner {
975 let config = &self.config.fuzz;
976 let failure_persist_path = config
977 .failure_persist_dir
978 .as_ref()
979 .unwrap()
980 .join(config.failure_persist_file.as_ref().unwrap())
981 .into_os_string()
982 .into_string()
983 .unwrap();
984 fuzzer_with_cases(
985 config.seed,
986 config.runs,
987 config.max_test_rejects,
988 Some(Box::new(FileFailurePersistence::Direct(failure_persist_path.leak()))),
989 )
990 }
991
992 fn invariant_runner(&self) -> TestRunner {
993 let config = &self.config.invariant;
994 fuzzer_with_cases(self.config.fuzz.seed, config.runs, config.max_assume_rejects, None)
995 }
996
997 fn clone_executor(&self) -> Executor {
998 self.executor.clone().into_owned()
999 }
1000}
1001
1002fn fuzzer_with_cases(
1003 seed: Option<U256>,
1004 cases: u32,
1005 max_global_rejects: u32,
1006 file_failure_persistence: Option<Box<dyn FailurePersistence>>,
1007) -> TestRunner {
1008 let config = proptest::test_runner::Config {
1009 failure_persistence: file_failure_persistence,
1010 cases,
1011 max_global_rejects,
1012 max_shrink_iters: 0,
1015 ..Default::default()
1016 };
1017
1018 if let Some(seed) = seed {
1019 trace!(target: "forge::test", %seed, "building deterministic fuzzer");
1020 let rng = TestRng::from_seed(RngAlgorithm::ChaCha, &seed.to_be_bytes::<32>());
1021 TestRunner::new_with_rng(config, rng)
1022 } else {
1023 trace!(target: "forge::test", "building stochastic fuzzer");
1024 TestRunner::new(config)
1025 }
1026}
1027
1028#[derive(Serialize, Deserialize)]
1030struct InvariantPersistedFailure {
1031 call_sequence: Vec<BaseCounterExample>,
1033 #[serde(skip_serializing_if = "Option::is_none")]
1035 driver_bytecode: Option<Bytes>,
1036}
1037
1038fn persisted_call_sequence(path: &Path, bytecode: &Bytes) -> Option<Vec<BaseCounterExample>> {
1041 foundry_common::fs::read_json_file::<InvariantPersistedFailure>(path).ok().and_then(
1042 |persisted_failure| {
1043 if let Some(persisted_bytecode) = &persisted_failure.driver_bytecode {
1044 if !bytecode.eq(persisted_bytecode) {
1046 let _= sh_warn!("\
1047 Failure from {:?} file was ignored because test contract bytecode has changed.",
1048 path
1049 );
1050 return None;
1051 }
1052 };
1053 Some(persisted_failure.call_sequence)
1054 },
1055 )
1056}
1057
1058fn invariant_failure_paths(
1060 config: &InvariantConfig,
1061 contract_name: &str,
1062 invariant_name: &str,
1063) -> (PathBuf, PathBuf) {
1064 let dir = config
1065 .failure_persist_dir
1066 .clone()
1067 .unwrap()
1068 .join("failures")
1069 .join(contract_name.split(':').next_back().unwrap());
1070 let dir = canonicalized(dir);
1071 let file = canonicalized(dir.join(invariant_name));
1072 (dir, file)
1073}