1use crate::{
2 executors::{Executor, RawCallResult},
3 inspectors::Fuzzer,
4};
5use alloy_primitives::{Address, Bytes, FixedBytes, Selector, U256, map::HashMap};
6use alloy_sol_types::{SolCall, sol};
7use eyre::{ContextCompat, Result, eyre};
8use foundry_common::contracts::{ContractsByAddress, ContractsByArtifact};
9use foundry_config::InvariantConfig;
10use foundry_evm_core::{
11 constants::{
12 CALLER, CHEATCODE_ADDRESS, DEFAULT_CREATE2_DEPLOYER, HARDHAT_CONSOLE_ADDRESS, MAGIC_ASSUME,
13 },
14 precompiles::PRECOMPILES,
15};
16use foundry_evm_fuzz::{
17 FuzzCase, FuzzFixtures, FuzzedCases,
18 invariant::{
19 ArtifactFilters, BasicTxDetails, FuzzRunIdentifiedContracts, InvariantContract,
20 RandomCallGenerator, SenderFilters, TargetedContract, TargetedContracts,
21 },
22 strategies::{EvmFuzzState, invariant_strat, override_call_strat},
23};
24use foundry_evm_traces::{CallTraceArena, SparsedTraceArena};
25use indicatif::ProgressBar;
26use parking_lot::RwLock;
27use proptest::{strategy::Strategy, test_runner::TestRunner};
28use result::{assert_after_invariant, assert_invariants, can_continue};
29use revm::state::Account;
30use shrink::shrink_sequence;
31use std::{
32 cell::RefCell,
33 collections::{HashMap as Map, btree_map::Entry},
34 sync::Arc,
35 time::{Duration, Instant, SystemTime, UNIX_EPOCH},
36};
37
38mod error;
39pub use error::{InvariantFailures, InvariantFuzzError};
40use foundry_evm_coverage::HitMaps;
41
42mod replay;
43pub use replay::{replay_error, replay_run};
44
45mod result;
46use foundry_common::{TestFunctionExt, sh_println};
47pub use result::InvariantFuzzTestResult;
48use serde::{Deserialize, Serialize};
49use serde_json::json;
50
51mod corpus;
52
53mod shrink;
54use crate::executors::{EvmError, FuzzTestTimer, invariant::corpus::TxCorpusManager};
55pub use shrink::check_sequence;
56
57sol! {
58 interface IInvariantTest {
59 #[derive(Default)]
60 struct FuzzSelector {
61 address addr;
62 bytes4[] selectors;
63 }
64
65 #[derive(Default)]
66 struct FuzzArtifactSelector {
67 string artifact;
68 bytes4[] selectors;
69 }
70
71 #[derive(Default)]
72 struct FuzzInterface {
73 address addr;
74 string[] artifacts;
75 }
76
77 function afterInvariant() external;
78
79 #[derive(Default)]
80 function excludeArtifacts() public view returns (string[] memory excludedArtifacts);
81
82 #[derive(Default)]
83 function excludeContracts() public view returns (address[] memory excludedContracts);
84
85 #[derive(Default)]
86 function excludeSelectors() public view returns (FuzzSelector[] memory excludedSelectors);
87
88 #[derive(Default)]
89 function excludeSenders() public view returns (address[] memory excludedSenders);
90
91 #[derive(Default)]
92 function targetArtifacts() public view returns (string[] memory targetedArtifacts);
93
94 #[derive(Default)]
95 function targetArtifactSelectors() public view returns (FuzzArtifactSelector[] memory targetedArtifactSelectors);
96
97 #[derive(Default)]
98 function targetContracts() public view returns (address[] memory targetedContracts);
99
100 #[derive(Default)]
101 function targetSelectors() public view returns (FuzzSelector[] memory targetedSelectors);
102
103 #[derive(Default)]
104 function targetSenders() public view returns (address[] memory targetedSenders);
105
106 #[derive(Default)]
107 function targetInterfaces() public view returns (FuzzInterface[] memory targetedInterfaces);
108 }
109}
110
111const DURATION_BETWEEN_METRICS_REPORT: Duration = Duration::from_secs(5);
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
124pub struct InvariantTestData {
126 pub fuzz_cases: Vec<FuzzedCases>,
128 pub failures: InvariantFailures,
130 pub last_run_inputs: Vec<BasicTxDetails>,
132 pub gas_report_traces: Vec<Vec<CallTraceArena>>,
134 pub last_call_results: Option<RawCallResult>,
136 pub line_coverage: Option<HitMaps>,
138 pub metrics: Map<String, InvariantMetrics>,
140
141 pub branch_runner: TestRunner,
146}
147
148pub struct InvariantTest {
150 pub fuzz_state: EvmFuzzState,
152 pub targeted_contracts: FuzzRunIdentifiedContracts,
154 pub execution_data: RefCell<InvariantTestData>,
156}
157
158impl InvariantTest {
159 pub 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 execution_data = RefCell::new(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, execution_data }
182 }
183
184 pub fn reverts(&self) -> usize {
186 self.execution_data.borrow().failures.reverts
187 }
188
189 pub fn has_errors(&self) -> bool {
191 self.execution_data.borrow().failures.error.is_some()
192 }
193
194 pub fn set_error(&self, error: InvariantFuzzError) {
196 self.execution_data.borrow_mut().failures.error = Some(error);
197 }
198
199 pub fn set_last_call_results(&self, call_result: Option<RawCallResult>) {
201 self.execution_data.borrow_mut().last_call_results = call_result;
202 }
203
204 pub fn set_last_run_inputs(&self, inputs: &Vec<BasicTxDetails>) {
206 self.execution_data.borrow_mut().last_run_inputs.clone_from(inputs);
207 }
208
209 pub fn merge_coverage(&self, new_coverage: Option<HitMaps>) {
211 HitMaps::merge_opt(&mut self.execution_data.borrow_mut().line_coverage, new_coverage);
212 }
213
214 pub fn record_metrics(&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.execution_data.borrow_mut().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 pub fn end_run(&self, run: InvariantTestRun, gas_samples: usize) {
235 self.targeted_contracts.clear_created_contracts(run.created_contracts);
237
238 let mut invariant_data = self.execution_data.borrow_mut();
239 if invariant_data.gas_report_traces.len() < gas_samples {
240 invariant_data
241 .gas_report_traces
242 .push(run.run_traces.into_iter().map(|arena| arena.arena).collect());
243 }
244 invariant_data.fuzz_cases.push(FuzzedCases::new(run.fuzz_runs));
245
246 self.fuzz_state.revert();
248 }
249}
250
251pub struct InvariantTestRun {
253 pub inputs: Vec<BasicTxDetails>,
255 pub executor: Executor,
257 pub fuzz_runs: Vec<FuzzCase>,
259 pub created_contracts: Vec<Address>,
261 pub run_traces: Vec<SparsedTraceArena>,
263 pub depth: u32,
265 pub assume_rejects_counter: u32,
267 pub new_coverage: bool,
269}
270
271impl InvariantTestRun {
272 pub fn new(first_input: BasicTxDetails, executor: Executor, depth: usize) -> Self {
274 Self {
275 inputs: vec![first_input],
276 executor,
277 fuzz_runs: Vec::with_capacity(depth),
278 created_contracts: vec![],
279 run_traces: vec![],
280 depth: 0,
281 assume_rejects_counter: 0,
282 new_coverage: false,
283 }
284 }
285}
286
287pub struct InvariantExecutor<'a> {
294 pub executor: Executor,
295 runner: TestRunner,
297 config: InvariantConfig,
299 setup_contracts: &'a ContractsByAddress,
301 project_contracts: &'a ContractsByArtifact,
304 artifact_filters: ArtifactFilters,
306 history_map: Vec<u8>,
308}
309const COVERAGE_MAP_SIZE: usize = 65536;
310
311impl<'a> InvariantExecutor<'a> {
312 pub fn new(
314 executor: Executor,
315 runner: TestRunner,
316 config: InvariantConfig,
317 setup_contracts: &'a ContractsByAddress,
318 project_contracts: &'a ContractsByArtifact,
319 ) -> Self {
320 Self {
321 executor,
322 runner,
323 config,
324 setup_contracts,
325 project_contracts,
326 artifact_filters: ArtifactFilters::default(),
327 history_map: vec![0u8; COVERAGE_MAP_SIZE],
328 }
329 }
330
331 pub fn invariant_fuzz(
333 &mut self,
334 invariant_contract: InvariantContract<'_>,
335 fuzz_fixtures: &FuzzFixtures,
336 deployed_libs: &[Address],
337 progress: Option<&ProgressBar>,
338 ) -> Result<InvariantFuzzTestResult> {
339 if !invariant_contract.invariant_function.inputs.is_empty() {
341 return Err(eyre!("Invariant test function should have no inputs"));
342 }
343
344 let (invariant_test, mut corpus_manager) =
345 self.prepare_test(&invariant_contract, fuzz_fixtures, deployed_libs)?;
346
347 let mut runs = 0;
349 let timer = FuzzTestTimer::new(self.config.timeout);
350 let mut last_metrics_report = Instant::now();
351 let continue_campaign = |runs: u32| {
352 if self.config.timeout.is_some() {
354 return !timer.is_timed_out();
355 }
356 runs < self.config.runs
358 };
359
360 let edge_coverage_enabled =
362 self.config.corpus_dir.is_some() || self.config.show_edge_coverage;
363
364 'stop: while continue_campaign(runs) {
365 let initial_seq = corpus_manager.new_sequence(&invariant_test)?;
366
367 let mut current_run = InvariantTestRun::new(
369 initial_seq[0].clone(),
370 self.executor.clone(),
372 self.config.depth as usize,
373 );
374
375 if self.config.fail_on_revert && invariant_test.reverts() > 0 {
377 return Err(eyre!("call reverted"));
378 }
379
380 while current_run.depth < self.config.depth {
381 if timer.is_timed_out() {
383 break 'stop;
388 }
389
390 let tx = current_run
391 .inputs
392 .last()
393 .ok_or_else(|| eyre!("no input generated to call fuzzed target."))?;
394
395 let mut call_result = current_run
398 .executor
399 .call_raw(
400 tx.sender,
401 tx.call_details.target,
402 tx.call_details.calldata.clone(),
403 U256::ZERO,
404 )
405 .map_err(|e| eyre!(format!("Could not make raw evm call: {e}")))?;
406
407 let discarded = call_result.result.as_ref() == MAGIC_ASSUME;
408 if self.config.show_metrics {
409 invariant_test.record_metrics(tx, call_result.reverted, discarded);
410 }
411
412 invariant_test.merge_coverage(call_result.line_coverage.clone());
414 if edge_coverage_enabled {
417 let (new_coverage, is_edge) =
418 call_result.merge_edge_coverage(&mut self.history_map);
419 if new_coverage {
420 current_run.new_coverage = true;
421 corpus_manager.update_seen_metrics(is_edge);
422 }
423 }
424
425 if discarded {
426 current_run.inputs.pop();
427 current_run.assume_rejects_counter += 1;
428 if current_run.assume_rejects_counter > self.config.max_assume_rejects {
429 invariant_test.set_error(InvariantFuzzError::MaxAssumeRejects(
430 self.config.max_assume_rejects,
431 ));
432 break 'stop;
433 }
434 } else {
435 current_run.executor.commit(&mut call_result);
437
438 let mut state_changeset = call_result.state_changeset.clone();
446 if !call_result.reverted {
447 collect_data(
448 &invariant_test,
449 &mut state_changeset,
450 tx,
451 &call_result,
452 self.config.depth,
453 );
454 }
455
456 if let Err(error) =
459 &invariant_test.targeted_contracts.collect_created_contracts(
460 &state_changeset,
461 self.project_contracts,
462 self.setup_contracts,
463 &self.artifact_filters,
464 &mut current_run.created_contracts,
465 )
466 {
467 warn!(target: "forge::test", "{error}");
468 }
469 current_run.fuzz_runs.push(FuzzCase {
470 calldata: tx.call_details.calldata.clone(),
471 gas: call_result.gas_used,
472 stipend: call_result.stipend,
473 });
474
475 let result = can_continue(
477 &invariant_contract,
478 &invariant_test,
479 &mut current_run,
480 &self.config,
481 call_result,
482 &state_changeset,
483 )
484 .map_err(|e| eyre!(e.to_string()))?;
485 if !result.can_continue || current_run.depth == self.config.depth - 1 {
486 invariant_test.set_last_run_inputs(¤t_run.inputs);
487 }
488 if !result.can_continue {
490 break 'stop;
491 }
492
493 invariant_test.set_last_call_results(result.call_result);
494 current_run.depth += 1;
495 }
496
497 current_run.inputs.push(corpus_manager.generate_next_input(
498 &invariant_test,
499 &initial_seq,
500 discarded,
501 current_run.depth as usize,
502 )?);
503 }
504
505 corpus_manager.collect_inputs(¤t_run);
507
508 if invariant_contract.call_after_invariant && !invariant_test.has_errors() {
510 assert_after_invariant(
511 &invariant_contract,
512 &invariant_test,
513 ¤t_run,
514 &self.config,
515 )
516 .map_err(|_| eyre!("Failed to call afterInvariant"))?;
517 }
518
519 invariant_test.end_run(current_run, self.config.gas_report_samples as usize);
521 if let Some(progress) = progress {
522 progress.inc(1);
524 if edge_coverage_enabled {
526 progress.set_message(format!("{}", &corpus_manager.metrics));
527 }
528 } else if edge_coverage_enabled
529 && last_metrics_report.elapsed() > DURATION_BETWEEN_METRICS_REPORT
530 {
531 let metrics = json!({
533 "timestamp": SystemTime::now()
534 .duration_since(UNIX_EPOCH)?
535 .as_secs(),
536 "invariant": invariant_contract.invariant_function.name,
537 "metrics": &corpus_manager.metrics,
538 });
539 let _ = sh_println!("{}", serde_json::to_string(&metrics)?);
540 last_metrics_report = Instant::now();
541 }
542
543 runs += 1;
544 }
545
546 trace!(?fuzz_fixtures);
547 invariant_test.fuzz_state.log_stats();
548
549 let result = invariant_test.execution_data.into_inner();
550 Ok(InvariantFuzzTestResult {
551 error: result.failures.error,
552 cases: result.fuzz_cases,
553 reverts: result.failures.reverts,
554 last_run_inputs: result.last_run_inputs,
555 gas_report_traces: result.gas_report_traces,
556 line_coverage: result.line_coverage,
557 metrics: result.metrics,
558 failed_corpus_replays: corpus_manager.failed_replays(),
559 })
560 }
561
562 fn prepare_test(
566 &mut self,
567 invariant_contract: &InvariantContract<'_>,
568 fuzz_fixtures: &FuzzFixtures,
569 deployed_libs: &[Address],
570 ) -> Result<(InvariantTest, TxCorpusManager)> {
571 self.select_contract_artifacts(invariant_contract.address)?;
573 let (targeted_senders, targeted_contracts) =
574 self.select_contracts_and_senders(invariant_contract.address)?;
575
576 let fuzz_state = EvmFuzzState::new(
578 self.executor.backend().mem_db(),
579 self.config.dictionary,
580 deployed_libs,
581 );
582
583 let strategy = invariant_strat(
585 fuzz_state.clone(),
586 targeted_senders,
587 targeted_contracts.clone(),
588 self.config.dictionary.dictionary_weight,
589 fuzz_fixtures.clone(),
590 )
591 .no_shrink();
592
593 let mut call_generator = None;
596 if self.config.call_override {
597 let target_contract_ref = Arc::new(RwLock::new(Address::ZERO));
598
599 call_generator = Some(RandomCallGenerator::new(
600 invariant_contract.address,
601 self.runner.clone(),
602 override_call_strat(
603 fuzz_state.clone(),
604 targeted_contracts.clone(),
605 target_contract_ref.clone(),
606 fuzz_fixtures.clone(),
607 ),
608 target_contract_ref,
609 ));
610 }
611
612 self.executor.inspector_mut().fuzzer =
613 Some(Fuzzer { call_generator, fuzz_state: fuzz_state.clone(), collect: true });
614
615 let mut failures = InvariantFailures::new();
620 let last_call_results = assert_invariants(
621 invariant_contract,
622 &self.config,
623 &targeted_contracts,
624 &self.executor,
625 &[],
626 &mut failures,
627 )?;
628 if let Some(error) = failures.error {
629 return Err(eyre!(error.revert_reason().unwrap_or_default()));
630 }
631
632 let corpus_manager = TxCorpusManager::new(
633 &self.config,
634 &invariant_contract.invariant_function.name,
635 &targeted_contracts,
636 strategy.boxed(),
637 &self.executor,
638 &mut self.history_map,
639 )?;
640
641 let invariant_test = InvariantTest::new(
642 fuzz_state,
643 targeted_contracts,
644 failures,
645 last_call_results,
646 self.runner.clone(),
647 );
648
649 Ok((invariant_test, corpus_manager))
650 }
651
652 pub fn select_contract_artifacts(&mut self, invariant_address: Address) -> Result<()> {
662 let targeted_artifact_selectors = self
663 .executor
664 .call_sol_default(invariant_address, &IInvariantTest::targetArtifactSelectorsCall {});
665
666 for IInvariantTest::FuzzArtifactSelector { artifact, selectors } in
668 targeted_artifact_selectors
669 {
670 let identifier = self.validate_selected_contract(artifact, &selectors)?;
671 self.artifact_filters.targeted.entry(identifier).or_default().extend(selectors);
672 }
673
674 let targeted_artifacts = self
675 .executor
676 .call_sol_default(invariant_address, &IInvariantTest::targetArtifactsCall {});
677 let excluded_artifacts = self
678 .executor
679 .call_sol_default(invariant_address, &IInvariantTest::excludeArtifactsCall {});
680
681 for contract in excluded_artifacts {
683 let identifier = self.validate_selected_contract(contract, &[])?;
684
685 if !self.artifact_filters.excluded.contains(&identifier) {
686 self.artifact_filters.excluded.push(identifier);
687 }
688 }
689
690 for (artifact, contract) in self.project_contracts.iter() {
692 if contract
693 .abi
694 .functions()
695 .filter(|func| {
696 !matches!(
697 func.state_mutability,
698 alloy_json_abi::StateMutability::Pure
699 | alloy_json_abi::StateMutability::View
700 )
701 })
702 .count()
703 == 0
704 && !self.artifact_filters.excluded.contains(&artifact.identifier())
705 {
706 self.artifact_filters.excluded.push(artifact.identifier());
707 }
708 }
709
710 for contract in targeted_artifacts {
713 let identifier = self.validate_selected_contract(contract, &[])?;
714
715 if !self.artifact_filters.targeted.contains_key(&identifier)
716 && !self.artifact_filters.excluded.contains(&identifier)
717 {
718 self.artifact_filters.targeted.insert(identifier, vec![]);
719 }
720 }
721 Ok(())
722 }
723
724 fn validate_selected_contract(
727 &mut self,
728 contract: String,
729 selectors: &[FixedBytes<4>],
730 ) -> Result<String> {
731 if let Some((artifact, contract_data)) =
732 self.project_contracts.find_by_name_or_identifier(&contract)?
733 {
734 for selector in selectors {
736 contract_data
737 .abi
738 .functions()
739 .find(|func| func.selector().as_slice() == selector.as_slice())
740 .wrap_err(format!("{contract} does not have the selector {selector:?}"))?;
741 }
742
743 return Ok(artifact.identifier());
744 }
745 eyre::bail!(
746 "{contract} not found in the project. Allowed format: `contract_name` or `contract_path:contract_name`."
747 );
748 }
749
750 pub fn select_contracts_and_senders(
753 &self,
754 to: Address,
755 ) -> Result<(SenderFilters, FuzzRunIdentifiedContracts)> {
756 let targeted_senders =
757 self.executor.call_sol_default(to, &IInvariantTest::targetSendersCall {});
758 let mut excluded_senders =
759 self.executor.call_sol_default(to, &IInvariantTest::excludeSendersCall {});
760 excluded_senders.extend([
762 CHEATCODE_ADDRESS,
763 HARDHAT_CONSOLE_ADDRESS,
764 DEFAULT_CREATE2_DEPLOYER,
765 ]);
766 excluded_senders.extend(PRECOMPILES);
768 let sender_filters = SenderFilters::new(targeted_senders, excluded_senders);
769
770 let selected = self.executor.call_sol_default(to, &IInvariantTest::targetContractsCall {});
771 let excluded = self.executor.call_sol_default(to, &IInvariantTest::excludeContractsCall {});
772
773 let contracts = self
774 .setup_contracts
775 .iter()
776 .filter(|&(addr, (identifier, _))| {
777 if *addr == to && selected.contains(&to) {
779 return true;
780 }
781
782 *addr != to
783 && *addr != CHEATCODE_ADDRESS
784 && *addr != HARDHAT_CONSOLE_ADDRESS
785 && (selected.is_empty() || selected.contains(addr))
786 && (excluded.is_empty() || !excluded.contains(addr))
787 && self.artifact_filters.matches(identifier)
788 })
789 .map(|(addr, (identifier, abi))| {
790 (*addr, TargetedContract::new(identifier.clone(), abi.clone()))
791 })
792 .collect();
793 let mut contracts = TargetedContracts { inner: contracts };
794
795 self.target_interfaces(to, &mut contracts)?;
796
797 self.select_selectors(to, &mut contracts)?;
798
799 if contracts.is_empty() {
801 eyre::bail!("No contracts to fuzz.");
802 }
803
804 Ok((sender_filters, FuzzRunIdentifiedContracts::new(contracts, selected.is_empty())))
805 }
806
807 pub fn target_interfaces(
812 &self,
813 invariant_address: Address,
814 targeted_contracts: &mut TargetedContracts,
815 ) -> Result<()> {
816 let interfaces = self
817 .executor
818 .call_sol_default(invariant_address, &IInvariantTest::targetInterfacesCall {});
819
820 let mut combined = TargetedContracts::new();
826
827 for IInvariantTest::FuzzInterface { addr, artifacts } in &interfaces {
830 for identifier in artifacts {
832 if let Some(abi) = self.project_contracts.find_abi_by_name_or_identifier(identifier)
834 {
835 combined
836 .entry(*addr)
838 .and_modify(|entry| {
840 entry.abi.functions.extend(abi.functions.clone());
842 })
843 .or_insert_with(|| TargetedContract::new(identifier.to_string(), abi));
845 }
846 }
847 }
848
849 targeted_contracts.extend(combined.inner);
850
851 Ok(())
852 }
853
854 pub fn select_selectors(
857 &self,
858 address: Address,
859 targeted_contracts: &mut TargetedContracts,
860 ) -> Result<()> {
861 for (address, (identifier, _)) in self.setup_contracts {
862 if let Some(selectors) = self.artifact_filters.targeted.get(identifier) {
863 self.add_address_with_functions(*address, selectors, false, targeted_contracts)?;
864 }
865 }
866
867 let mut target_test_selectors = vec![];
868 let mut excluded_test_selectors = vec![];
869
870 let selectors =
872 self.executor.call_sol_default(address, &IInvariantTest::targetSelectorsCall {});
873 for IInvariantTest::FuzzSelector { addr, selectors } in selectors {
874 if addr == address {
875 target_test_selectors = selectors.clone();
876 }
877 self.add_address_with_functions(addr, &selectors, false, targeted_contracts)?;
878 }
879
880 let excluded_selectors =
882 self.executor.call_sol_default(address, &IInvariantTest::excludeSelectorsCall {});
883 for IInvariantTest::FuzzSelector { addr, selectors } in excluded_selectors {
884 if addr == address {
885 excluded_test_selectors = selectors.clone();
888 }
889 self.add_address_with_functions(addr, &selectors, true, targeted_contracts)?;
890 }
891
892 if target_test_selectors.is_empty()
893 && let Some(target) = targeted_contracts.get(&address)
894 {
895 let selectors: Vec<_> = target
899 .abi
900 .functions()
901 .filter_map(|func| {
902 if matches!(
903 func.state_mutability,
904 alloy_json_abi::StateMutability::Pure
905 | alloy_json_abi::StateMutability::View
906 ) || func.is_reserved()
907 || excluded_test_selectors.contains(&func.selector())
908 {
909 None
910 } else {
911 Some(func.selector())
912 }
913 })
914 .collect();
915 self.add_address_with_functions(address, &selectors, false, targeted_contracts)?;
916 }
917
918 Ok(())
919 }
920
921 fn add_address_with_functions(
923 &self,
924 address: Address,
925 selectors: &[Selector],
926 should_exclude: bool,
927 targeted_contracts: &mut TargetedContracts,
928 ) -> eyre::Result<()> {
929 if selectors.is_empty() {
931 return Ok(());
932 }
933
934 let contract = match targeted_contracts.entry(address) {
935 Entry::Occupied(entry) => entry.into_mut(),
936 Entry::Vacant(entry) => {
937 let (identifier, abi) = self.setup_contracts.get(&address).ok_or_else(|| {
938 eyre::eyre!(
939 "[{}] address does not have an associated contract: {}",
940 if should_exclude { "excludeSelectors" } else { "targetSelectors" },
941 address
942 )
943 })?;
944 entry.insert(TargetedContract::new(identifier.clone(), abi.clone()))
945 }
946 };
947 contract.add_selectors(selectors.iter().copied(), should_exclude)?;
948 Ok(())
949 }
950}
951
952fn collect_data(
956 invariant_test: &InvariantTest,
957 state_changeset: &mut HashMap<Address, Account>,
958 tx: &BasicTxDetails,
959 call_result: &RawCallResult,
960 run_depth: u32,
961) {
962 let mut has_code = false;
964 if let Some(Some(code)) =
965 state_changeset.get(&tx.sender).map(|account| account.info.code.as_ref())
966 {
967 has_code = !code.is_empty();
968 }
969
970 let mut sender_changeset = None;
972 if !has_code {
973 sender_changeset = state_changeset.remove(&tx.sender);
974 }
975
976 invariant_test.fuzz_state.collect_values_from_call(
978 &invariant_test.targeted_contracts,
979 tx,
980 &call_result.result,
981 &call_result.logs,
982 &*state_changeset,
983 run_depth,
984 );
985
986 if let Some(changed) = sender_changeset {
988 state_changeset.insert(tx.sender, changed);
989 }
990}
991
992pub(crate) fn call_after_invariant_function(
996 executor: &Executor,
997 to: Address,
998) -> Result<(RawCallResult, bool), EvmError> {
999 let calldata = Bytes::from_static(&IInvariantTest::afterInvariantCall::SELECTOR);
1000 let mut call_result = executor.call_raw(CALLER, to, calldata, U256::ZERO)?;
1001 let success = executor.is_raw_call_mut_success(to, &mut call_result, false);
1002 Ok((call_result, success))
1003}
1004
1005pub(crate) fn call_invariant_function(
1007 executor: &Executor,
1008 address: Address,
1009 calldata: Bytes,
1010) -> Result<(RawCallResult, bool)> {
1011 let mut call_result = executor.call_raw(CALLER, address, calldata, U256::ZERO)?;
1012 let success = executor.is_raw_call_mut_success(address, &mut call_result, false);
1013 Ok((call_result, success))
1014}