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, EarlyExit, EvmError, 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 early_exit: &EarlyExit,
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 early_exit.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 inspector = self.executor.inspector();
570 let fuzz_state = EvmFuzzState::new(
571 self.executor.backend().mem_db(),
572 self.config.dictionary,
573 deployed_libs,
574 inspector.analysis.as_ref(),
575 inspector.paths_config(),
576 );
577
578 let strategy = invariant_strat(
580 fuzz_state.clone(),
581 targeted_senders,
582 targeted_contracts.clone(),
583 self.config.dictionary.dictionary_weight,
584 fuzz_fixtures.clone(),
585 )
586 .no_shrink();
587
588 let mut call_generator = None;
591 if self.config.call_override {
592 let target_contract_ref = Arc::new(RwLock::new(Address::ZERO));
593
594 call_generator = Some(RandomCallGenerator::new(
595 invariant_contract.address,
596 self.runner.clone(),
597 override_call_strat(
598 fuzz_state.clone(),
599 targeted_contracts.clone(),
600 target_contract_ref.clone(),
601 fuzz_fixtures.clone(),
602 ),
603 target_contract_ref,
604 ));
605 }
606
607 let fuzz_state =
610 if targeted_contracts.targets.lock().iter().any(|(_, t)| t.storage_layout.is_some()) {
611 fuzz_state.with_mapping_slots(AddressMap::default())
612 } else {
613 fuzz_state
614 };
615
616 self.executor.inspector_mut().set_fuzzer(Fuzzer {
617 call_generator,
618 fuzz_state: fuzz_state.clone(),
619 collect: true,
620 });
621
622 let mut failures = InvariantFailures::new();
627 let last_call_results = assert_invariants(
628 invariant_contract,
629 &self.config,
630 &targeted_contracts,
631 &self.executor,
632 &[],
633 &mut failures,
634 )?;
635 if let Some(error) = failures.error {
636 return Err(eyre!(error.revert_reason().unwrap_or_default()));
637 }
638
639 let corpus_manager = CorpusManager::new(
640 self.config.corpus.clone(),
641 strategy.boxed(),
642 &self.executor,
643 None,
644 Some(&targeted_contracts),
645 )?;
646 let invariant_test = InvariantTest::new(
647 fuzz_state,
648 targeted_contracts,
649 failures,
650 last_call_results,
651 self.runner.clone(),
652 );
653
654 Ok((invariant_test, corpus_manager))
655 }
656
657 pub fn select_contract_artifacts(&mut self, invariant_address: Address) -> Result<()> {
667 let targeted_artifact_selectors = self
668 .executor
669 .call_sol_default(invariant_address, &IInvariantTest::targetArtifactSelectorsCall {});
670
671 for IInvariantTest::FuzzArtifactSelector { artifact, selectors } in
673 targeted_artifact_selectors
674 {
675 let identifier = self.validate_selected_contract(artifact, &selectors)?;
676 self.artifact_filters.targeted.entry(identifier).or_default().extend(selectors);
677 }
678
679 let targeted_artifacts = self
680 .executor
681 .call_sol_default(invariant_address, &IInvariantTest::targetArtifactsCall {});
682 let excluded_artifacts = self
683 .executor
684 .call_sol_default(invariant_address, &IInvariantTest::excludeArtifactsCall {});
685
686 for contract in excluded_artifacts {
688 let identifier = self.validate_selected_contract(contract, &[])?;
689
690 if !self.artifact_filters.excluded.contains(&identifier) {
691 self.artifact_filters.excluded.push(identifier);
692 }
693 }
694
695 for (artifact, contract) in self.project_contracts.iter() {
697 if contract
698 .abi
699 .functions()
700 .filter(|func| {
701 !matches!(
702 func.state_mutability,
703 alloy_json_abi::StateMutability::Pure
704 | alloy_json_abi::StateMutability::View
705 )
706 })
707 .count()
708 == 0
709 && !self.artifact_filters.excluded.contains(&artifact.identifier())
710 {
711 self.artifact_filters.excluded.push(artifact.identifier());
712 }
713 }
714
715 for contract in targeted_artifacts {
718 let identifier = self.validate_selected_contract(contract, &[])?;
719
720 if !self.artifact_filters.targeted.contains_key(&identifier)
721 && !self.artifact_filters.excluded.contains(&identifier)
722 {
723 self.artifact_filters.targeted.insert(identifier, vec![]);
724 }
725 }
726 Ok(())
727 }
728
729 fn validate_selected_contract(
732 &mut self,
733 contract: String,
734 selectors: &[FixedBytes<4>],
735 ) -> Result<String> {
736 if let Some((artifact, contract_data)) =
737 self.project_contracts.find_by_name_or_identifier(&contract)?
738 {
739 for selector in selectors {
741 contract_data
742 .abi
743 .functions()
744 .find(|func| func.selector().as_slice() == selector.as_slice())
745 .wrap_err(format!("{contract} does not have the selector {selector:?}"))?;
746 }
747
748 return Ok(artifact.identifier());
749 }
750 eyre::bail!(
751 "{contract} not found in the project. Allowed format: `contract_name` or `contract_path:contract_name`."
752 );
753 }
754
755 pub fn select_contracts_and_senders(
758 &self,
759 to: Address,
760 ) -> Result<(SenderFilters, FuzzRunIdentifiedContracts)> {
761 let targeted_senders =
762 self.executor.call_sol_default(to, &IInvariantTest::targetSendersCall {});
763 let mut excluded_senders =
764 self.executor.call_sol_default(to, &IInvariantTest::excludeSendersCall {});
765 excluded_senders.extend([
767 CHEATCODE_ADDRESS,
768 HARDHAT_CONSOLE_ADDRESS,
769 DEFAULT_CREATE2_DEPLOYER,
770 ]);
771 excluded_senders.extend(PRECOMPILES);
773 let sender_filters = SenderFilters::new(targeted_senders, excluded_senders);
774
775 let selected = self.executor.call_sol_default(to, &IInvariantTest::targetContractsCall {});
776 let excluded = self.executor.call_sol_default(to, &IInvariantTest::excludeContractsCall {});
777
778 let contracts = self
779 .setup_contracts
780 .iter()
781 .filter(|&(addr, (identifier, _))| {
782 if *addr == to && selected.contains(&to) {
784 return true;
785 }
786
787 *addr != to
788 && *addr != CHEATCODE_ADDRESS
789 && *addr != HARDHAT_CONSOLE_ADDRESS
790 && (selected.is_empty() || selected.contains(addr))
791 && (excluded.is_empty() || !excluded.contains(addr))
792 && self.artifact_filters.matches(identifier)
793 })
794 .map(|(addr, (identifier, abi))| {
795 (
796 *addr,
797 TargetedContract::new(identifier.clone(), abi.clone())
798 .with_project_contracts(self.project_contracts),
799 )
800 })
801 .collect();
802 let mut contracts = TargetedContracts { inner: contracts };
803
804 self.target_interfaces(to, &mut contracts)?;
805
806 self.select_selectors(to, &mut contracts)?;
807
808 if contracts.is_empty() {
810 eyre::bail!("No contracts to fuzz.");
811 }
812
813 Ok((sender_filters, FuzzRunIdentifiedContracts::new(contracts, selected.is_empty())))
814 }
815
816 pub fn target_interfaces(
821 &self,
822 invariant_address: Address,
823 targeted_contracts: &mut TargetedContracts,
824 ) -> Result<()> {
825 let interfaces = self
826 .executor
827 .call_sol_default(invariant_address, &IInvariantTest::targetInterfacesCall {});
828
829 let mut combined = TargetedContracts::new();
835
836 for IInvariantTest::FuzzInterface { addr, artifacts } in &interfaces {
839 for identifier in artifacts {
841 if let Some((_, contract_data)) =
843 self.project_contracts.iter().find(|(artifact, _)| {
844 &artifact.name == identifier || &artifact.identifier() == identifier
845 })
846 {
847 let abi = &contract_data.abi;
848 combined
849 .entry(*addr)
851 .and_modify(|entry| {
853 entry.abi.functions.extend(abi.functions.clone());
855 })
856 .or_insert_with(|| {
858 let mut contract =
859 TargetedContract::new(identifier.to_string(), abi.clone());
860 contract.storage_layout =
861 contract_data.storage_layout.as_ref().map(Arc::clone);
862 contract
863 });
864 }
865 }
866 }
867
868 targeted_contracts.extend(combined.inner);
869
870 Ok(())
871 }
872
873 pub fn select_selectors(
876 &self,
877 address: Address,
878 targeted_contracts: &mut TargetedContracts,
879 ) -> Result<()> {
880 for (address, (identifier, _)) in self.setup_contracts {
881 if let Some(selectors) = self.artifact_filters.targeted.get(identifier) {
882 self.add_address_with_functions(*address, selectors, false, targeted_contracts)?;
883 }
884 }
885
886 let mut target_test_selectors = vec![];
887 let mut excluded_test_selectors = vec![];
888
889 let selectors =
891 self.executor.call_sol_default(address, &IInvariantTest::targetSelectorsCall {});
892 for IInvariantTest::FuzzSelector { addr, selectors } in selectors {
893 if addr == address {
894 target_test_selectors = selectors.clone();
895 }
896 self.add_address_with_functions(addr, &selectors, false, targeted_contracts)?;
897 }
898
899 let excluded_selectors =
901 self.executor.call_sol_default(address, &IInvariantTest::excludeSelectorsCall {});
902 for IInvariantTest::FuzzSelector { addr, selectors } in excluded_selectors {
903 if addr == address {
904 excluded_test_selectors = selectors.clone();
907 }
908 self.add_address_with_functions(addr, &selectors, true, targeted_contracts)?;
909 }
910
911 if target_test_selectors.is_empty()
912 && let Some(target) = targeted_contracts.get(&address)
913 {
914 let selectors: Vec<_> = target
918 .abi
919 .functions()
920 .filter_map(|func| {
921 if matches!(
922 func.state_mutability,
923 alloy_json_abi::StateMutability::Pure
924 | alloy_json_abi::StateMutability::View
925 ) || func.is_reserved()
926 || excluded_test_selectors.contains(&func.selector())
927 {
928 None
929 } else {
930 Some(func.selector())
931 }
932 })
933 .collect();
934 self.add_address_with_functions(address, &selectors, false, targeted_contracts)?;
935 }
936
937 Ok(())
938 }
939
940 fn add_address_with_functions(
942 &self,
943 address: Address,
944 selectors: &[Selector],
945 should_exclude: bool,
946 targeted_contracts: &mut TargetedContracts,
947 ) -> eyre::Result<()> {
948 if selectors.is_empty() {
950 return Ok(());
951 }
952
953 let contract = match targeted_contracts.entry(address) {
954 Entry::Occupied(entry) => entry.into_mut(),
955 Entry::Vacant(entry) => {
956 let (identifier, abi) = self.setup_contracts.get(&address).ok_or_else(|| {
957 eyre::eyre!(
958 "[{}] address does not have an associated contract: {}",
959 if should_exclude { "excludeSelectors" } else { "targetSelectors" },
960 address
961 )
962 })?;
963 entry.insert(
964 TargetedContract::new(identifier.clone(), abi.clone())
965 .with_project_contracts(self.project_contracts),
966 )
967 }
968 };
969 contract.add_selectors(selectors.iter().copied(), should_exclude)?;
970 Ok(())
971 }
972}
973
974fn collect_data(
978 invariant_test: &InvariantTest,
979 state_changeset: &mut HashMap<Address, Account>,
980 tx: &BasicTxDetails,
981 call_result: &RawCallResult,
982 run_depth: u32,
983) {
984 let mut has_code = false;
986 if let Some(Some(code)) =
987 state_changeset.get(&tx.sender).map(|account| account.info.code.as_ref())
988 {
989 has_code = !code.is_empty();
990 }
991
992 let mut sender_changeset = None;
994 if !has_code {
995 sender_changeset = state_changeset.remove(&tx.sender);
996 }
997
998 invariant_test.fuzz_state.collect_values_from_call(
1000 &invariant_test.targeted_contracts,
1001 tx,
1002 &call_result.result,
1003 &call_result.logs,
1004 &*state_changeset,
1005 run_depth,
1006 );
1007
1008 if let Some(changed) = sender_changeset {
1010 state_changeset.insert(tx.sender, changed);
1011 }
1012}
1013
1014pub(crate) fn call_after_invariant_function(
1018 executor: &Executor,
1019 to: Address,
1020) -> Result<(RawCallResult, bool), EvmError> {
1021 let calldata = Bytes::from_static(&IInvariantTest::afterInvariantCall::SELECTOR);
1022 let mut call_result = executor.call_raw(CALLER, to, calldata, U256::ZERO)?;
1023 let success = executor.is_raw_call_mut_success(to, &mut call_result, false);
1024 Ok((call_result, success))
1025}
1026
1027pub(crate) fn call_invariant_function(
1029 executor: &Executor,
1030 address: Address,
1031 calldata: Bytes,
1032) -> Result<(RawCallResult, bool)> {
1033 let mut call_result = executor.call_raw(CALLER, address, calldata, U256::ZERO)?;
1034 let success = executor.is_raw_call_mut_success(address, &mut call_result, false);
1035 Ok((call_result, success))
1036}