1use crate::{
2 executors::{Executor, RawCallResult},
3 inspectors::Fuzzer,
4};
5use alloy_primitives::{
6 Address, Bytes, FixedBytes, Selector, U256,
7 map::{AddressMap, HashMap},
8};
9use alloy_sol_types::{SolCall, sol};
10use eyre::{ContextCompat, Result, eyre};
11use foundry_common::contracts::{ContractsByAddress, ContractsByArtifact};
12use foundry_config::InvariantConfig;
13use foundry_evm_core::{
14 constants::{
15 CALLER, CHEATCODE_ADDRESS, DEFAULT_CREATE2_DEPLOYER, HARDHAT_CONSOLE_ADDRESS, MAGIC_ASSUME,
16 },
17 precompiles::PRECOMPILES,
18};
19use foundry_evm_fuzz::{
20 BasicTxDetails, FuzzCase, FuzzFixtures, FuzzedCases,
21 invariant::{
22 ArtifactFilters, FuzzRunIdentifiedContracts, InvariantContract, RandomCallGenerator,
23 SenderFilters, TargetedContract, TargetedContracts,
24 },
25 strategies::{EvmFuzzState, invariant_strat, override_call_strat},
26};
27use foundry_evm_traces::{CallTraceArena, SparsedTraceArena};
28use indicatif::ProgressBar;
29use parking_lot::RwLock;
30use proptest::{strategy::Strategy, test_runner::TestRunner};
31use result::{assert_after_invariant, assert_invariants, can_continue};
32use revm::state::Account;
33use shrink::shrink_sequence;
34use std::{
35 collections::{HashMap as Map, btree_map::Entry},
36 sync::Arc,
37 time::{Instant, SystemTime, UNIX_EPOCH},
38};
39
40mod error;
41pub use error::{InvariantFailures, InvariantFuzzError};
42use foundry_evm_coverage::HitMaps;
43
44mod replay;
45pub use replay::{replay_error, replay_run};
46
47mod result;
48use foundry_common::{TestFunctionExt, sh_println};
49pub use result::InvariantFuzzTestResult;
50use serde::{Deserialize, Serialize};
51use serde_json::json;
52
53mod shrink;
54use crate::executors::{
55 DURATION_BETWEEN_METRICS_REPORT, EvmError, FailFast, FuzzTestTimer, corpus::CorpusManager,
56};
57pub use shrink::check_sequence;
58
59sol! {
60 interface IInvariantTest {
61 #[derive(Default)]
62 struct FuzzSelector {
63 address addr;
64 bytes4[] selectors;
65 }
66
67 #[derive(Default)]
68 struct FuzzArtifactSelector {
69 string artifact;
70 bytes4[] selectors;
71 }
72
73 #[derive(Default)]
74 struct FuzzInterface {
75 address addr;
76 string[] artifacts;
77 }
78
79 function afterInvariant() external;
80
81 #[derive(Default)]
82 function excludeArtifacts() public view returns (string[] memory excludedArtifacts);
83
84 #[derive(Default)]
85 function excludeContracts() public view returns (address[] memory excludedContracts);
86
87 #[derive(Default)]
88 function excludeSelectors() public view returns (FuzzSelector[] memory excludedSelectors);
89
90 #[derive(Default)]
91 function excludeSenders() public view returns (address[] memory excludedSenders);
92
93 #[derive(Default)]
94 function targetArtifacts() public view returns (string[] memory targetedArtifacts);
95
96 #[derive(Default)]
97 function targetArtifactSelectors() public view returns (FuzzArtifactSelector[] memory targetedArtifactSelectors);
98
99 #[derive(Default)]
100 function targetContracts() public view returns (address[] memory targetedContracts);
101
102 #[derive(Default)]
103 function targetSelectors() public view returns (FuzzSelector[] memory targetedSelectors);
104
105 #[derive(Default)]
106 function targetSenders() public view returns (address[] memory targetedSenders);
107
108 #[derive(Default)]
109 function targetInterfaces() public view returns (FuzzInterface[] memory targetedInterfaces);
110 }
111}
112
113#[derive(Default, Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
115pub struct InvariantMetrics {
116 pub calls: usize,
118 pub reverts: usize,
120 pub discards: usize,
122}
123
124struct InvariantTestData {
126 fuzz_cases: Vec<FuzzedCases>,
128 failures: InvariantFailures,
130 last_run_inputs: Vec<BasicTxDetails>,
132 gas_report_traces: Vec<Vec<CallTraceArena>>,
134 last_call_results: Option<RawCallResult>,
136 line_coverage: Option<HitMaps>,
138 metrics: Map<String, InvariantMetrics>,
140
141 branch_runner: TestRunner,
146}
147
148struct InvariantTest {
150 fuzz_state: EvmFuzzState,
152 targeted_contracts: FuzzRunIdentifiedContracts,
154 test_data: InvariantTestData,
156}
157
158impl InvariantTest {
159 fn new(
161 fuzz_state: EvmFuzzState,
162 targeted_contracts: FuzzRunIdentifiedContracts,
163 failures: InvariantFailures,
164 last_call_results: Option<RawCallResult>,
165 branch_runner: TestRunner,
166 ) -> Self {
167 let mut fuzz_cases = vec![];
168 if last_call_results.is_none() {
169 fuzz_cases.push(FuzzedCases::new(vec![]));
170 }
171 let test_data = InvariantTestData {
172 fuzz_cases,
173 failures,
174 last_run_inputs: vec![],
175 gas_report_traces: vec![],
176 last_call_results,
177 line_coverage: None,
178 metrics: Map::default(),
179 branch_runner,
180 };
181 Self { fuzz_state, targeted_contracts, test_data }
182 }
183
184 fn reverts(&self) -> usize {
186 self.test_data.failures.reverts
187 }
188
189 fn has_errors(&self) -> bool {
191 self.test_data.failures.error.is_some()
192 }
193
194 fn set_error(&mut self, error: InvariantFuzzError) {
196 self.test_data.failures.error = Some(error);
197 }
198
199 fn set_last_call_results(&mut self, call_result: Option<RawCallResult>) {
201 self.test_data.last_call_results = call_result;
202 }
203
204 fn set_last_run_inputs(&mut self, inputs: &Vec<BasicTxDetails>) {
206 self.test_data.last_run_inputs.clone_from(inputs);
207 }
208
209 fn merge_line_coverage(&mut self, new_coverage: Option<HitMaps>) {
211 HitMaps::merge_opt(&mut self.test_data.line_coverage, new_coverage);
212 }
213
214 fn record_metrics(&mut self, tx_details: &BasicTxDetails, reverted: bool, discarded: bool) {
218 if let Some(metric_key) =
219 self.targeted_contracts.targets.lock().fuzzed_metric_key(tx_details)
220 {
221 let test_metrics = &mut self.test_data.metrics;
222 let invariant_metrics = test_metrics.entry(metric_key).or_default();
223 invariant_metrics.calls += 1;
224 if discarded {
225 invariant_metrics.discards += 1;
226 } else if reverted {
227 invariant_metrics.reverts += 1;
228 }
229 }
230 }
231
232 fn end_run(&mut self, run: InvariantTestRun, gas_samples: usize) {
235 self.targeted_contracts.clear_created_contracts(run.created_contracts);
237
238 if self.test_data.gas_report_traces.len() < gas_samples {
239 self.test_data
240 .gas_report_traces
241 .push(run.run_traces.into_iter().map(|arena| arena.arena).collect());
242 }
243 self.test_data.fuzz_cases.push(FuzzedCases::new(run.fuzz_runs));
244
245 self.fuzz_state.revert();
247 }
248}
249
250struct InvariantTestRun {
252 inputs: Vec<BasicTxDetails>,
254 executor: Executor,
256 fuzz_runs: Vec<FuzzCase>,
258 created_contracts: Vec<Address>,
260 run_traces: Vec<SparsedTraceArena>,
262 depth: u32,
264 rejects: u32,
266 new_coverage: bool,
268}
269
270impl InvariantTestRun {
271 fn new(first_input: BasicTxDetails, executor: Executor, depth: usize) -> Self {
273 Self {
274 inputs: vec![first_input],
275 executor,
276 fuzz_runs: Vec::with_capacity(depth),
277 created_contracts: vec![],
278 run_traces: vec![],
279 depth: 0,
280 rejects: 0,
281 new_coverage: false,
282 }
283 }
284}
285
286pub struct InvariantExecutor<'a> {
293 pub executor: Executor,
294 runner: TestRunner,
296 config: InvariantConfig,
298 setup_contracts: &'a ContractsByAddress,
300 project_contracts: &'a ContractsByArtifact,
303 artifact_filters: ArtifactFilters,
305}
306
307impl<'a> InvariantExecutor<'a> {
308 pub fn new(
310 executor: Executor,
311 runner: TestRunner,
312 config: InvariantConfig,
313 setup_contracts: &'a ContractsByAddress,
314 project_contracts: &'a ContractsByArtifact,
315 ) -> Self {
316 Self {
317 executor,
318 runner,
319 config,
320 setup_contracts,
321 project_contracts,
322 artifact_filters: ArtifactFilters::default(),
323 }
324 }
325
326 pub fn invariant_fuzz(
328 &mut self,
329 invariant_contract: InvariantContract<'_>,
330 fuzz_fixtures: &FuzzFixtures,
331 deployed_libs: &[Address],
332 progress: Option<&ProgressBar>,
333 fail_fast: &FailFast,
334 ) -> Result<InvariantFuzzTestResult> {
335 if !invariant_contract.invariant_function.inputs.is_empty() {
337 return Err(eyre!("Invariant test function should have no inputs"));
338 }
339
340 let (mut invariant_test, mut corpus_manager) =
341 self.prepare_test(&invariant_contract, fuzz_fixtures, deployed_libs)?;
342
343 let mut runs = 0;
345 let timer = FuzzTestTimer::new(self.config.timeout);
346 let mut last_metrics_report = Instant::now();
347 let continue_campaign = |runs: u32| {
348 if fail_fast.should_stop() {
349 return false;
350 }
351
352 if timer.is_enabled() { !timer.is_timed_out() } else { runs < self.config.runs }
353 };
354
355 let edge_coverage_enabled = self.config.corpus.collect_edge_coverage();
357
358 'stop: while continue_campaign(runs) {
359 let initial_seq = corpus_manager.new_inputs(
360 &mut invariant_test.test_data.branch_runner,
361 &invariant_test.fuzz_state,
362 &invariant_test.targeted_contracts,
363 )?;
364
365 let mut current_run = InvariantTestRun::new(
367 initial_seq[0].clone(),
368 self.executor.clone(),
370 self.config.depth as usize,
371 );
372
373 if self.config.fail_on_revert && invariant_test.reverts() > 0 {
375 return Err(eyre!("call reverted"));
376 }
377
378 while current_run.depth < self.config.depth {
379 if timer.is_timed_out() {
381 break 'stop;
386 }
387
388 let tx = current_run
389 .inputs
390 .last()
391 .ok_or_else(|| eyre!("no input generated to call fuzzed target."))?;
392
393 let mut call_result = current_run
396 .executor
397 .call_raw(
398 tx.sender,
399 tx.call_details.target,
400 tx.call_details.calldata.clone(),
401 U256::ZERO,
402 )
403 .map_err(|e| eyre!(format!("Could not make raw evm call: {e}")))?;
404
405 let discarded = call_result.result.as_ref() == MAGIC_ASSUME;
406 if self.config.show_metrics {
407 invariant_test.record_metrics(tx, call_result.reverted, discarded);
408 }
409
410 invariant_test.merge_line_coverage(call_result.line_coverage.clone());
412 if corpus_manager.merge_edge_coverage(&mut call_result) {
414 current_run.new_coverage = true;
415 }
416
417 if discarded {
418 current_run.inputs.pop();
419 current_run.rejects += 1;
420 if current_run.rejects > self.config.max_assume_rejects {
421 invariant_test.set_error(InvariantFuzzError::MaxAssumeRejects(
422 self.config.max_assume_rejects,
423 ));
424 break 'stop;
425 }
426 } else {
427 current_run.executor.commit(&mut call_result);
429
430 let mut state_changeset = call_result.state_changeset.clone();
438 if !call_result.reverted {
439 collect_data(
440 &invariant_test,
441 &mut state_changeset,
442 tx,
443 &call_result,
444 self.config.depth,
445 );
446 }
447
448 if let Err(error) =
451 &invariant_test.targeted_contracts.collect_created_contracts(
452 &state_changeset,
453 self.project_contracts,
454 self.setup_contracts,
455 &self.artifact_filters,
456 &mut current_run.created_contracts,
457 )
458 {
459 warn!(target: "forge::test", "{error}");
460 }
461 current_run.fuzz_runs.push(FuzzCase {
462 calldata: tx.call_details.calldata.clone(),
463 gas: call_result.gas_used,
464 stipend: call_result.stipend,
465 });
466
467 let result = can_continue(
469 &invariant_contract,
470 &mut invariant_test,
471 &mut current_run,
472 &self.config,
473 call_result,
474 &state_changeset,
475 )
476 .map_err(|e| eyre!(e.to_string()))?;
477 if !result.can_continue || current_run.depth == self.config.depth - 1 {
478 invariant_test.set_last_run_inputs(¤t_run.inputs);
479 }
480 if !result.can_continue {
482 break 'stop;
483 }
484
485 invariant_test.set_last_call_results(result.call_result);
486 current_run.depth += 1;
487 }
488
489 current_run.inputs.push(corpus_manager.generate_next_input(
490 &mut invariant_test.test_data.branch_runner,
491 &initial_seq,
492 discarded,
493 current_run.depth as usize,
494 )?);
495 }
496
497 corpus_manager.process_inputs(¤t_run.inputs, current_run.new_coverage);
499
500 if invariant_contract.call_after_invariant && !invariant_test.has_errors() {
502 assert_after_invariant(
503 &invariant_contract,
504 &mut invariant_test,
505 ¤t_run,
506 &self.config,
507 )
508 .map_err(|_| eyre!("Failed to call afterInvariant"))?;
509 }
510
511 invariant_test.end_run(current_run, self.config.gas_report_samples as usize);
513 if let Some(progress) = progress {
514 progress.inc(1);
516 if edge_coverage_enabled {
518 progress.set_message(format!("{}", &corpus_manager.metrics));
519 }
520 } else if edge_coverage_enabled
521 && last_metrics_report.elapsed() > DURATION_BETWEEN_METRICS_REPORT
522 {
523 let metrics = json!({
525 "timestamp": SystemTime::now()
526 .duration_since(UNIX_EPOCH)?
527 .as_secs(),
528 "invariant": invariant_contract.invariant_function.name,
529 "metrics": &corpus_manager.metrics,
530 });
531 let _ = sh_println!("{}", serde_json::to_string(&metrics)?);
532 last_metrics_report = Instant::now();
533 }
534
535 runs += 1;
536 }
537
538 trace!(?fuzz_fixtures);
539 invariant_test.fuzz_state.log_stats();
540
541 let result = invariant_test.test_data;
542 Ok(InvariantFuzzTestResult {
543 error: result.failures.error,
544 cases: result.fuzz_cases,
545 reverts: result.failures.reverts,
546 last_run_inputs: result.last_run_inputs,
547 gas_report_traces: result.gas_report_traces,
548 line_coverage: result.line_coverage,
549 metrics: result.metrics,
550 failed_corpus_replays: corpus_manager.failed_replays(),
551 })
552 }
553
554 fn prepare_test(
558 &mut self,
559 invariant_contract: &InvariantContract<'_>,
560 fuzz_fixtures: &FuzzFixtures,
561 deployed_libs: &[Address],
562 ) -> Result<(InvariantTest, CorpusManager)> {
563 self.select_contract_artifacts(invariant_contract.address)?;
565 let (targeted_senders, targeted_contracts) =
566 self.select_contracts_and_senders(invariant_contract.address)?;
567
568 let fuzz_state = EvmFuzzState::new(
570 self.executor.backend().mem_db(),
571 self.config.dictionary,
572 deployed_libs,
573 );
574
575 let strategy = invariant_strat(
577 fuzz_state.clone(),
578 targeted_senders,
579 targeted_contracts.clone(),
580 self.config.dictionary.dictionary_weight,
581 fuzz_fixtures.clone(),
582 )
583 .no_shrink();
584
585 let mut call_generator = None;
588 if self.config.call_override {
589 let target_contract_ref = Arc::new(RwLock::new(Address::ZERO));
590
591 call_generator = Some(RandomCallGenerator::new(
592 invariant_contract.address,
593 self.runner.clone(),
594 override_call_strat(
595 fuzz_state.clone(),
596 targeted_contracts.clone(),
597 target_contract_ref.clone(),
598 fuzz_fixtures.clone(),
599 ),
600 target_contract_ref,
601 ));
602 }
603
604 let fuzz_state =
607 if targeted_contracts.targets.lock().iter().any(|(_, t)| t.storage_layout.is_some()) {
608 fuzz_state.with_mapping_slots(AddressMap::default())
609 } else {
610 fuzz_state
611 };
612
613 self.executor.inspector_mut().set_fuzzer(Fuzzer {
614 call_generator,
615 fuzz_state: fuzz_state.clone(),
616 collect: true,
617 });
618
619 let mut failures = InvariantFailures::new();
624 let last_call_results = assert_invariants(
625 invariant_contract,
626 &self.config,
627 &targeted_contracts,
628 &self.executor,
629 &[],
630 &mut failures,
631 )?;
632 if let Some(error) = failures.error {
633 return Err(eyre!(error.revert_reason().unwrap_or_default()));
634 }
635
636 let corpus_manager = CorpusManager::new(
637 self.config.corpus.clone(),
638 strategy.boxed(),
639 &self.executor,
640 None,
641 Some(&targeted_contracts),
642 )?;
643 let invariant_test = InvariantTest::new(
644 fuzz_state,
645 targeted_contracts,
646 failures,
647 last_call_results,
648 self.runner.clone(),
649 );
650
651 Ok((invariant_test, corpus_manager))
652 }
653
654 pub fn select_contract_artifacts(&mut self, invariant_address: Address) -> Result<()> {
664 let targeted_artifact_selectors = self
665 .executor
666 .call_sol_default(invariant_address, &IInvariantTest::targetArtifactSelectorsCall {});
667
668 for IInvariantTest::FuzzArtifactSelector { artifact, selectors } in
670 targeted_artifact_selectors
671 {
672 let identifier = self.validate_selected_contract(artifact, &selectors)?;
673 self.artifact_filters.targeted.entry(identifier).or_default().extend(selectors);
674 }
675
676 let targeted_artifacts = self
677 .executor
678 .call_sol_default(invariant_address, &IInvariantTest::targetArtifactsCall {});
679 let excluded_artifacts = self
680 .executor
681 .call_sol_default(invariant_address, &IInvariantTest::excludeArtifactsCall {});
682
683 for contract in excluded_artifacts {
685 let identifier = self.validate_selected_contract(contract, &[])?;
686
687 if !self.artifact_filters.excluded.contains(&identifier) {
688 self.artifact_filters.excluded.push(identifier);
689 }
690 }
691
692 for (artifact, contract) in self.project_contracts.iter() {
694 if contract
695 .abi
696 .functions()
697 .filter(|func| {
698 !matches!(
699 func.state_mutability,
700 alloy_json_abi::StateMutability::Pure
701 | alloy_json_abi::StateMutability::View
702 )
703 })
704 .count()
705 == 0
706 && !self.artifact_filters.excluded.contains(&artifact.identifier())
707 {
708 self.artifact_filters.excluded.push(artifact.identifier());
709 }
710 }
711
712 for contract in targeted_artifacts {
715 let identifier = self.validate_selected_contract(contract, &[])?;
716
717 if !self.artifact_filters.targeted.contains_key(&identifier)
718 && !self.artifact_filters.excluded.contains(&identifier)
719 {
720 self.artifact_filters.targeted.insert(identifier, vec![]);
721 }
722 }
723 Ok(())
724 }
725
726 fn validate_selected_contract(
729 &mut self,
730 contract: String,
731 selectors: &[FixedBytes<4>],
732 ) -> Result<String> {
733 if let Some((artifact, contract_data)) =
734 self.project_contracts.find_by_name_or_identifier(&contract)?
735 {
736 for selector in selectors {
738 contract_data
739 .abi
740 .functions()
741 .find(|func| func.selector().as_slice() == selector.as_slice())
742 .wrap_err(format!("{contract} does not have the selector {selector:?}"))?;
743 }
744
745 return Ok(artifact.identifier());
746 }
747 eyre::bail!(
748 "{contract} not found in the project. Allowed format: `contract_name` or `contract_path:contract_name`."
749 );
750 }
751
752 pub fn select_contracts_and_senders(
755 &self,
756 to: Address,
757 ) -> Result<(SenderFilters, FuzzRunIdentifiedContracts)> {
758 let targeted_senders =
759 self.executor.call_sol_default(to, &IInvariantTest::targetSendersCall {});
760 let mut excluded_senders =
761 self.executor.call_sol_default(to, &IInvariantTest::excludeSendersCall {});
762 excluded_senders.extend([
764 CHEATCODE_ADDRESS,
765 HARDHAT_CONSOLE_ADDRESS,
766 DEFAULT_CREATE2_DEPLOYER,
767 ]);
768 excluded_senders.extend(PRECOMPILES);
770 let sender_filters = SenderFilters::new(targeted_senders, excluded_senders);
771
772 let selected = self.executor.call_sol_default(to, &IInvariantTest::targetContractsCall {});
773 let excluded = self.executor.call_sol_default(to, &IInvariantTest::excludeContractsCall {});
774
775 let contracts = self
776 .setup_contracts
777 .iter()
778 .filter(|&(addr, (identifier, _))| {
779 if *addr == to && selected.contains(&to) {
781 return true;
782 }
783
784 *addr != to
785 && *addr != CHEATCODE_ADDRESS
786 && *addr != HARDHAT_CONSOLE_ADDRESS
787 && (selected.is_empty() || selected.contains(addr))
788 && (excluded.is_empty() || !excluded.contains(addr))
789 && self.artifact_filters.matches(identifier)
790 })
791 .map(|(addr, (identifier, abi))| {
792 (
793 *addr,
794 TargetedContract::new(identifier.clone(), abi.clone())
795 .with_project_contracts(self.project_contracts),
796 )
797 })
798 .collect();
799 let mut contracts = TargetedContracts { inner: contracts };
800
801 self.target_interfaces(to, &mut contracts)?;
802
803 self.select_selectors(to, &mut contracts)?;
804
805 if contracts.is_empty() {
807 eyre::bail!("No contracts to fuzz.");
808 }
809
810 Ok((sender_filters, FuzzRunIdentifiedContracts::new(contracts, selected.is_empty())))
811 }
812
813 pub fn target_interfaces(
818 &self,
819 invariant_address: Address,
820 targeted_contracts: &mut TargetedContracts,
821 ) -> Result<()> {
822 let interfaces = self
823 .executor
824 .call_sol_default(invariant_address, &IInvariantTest::targetInterfacesCall {});
825
826 let mut combined = TargetedContracts::new();
832
833 for IInvariantTest::FuzzInterface { addr, artifacts } in &interfaces {
836 for identifier in artifacts {
838 if let Some((_, contract_data)) =
840 self.project_contracts.iter().find(|(artifact, _)| {
841 &artifact.name == identifier || &artifact.identifier() == identifier
842 })
843 {
844 let abi = &contract_data.abi;
845 combined
846 .entry(*addr)
848 .and_modify(|entry| {
850 entry.abi.functions.extend(abi.functions.clone());
852 })
853 .or_insert_with(|| {
855 let mut contract =
856 TargetedContract::new(identifier.to_string(), abi.clone());
857 contract.storage_layout =
858 contract_data.storage_layout.as_ref().map(Arc::clone);
859 contract
860 });
861 }
862 }
863 }
864
865 targeted_contracts.extend(combined.inner);
866
867 Ok(())
868 }
869
870 pub fn select_selectors(
873 &self,
874 address: Address,
875 targeted_contracts: &mut TargetedContracts,
876 ) -> Result<()> {
877 for (address, (identifier, _)) in self.setup_contracts {
878 if let Some(selectors) = self.artifact_filters.targeted.get(identifier) {
879 self.add_address_with_functions(*address, selectors, false, targeted_contracts)?;
880 }
881 }
882
883 let mut target_test_selectors = vec![];
884 let mut excluded_test_selectors = vec![];
885
886 let selectors =
888 self.executor.call_sol_default(address, &IInvariantTest::targetSelectorsCall {});
889 for IInvariantTest::FuzzSelector { addr, selectors } in selectors {
890 if addr == address {
891 target_test_selectors = selectors.clone();
892 }
893 self.add_address_with_functions(addr, &selectors, false, targeted_contracts)?;
894 }
895
896 let excluded_selectors =
898 self.executor.call_sol_default(address, &IInvariantTest::excludeSelectorsCall {});
899 for IInvariantTest::FuzzSelector { addr, selectors } in excluded_selectors {
900 if addr == address {
901 excluded_test_selectors = selectors.clone();
904 }
905 self.add_address_with_functions(addr, &selectors, true, targeted_contracts)?;
906 }
907
908 if target_test_selectors.is_empty()
909 && let Some(target) = targeted_contracts.get(&address)
910 {
911 let selectors: Vec<_> = target
915 .abi
916 .functions()
917 .filter_map(|func| {
918 if matches!(
919 func.state_mutability,
920 alloy_json_abi::StateMutability::Pure
921 | alloy_json_abi::StateMutability::View
922 ) || func.is_reserved()
923 || excluded_test_selectors.contains(&func.selector())
924 {
925 None
926 } else {
927 Some(func.selector())
928 }
929 })
930 .collect();
931 self.add_address_with_functions(address, &selectors, false, targeted_contracts)?;
932 }
933
934 Ok(())
935 }
936
937 fn add_address_with_functions(
939 &self,
940 address: Address,
941 selectors: &[Selector],
942 should_exclude: bool,
943 targeted_contracts: &mut TargetedContracts,
944 ) -> eyre::Result<()> {
945 if selectors.is_empty() {
947 return Ok(());
948 }
949
950 let contract = match targeted_contracts.entry(address) {
951 Entry::Occupied(entry) => entry.into_mut(),
952 Entry::Vacant(entry) => {
953 let (identifier, abi) = self.setup_contracts.get(&address).ok_or_else(|| {
954 eyre::eyre!(
955 "[{}] address does not have an associated contract: {}",
956 if should_exclude { "excludeSelectors" } else { "targetSelectors" },
957 address
958 )
959 })?;
960 entry.insert(
961 TargetedContract::new(identifier.clone(), abi.clone())
962 .with_project_contracts(self.project_contracts),
963 )
964 }
965 };
966 contract.add_selectors(selectors.iter().copied(), should_exclude)?;
967 Ok(())
968 }
969}
970
971fn collect_data(
975 invariant_test: &InvariantTest,
976 state_changeset: &mut HashMap<Address, Account>,
977 tx: &BasicTxDetails,
978 call_result: &RawCallResult,
979 run_depth: u32,
980) {
981 let mut has_code = false;
983 if let Some(Some(code)) =
984 state_changeset.get(&tx.sender).map(|account| account.info.code.as_ref())
985 {
986 has_code = !code.is_empty();
987 }
988
989 let mut sender_changeset = None;
991 if !has_code {
992 sender_changeset = state_changeset.remove(&tx.sender);
993 }
994
995 invariant_test.fuzz_state.collect_values_from_call(
997 &invariant_test.targeted_contracts,
998 tx,
999 &call_result.result,
1000 &call_result.logs,
1001 &*state_changeset,
1002 run_depth,
1003 );
1004
1005 if let Some(changed) = sender_changeset {
1007 state_changeset.insert(tx.sender, changed);
1008 }
1009}
1010
1011pub(crate) fn call_after_invariant_function(
1015 executor: &Executor,
1016 to: Address,
1017) -> Result<(RawCallResult, bool), EvmError> {
1018 let calldata = Bytes::from_static(&IInvariantTest::afterInvariantCall::SELECTOR);
1019 let mut call_result = executor.call_raw(CALLER, to, calldata, U256::ZERO)?;
1020 let success = executor.is_raw_call_mut_success(to, &mut call_result, false);
1021 Ok((call_result, success))
1022}
1023
1024pub(crate) fn call_invariant_function(
1026 executor: &Executor,
1027 address: Address,
1028 calldata: Bytes,
1029) -> Result<(RawCallResult, bool)> {
1030 let mut call_result = executor.call_raw(CALLER, address, calldata, U256::ZERO)?;
1031 let success = executor.is_raw_call_mut_success(address, &mut call_result, false);
1032 Ok((call_result, success))
1033}