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_functions =
387 functions.iter().filter(|func| func.test_function_kind().is_any_test_fail());
388 if test_fail_functions.clone().next().is_some() {
389 let fail = || {
390 TestResult::fail("`testFail*` has been removed. Consider changing to test_Revert[If|When]_Condition and expecting a revert".to_string())
391 };
392 let test_results = test_fail_functions.map(|func| (func.signature(), fail())).collect();
393 return SuiteResult::new(start.elapsed(), test_results, warnings);
394 }
395
396 let fail_fast = &self.tcfg.fail_fast;
397
398 let test_results = functions
399 .par_iter()
400 .map(|&func| {
401 if fail_fast.should_stop() {
403 return (func.signature(), TestResult::setup_result(setup.clone()));
404 }
405
406 let start = Instant::now();
407
408 let _guard = self.tokio_handle.enter();
409
410 let _guard;
411 let current_span = tracing::Span::current();
412 if current_span.is_none() || current_span.id() != self.span.id() {
413 _guard = self.span.enter();
414 }
415
416 let sig = func.signature();
417 let kind = func.test_function_kind();
418
419 let _guard = debug_span!(
420 "test",
421 %kind,
422 name = %if enabled!(tracing::Level::TRACE) { &sig } else { &func.name },
423 )
424 .entered();
425
426 let mut res = FunctionRunner::new(&self, &setup).run(
427 func,
428 kind,
429 call_after_invariant,
430 identified_contracts.as_ref(),
431 );
432 res.duration = start.elapsed();
433
434 if res.status.is_failure() {
436 fail_fast.record_fail();
437 }
438
439 (sig, res)
440 })
441 .collect::<BTreeMap<_, _>>();
442
443 let duration = start.elapsed();
444 SuiteResult::new(duration, test_results, warnings)
445 }
446}
447
448struct FunctionRunner<'a> {
450 tcfg: Cow<'a, TestRunnerConfig>,
452 executor: Cow<'a, Executor>,
454 cr: &'a ContractRunner<'a>,
456 address: Address,
458 setup: &'a TestSetup,
460 result: TestResult,
462}
463
464impl<'a> std::ops::Deref for FunctionRunner<'a> {
465 type Target = Cow<'a, TestRunnerConfig>;
466
467 #[inline(always)]
468 fn deref(&self) -> &Self::Target {
469 &self.tcfg
470 }
471}
472
473impl<'a> FunctionRunner<'a> {
474 fn new(cr: &'a ContractRunner<'a>, setup: &'a TestSetup) -> Self {
475 Self {
476 tcfg: match &cr.tcfg {
477 Cow::Borrowed(tcfg) => Cow::Borrowed(tcfg),
478 Cow::Owned(tcfg) => Cow::Owned(tcfg.clone()),
479 },
480 executor: Cow::Borrowed(&cr.executor),
481 cr,
482 address: setup.address,
483 setup,
484 result: TestResult::new(setup),
485 }
486 }
487
488 fn revert_decoder(&self) -> &'a RevertDecoder {
489 &self.cr.mcr.revert_decoder
490 }
491
492 fn apply_function_inline_config(&mut self, func: &Function) -> Result<()> {
494 if self.inline_config.contains_function(self.cr.name, &func.name) {
495 let new_config = Arc::new(self.cr.inline_config(Some(func))?);
496 self.tcfg.to_mut().reconfigure_with(new_config);
497 self.tcfg.configure_executor(self.executor.to_mut());
498 }
499 Ok(())
500 }
501
502 fn run(
503 mut self,
504 func: &Function,
505 kind: TestFunctionKind,
506 call_after_invariant: bool,
507 identified_contracts: Option<&ContractsByAddress>,
508 ) -> TestResult {
509 if let Err(e) = self.apply_function_inline_config(func) {
510 self.result.single_fail(Some(e.to_string()));
511 return self.result;
512 }
513
514 match kind {
515 TestFunctionKind::UnitTest { .. } => self.run_unit_test(func),
516 TestFunctionKind::FuzzTest { .. } => self.run_fuzz_test(func),
517 TestFunctionKind::TableTest => self.run_table_test(func),
518 TestFunctionKind::InvariantTest => {
519 let test_bytecode = &self.cr.contract.bytecode;
520 self.run_invariant_test(
521 func,
522 call_after_invariant,
523 identified_contracts.unwrap(),
524 test_bytecode,
525 )
526 }
527 _ => unreachable!(),
528 }
529 }
530
531 fn run_unit_test(mut self, func: &Function) -> TestResult {
540 if self.prepare_test(func).is_err() {
542 return self.result;
543 }
544
545 let (mut raw_call_result, reason) = match self.executor.call(
547 self.sender,
548 self.address,
549 func,
550 &[],
551 U256::ZERO,
552 Some(self.revert_decoder()),
553 ) {
554 Ok(res) => (res.raw, None),
555 Err(EvmError::Execution(err)) => (err.raw, Some(err.reason)),
556 Err(EvmError::Skip(reason)) => {
557 self.result.single_skip(reason);
558 return self.result;
559 }
560 Err(err) => {
561 self.result.single_fail(Some(err.to_string()));
562 return self.result;
563 }
564 };
565
566 let success =
567 self.executor.is_raw_call_mut_success(self.address, &mut raw_call_result, false);
568 self.result.single_result(success, reason, raw_call_result);
569 self.result
570 }
571
572 fn run_table_test(mut self, func: &Function) -> TestResult {
581 if self.prepare_test(func).is_err() {
583 return self.result;
584 }
585
586 let Some(first_param) = func.inputs.first() else {
588 self.result.single_fail(Some("Table test should have at least one parameter".into()));
589 return self.result;
590 };
591
592 let Some(first_param_fixtures) =
593 &self.setup.fuzz_fixtures.param_fixtures(first_param.name())
594 else {
595 self.result.single_fail(Some("Table test should have fixtures defined".into()));
596 return self.result;
597 };
598
599 if first_param_fixtures.is_empty() {
600 self.result.single_fail(Some("Table test should have at least one fixture".into()));
601 return self.result;
602 }
603
604 let fixtures_len = first_param_fixtures.len();
605 let mut table_fixtures = vec![&first_param_fixtures[..]];
606
607 for param in &func.inputs[1..] {
609 let param_name = param.name();
610 let Some(fixtures) = &self.setup.fuzz_fixtures.param_fixtures(param.name()) else {
611 self.result.single_fail(Some(format!("No fixture defined for param {param_name}")));
612 return self.result;
613 };
614
615 if fixtures.len() != fixtures_len {
616 self.result.single_fail(Some(format!(
617 "{} fixtures defined for {param_name} (expected {})",
618 fixtures.len(),
619 fixtures_len
620 )));
621 return self.result;
622 }
623
624 table_fixtures.push(&fixtures[..]);
625 }
626
627 let progress = start_fuzz_progress(
628 self.cr.progress,
629 self.cr.name,
630 &func.name,
631 None,
632 fixtures_len as u32,
633 );
634
635 let mut result = FuzzTestResult::default();
636
637 for i in 0..fixtures_len {
638 if self.tcfg.fail_fast.should_stop() {
639 return self.result;
640 }
641
642 if let Some(progress) = progress.as_ref() {
644 progress.inc(1);
645 }
646
647 let args = table_fixtures.iter().map(|row| row[i].clone()).collect_vec();
648 let (mut raw_call_result, reason) = match self.executor.call(
649 self.sender,
650 self.address,
651 func,
652 &args,
653 U256::ZERO,
654 Some(self.revert_decoder()),
655 ) {
656 Ok(res) => (res.raw, None),
657 Err(EvmError::Execution(err)) => (err.raw, Some(err.reason)),
658 Err(EvmError::Skip(reason)) => {
659 self.result.single_skip(reason);
660 return self.result;
661 }
662 Err(err) => {
663 self.result.single_fail(Some(err.to_string()));
664 return self.result;
665 }
666 };
667
668 result.gas_by_case.push((raw_call_result.gas_used, raw_call_result.stipend));
669 result.logs.extend(raw_call_result.logs.clone());
670 result.labels.extend(raw_call_result.labels.clone());
671 HitMaps::merge_opt(&mut result.line_coverage, raw_call_result.line_coverage.clone());
672
673 let is_success =
674 self.executor.is_raw_call_mut_success(self.address, &mut raw_call_result, false);
675 if !is_success {
677 result.counterexample =
678 Some(CounterExample::Single(BaseCounterExample::from_fuzz_call(
679 Bytes::from(func.abi_encode_input(&args).unwrap()),
680 args,
681 raw_call_result.traces.clone(),
682 )));
683 result.reason = reason;
684 result.traces = raw_call_result.traces;
685 self.result.table_result(result);
686 return self.result;
687 }
688
689 if i == fixtures_len - 1 {
692 result.success = true;
693 result.traces = raw_call_result.traces;
694 self.result.table_result(result);
695 return self.result;
696 }
697 }
698
699 self.result
700 }
701
702 fn run_invariant_test(
703 mut self,
704 func: &Function,
705 call_after_invariant: bool,
706 identified_contracts: &ContractsByAddress,
707 test_bytecode: &Bytes,
708 ) -> TestResult {
709 if let Err(EvmError::Skip(reason)) = self.executor.call(
711 self.sender,
712 self.address,
713 func,
714 &[],
715 U256::ZERO,
716 Some(self.revert_decoder()),
717 ) {
718 self.result.invariant_skip(reason);
719 return self.result;
720 };
721
722 let runner = self.invariant_runner();
723 let invariant_config = &self.config.invariant;
724
725 let mut executor = self.clone_executor();
726 executor
729 .inspector_mut()
730 .collect_edge_coverage(invariant_config.corpus.collect_edge_coverage());
731 let mut config = invariant_config.clone();
732 let (failure_dir, failure_file) = test_paths(
733 &mut config.corpus,
734 invariant_config.failure_persist_dir.clone().unwrap(),
735 self.cr.name,
736 &func.name,
737 );
738
739 let mut evm = InvariantExecutor::new(
740 executor,
741 runner,
742 config,
743 identified_contracts,
744 &self.cr.mcr.known_contracts,
745 );
746 let invariant_contract = InvariantContract {
747 address: self.address,
748 invariant_function: func,
749 call_after_invariant,
750 abi: &self.cr.contract.abi,
751 };
752 let show_solidity = invariant_config.show_solidity;
753
754 if let Some(mut call_sequence) =
756 persisted_call_sequence(failure_file.as_path(), test_bytecode)
757 {
758 let txes = call_sequence
760 .iter_mut()
761 .map(|seq| {
762 seq.show_solidity = show_solidity;
763 BasicTxDetails {
764 sender: seq.sender.unwrap_or_default(),
765 call_details: CallDetails {
766 target: seq.addr.unwrap_or_default(),
767 calldata: seq.calldata.clone(),
768 },
769 }
770 })
771 .collect::<Vec<BasicTxDetails>>();
772 if let Ok((success, replayed_entirely)) = check_sequence(
773 self.clone_executor(),
774 &txes,
775 (0..min(txes.len(), invariant_config.depth as usize)).collect(),
776 invariant_contract.address,
777 invariant_contract.invariant_function.selector().to_vec().into(),
778 invariant_config.fail_on_revert,
779 invariant_contract.call_after_invariant,
780 ) && !success
781 {
782 let _ = sh_warn!(
783 "\
784 Replayed invariant failure from {:?} file. \
785 Run `forge clean` or remove file to ignore failure and to continue invariant test campaign.",
786 failure_file.as_path()
787 );
788 let _ = replay_run(
791 &invariant_contract,
792 self.clone_executor(),
793 &self.cr.mcr.known_contracts,
794 identified_contracts.clone(),
795 &mut self.result.logs,
796 &mut self.result.traces,
797 &mut self.result.line_coverage,
798 &mut self.result.deprecated_cheatcodes,
799 &txes,
800 show_solidity,
801 );
802 self.result.invariant_replay_fail(
803 replayed_entirely,
804 &invariant_contract.invariant_function.name,
805 call_sequence,
806 );
807 return self.result;
808 }
809 }
810
811 let progress = start_fuzz_progress(
812 self.cr.progress,
813 self.cr.name,
814 &func.name,
815 invariant_config.timeout,
816 invariant_config.runs,
817 );
818 let invariant_result = match evm.invariant_fuzz(
819 invariant_contract.clone(),
820 &self.setup.fuzz_fixtures,
821 &self.setup.deployed_libs,
822 progress.as_ref(),
823 &self.tcfg.fail_fast,
824 ) {
825 Ok(x) => x,
826 Err(e) => {
827 self.result.invariant_setup_fail(e);
828 return self.result;
829 }
830 };
831 self.result.merge_coverages(invariant_result.line_coverage);
833
834 let mut counterexample = None;
835 let success = invariant_result.error.is_none();
836 let reason = invariant_result.error.as_ref().and_then(|err| err.revert_reason());
837
838 match invariant_result.error {
839 Some(error) => match error {
841 InvariantFuzzError::BrokenInvariant(case_data)
842 | InvariantFuzzError::Revert(case_data) => {
843 match replay_error(
846 &case_data,
847 &invariant_contract,
848 self.clone_executor(),
849 &self.cr.mcr.known_contracts,
850 identified_contracts.clone(),
851 &mut self.result.logs,
852 &mut self.result.traces,
853 &mut self.result.line_coverage,
854 &mut self.result.deprecated_cheatcodes,
855 progress.as_ref(),
856 show_solidity,
857 ) {
858 Ok(call_sequence) => {
859 if !call_sequence.is_empty() {
860 if let Err(err) = foundry_common::fs::create_dir_all(failure_dir) {
862 error!(%err, "Failed to create invariant failure dir");
863 } else if let Err(err) = foundry_common::fs::write_json_file(
864 failure_file.as_path(),
865 &InvariantPersistedFailure {
866 call_sequence: call_sequence.clone(),
867 driver_bytecode: Some(test_bytecode.clone()),
868 },
869 ) {
870 error!(%err, "Failed to record call sequence");
871 }
872
873 let original_seq_len =
874 if let TestError::Fail(_, calls) = &case_data.test_error {
875 calls.len()
876 } else {
877 call_sequence.len()
878 };
879
880 counterexample =
881 Some(CounterExample::Sequence(original_seq_len, call_sequence))
882 }
883 }
884 Err(err) => {
885 error!(%err, "Failed to replay invariant error");
886 }
887 };
888 }
889 InvariantFuzzError::MaxAssumeRejects(_) => {}
890 },
891
892 _ => {
895 if let Err(err) = replay_run(
896 &invariant_contract,
897 self.clone_executor(),
898 &self.cr.mcr.known_contracts,
899 identified_contracts.clone(),
900 &mut self.result.logs,
901 &mut self.result.traces,
902 &mut self.result.line_coverage,
903 &mut self.result.deprecated_cheatcodes,
904 &invariant_result.last_run_inputs,
905 show_solidity,
906 ) {
907 error!(%err, "Failed to replay last invariant run");
908 }
909 }
910 }
911
912 self.result.invariant_result(
913 invariant_result.gas_report_traces,
914 success,
915 reason,
916 counterexample,
917 invariant_result.cases,
918 invariant_result.reverts,
919 invariant_result.metrics,
920 invariant_result.failed_corpus_replays,
921 );
922 self.result
923 }
924
925 fn run_fuzz_test(mut self, func: &Function) -> TestResult {
935 if self.prepare_test(func).is_err() {
937 return self.result;
938 }
939
940 let runner = self.fuzz_runner();
941 let mut fuzz_config = self.config.fuzz.clone();
942 let (failure_dir, failure_file) = test_paths(
943 &mut fuzz_config.corpus,
944 fuzz_config.failure_persist_dir.clone().unwrap(),
945 self.cr.name,
946 &func.name,
947 );
948
949 let progress = start_fuzz_progress(
950 self.cr.progress,
951 self.cr.name,
952 &func.name,
953 fuzz_config.timeout,
954 fuzz_config.runs,
955 );
956
957 let mut executor = self.executor.into_owned();
958 executor.inspector_mut().collect_edge_coverage(fuzz_config.corpus.collect_edge_coverage());
961 let persisted_failure =
963 foundry_common::fs::read_json_file::<BaseCounterExample>(failure_file.as_path()).ok();
964 let mut fuzzed_executor =
966 FuzzedExecutor::new(executor, runner, self.tcfg.sender, fuzz_config, persisted_failure);
967 let result = match fuzzed_executor.fuzz(
968 func,
969 &self.setup.fuzz_fixtures,
970 &self.setup.deployed_libs,
971 self.address,
972 &self.cr.mcr.revert_decoder,
973 progress.as_ref(),
974 &self.tcfg.fail_fast,
975 ) {
976 Ok(x) => x,
977 Err(e) => {
978 self.result.fuzz_setup_fail(e);
979 return self.result;
980 }
981 };
982
983 if let Some(CounterExample::Single(counterexample)) = &result.counterexample {
985 if let Err(err) = foundry_common::fs::create_dir_all(failure_dir) {
986 error!(%err, "Failed to create fuzz failure dir");
987 } else if let Err(err) =
988 foundry_common::fs::write_json_file(failure_file.as_path(), counterexample)
989 {
990 error!(%err, "Failed to record call sequence");
991 }
992 }
993
994 self.result.fuzz_result(result);
995 self.result
996 }
997
998 fn prepare_test(&mut self, func: &Function) -> Result<(), ()> {
1008 let address = self.setup.address;
1009
1010 if self.cr.contract.abi.functions().filter(|func| func.name.is_before_test_setup()).count()
1012 == 1
1013 {
1014 for calldata in self.executor.call_sol_default(
1015 address,
1016 &ITest::beforeTestSetupCall { testSelector: func.selector() },
1017 ) {
1018 match self.executor.to_mut().transact_raw(
1020 self.tcfg.sender,
1021 address,
1022 calldata,
1023 U256::ZERO,
1024 ) {
1025 Ok(call_result) => {
1026 let reverted = call_result.reverted;
1027
1028 self.result.extend(call_result);
1030
1031 if reverted {
1033 self.result.single_fail(None);
1034 return Err(());
1035 }
1036 }
1037 Err(_) => {
1038 self.result.single_fail(None);
1039 return Err(());
1040 }
1041 }
1042 }
1043 }
1044 Ok(())
1045 }
1046
1047 fn fuzz_runner(&self) -> TestRunner {
1048 let config = &self.config.fuzz;
1049 fuzzer_with_cases(config.seed, config.runs, config.max_test_rejects)
1050 }
1051
1052 fn invariant_runner(&self) -> TestRunner {
1053 let config = &self.config.invariant;
1054 fuzzer_with_cases(self.config.fuzz.seed, config.runs, config.max_assume_rejects)
1055 }
1056
1057 fn clone_executor(&self) -> Executor {
1058 self.executor.clone().into_owned()
1059 }
1060}
1061
1062fn fuzzer_with_cases(seed: Option<U256>, cases: u32, max_global_rejects: u32) -> TestRunner {
1063 let config = proptest::test_runner::Config {
1064 cases,
1065 max_global_rejects,
1066 max_shrink_iters: 0,
1069 ..Default::default()
1070 };
1071
1072 if let Some(seed) = seed {
1073 trace!(target: "forge::test", %seed, "building deterministic fuzzer");
1074 let rng = TestRng::from_seed(RngAlgorithm::ChaCha, &seed.to_be_bytes::<32>());
1075 TestRunner::new_with_rng(config, rng)
1076 } else {
1077 trace!(target: "forge::test", "building stochastic fuzzer");
1078 TestRunner::new(config)
1079 }
1080}
1081
1082#[derive(Serialize, Deserialize)]
1084struct InvariantPersistedFailure {
1085 call_sequence: Vec<BaseCounterExample>,
1087 #[serde(skip_serializing_if = "Option::is_none")]
1089 driver_bytecode: Option<Bytes>,
1090}
1091
1092fn persisted_call_sequence(path: &Path, bytecode: &Bytes) -> Option<Vec<BaseCounterExample>> {
1095 foundry_common::fs::read_json_file::<InvariantPersistedFailure>(path).ok().and_then(
1096 |persisted_failure| {
1097 if let Some(persisted_bytecode) = &persisted_failure.driver_bytecode {
1098 if !bytecode.eq(persisted_bytecode) {
1100 let _= sh_warn!("\
1101 Failure from {:?} file was ignored because test contract bytecode has changed.",
1102 path
1103 );
1104 return None;
1105 }
1106 };
1107 Some(persisted_failure.call_sequence)
1108 },
1109 )
1110}
1111
1112fn test_paths(
1114 corpus_config: &mut FuzzCorpusConfig,
1115 persist_dir: PathBuf,
1116 contract_name: &str,
1117 test_name: &str,
1118) -> (PathBuf, PathBuf) {
1119 let contract = contract_name.split(':').next_back().unwrap();
1120 corpus_config.with_test(contract, test_name);
1122
1123 let failures_dir = canonicalized(persist_dir.join("failures").join(contract));
1124 let failure_file = canonicalized(failures_dir.join(test_name));
1125 (failures_dir, failure_file)
1126}