1use crate::{
2 executors::{
3 DURATION_BETWEEN_METRICS_REPORT, EarlyExit, EvmError, Executor, FuzzTestTimer,
4 RawCallResult, corpus::WorkerCorpus,
5 },
6 inspectors::Fuzzer,
7};
8use alloy_primitives::{Address, Bytes, FixedBytes, I256, Selector, U256, map::AddressMap};
9use alloy_sol_types::{SolCall, sol};
10use eyre::{ContextCompat, Result, eyre};
11use foundry_common::{
12 TestFunctionExt,
13 contracts::{ContractsByAddress, ContractsByArtifact},
14 sh_println,
15};
16use foundry_config::InvariantConfig;
17use foundry_evm_core::{
18 FoundryBlock,
19 constants::{
20 CALLER, CHEATCODE_ADDRESS, DEFAULT_CREATE2_DEPLOYER, HARDHAT_CONSOLE_ADDRESS, MAGIC_ASSUME,
21 },
22 evm::FoundryEvmNetwork,
23 precompiles::PRECOMPILES,
24};
25use foundry_evm_fuzz::{
26 BasicTxDetails, FuzzCase, FuzzFixtures, FuzzedCases,
27 invariant::{
28 ArtifactFilters, FuzzRunIdentifiedContracts, InvariantContract, InvariantSettings,
29 RandomCallGenerator, SenderFilters, TargetedContract, TargetedContracts,
30 },
31 strategies::{EvmFuzzState, invariant_strat, override_call_strat},
32};
33use foundry_evm_traces::{CallTraceArena, SparsedTraceArena};
34use indicatif::ProgressBar;
35use parking_lot::RwLock;
36use proptest::{strategy::Strategy, test_runner::TestRunner};
37use result::{assert_after_invariant, assert_invariants, can_continue};
38use revm::{context::Block, state::Account};
39use serde::{Deserialize, Serialize};
40use serde_json::json;
41use std::{
42 collections::{HashMap as Map, btree_map::Entry},
43 sync::Arc,
44 time::{Instant, SystemTime, UNIX_EPOCH},
45};
46
47mod error;
48pub use error::{InvariantFailures, InvariantFuzzError};
49use foundry_evm_coverage::HitMaps;
50
51mod replay;
52pub use replay::{replay_error, replay_run};
53
54mod result;
55pub use result::InvariantFuzzTestResult;
56
57mod shrink;
58pub use shrink::{CheckSequenceOptions, check_sequence, check_sequence_value};
59
60sol! {
61 interface IInvariantTest {
62 #[derive(Default)]
63 struct FuzzSelector {
64 address addr;
65 bytes4[] selectors;
66 }
67
68 #[derive(Default)]
69 struct FuzzArtifactSelector {
70 string artifact;
71 bytes4[] selectors;
72 }
73
74 #[derive(Default)]
75 struct FuzzInterface {
76 address addr;
77 string[] artifacts;
78 }
79
80 function afterInvariant() external;
81
82 #[derive(Default)]
83 function excludeArtifacts() public view returns (string[] memory excludedArtifacts);
84
85 #[derive(Default)]
86 function excludeContracts() public view returns (address[] memory excludedContracts);
87
88 #[derive(Default)]
89 function excludeSelectors() public view returns (FuzzSelector[] memory excludedSelectors);
90
91 #[derive(Default)]
92 function excludeSenders() public view returns (address[] memory excludedSenders);
93
94 #[derive(Default)]
95 function targetArtifacts() public view returns (string[] memory targetedArtifacts);
96
97 #[derive(Default)]
98 function targetArtifactSelectors() public view returns (FuzzArtifactSelector[] memory targetedArtifactSelectors);
99
100 #[derive(Default)]
101 function targetContracts() public view returns (address[] memory targetedContracts);
102
103 #[derive(Default)]
104 function targetSelectors() public view returns (FuzzSelector[] memory targetedSelectors);
105
106 #[derive(Default)]
107 function targetSenders() public view returns (address[] memory targetedSenders);
108
109 #[derive(Default)]
110 function targetInterfaces() public view returns (FuzzInterface[] memory targetedInterfaces);
111 }
112}
113
114#[derive(Default, Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
116pub struct InvariantMetrics {
117 pub calls: usize,
119 pub reverts: usize,
121 pub discards: usize,
123}
124
125struct InvariantTestData<FEN: FoundryEvmNetwork> {
127 fuzz_cases: Vec<FuzzedCases>,
129 failures: InvariantFailures,
131 last_run_inputs: Vec<BasicTxDetails>,
133 gas_report_traces: Vec<Vec<CallTraceArena>>,
135 last_call_results: Option<RawCallResult<FEN>>,
137 line_coverage: Option<HitMaps>,
139 metrics: Map<String, InvariantMetrics>,
141
142 branch_runner: TestRunner,
147
148 optimization_best_value: Option<I256>,
151 optimization_best_sequence: Vec<BasicTxDetails>,
152}
153
154struct InvariantTest<FEN: FoundryEvmNetwork> {
156 fuzz_state: EvmFuzzState,
158 targeted_contracts: FuzzRunIdentifiedContracts,
160 test_data: InvariantTestData<FEN>,
162}
163
164impl<FEN: FoundryEvmNetwork> InvariantTest<FEN> {
165 fn new(
167 fuzz_state: EvmFuzzState,
168 targeted_contracts: FuzzRunIdentifiedContracts,
169 failures: InvariantFailures,
170 last_call_results: Option<RawCallResult<FEN>>,
171 branch_runner: TestRunner,
172 ) -> Self {
173 let mut fuzz_cases = vec![];
174 if last_call_results.is_none() {
175 fuzz_cases.push(FuzzedCases::new(vec![]));
176 }
177 let test_data = InvariantTestData {
178 fuzz_cases,
179 failures,
180 last_run_inputs: vec![],
181 gas_report_traces: vec![],
182 last_call_results,
183 line_coverage: None,
184 metrics: Map::default(),
185 branch_runner,
186 optimization_best_value: None,
187 optimization_best_sequence: vec![],
188 };
189 Self { fuzz_state, targeted_contracts, test_data }
190 }
191
192 fn reverts(&self) -> usize {
194 self.test_data.failures.reverts
195 }
196
197 fn has_errors(&self) -> bool {
199 self.test_data.failures.error.is_some()
200 }
201
202 fn set_error(&mut self, error: InvariantFuzzError) {
204 self.test_data.failures.error = Some(error);
205 }
206
207 fn set_last_call_results(&mut self, call_result: Option<RawCallResult<FEN>>) {
209 self.test_data.last_call_results = call_result;
210 }
211
212 fn set_last_run_inputs(&mut self, inputs: &Vec<BasicTxDetails>) {
214 self.test_data.last_run_inputs.clone_from(inputs);
215 }
216
217 fn merge_line_coverage(&mut self, new_coverage: Option<HitMaps>) {
219 HitMaps::merge_opt(&mut self.test_data.line_coverage, new_coverage);
220 }
221
222 fn record_metrics(&mut self, tx_details: &BasicTxDetails, reverted: bool, discarded: bool) {
226 if let Some(metric_key) =
227 self.targeted_contracts.targets.lock().fuzzed_metric_key(tx_details)
228 {
229 let test_metrics = &mut self.test_data.metrics;
230 let invariant_metrics = test_metrics.entry(metric_key).or_default();
231 invariant_metrics.calls += 1;
232 if discarded {
233 invariant_metrics.discards += 1;
234 } else if reverted {
235 invariant_metrics.reverts += 1;
236 }
237 }
238 }
239
240 fn end_run(&mut self, run: InvariantTestRun<FEN>, gas_samples: usize) {
243 self.targeted_contracts.clear_created_contracts(run.created_contracts);
245
246 if self.test_data.gas_report_traces.len() < gas_samples {
247 self.test_data
248 .gas_report_traces
249 .push(run.run_traces.into_iter().map(|arena| arena.arena).collect());
250 }
251 self.test_data.fuzz_cases.push(FuzzedCases::new(run.fuzz_runs));
252
253 self.fuzz_state.revert();
255 }
256
257 fn update_optimization_value(&mut self, value: I256, sequence: &[BasicTxDetails]) {
259 if self.test_data.optimization_best_value.is_none_or(|best| value > best) {
260 self.test_data.optimization_best_value = Some(value);
261 self.test_data.optimization_best_sequence = sequence.to_vec();
262 }
263 }
264}
265
266struct InvariantTestRun<FEN: FoundryEvmNetwork> {
268 inputs: Vec<BasicTxDetails>,
270 executor: Executor<FEN>,
272 fuzz_runs: Vec<FuzzCase>,
274 created_contracts: Vec<Address>,
276 run_traces: Vec<SparsedTraceArena>,
278 depth: u32,
280 rejects: u32,
282 new_coverage: bool,
284 optimization_value: Option<I256>,
286 optimization_prefix_len: usize,
288}
289
290impl<FEN: FoundryEvmNetwork> InvariantTestRun<FEN> {
291 fn new(first_input: BasicTxDetails, executor: Executor<FEN>, depth: usize) -> Self {
293 Self {
294 inputs: vec![first_input],
295 executor,
296 fuzz_runs: Vec::with_capacity(depth),
297 created_contracts: vec![],
298 run_traces: vec![],
299 depth: 0,
300 rejects: 0,
301 new_coverage: false,
302 optimization_value: None,
303 optimization_prefix_len: 0,
304 }
305 }
306}
307
308pub struct InvariantExecutor<'a, FEN: FoundryEvmNetwork> {
315 pub executor: Executor<FEN>,
316 runner: TestRunner,
318 config: InvariantConfig,
320 setup_contracts: &'a ContractsByAddress,
322 project_contracts: &'a ContractsByArtifact,
325 artifact_filters: ArtifactFilters,
327}
328
329impl<'a, FEN: FoundryEvmNetwork> InvariantExecutor<'a, FEN> {
330 pub fn new(
332 executor: Executor<FEN>,
333 runner: TestRunner,
334 config: InvariantConfig,
335 setup_contracts: &'a ContractsByAddress,
336 project_contracts: &'a ContractsByArtifact,
337 ) -> Self {
338 Self {
339 executor,
340 runner,
341 config,
342 setup_contracts,
343 project_contracts,
344 artifact_filters: ArtifactFilters::default(),
345 }
346 }
347
348 pub fn config(self) -> InvariantConfig {
349 self.config
350 }
351
352 pub fn invariant_fuzz(
354 &mut self,
355 invariant_contract: InvariantContract<'_>,
356 fuzz_fixtures: &FuzzFixtures,
357 fuzz_state: EvmFuzzState,
358 progress: Option<&ProgressBar>,
359 early_exit: &EarlyExit,
360 ) -> Result<InvariantFuzzTestResult> {
361 if !invariant_contract.invariant_function.inputs.is_empty() {
363 return Err(eyre!("Invariant test function should have no inputs"));
364 }
365
366 let (mut invariant_test, mut corpus_manager) =
367 self.prepare_test(&invariant_contract, fuzz_fixtures, fuzz_state)?;
368
369 let mut runs = 0;
371 let timer = FuzzTestTimer::new(self.config.timeout);
372 let mut last_metrics_report = Instant::now();
373 let continue_campaign = |runs: u32| {
374 if early_exit.should_stop() {
375 return false;
376 }
377
378 if timer.is_enabled() { !timer.is_timed_out() } else { runs < self.config.runs }
379 };
380
381 let edge_coverage_enabled = self.config.corpus.collect_edge_coverage();
383
384 'stop: while continue_campaign(runs) {
385 let initial_seq = corpus_manager.new_inputs(
386 &mut invariant_test.test_data.branch_runner,
387 &invariant_test.fuzz_state,
388 &invariant_test.targeted_contracts,
389 )?;
390
391 let mut current_run = InvariantTestRun::new(
393 initial_seq[0].clone(),
394 self.executor.clone(),
396 self.config.depth as usize,
397 );
398
399 if self.config.fail_on_revert && invariant_test.reverts() > 0 {
401 return Err(eyre!("call reverted"));
402 }
403
404 while current_run.depth < self.config.depth {
405 if timer.is_timed_out() {
407 break 'stop;
412 }
413
414 let tx = current_run
415 .inputs
416 .last()
417 .ok_or_else(|| eyre!("no input generated to call fuzzed target."))?;
418
419 let mut call_result = execute_tx(&mut current_run.executor, tx)?;
422 let discarded = call_result.result.as_ref() == MAGIC_ASSUME;
423 if self.config.show_metrics {
424 invariant_test.record_metrics(tx, call_result.reverted, discarded);
425 }
426
427 invariant_test.merge_line_coverage(call_result.line_coverage.clone());
429 if corpus_manager.merge_edge_coverage(&mut call_result) {
431 current_run.new_coverage = true;
432 }
433
434 if discarded {
435 current_run.inputs.pop();
436 current_run.rejects += 1;
437 if current_run.rejects > self.config.max_assume_rejects {
438 invariant_test.set_error(InvariantFuzzError::MaxAssumeRejects(
439 self.config.max_assume_rejects,
440 ));
441 break 'stop;
442 }
443 } else {
444 current_run.executor.commit(&mut call_result);
446
447 let mut state_changeset = std::mem::take(&mut call_result.state_changeset);
455 if !call_result.reverted {
456 collect_data(
457 &invariant_test,
458 &mut state_changeset,
459 tx,
460 &call_result,
461 self.config.depth,
462 );
463 }
464
465 if let Err(error) =
468 &invariant_test.targeted_contracts.collect_created_contracts(
469 &state_changeset,
470 self.project_contracts,
471 self.setup_contracts,
472 &self.artifact_filters,
473 &mut current_run.created_contracts,
474 )
475 {
476 warn!(target: "forge::test", "{error}");
477 }
478 current_run
479 .fuzz_runs
480 .push(FuzzCase { gas: call_result.gas_used, stipend: call_result.stipend });
481
482 let is_last_call = current_run.depth == self.config.depth - 1;
488 let is_optimization = invariant_contract.is_optimization();
492 let should_check_invariant = is_optimization
493 || if self.config.check_interval == 0 {
494 is_last_call
495 } else {
496 self.config.check_interval == 1
497 || (current_run.depth + 1)
498 .is_multiple_of(self.config.check_interval)
499 || is_last_call
500 };
501
502 let result = if should_check_invariant {
503 can_continue(
504 &invariant_contract,
505 &mut invariant_test,
506 &mut current_run,
507 &self.config,
508 call_result,
509 &state_changeset,
510 )
511 .map_err(|e| eyre!(e.to_string()))?
512 } else {
513 if call_result.reverted {
515 invariant_test.test_data.failures.reverts += 1;
516 if self.config.fail_on_revert {
517 let case_data = error::FailedInvariantCaseData::new(
518 &invariant_contract,
519 &self.config,
520 &invariant_test.targeted_contracts,
521 ¤t_run.inputs,
522 call_result,
523 &[],
524 );
525 invariant_test.test_data.failures.revert_reason =
526 Some(case_data.revert_reason.clone());
527 invariant_test.test_data.failures.error =
528 Some(InvariantFuzzError::Revert(case_data));
529 result::RichInvariantResults::new(false, None)
530 } else if !invariant_contract.is_optimization()
531 && !self.config.has_delay()
532 {
533 current_run.inputs.pop();
537 result::RichInvariantResults::new(true, None)
538 } else {
539 result::RichInvariantResults::new(true, None)
540 }
541 } else {
542 result::RichInvariantResults::new(true, None)
543 }
544 };
545
546 if !result.can_continue || current_run.depth == self.config.depth - 1 {
547 invariant_test.set_last_run_inputs(¤t_run.inputs);
548 }
549 if !result.can_continue {
551 break 'stop;
552 }
553
554 invariant_test.set_last_call_results(result.call_result);
555 current_run.depth += 1;
556 }
557
558 current_run.inputs.push(corpus_manager.generate_next_input(
559 &mut invariant_test.test_data.branch_runner,
560 &initial_seq,
561 discarded,
562 current_run.depth as usize,
563 )?);
564 }
565
566 let optimization = current_run.optimization_value.map(|v| {
570 let prefix = current_run.inputs[..current_run.optimization_prefix_len].to_vec();
571 (v, prefix)
572 });
573 corpus_manager.process_inputs(
574 ¤t_run.inputs,
575 current_run.new_coverage,
576 optimization,
577 );
578
579 if invariant_contract.call_after_invariant && !invariant_test.has_errors() {
581 assert_after_invariant(
582 &invariant_contract,
583 &mut invariant_test,
584 ¤t_run,
585 &self.config,
586 )
587 .map_err(|_| eyre!("Failed to call afterInvariant"))?;
588 }
589
590 invariant_test.end_run(current_run, self.config.gas_report_samples as usize);
592 if let Some(progress) = progress {
593 progress.inc(1);
595 let best = invariant_test.test_data.optimization_best_value;
597 if edge_coverage_enabled || best.is_some() {
598 let mut msg = String::new();
599 if let Some(best) = best {
600 msg.push_str(&format!("best: {best}"));
601 }
602 if edge_coverage_enabled {
603 if !msg.is_empty() {
604 msg.push_str(", ");
605 }
606 msg.push_str(&format!("{}", &corpus_manager.metrics));
607 }
608 progress.set_message(msg);
609 }
610 } else if edge_coverage_enabled
611 && last_metrics_report.elapsed() > DURATION_BETWEEN_METRICS_REPORT
612 {
613 let mut metrics = json!({
615 "timestamp": SystemTime::now()
616 .duration_since(UNIX_EPOCH)?
617 .as_secs(),
618 "invariant": invariant_contract.invariant_function.name,
619 "metrics": &corpus_manager.metrics,
620 });
621 if let Some(best) = invariant_test.test_data.optimization_best_value {
622 metrics["optimization_best"] = json!(best.to_string());
623 }
624 let _ = sh_println!("{}", serde_json::to_string(&metrics)?);
625 last_metrics_report = Instant::now();
626 }
627
628 runs += 1;
629 }
630
631 trace!(?fuzz_fixtures);
632 invariant_test.fuzz_state.log_stats();
633
634 let result = invariant_test.test_data;
635 Ok(InvariantFuzzTestResult {
636 error: result.failures.error,
637 cases: result.fuzz_cases,
638 reverts: result.failures.reverts,
639 last_run_inputs: result.last_run_inputs,
640 gas_report_traces: result.gas_report_traces,
641 line_coverage: result.line_coverage,
642 metrics: result.metrics,
643 failed_corpus_replays: corpus_manager.failed_replays,
644 optimization_best_value: result.optimization_best_value,
645 optimization_best_sequence: result.optimization_best_sequence,
646 })
647 }
648
649 fn prepare_test(
653 &mut self,
654 invariant_contract: &InvariantContract<'_>,
655 fuzz_fixtures: &FuzzFixtures,
656 fuzz_state: EvmFuzzState,
657 ) -> Result<(InvariantTest<FEN>, WorkerCorpus)> {
658 self.select_contract_artifacts(invariant_contract.address)?;
660 let (targeted_senders, targeted_contracts) =
661 self.select_contracts_and_senders(invariant_contract.address)?;
662
663 let strategy = invariant_strat(
665 fuzz_state.clone(),
666 targeted_senders,
667 targeted_contracts.clone(),
668 self.config.clone(),
669 fuzz_fixtures.clone(),
670 )
671 .no_shrink();
672
673 let fuzz_state =
676 if targeted_contracts.targets.lock().iter().any(|(_, t)| t.storage_layout.is_some()) {
677 fuzz_state.with_mapping_slots(AddressMap::default())
678 } else {
679 fuzz_state
680 };
681
682 self.executor.inspector_mut().set_fuzzer(Fuzzer {
686 call_generator: None,
687 fuzz_state: fuzz_state.clone(),
688 collect: true,
689 });
690
691 let mut failures = InvariantFailures::new();
696 let last_call_results = assert_invariants(
697 invariant_contract,
698 &self.config,
699 &targeted_contracts,
700 &self.executor,
701 &[],
702 &mut failures,
703 )?;
704 if let Some(error) = failures.error {
705 return Err(eyre!(error.revert_reason().unwrap_or_default()));
706 }
707
708 if self.config.call_override {
712 let target_contract_ref = Arc::new(RwLock::new(Address::ZERO));
713
714 let handler_addresses: std::collections::HashSet<Address> =
717 targeted_contracts.targets.lock().keys().copied().collect();
718
719 let call_generator = RandomCallGenerator::new(
720 invariant_contract.address,
721 handler_addresses,
722 self.runner.clone(),
723 override_call_strat(
724 fuzz_state.clone(),
725 targeted_contracts.clone(),
726 target_contract_ref.clone(),
727 fuzz_fixtures.clone(),
728 ),
729 target_contract_ref,
730 );
731
732 if let Some(fuzzer) = self.executor.inspector_mut().fuzzer.as_mut() {
733 fuzzer.call_generator = Some(call_generator);
734 }
735 }
736
737 let worker = WorkerCorpus::new(
738 0,
739 self.config.corpus.clone(),
740 strategy.boxed(),
741 Some(&self.executor),
742 None,
743 Some(&targeted_contracts),
744 )?;
745
746 let mut invariant_test = InvariantTest::new(
747 fuzz_state,
748 targeted_contracts,
749 failures,
750 last_call_results,
751 self.runner.clone(),
752 );
753
754 if invariant_contract.is_optimization() {
757 let (opt_best_value, opt_best_sequence) = worker.optimization_initial_state();
758 invariant_test.test_data.optimization_best_value = opt_best_value;
759 invariant_test.test_data.optimization_best_sequence = opt_best_sequence;
760 }
761
762 Ok((invariant_test, worker))
763 }
764
765 pub fn select_contract_artifacts(&mut self, invariant_address: Address) -> Result<()> {
775 let targeted_artifact_selectors = self
776 .executor
777 .call_sol_default(invariant_address, &IInvariantTest::targetArtifactSelectorsCall {});
778
779 for IInvariantTest::FuzzArtifactSelector { artifact, selectors } in
781 targeted_artifact_selectors
782 {
783 let identifier = self.validate_selected_contract(artifact, &selectors)?;
784 self.artifact_filters.targeted.entry(identifier).or_default().extend(selectors);
785 }
786
787 let targeted_artifacts = self
788 .executor
789 .call_sol_default(invariant_address, &IInvariantTest::targetArtifactsCall {});
790 let excluded_artifacts = self
791 .executor
792 .call_sol_default(invariant_address, &IInvariantTest::excludeArtifactsCall {});
793
794 for contract in excluded_artifacts {
796 let identifier = self.validate_selected_contract(contract, &[])?;
797
798 if !self.artifact_filters.excluded.contains(&identifier) {
799 self.artifact_filters.excluded.push(identifier);
800 }
801 }
802
803 for (artifact, contract) in self.project_contracts.iter() {
805 if contract
806 .abi
807 .functions()
808 .filter(|func| {
809 !matches!(
810 func.state_mutability,
811 alloy_json_abi::StateMutability::Pure
812 | alloy_json_abi::StateMutability::View
813 )
814 })
815 .count()
816 == 0
817 && !self.artifact_filters.excluded.contains(&artifact.identifier())
818 {
819 self.artifact_filters.excluded.push(artifact.identifier());
820 }
821 }
822
823 for contract in targeted_artifacts {
826 let identifier = self.validate_selected_contract(contract, &[])?;
827
828 if !self.artifact_filters.targeted.contains_key(&identifier)
829 && !self.artifact_filters.excluded.contains(&identifier)
830 {
831 self.artifact_filters.targeted.insert(identifier, vec![]);
832 }
833 }
834 Ok(())
835 }
836
837 fn validate_selected_contract(
840 &mut self,
841 contract: String,
842 selectors: &[FixedBytes<4>],
843 ) -> Result<String> {
844 if let Some((artifact, contract_data)) =
845 self.project_contracts.find_by_name_or_identifier(&contract)?
846 {
847 for selector in selectors {
849 contract_data
850 .abi
851 .functions()
852 .find(|func| func.selector().as_slice() == selector.as_slice())
853 .wrap_err(format!("{contract} does not have the selector {selector:?}"))?;
854 }
855
856 return Ok(artifact.identifier());
857 }
858 eyre::bail!(
859 "{contract} not found in the project. Allowed format: `contract_name` or `contract_path:contract_name`."
860 );
861 }
862
863 pub fn select_contracts_and_senders(
866 &self,
867 to: Address,
868 ) -> Result<(SenderFilters, FuzzRunIdentifiedContracts)> {
869 let targeted_senders =
870 self.executor.call_sol_default(to, &IInvariantTest::targetSendersCall {});
871 let mut excluded_senders =
872 self.executor.call_sol_default(to, &IInvariantTest::excludeSendersCall {});
873 excluded_senders.extend([
875 CHEATCODE_ADDRESS,
876 HARDHAT_CONSOLE_ADDRESS,
877 DEFAULT_CREATE2_DEPLOYER,
878 ]);
879 excluded_senders.extend(PRECOMPILES);
881 let sender_filters = SenderFilters::new(targeted_senders, excluded_senders);
882
883 let selected = self.executor.call_sol_default(to, &IInvariantTest::targetContractsCall {});
884 let excluded = self.executor.call_sol_default(to, &IInvariantTest::excludeContractsCall {});
885
886 let contracts = self
887 .setup_contracts
888 .iter()
889 .filter(|&(addr, (identifier, _))| {
890 if *addr == to && selected.contains(&to) {
892 return true;
893 }
894
895 *addr != to
896 && *addr != CHEATCODE_ADDRESS
897 && *addr != HARDHAT_CONSOLE_ADDRESS
898 && (selected.is_empty() || selected.contains(addr))
899 && (excluded.is_empty() || !excluded.contains(addr))
900 && self.artifact_filters.matches(identifier)
901 })
902 .map(|(addr, (identifier, abi))| {
903 (
904 *addr,
905 TargetedContract::new(identifier.clone(), abi.clone())
906 .with_project_contracts(self.project_contracts),
907 )
908 })
909 .collect();
910 let mut contracts = TargetedContracts { inner: contracts };
911
912 self.target_interfaces(to, &mut contracts)?;
913
914 self.select_selectors(to, &mut contracts)?;
915
916 if contracts.is_empty() {
918 eyre::bail!("No contracts to fuzz.");
919 }
920
921 Ok((sender_filters, FuzzRunIdentifiedContracts::new(contracts, selected.is_empty())))
922 }
923
924 pub fn target_interfaces(
929 &self,
930 invariant_address: Address,
931 targeted_contracts: &mut TargetedContracts,
932 ) -> Result<()> {
933 let interfaces = self
934 .executor
935 .call_sol_default(invariant_address, &IInvariantTest::targetInterfacesCall {});
936
937 let mut combined = TargetedContracts::new();
943
944 for IInvariantTest::FuzzInterface { addr, artifacts } in &interfaces {
947 for identifier in artifacts {
949 if let Some((_, contract_data)) =
951 self.project_contracts.iter().find(|(artifact, _)| {
952 &artifact.name == identifier || &artifact.identifier() == identifier
953 })
954 {
955 let abi = &contract_data.abi;
956 combined
957 .entry(*addr)
959 .and_modify(|entry| {
961 entry.abi.functions.extend(abi.functions.clone());
963 })
964 .or_insert_with(|| {
966 let mut contract =
967 TargetedContract::new(identifier.clone(), abi.clone());
968 contract.storage_layout =
969 contract_data.storage_layout.as_ref().map(Arc::clone);
970 contract
971 });
972 }
973 }
974 }
975
976 targeted_contracts.extend(combined.inner);
977
978 Ok(())
979 }
980
981 pub fn select_selectors(
984 &self,
985 address: Address,
986 targeted_contracts: &mut TargetedContracts,
987 ) -> Result<()> {
988 for (address, (identifier, _)) in self.setup_contracts {
989 if let Some(selectors) = self.artifact_filters.targeted.get(identifier) {
990 self.add_address_with_functions(*address, selectors, false, targeted_contracts)?;
991 }
992 }
993
994 let mut target_test_selectors = vec![];
995 let mut excluded_test_selectors = vec![];
996
997 let selectors =
999 self.executor.call_sol_default(address, &IInvariantTest::targetSelectorsCall {});
1000 for IInvariantTest::FuzzSelector { addr, selectors } in selectors {
1001 if addr == address {
1002 target_test_selectors = selectors.clone();
1003 }
1004 self.add_address_with_functions(addr, &selectors, false, targeted_contracts)?;
1005 }
1006
1007 let excluded_selectors =
1009 self.executor.call_sol_default(address, &IInvariantTest::excludeSelectorsCall {});
1010 for IInvariantTest::FuzzSelector { addr, selectors } in excluded_selectors {
1011 if addr == address {
1012 excluded_test_selectors = selectors.clone();
1015 }
1016 self.add_address_with_functions(addr, &selectors, true, targeted_contracts)?;
1017 }
1018
1019 if target_test_selectors.is_empty()
1020 && let Some(target) = targeted_contracts.get(&address)
1021 {
1022 let selectors: Vec<_> = target
1026 .abi
1027 .functions()
1028 .filter_map(|func| {
1029 if matches!(
1030 func.state_mutability,
1031 alloy_json_abi::StateMutability::Pure
1032 | alloy_json_abi::StateMutability::View
1033 ) || func.is_reserved()
1034 || excluded_test_selectors.contains(&func.selector())
1035 {
1036 None
1037 } else {
1038 Some(func.selector())
1039 }
1040 })
1041 .collect();
1042 self.add_address_with_functions(address, &selectors, false, targeted_contracts)?;
1043 }
1044
1045 Ok(())
1046 }
1047
1048 fn add_address_with_functions(
1050 &self,
1051 address: Address,
1052 selectors: &[Selector],
1053 should_exclude: bool,
1054 targeted_contracts: &mut TargetedContracts,
1055 ) -> eyre::Result<()> {
1056 if selectors.is_empty() {
1058 return Ok(());
1059 }
1060
1061 let contract = match targeted_contracts.entry(address) {
1062 Entry::Occupied(entry) => entry.into_mut(),
1063 Entry::Vacant(entry) => {
1064 let (identifier, abi) = self.setup_contracts.get(&address).ok_or_else(|| {
1065 eyre::eyre!(
1066 "[{}] address does not have an associated contract: {}",
1067 if should_exclude { "excludeSelectors" } else { "targetSelectors" },
1068 address
1069 )
1070 })?;
1071 entry.insert(
1072 TargetedContract::new(identifier.clone(), abi.clone())
1073 .with_project_contracts(self.project_contracts),
1074 )
1075 }
1076 };
1077 contract.add_selectors(selectors.iter().copied(), should_exclude)?;
1078 Ok(())
1079 }
1080
1081 pub fn compute_settings(&mut self, invariant_address: Address) -> Result<InvariantSettings> {
1086 self.select_contract_artifacts(invariant_address)?;
1087 let (sender_filters, targeted_contracts) =
1088 self.select_contracts_and_senders(invariant_address)?;
1089 let targets = targeted_contracts.targets.lock();
1090 Ok(InvariantSettings::new(&targets, &sender_filters, self.config.fail_on_revert))
1091 }
1092}
1093
1094fn collect_data<FEN: FoundryEvmNetwork>(
1098 invariant_test: &InvariantTest<FEN>,
1099 state_changeset: &mut AddressMap<Account>,
1100 tx: &BasicTxDetails,
1101 call_result: &RawCallResult<FEN>,
1102 run_depth: u32,
1103) {
1104 let mut has_code = false;
1106 if let Some(Some(code)) =
1107 state_changeset.get(&tx.sender).map(|account| account.info.code.as_ref())
1108 {
1109 has_code = !code.is_empty();
1110 }
1111
1112 let mut sender_changeset = None;
1114 if !has_code {
1115 sender_changeset = state_changeset.remove(&tx.sender);
1116 }
1117
1118 invariant_test.fuzz_state.collect_values_from_call(
1120 &invariant_test.targeted_contracts,
1121 tx,
1122 &call_result.result,
1123 &call_result.logs,
1124 &*state_changeset,
1125 run_depth,
1126 );
1127
1128 if let Some(changed) = sender_changeset {
1130 state_changeset.insert(tx.sender, changed);
1131 }
1132}
1133
1134pub(crate) fn call_after_invariant_function<FEN: FoundryEvmNetwork>(
1138 executor: &Executor<FEN>,
1139 to: Address,
1140) -> Result<(RawCallResult<FEN>, bool), EvmError<FEN>> {
1141 let calldata = Bytes::from_static(&IInvariantTest::afterInvariantCall::SELECTOR);
1142 let mut call_result = executor.call_raw(CALLER, to, calldata, U256::ZERO)?;
1143 let success = executor.is_raw_call_mut_success(to, &mut call_result, false);
1144 Ok((call_result, success))
1145}
1146
1147pub(crate) fn call_invariant_function<FEN: FoundryEvmNetwork>(
1149 executor: &Executor<FEN>,
1150 address: Address,
1151 calldata: Bytes,
1152) -> Result<(RawCallResult<FEN>, bool)> {
1153 let mut call_result = executor.call_raw(CALLER, address, calldata, U256::ZERO)?;
1154 let success = executor.is_raw_call_mut_success(address, &mut call_result, false);
1155 Ok((call_result, success))
1156}
1157
1158pub(crate) fn execute_tx<FEN: FoundryEvmNetwork>(
1161 executor: &mut Executor<FEN>,
1162 tx: &BasicTxDetails,
1163) -> Result<RawCallResult<FEN>> {
1164 let warp = tx.warp.unwrap_or_default();
1165 let roll = tx.roll.unwrap_or_default();
1166
1167 if warp > 0 || roll > 0 {
1168 let ts = executor.evm_env().block_env.timestamp();
1170 let num = executor.evm_env().block_env.number();
1171 executor.evm_env_mut().block_env.set_timestamp(ts + warp);
1172 executor.evm_env_mut().block_env.set_number(num + roll);
1173
1174 let block_env = executor.evm_env().block_env.clone();
1178 if let Some(cheatcodes) = executor.inspector_mut().cheatcodes.as_mut() {
1179 if let Some(block) = cheatcodes.block.as_mut() {
1180 let bts = block.timestamp();
1181 let bnum = block.number();
1182 block.set_timestamp(bts + warp);
1183 block.set_number(bnum + roll);
1184 } else {
1185 cheatcodes.block = Some(block_env);
1186 }
1187 }
1188 }
1189
1190 executor
1191 .call_raw(tx.sender, tx.call_details.target, tx.call_details.calldata.clone(), U256::ZERO)
1192 .map_err(|e| eyre!(format!("Could not make raw evm call: {e}")))
1193}