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