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::{
9 Address, Bytes, FixedBytes, I256, Selector, U256,
10 map::{AddressMap, HashMap},
11};
12use alloy_sol_types::{SolCall, sol};
13use eyre::{ContextCompat, Result, eyre};
14use foundry_common::{
15 TestFunctionExt,
16 contracts::{ContractsByAddress, ContractsByArtifact},
17 sh_println,
18};
19use foundry_config::InvariantConfig;
20use foundry_evm_core::{
21 constants::{
22 CALLER, CHEATCODE_ADDRESS, DEFAULT_CREATE2_DEPLOYER, HARDHAT_CONSOLE_ADDRESS, MAGIC_ASSUME,
23 },
24 precompiles::PRECOMPILES,
25};
26use foundry_evm_fuzz::{
27 BasicTxDetails, FuzzCase, FuzzFixtures, FuzzedCases,
28 invariant::{
29 ArtifactFilters, FuzzRunIdentifiedContracts, InvariantContract, RandomCallGenerator,
30 SenderFilters, TargetedContract, TargetedContracts,
31 },
32 strategies::{EvmFuzzState, invariant_strat, override_call_strat},
33};
34use foundry_evm_traces::{CallTraceArena, SparsedTraceArena};
35use indicatif::ProgressBar;
36use parking_lot::RwLock;
37use proptest::{strategy::Strategy, test_runner::TestRunner};
38use result::{assert_after_invariant, assert_invariants, can_continue};
39use revm::state::Account;
40use serde::{Deserialize, Serialize};
41use serde_json::json;
42use std::{
43 collections::{HashMap as Map, btree_map::Entry},
44 sync::Arc,
45 time::{Instant, SystemTime, UNIX_EPOCH},
46};
47
48mod error;
49pub use error::{InvariantFailures, InvariantFuzzError};
50use foundry_evm_coverage::HitMaps;
51
52mod replay;
53pub use replay::{replay_error, replay_run};
54
55mod result;
56pub use result::InvariantFuzzTestResult;
57
58mod shrink;
59pub use shrink::{check_sequence, check_sequence_value};
60
61sol! {
62 interface IInvariantTest {
63 #[derive(Default)]
64 struct FuzzSelector {
65 address addr;
66 bytes4[] selectors;
67 }
68
69 #[derive(Default)]
70 struct FuzzArtifactSelector {
71 string artifact;
72 bytes4[] selectors;
73 }
74
75 #[derive(Default)]
76 struct FuzzInterface {
77 address addr;
78 string[] artifacts;
79 }
80
81 function afterInvariant() external;
82
83 #[derive(Default)]
84 function excludeArtifacts() public view returns (string[] memory excludedArtifacts);
85
86 #[derive(Default)]
87 function excludeContracts() public view returns (address[] memory excludedContracts);
88
89 #[derive(Default)]
90 function excludeSelectors() public view returns (FuzzSelector[] memory excludedSelectors);
91
92 #[derive(Default)]
93 function excludeSenders() public view returns (address[] memory excludedSenders);
94
95 #[derive(Default)]
96 function targetArtifacts() public view returns (string[] memory targetedArtifacts);
97
98 #[derive(Default)]
99 function targetArtifactSelectors() public view returns (FuzzArtifactSelector[] memory targetedArtifactSelectors);
100
101 #[derive(Default)]
102 function targetContracts() public view returns (address[] memory targetedContracts);
103
104 #[derive(Default)]
105 function targetSelectors() public view returns (FuzzSelector[] memory targetedSelectors);
106
107 #[derive(Default)]
108 function targetSenders() public view returns (address[] memory targetedSenders);
109
110 #[derive(Default)]
111 function targetInterfaces() public view returns (FuzzInterface[] memory targetedInterfaces);
112 }
113}
114
115#[derive(Default, Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
117pub struct InvariantMetrics {
118 pub calls: usize,
120 pub reverts: usize,
122 pub discards: usize,
124}
125
126struct InvariantTestData {
128 fuzz_cases: Vec<FuzzedCases>,
130 failures: InvariantFailures,
132 last_run_inputs: Vec<BasicTxDetails>,
134 gas_report_traces: Vec<Vec<CallTraceArena>>,
136 last_call_results: Option<RawCallResult>,
138 line_coverage: Option<HitMaps>,
140 metrics: Map<String, InvariantMetrics>,
142
143 branch_runner: TestRunner,
148
149 optimization_best_value: Option<I256>,
152 optimization_best_sequence: Vec<BasicTxDetails>,
153}
154
155struct InvariantTest {
157 fuzz_state: EvmFuzzState,
159 targeted_contracts: FuzzRunIdentifiedContracts,
161 test_data: InvariantTestData,
163}
164
165impl InvariantTest {
166 fn new(
168 fuzz_state: EvmFuzzState,
169 targeted_contracts: FuzzRunIdentifiedContracts,
170 failures: InvariantFailures,
171 last_call_results: Option<RawCallResult>,
172 branch_runner: TestRunner,
173 ) -> Self {
174 let mut fuzz_cases = vec![];
175 if last_call_results.is_none() {
176 fuzz_cases.push(FuzzedCases::new(vec![]));
177 }
178 let test_data = InvariantTestData {
179 fuzz_cases,
180 failures,
181 last_run_inputs: vec![],
182 gas_report_traces: vec![],
183 last_call_results,
184 line_coverage: None,
185 metrics: Map::default(),
186 branch_runner,
187 optimization_best_value: None,
188 optimization_best_sequence: vec![],
189 };
190 Self { fuzz_state, targeted_contracts, test_data }
191 }
192
193 fn reverts(&self) -> usize {
195 self.test_data.failures.reverts
196 }
197
198 fn has_errors(&self) -> bool {
200 self.test_data.failures.error.is_some()
201 }
202
203 fn set_error(&mut self, error: InvariantFuzzError) {
205 self.test_data.failures.error = Some(error);
206 }
207
208 fn set_last_call_results(&mut self, call_result: Option<RawCallResult>) {
210 self.test_data.last_call_results = call_result;
211 }
212
213 fn set_last_run_inputs(&mut self, inputs: &Vec<BasicTxDetails>) {
215 self.test_data.last_run_inputs.clone_from(inputs);
216 }
217
218 fn merge_line_coverage(&mut self, new_coverage: Option<HitMaps>) {
220 HitMaps::merge_opt(&mut self.test_data.line_coverage, new_coverage);
221 }
222
223 fn record_metrics(&mut self, tx_details: &BasicTxDetails, reverted: bool, discarded: bool) {
227 if let Some(metric_key) =
228 self.targeted_contracts.targets.lock().fuzzed_metric_key(tx_details)
229 {
230 let test_metrics = &mut self.test_data.metrics;
231 let invariant_metrics = test_metrics.entry(metric_key).or_default();
232 invariant_metrics.calls += 1;
233 if discarded {
234 invariant_metrics.discards += 1;
235 } else if reverted {
236 invariant_metrics.reverts += 1;
237 }
238 }
239 }
240
241 fn end_run(&mut self, run: InvariantTestRun, gas_samples: usize) {
244 self.targeted_contracts.clear_created_contracts(run.created_contracts);
246
247 if self.test_data.gas_report_traces.len() < gas_samples {
248 self.test_data
249 .gas_report_traces
250 .push(run.run_traces.into_iter().map(|arena| arena.arena).collect());
251 }
252 self.test_data.fuzz_cases.push(FuzzedCases::new(run.fuzz_runs));
253
254 self.fuzz_state.revert();
256 }
257
258 fn update_optimization_value(&mut self, value: I256, sequence: &[BasicTxDetails]) {
260 if self.test_data.optimization_best_value.is_none_or(|best| value > best) {
261 self.test_data.optimization_best_value = Some(value);
262 self.test_data.optimization_best_sequence = sequence.to_vec();
263 }
264 }
265}
266
267struct InvariantTestRun {
269 inputs: Vec<BasicTxDetails>,
271 executor: Executor,
273 fuzz_runs: Vec<FuzzCase>,
275 created_contracts: Vec<Address>,
277 run_traces: Vec<SparsedTraceArena>,
279 depth: u32,
281 rejects: u32,
283 new_coverage: bool,
285}
286
287impl InvariantTestRun {
288 fn new(first_input: BasicTxDetails, executor: Executor, depth: usize) -> Self {
290 Self {
291 inputs: vec![first_input],
292 executor,
293 fuzz_runs: Vec::with_capacity(depth),
294 created_contracts: vec![],
295 run_traces: vec![],
296 depth: 0,
297 rejects: 0,
298 new_coverage: false,
299 }
300 }
301}
302
303pub struct InvariantExecutor<'a> {
310 pub executor: Executor,
311 runner: TestRunner,
313 config: InvariantConfig,
315 setup_contracts: &'a ContractsByAddress,
317 project_contracts: &'a ContractsByArtifact,
320 artifact_filters: ArtifactFilters,
322}
323
324impl<'a> InvariantExecutor<'a> {
325 pub fn new(
327 executor: Executor,
328 runner: TestRunner,
329 config: InvariantConfig,
330 setup_contracts: &'a ContractsByAddress,
331 project_contracts: &'a ContractsByArtifact,
332 ) -> Self {
333 Self {
334 executor,
335 runner,
336 config,
337 setup_contracts,
338 project_contracts,
339 artifact_filters: ArtifactFilters::default(),
340 }
341 }
342
343 pub fn config(self) -> InvariantConfig {
344 self.config
345 }
346
347 pub fn invariant_fuzz(
349 &mut self,
350 invariant_contract: InvariantContract<'_>,
351 fuzz_fixtures: &FuzzFixtures,
352 fuzz_state: EvmFuzzState,
353 progress: Option<&ProgressBar>,
354 early_exit: &EarlyExit,
355 ) -> Result<InvariantFuzzTestResult> {
356 if !invariant_contract.invariant_function.inputs.is_empty() {
358 return Err(eyre!("Invariant test function should have no inputs"));
359 }
360
361 let (mut invariant_test, mut corpus_manager) =
362 self.prepare_test(&invariant_contract, fuzz_fixtures, fuzz_state)?;
363
364 let mut runs = 0;
366 let timer = FuzzTestTimer::new(self.config.timeout);
367 let mut last_metrics_report = Instant::now();
368 let continue_campaign = |runs: u32| {
369 if early_exit.should_stop() {
370 return false;
371 }
372
373 if timer.is_enabled() { !timer.is_timed_out() } else { runs < self.config.runs }
374 };
375
376 let edge_coverage_enabled = self.config.corpus.collect_edge_coverage();
378
379 'stop: while continue_campaign(runs) {
380 let initial_seq = corpus_manager.new_inputs(
381 &mut invariant_test.test_data.branch_runner,
382 &invariant_test.fuzz_state,
383 &invariant_test.targeted_contracts,
384 )?;
385
386 let mut current_run = InvariantTestRun::new(
388 initial_seq[0].clone(),
389 self.executor.clone(),
391 self.config.depth as usize,
392 );
393
394 if self.config.fail_on_revert && invariant_test.reverts() > 0 {
396 return Err(eyre!("call reverted"));
397 }
398
399 while current_run.depth < self.config.depth {
400 if timer.is_timed_out() {
402 break 'stop;
407 }
408
409 let tx = current_run
410 .inputs
411 .last()
412 .ok_or_else(|| eyre!("no input generated to call fuzzed target."))?;
413
414 let mut call_result = execute_tx(&mut current_run.executor, tx)?;
417 let discarded = call_result.result.as_ref() == MAGIC_ASSUME;
418 if self.config.show_metrics {
419 invariant_test.record_metrics(tx, call_result.reverted, discarded);
420 }
421
422 invariant_test.merge_line_coverage(call_result.line_coverage.clone());
424 if corpus_manager.merge_edge_coverage(&mut call_result) {
426 current_run.new_coverage = true;
427 }
428
429 if discarded {
430 current_run.inputs.pop();
431 current_run.rejects += 1;
432 if current_run.rejects > self.config.max_assume_rejects {
433 invariant_test.set_error(InvariantFuzzError::MaxAssumeRejects(
434 self.config.max_assume_rejects,
435 ));
436 break 'stop;
437 }
438 } else {
439 current_run.executor.commit(&mut call_result);
441
442 let mut state_changeset = std::mem::take(&mut call_result.state_changeset);
450 if !call_result.reverted {
451 collect_data(
452 &invariant_test,
453 &mut state_changeset,
454 tx,
455 &call_result,
456 self.config.depth,
457 );
458 }
459
460 if let Err(error) =
463 &invariant_test.targeted_contracts.collect_created_contracts(
464 &state_changeset,
465 self.project_contracts,
466 self.setup_contracts,
467 &self.artifact_filters,
468 &mut current_run.created_contracts,
469 )
470 {
471 warn!(target: "forge::test", "{error}");
472 }
473 current_run
474 .fuzz_runs
475 .push(FuzzCase { gas: call_result.gas_used, stipend: call_result.stipend });
476
477 let is_last_call = current_run.depth == self.config.depth - 1;
483 let should_check_invariant = if self.config.check_interval == 0 {
484 is_last_call
485 } else {
486 self.config.check_interval == 1
487 || (current_run.depth + 1).is_multiple_of(self.config.check_interval)
488 || is_last_call
489 };
490
491 let result = if should_check_invariant {
492 can_continue(
493 &invariant_contract,
494 &mut invariant_test,
495 &mut current_run,
496 &self.config,
497 call_result,
498 &state_changeset,
499 )
500 .map_err(|e| eyre!(e.to_string()))?
501 } else {
502 if call_result.reverted {
504 invariant_test.test_data.failures.reverts += 1;
505 if self.config.fail_on_revert {
506 let case_data = error::FailedInvariantCaseData::new(
507 &invariant_contract,
508 &self.config,
509 &invariant_test.targeted_contracts,
510 ¤t_run.inputs,
511 call_result,
512 &[],
513 );
514 invariant_test.test_data.failures.revert_reason =
515 Some(case_data.revert_reason.clone());
516 invariant_test.test_data.failures.error =
517 Some(InvariantFuzzError::Revert(case_data));
518 result::RichInvariantResults::new(false, None)
519 } else if !invariant_contract.is_optimization() {
520 current_run.inputs.pop();
523 result::RichInvariantResults::new(true, None)
524 } else {
525 result::RichInvariantResults::new(true, None)
526 }
527 } else {
528 result::RichInvariantResults::new(true, None)
529 }
530 };
531
532 if !result.can_continue || current_run.depth == self.config.depth - 1 {
533 invariant_test.set_last_run_inputs(¤t_run.inputs);
534 }
535 if !result.can_continue {
537 break 'stop;
538 }
539
540 invariant_test.set_last_call_results(result.call_result);
541 current_run.depth += 1;
542 }
543
544 current_run.inputs.push(corpus_manager.generate_next_input(
545 &mut invariant_test.test_data.branch_runner,
546 &initial_seq,
547 discarded,
548 current_run.depth as usize,
549 )?);
550 }
551
552 corpus_manager.process_inputs(¤t_run.inputs, current_run.new_coverage);
554
555 if invariant_contract.call_after_invariant && !invariant_test.has_errors() {
557 assert_after_invariant(
558 &invariant_contract,
559 &mut invariant_test,
560 ¤t_run,
561 &self.config,
562 )
563 .map_err(|_| eyre!("Failed to call afterInvariant"))?;
564 }
565
566 invariant_test.end_run(current_run, self.config.gas_report_samples as usize);
568 if let Some(progress) = progress {
569 progress.inc(1);
571 if edge_coverage_enabled {
573 progress.set_message(format!("{}", &corpus_manager.metrics));
574 }
575 } else if edge_coverage_enabled
576 && last_metrics_report.elapsed() > DURATION_BETWEEN_METRICS_REPORT
577 {
578 let metrics = json!({
580 "timestamp": SystemTime::now()
581 .duration_since(UNIX_EPOCH)?
582 .as_secs(),
583 "invariant": invariant_contract.invariant_function.name,
584 "metrics": &corpus_manager.metrics,
585 });
586 let _ = sh_println!("{}", serde_json::to_string(&metrics)?);
587 last_metrics_report = Instant::now();
588 }
589
590 runs += 1;
591 }
592
593 trace!(?fuzz_fixtures);
594 invariant_test.fuzz_state.log_stats();
595
596 let result = invariant_test.test_data;
597 Ok(InvariantFuzzTestResult {
598 error: result.failures.error,
599 cases: result.fuzz_cases,
600 reverts: result.failures.reverts,
601 last_run_inputs: result.last_run_inputs,
602 gas_report_traces: result.gas_report_traces,
603 line_coverage: result.line_coverage,
604 metrics: result.metrics,
605 failed_corpus_replays: corpus_manager.failed_replays,
606 optimization_best_value: result.optimization_best_value,
607 optimization_best_sequence: result.optimization_best_sequence,
608 })
609 }
610
611 fn prepare_test(
615 &mut self,
616 invariant_contract: &InvariantContract<'_>,
617 fuzz_fixtures: &FuzzFixtures,
618 fuzz_state: EvmFuzzState,
619 ) -> Result<(InvariantTest, WorkerCorpus)> {
620 self.select_contract_artifacts(invariant_contract.address)?;
622 let (targeted_senders, targeted_contracts) =
623 self.select_contracts_and_senders(invariant_contract.address)?;
624
625 let strategy = invariant_strat(
627 fuzz_state.clone(),
628 targeted_senders,
629 targeted_contracts.clone(),
630 self.config.clone(),
631 fuzz_fixtures.clone(),
632 )
633 .no_shrink();
634
635 let fuzz_state =
638 if targeted_contracts.targets.lock().iter().any(|(_, t)| t.storage_layout.is_some()) {
639 fuzz_state.with_mapping_slots(AddressMap::default())
640 } else {
641 fuzz_state
642 };
643
644 self.executor.inspector_mut().set_fuzzer(Fuzzer {
648 call_generator: None,
649 fuzz_state: fuzz_state.clone(),
650 collect: true,
651 });
652
653 let mut failures = InvariantFailures::new();
658 let last_call_results = assert_invariants(
659 invariant_contract,
660 &self.config,
661 &targeted_contracts,
662 &self.executor,
663 &[],
664 &mut failures,
665 )?;
666 if let Some(error) = failures.error {
667 return Err(eyre!(error.revert_reason().unwrap_or_default()));
668 }
669
670 if self.config.call_override {
674 let target_contract_ref = Arc::new(RwLock::new(Address::ZERO));
675
676 let handler_addresses: std::collections::HashSet<Address> =
679 targeted_contracts.targets.lock().keys().copied().collect();
680
681 let call_generator = RandomCallGenerator::new(
682 invariant_contract.address,
683 handler_addresses,
684 self.runner.clone(),
685 override_call_strat(
686 fuzz_state.clone(),
687 targeted_contracts.clone(),
688 target_contract_ref.clone(),
689 fuzz_fixtures.clone(),
690 ),
691 target_contract_ref,
692 );
693
694 if let Some(fuzzer) = self.executor.inspector_mut().fuzzer.as_mut() {
695 fuzzer.call_generator = Some(call_generator);
696 }
697 }
698
699 let worker = WorkerCorpus::new(
700 0,
701 self.config.corpus.clone(),
702 strategy.boxed(),
703 Some(&self.executor),
704 None,
705 Some(&targeted_contracts),
706 )?;
707
708 let invariant_test = InvariantTest::new(
709 fuzz_state,
710 targeted_contracts,
711 failures,
712 last_call_results,
713 self.runner.clone(),
714 );
715
716 Ok((invariant_test, worker))
717 }
718
719 pub fn select_contract_artifacts(&mut self, invariant_address: Address) -> Result<()> {
729 let targeted_artifact_selectors = self
730 .executor
731 .call_sol_default(invariant_address, &IInvariantTest::targetArtifactSelectorsCall {});
732
733 for IInvariantTest::FuzzArtifactSelector { artifact, selectors } in
735 targeted_artifact_selectors
736 {
737 let identifier = self.validate_selected_contract(artifact, &selectors)?;
738 self.artifact_filters.targeted.entry(identifier).or_default().extend(selectors);
739 }
740
741 let targeted_artifacts = self
742 .executor
743 .call_sol_default(invariant_address, &IInvariantTest::targetArtifactsCall {});
744 let excluded_artifacts = self
745 .executor
746 .call_sol_default(invariant_address, &IInvariantTest::excludeArtifactsCall {});
747
748 for contract in excluded_artifacts {
750 let identifier = self.validate_selected_contract(contract, &[])?;
751
752 if !self.artifact_filters.excluded.contains(&identifier) {
753 self.artifact_filters.excluded.push(identifier);
754 }
755 }
756
757 for (artifact, contract) in self.project_contracts.iter() {
759 if contract
760 .abi
761 .functions()
762 .filter(|func| {
763 !matches!(
764 func.state_mutability,
765 alloy_json_abi::StateMutability::Pure
766 | alloy_json_abi::StateMutability::View
767 )
768 })
769 .count()
770 == 0
771 && !self.artifact_filters.excluded.contains(&artifact.identifier())
772 {
773 self.artifact_filters.excluded.push(artifact.identifier());
774 }
775 }
776
777 for contract in targeted_artifacts {
780 let identifier = self.validate_selected_contract(contract, &[])?;
781
782 if !self.artifact_filters.targeted.contains_key(&identifier)
783 && !self.artifact_filters.excluded.contains(&identifier)
784 {
785 self.artifact_filters.targeted.insert(identifier, vec![]);
786 }
787 }
788 Ok(())
789 }
790
791 fn validate_selected_contract(
794 &mut self,
795 contract: String,
796 selectors: &[FixedBytes<4>],
797 ) -> Result<String> {
798 if let Some((artifact, contract_data)) =
799 self.project_contracts.find_by_name_or_identifier(&contract)?
800 {
801 for selector in selectors {
803 contract_data
804 .abi
805 .functions()
806 .find(|func| func.selector().as_slice() == selector.as_slice())
807 .wrap_err(format!("{contract} does not have the selector {selector:?}"))?;
808 }
809
810 return Ok(artifact.identifier());
811 }
812 eyre::bail!(
813 "{contract} not found in the project. Allowed format: `contract_name` or `contract_path:contract_name`."
814 );
815 }
816
817 pub fn select_contracts_and_senders(
820 &self,
821 to: Address,
822 ) -> Result<(SenderFilters, FuzzRunIdentifiedContracts)> {
823 let targeted_senders =
824 self.executor.call_sol_default(to, &IInvariantTest::targetSendersCall {});
825 let mut excluded_senders =
826 self.executor.call_sol_default(to, &IInvariantTest::excludeSendersCall {});
827 excluded_senders.extend([
829 CHEATCODE_ADDRESS,
830 HARDHAT_CONSOLE_ADDRESS,
831 DEFAULT_CREATE2_DEPLOYER,
832 ]);
833 excluded_senders.extend(PRECOMPILES);
835 let sender_filters = SenderFilters::new(targeted_senders, excluded_senders);
836
837 let selected = self.executor.call_sol_default(to, &IInvariantTest::targetContractsCall {});
838 let excluded = self.executor.call_sol_default(to, &IInvariantTest::excludeContractsCall {});
839
840 let contracts = self
841 .setup_contracts
842 .iter()
843 .filter(|&(addr, (identifier, _))| {
844 if *addr == to && selected.contains(&to) {
846 return true;
847 }
848
849 *addr != to
850 && *addr != CHEATCODE_ADDRESS
851 && *addr != HARDHAT_CONSOLE_ADDRESS
852 && (selected.is_empty() || selected.contains(addr))
853 && (excluded.is_empty() || !excluded.contains(addr))
854 && self.artifact_filters.matches(identifier)
855 })
856 .map(|(addr, (identifier, abi))| {
857 (
858 *addr,
859 TargetedContract::new(identifier.clone(), abi.clone())
860 .with_project_contracts(self.project_contracts),
861 )
862 })
863 .collect();
864 let mut contracts = TargetedContracts { inner: contracts };
865
866 self.target_interfaces(to, &mut contracts)?;
867
868 self.select_selectors(to, &mut contracts)?;
869
870 if contracts.is_empty() {
872 eyre::bail!("No contracts to fuzz.");
873 }
874
875 Ok((sender_filters, FuzzRunIdentifiedContracts::new(contracts, selected.is_empty())))
876 }
877
878 pub fn target_interfaces(
883 &self,
884 invariant_address: Address,
885 targeted_contracts: &mut TargetedContracts,
886 ) -> Result<()> {
887 let interfaces = self
888 .executor
889 .call_sol_default(invariant_address, &IInvariantTest::targetInterfacesCall {});
890
891 let mut combined = TargetedContracts::new();
897
898 for IInvariantTest::FuzzInterface { addr, artifacts } in &interfaces {
901 for identifier in artifacts {
903 if let Some((_, contract_data)) =
905 self.project_contracts.iter().find(|(artifact, _)| {
906 &artifact.name == identifier || &artifact.identifier() == identifier
907 })
908 {
909 let abi = &contract_data.abi;
910 combined
911 .entry(*addr)
913 .and_modify(|entry| {
915 entry.abi.functions.extend(abi.functions.clone());
917 })
918 .or_insert_with(|| {
920 let mut contract =
921 TargetedContract::new(identifier.to_string(), abi.clone());
922 contract.storage_layout =
923 contract_data.storage_layout.as_ref().map(Arc::clone);
924 contract
925 });
926 }
927 }
928 }
929
930 targeted_contracts.extend(combined.inner);
931
932 Ok(())
933 }
934
935 pub fn select_selectors(
938 &self,
939 address: Address,
940 targeted_contracts: &mut TargetedContracts,
941 ) -> Result<()> {
942 for (address, (identifier, _)) in self.setup_contracts {
943 if let Some(selectors) = self.artifact_filters.targeted.get(identifier) {
944 self.add_address_with_functions(*address, selectors, false, targeted_contracts)?;
945 }
946 }
947
948 let mut target_test_selectors = vec![];
949 let mut excluded_test_selectors = vec![];
950
951 let selectors =
953 self.executor.call_sol_default(address, &IInvariantTest::targetSelectorsCall {});
954 for IInvariantTest::FuzzSelector { addr, selectors } in selectors {
955 if addr == address {
956 target_test_selectors = selectors.clone();
957 }
958 self.add_address_with_functions(addr, &selectors, false, targeted_contracts)?;
959 }
960
961 let excluded_selectors =
963 self.executor.call_sol_default(address, &IInvariantTest::excludeSelectorsCall {});
964 for IInvariantTest::FuzzSelector { addr, selectors } in excluded_selectors {
965 if addr == address {
966 excluded_test_selectors = selectors.clone();
969 }
970 self.add_address_with_functions(addr, &selectors, true, targeted_contracts)?;
971 }
972
973 if target_test_selectors.is_empty()
974 && let Some(target) = targeted_contracts.get(&address)
975 {
976 let selectors: Vec<_> = target
980 .abi
981 .functions()
982 .filter_map(|func| {
983 if matches!(
984 func.state_mutability,
985 alloy_json_abi::StateMutability::Pure
986 | alloy_json_abi::StateMutability::View
987 ) || func.is_reserved()
988 || excluded_test_selectors.contains(&func.selector())
989 {
990 None
991 } else {
992 Some(func.selector())
993 }
994 })
995 .collect();
996 self.add_address_with_functions(address, &selectors, false, targeted_contracts)?;
997 }
998
999 Ok(())
1000 }
1001
1002 fn add_address_with_functions(
1004 &self,
1005 address: Address,
1006 selectors: &[Selector],
1007 should_exclude: bool,
1008 targeted_contracts: &mut TargetedContracts,
1009 ) -> eyre::Result<()> {
1010 if selectors.is_empty() {
1012 return Ok(());
1013 }
1014
1015 let contract = match targeted_contracts.entry(address) {
1016 Entry::Occupied(entry) => entry.into_mut(),
1017 Entry::Vacant(entry) => {
1018 let (identifier, abi) = self.setup_contracts.get(&address).ok_or_else(|| {
1019 eyre::eyre!(
1020 "[{}] address does not have an associated contract: {}",
1021 if should_exclude { "excludeSelectors" } else { "targetSelectors" },
1022 address
1023 )
1024 })?;
1025 entry.insert(
1026 TargetedContract::new(identifier.clone(), abi.clone())
1027 .with_project_contracts(self.project_contracts),
1028 )
1029 }
1030 };
1031 contract.add_selectors(selectors.iter().copied(), should_exclude)?;
1032 Ok(())
1033 }
1034}
1035
1036fn collect_data(
1040 invariant_test: &InvariantTest,
1041 state_changeset: &mut HashMap<Address, Account>,
1042 tx: &BasicTxDetails,
1043 call_result: &RawCallResult,
1044 run_depth: u32,
1045) {
1046 let mut has_code = false;
1048 if let Some(Some(code)) =
1049 state_changeset.get(&tx.sender).map(|account| account.info.code.as_ref())
1050 {
1051 has_code = !code.is_empty();
1052 }
1053
1054 let mut sender_changeset = None;
1056 if !has_code {
1057 sender_changeset = state_changeset.remove(&tx.sender);
1058 }
1059
1060 invariant_test.fuzz_state.collect_values_from_call(
1062 &invariant_test.targeted_contracts,
1063 tx,
1064 &call_result.result,
1065 &call_result.logs,
1066 &*state_changeset,
1067 run_depth,
1068 );
1069
1070 if let Some(changed) = sender_changeset {
1072 state_changeset.insert(tx.sender, changed);
1073 }
1074}
1075
1076pub(crate) fn call_after_invariant_function(
1080 executor: &Executor,
1081 to: Address,
1082) -> Result<(RawCallResult, bool), EvmError> {
1083 let calldata = Bytes::from_static(&IInvariantTest::afterInvariantCall::SELECTOR);
1084 let mut call_result = executor.call_raw(CALLER, to, calldata, U256::ZERO)?;
1085 let success = executor.is_raw_call_mut_success(to, &mut call_result, false);
1086 Ok((call_result, success))
1087}
1088
1089pub(crate) fn call_invariant_function(
1091 executor: &Executor,
1092 address: Address,
1093 calldata: Bytes,
1094) -> Result<(RawCallResult, bool)> {
1095 let mut call_result = executor.call_raw(CALLER, address, calldata, U256::ZERO)?;
1096 let success = executor.is_raw_call_mut_success(address, &mut call_result, false);
1097 Ok((call_result, success))
1098}
1099
1100pub(crate) fn execute_tx(executor: &mut Executor, tx: &BasicTxDetails) -> Result<RawCallResult> {
1103 let warp = tx.warp.unwrap_or_default();
1104 let roll = tx.roll.unwrap_or_default();
1105
1106 if warp > 0 || roll > 0 {
1107 executor.env_mut().evm_env.block_env.timestamp += warp;
1109 executor.env_mut().evm_env.block_env.number += roll;
1110
1111 let block_env = executor.env().evm_env.block_env.clone();
1115 if let Some(cheatcodes) = executor.inspector_mut().cheatcodes.as_mut() {
1116 if let Some(block) = cheatcodes.block.as_mut() {
1117 block.timestamp += warp;
1118 block.number += roll;
1119 } else {
1120 cheatcodes.block = Some(block_env);
1121 }
1122 }
1123 }
1124
1125 executor
1126 .call_raw(tx.sender, tx.call_details.target, tx.call_details.calldata.clone(), U256::ZERO)
1127 .map_err(|e| eyre!(format!("Could not make raw evm call: {e}")))
1128}