foundry_evm/executors/invariant/
mod.rs

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    BasicTxDetails, FuzzCase, FuzzFixtures, FuzzedCases,
18    invariant::{
19        ArtifactFilters, FuzzRunIdentifiedContracts, InvariantContract, RandomCallGenerator,
20        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    collections::{HashMap as Map, btree_map::Entry},
33    sync::Arc,
34    time::{Instant, SystemTime, UNIX_EPOCH},
35};
36
37mod error;
38pub use error::{InvariantFailures, InvariantFuzzError};
39use foundry_evm_coverage::HitMaps;
40
41mod replay;
42pub use replay::{replay_error, replay_run};
43
44mod result;
45use foundry_common::{TestFunctionExt, sh_println};
46pub use result::InvariantFuzzTestResult;
47use serde::{Deserialize, Serialize};
48use serde_json::json;
49
50mod shrink;
51use crate::executors::{
52    DURATION_BETWEEN_METRICS_REPORT, EvmError, FailFast, FuzzTestTimer, corpus::CorpusManager,
53};
54pub use shrink::check_sequence;
55
56sol! {
57    interface IInvariantTest {
58        #[derive(Default)]
59        struct FuzzSelector {
60            address addr;
61            bytes4[] selectors;
62        }
63
64        #[derive(Default)]
65        struct FuzzArtifactSelector {
66            string artifact;
67            bytes4[] selectors;
68        }
69
70        #[derive(Default)]
71        struct FuzzInterface {
72            address addr;
73            string[] artifacts;
74        }
75
76        function afterInvariant() external;
77
78        #[derive(Default)]
79        function excludeArtifacts() public view returns (string[] memory excludedArtifacts);
80
81        #[derive(Default)]
82        function excludeContracts() public view returns (address[] memory excludedContracts);
83
84        #[derive(Default)]
85        function excludeSelectors() public view returns (FuzzSelector[] memory excludedSelectors);
86
87        #[derive(Default)]
88        function excludeSenders() public view returns (address[] memory excludedSenders);
89
90        #[derive(Default)]
91        function targetArtifacts() public view returns (string[] memory targetedArtifacts);
92
93        #[derive(Default)]
94        function targetArtifactSelectors() public view returns (FuzzArtifactSelector[] memory targetedArtifactSelectors);
95
96        #[derive(Default)]
97        function targetContracts() public view returns (address[] memory targetedContracts);
98
99        #[derive(Default)]
100        function targetSelectors() public view returns (FuzzSelector[] memory targetedSelectors);
101
102        #[derive(Default)]
103        function targetSenders() public view returns (address[] memory targetedSenders);
104
105        #[derive(Default)]
106        function targetInterfaces() public view returns (FuzzInterface[] memory targetedInterfaces);
107    }
108}
109
110/// Contains invariant metrics for a single fuzzed selector.
111#[derive(Default, Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
112pub struct InvariantMetrics {
113    // Count of fuzzed selector calls.
114    pub calls: usize,
115    // Count of fuzzed selector reverts.
116    pub reverts: usize,
117    // Count of fuzzed selector discards (through assume cheatcodes).
118    pub discards: usize,
119}
120
121/// Contains data collected during invariant test runs.
122struct InvariantTestData {
123    // Consumed gas and calldata of every successful fuzz call.
124    fuzz_cases: Vec<FuzzedCases>,
125    // Data related to reverts or failed assertions of the test.
126    failures: InvariantFailures,
127    // Calldata in the last invariant run.
128    last_run_inputs: Vec<BasicTxDetails>,
129    // Additional traces for gas report.
130    gas_report_traces: Vec<Vec<CallTraceArena>>,
131    // Last call results of the invariant test.
132    last_call_results: Option<RawCallResult>,
133    // Line coverage information collected from all fuzzed calls.
134    line_coverage: Option<HitMaps>,
135    // Metrics for each fuzzed selector.
136    metrics: Map<String, InvariantMetrics>,
137
138    // Proptest runner to query for random values.
139    // The strategy only comes with the first `input`. We fill the rest of the `inputs`
140    // until the desired `depth` so we can use the evolving fuzz dictionary
141    // during the run.
142    branch_runner: TestRunner,
143}
144
145/// Contains invariant test data.
146struct InvariantTest {
147    // Fuzz state of invariant test.
148    fuzz_state: EvmFuzzState,
149    // Contracts fuzzed by the invariant test.
150    targeted_contracts: FuzzRunIdentifiedContracts,
151    // Data collected during invariant runs.
152    test_data: InvariantTestData,
153}
154
155impl InvariantTest {
156    /// Instantiates an invariant test.
157    fn new(
158        fuzz_state: EvmFuzzState,
159        targeted_contracts: FuzzRunIdentifiedContracts,
160        failures: InvariantFailures,
161        last_call_results: Option<RawCallResult>,
162        branch_runner: TestRunner,
163    ) -> Self {
164        let mut fuzz_cases = vec![];
165        if last_call_results.is_none() {
166            fuzz_cases.push(FuzzedCases::new(vec![]));
167        }
168        let test_data = InvariantTestData {
169            fuzz_cases,
170            failures,
171            last_run_inputs: vec![],
172            gas_report_traces: vec![],
173            last_call_results,
174            line_coverage: None,
175            metrics: Map::default(),
176            branch_runner,
177        };
178        Self { fuzz_state, targeted_contracts, test_data }
179    }
180
181    /// Returns number of invariant test reverts.
182    fn reverts(&self) -> usize {
183        self.test_data.failures.reverts
184    }
185
186    /// Whether invariant test has errors or not.
187    fn has_errors(&self) -> bool {
188        self.test_data.failures.error.is_some()
189    }
190
191    /// Set invariant test error.
192    fn set_error(&mut self, error: InvariantFuzzError) {
193        self.test_data.failures.error = Some(error);
194    }
195
196    /// Set last invariant test call results.
197    fn set_last_call_results(&mut self, call_result: Option<RawCallResult>) {
198        self.test_data.last_call_results = call_result;
199    }
200
201    /// Set last invariant run call sequence.
202    fn set_last_run_inputs(&mut self, inputs: &Vec<BasicTxDetails>) {
203        self.test_data.last_run_inputs.clone_from(inputs);
204    }
205
206    /// Merge current collected line coverage with the new coverage from last fuzzed call.
207    fn merge_line_coverage(&mut self, new_coverage: Option<HitMaps>) {
208        HitMaps::merge_opt(&mut self.test_data.line_coverage, new_coverage);
209    }
210
211    /// Update metrics for a fuzzed selector, extracted from tx details.
212    /// Always increments number of calls; discarded runs (through assume cheatcodes) are tracked
213    /// separated from reverts.
214    fn record_metrics(&mut self, tx_details: &BasicTxDetails, reverted: bool, discarded: bool) {
215        if let Some(metric_key) =
216            self.targeted_contracts.targets.lock().fuzzed_metric_key(tx_details)
217        {
218            let test_metrics = &mut self.test_data.metrics;
219            let invariant_metrics = test_metrics.entry(metric_key).or_default();
220            invariant_metrics.calls += 1;
221            if discarded {
222                invariant_metrics.discards += 1;
223            } else if reverted {
224                invariant_metrics.reverts += 1;
225            }
226        }
227    }
228
229    /// End invariant test run by collecting results, cleaning collected artifacts and reverting
230    /// created fuzz state.
231    fn end_run(&mut self, run: InvariantTestRun, gas_samples: usize) {
232        // We clear all the targeted contracts created during this run.
233        self.targeted_contracts.clear_created_contracts(run.created_contracts);
234
235        if self.test_data.gas_report_traces.len() < gas_samples {
236            self.test_data
237                .gas_report_traces
238                .push(run.run_traces.into_iter().map(|arena| arena.arena).collect());
239        }
240        self.test_data.fuzz_cases.push(FuzzedCases::new(run.fuzz_runs));
241
242        // Revert state to not persist values between runs.
243        self.fuzz_state.revert();
244    }
245}
246
247/// Contains data for an invariant test run.
248struct InvariantTestRun {
249    // Invariant run call sequence.
250    inputs: Vec<BasicTxDetails>,
251    // Current invariant run executor.
252    executor: Executor,
253    // Invariant run stat reports (eg. gas usage).
254    fuzz_runs: Vec<FuzzCase>,
255    // Contracts created during current invariant run.
256    created_contracts: Vec<Address>,
257    // Traces of each call of the invariant run call sequence.
258    run_traces: Vec<SparsedTraceArena>,
259    // Current depth of invariant run.
260    depth: u32,
261    // Current assume rejects of the invariant run.
262    rejects: u32,
263    // Whether new coverage was discovered during this run.
264    new_coverage: bool,
265}
266
267impl InvariantTestRun {
268    /// Instantiates an invariant test run.
269    fn new(first_input: BasicTxDetails, executor: Executor, depth: usize) -> Self {
270        Self {
271            inputs: vec![first_input],
272            executor,
273            fuzz_runs: Vec::with_capacity(depth),
274            created_contracts: vec![],
275            run_traces: vec![],
276            depth: 0,
277            rejects: 0,
278            new_coverage: false,
279        }
280    }
281}
282
283/// Wrapper around any [`Executor`] implementer which provides fuzzing support using [`proptest`].
284///
285/// After instantiation, calling `invariant_fuzz` will proceed to hammer the deployed smart
286/// contracts with inputs, until it finds a counterexample sequence. The provided [`TestRunner`]
287/// contains all the configuration which can be overridden via [environment
288/// variables](proptest::test_runner::Config)
289pub struct InvariantExecutor<'a> {
290    pub executor: Executor,
291    /// Proptest runner.
292    runner: TestRunner,
293    /// The invariant configuration
294    config: InvariantConfig,
295    /// Contracts deployed with `setUp()`
296    setup_contracts: &'a ContractsByAddress,
297    /// Contracts that are part of the project but have not been deployed yet. We need the bytecode
298    /// to identify them from the stateset changes.
299    project_contracts: &'a ContractsByArtifact,
300    /// Filters contracts to be fuzzed through their artifact identifiers.
301    artifact_filters: ArtifactFilters,
302}
303
304impl<'a> InvariantExecutor<'a> {
305    /// Instantiates a fuzzed executor EVM given a testrunner
306    pub fn new(
307        executor: Executor,
308        runner: TestRunner,
309        config: InvariantConfig,
310        setup_contracts: &'a ContractsByAddress,
311        project_contracts: &'a ContractsByArtifact,
312    ) -> Self {
313        Self {
314            executor,
315            runner,
316            config,
317            setup_contracts,
318            project_contracts,
319            artifact_filters: ArtifactFilters::default(),
320        }
321    }
322
323    /// Fuzzes any deployed contract and checks any broken invariant at `invariant_address`.
324    pub fn invariant_fuzz(
325        &mut self,
326        invariant_contract: InvariantContract<'_>,
327        fuzz_fixtures: &FuzzFixtures,
328        deployed_libs: &[Address],
329        progress: Option<&ProgressBar>,
330        fail_fast: &FailFast,
331    ) -> Result<InvariantFuzzTestResult> {
332        // Throw an error to abort test run if the invariant function accepts input params
333        if !invariant_contract.invariant_function.inputs.is_empty() {
334            return Err(eyre!("Invariant test function should have no inputs"));
335        }
336
337        let (mut invariant_test, mut corpus_manager) =
338            self.prepare_test(&invariant_contract, fuzz_fixtures, deployed_libs)?;
339
340        // Start timer for this invariant test.
341        let mut runs = 0;
342        let timer = FuzzTestTimer::new(self.config.timeout);
343        let mut last_metrics_report = Instant::now();
344        let continue_campaign = |runs: u32| {
345            if fail_fast.should_stop() {
346                return false;
347            }
348
349            if timer.is_enabled() { !timer.is_timed_out() } else { runs < self.config.runs }
350        };
351
352        // Invariant runs with edge coverage if corpus dir is set or showing edge coverage.
353        let edge_coverage_enabled = self.config.corpus.collect_edge_coverage();
354
355        'stop: while continue_campaign(runs) {
356            let initial_seq = corpus_manager.new_inputs(
357                &mut invariant_test.test_data.branch_runner,
358                &invariant_test.fuzz_state,
359                &invariant_test.targeted_contracts,
360            )?;
361
362            // Create current invariant run data.
363            let mut current_run = InvariantTestRun::new(
364                initial_seq[0].clone(),
365                // Before each run, we must reset the backend state.
366                self.executor.clone(),
367                self.config.depth as usize,
368            );
369
370            // We stop the run immediately if we have reverted, and `fail_on_revert` is set.
371            if self.config.fail_on_revert && invariant_test.reverts() > 0 {
372                return Err(eyre!("call reverted"));
373            }
374
375            while current_run.depth < self.config.depth {
376                // Check if the timeout has been reached.
377                if timer.is_timed_out() {
378                    // Since we never record a revert here the test is still considered
379                    // successful even though it timed out. We *want*
380                    // this behavior for now, so that's ok, but
381                    // future developers should be aware of this.
382                    break 'stop;
383                }
384
385                let tx = current_run
386                    .inputs
387                    .last()
388                    .ok_or_else(|| eyre!("no input generated to call fuzzed target."))?;
389
390                // Execute call from the randomly generated sequence without committing state.
391                // State is committed only if call is not a magic assume.
392                let mut call_result = current_run
393                    .executor
394                    .call_raw(
395                        tx.sender,
396                        tx.call_details.target,
397                        tx.call_details.calldata.clone(),
398                        U256::ZERO,
399                    )
400                    .map_err(|e| eyre!(format!("Could not make raw evm call: {e}")))?;
401
402                let discarded = call_result.result.as_ref() == MAGIC_ASSUME;
403                if self.config.show_metrics {
404                    invariant_test.record_metrics(tx, call_result.reverted, discarded);
405                }
406
407                // Collect line coverage from last fuzzed call.
408                invariant_test.merge_line_coverage(call_result.line_coverage.clone());
409                // Collect edge coverage and set the flag in the current run.
410                if corpus_manager.merge_edge_coverage(&mut call_result) {
411                    current_run.new_coverage = true;
412                }
413
414                if discarded {
415                    current_run.inputs.pop();
416                    current_run.rejects += 1;
417                    if current_run.rejects > self.config.max_assume_rejects {
418                        invariant_test.set_error(InvariantFuzzError::MaxAssumeRejects(
419                            self.config.max_assume_rejects,
420                        ));
421                        break 'stop;
422                    }
423                } else {
424                    // Commit executed call result.
425                    current_run.executor.commit(&mut call_result);
426
427                    // Collect data for fuzzing from the state changeset.
428                    // This step updates the state dictionary and therefore invalidates the
429                    // ValueTree in use by the current run. This manifestsitself in proptest
430                    // observing a different input case than what it was called with, and creates
431                    // inconsistencies whenever proptest tries to use the input case after test
432                    // execution.
433                    // See <https://github.com/foundry-rs/foundry/issues/9764>.
434                    let mut state_changeset = call_result.state_changeset.clone();
435                    if !call_result.reverted {
436                        collect_data(
437                            &invariant_test,
438                            &mut state_changeset,
439                            tx,
440                            &call_result,
441                            self.config.depth,
442                        );
443                    }
444
445                    // Collect created contracts and add to fuzz targets only if targeted contracts
446                    // are updatable.
447                    if let Err(error) =
448                        &invariant_test.targeted_contracts.collect_created_contracts(
449                            &state_changeset,
450                            self.project_contracts,
451                            self.setup_contracts,
452                            &self.artifact_filters,
453                            &mut current_run.created_contracts,
454                        )
455                    {
456                        warn!(target: "forge::test", "{error}");
457                    }
458                    current_run.fuzz_runs.push(FuzzCase {
459                        calldata: tx.call_details.calldata.clone(),
460                        gas: call_result.gas_used,
461                        stipend: call_result.stipend,
462                    });
463
464                    // Determine if test can continue or should exit.
465                    let result = can_continue(
466                        &invariant_contract,
467                        &mut invariant_test,
468                        &mut current_run,
469                        &self.config,
470                        call_result,
471                        &state_changeset,
472                    )
473                    .map_err(|e| eyre!(e.to_string()))?;
474                    if !result.can_continue || current_run.depth == self.config.depth - 1 {
475                        invariant_test.set_last_run_inputs(&current_run.inputs);
476                    }
477                    // If test cannot continue then stop current run and exit test suite.
478                    if !result.can_continue {
479                        break 'stop;
480                    }
481
482                    invariant_test.set_last_call_results(result.call_result);
483                    current_run.depth += 1;
484                }
485
486                current_run.inputs.push(corpus_manager.generate_next_input(
487                    &mut invariant_test.test_data.branch_runner,
488                    &initial_seq,
489                    discarded,
490                    current_run.depth as usize,
491                )?);
492            }
493
494            // Extend corpus with current run data.
495            corpus_manager.process_inputs(&current_run.inputs, current_run.new_coverage);
496
497            // Call `afterInvariant` only if it is declared and test didn't fail already.
498            if invariant_contract.call_after_invariant && !invariant_test.has_errors() {
499                assert_after_invariant(
500                    &invariant_contract,
501                    &mut invariant_test,
502                    &current_run,
503                    &self.config,
504                )
505                .map_err(|_| eyre!("Failed to call afterInvariant"))?;
506            }
507
508            // End current invariant test run.
509            invariant_test.end_run(current_run, self.config.gas_report_samples as usize);
510            if let Some(progress) = progress {
511                // If running with progress then increment completed runs.
512                progress.inc(1);
513                // Display metrics in progress bar.
514                if edge_coverage_enabled {
515                    progress.set_message(format!("{}", &corpus_manager.metrics));
516                }
517            } else if edge_coverage_enabled
518                && last_metrics_report.elapsed() > DURATION_BETWEEN_METRICS_REPORT
519            {
520                // Display metrics inline if corpus dir set.
521                let metrics = json!({
522                    "timestamp": SystemTime::now()
523                        .duration_since(UNIX_EPOCH)?
524                        .as_secs(),
525                    "invariant": invariant_contract.invariant_function.name,
526                    "metrics": &corpus_manager.metrics,
527                });
528                let _ = sh_println!("{}", serde_json::to_string(&metrics)?);
529                last_metrics_report = Instant::now();
530            }
531
532            runs += 1;
533        }
534
535        trace!(?fuzz_fixtures);
536        invariant_test.fuzz_state.log_stats();
537
538        let result = invariant_test.test_data;
539        Ok(InvariantFuzzTestResult {
540            error: result.failures.error,
541            cases: result.fuzz_cases,
542            reverts: result.failures.reverts,
543            last_run_inputs: result.last_run_inputs,
544            gas_report_traces: result.gas_report_traces,
545            line_coverage: result.line_coverage,
546            metrics: result.metrics,
547            failed_corpus_replays: corpus_manager.failed_replays(),
548        })
549    }
550
551    /// Prepares certain structures to execute the invariant tests:
552    /// * Invariant Fuzz Test.
553    /// * Invariant Corpus Manager.
554    fn prepare_test(
555        &mut self,
556        invariant_contract: &InvariantContract<'_>,
557        fuzz_fixtures: &FuzzFixtures,
558        deployed_libs: &[Address],
559    ) -> Result<(InvariantTest, CorpusManager)> {
560        // Finds out the chosen deployed contracts and/or senders.
561        self.select_contract_artifacts(invariant_contract.address)?;
562        let (targeted_senders, targeted_contracts) =
563            self.select_contracts_and_senders(invariant_contract.address)?;
564
565        // Stores fuzz state for use with [fuzz_calldata_from_state].
566        let fuzz_state = EvmFuzzState::new(
567            self.executor.backend().mem_db(),
568            self.config.dictionary,
569            deployed_libs,
570        );
571
572        // Creates the invariant strategy.
573        let strategy = invariant_strat(
574            fuzz_state.clone(),
575            targeted_senders,
576            targeted_contracts.clone(),
577            self.config.dictionary.dictionary_weight,
578            fuzz_fixtures.clone(),
579        )
580        .no_shrink();
581
582        // Allows `override_call_strat` to use the address given by the Fuzzer inspector during
583        // EVM execution.
584        let mut call_generator = None;
585        if self.config.call_override {
586            let target_contract_ref = Arc::new(RwLock::new(Address::ZERO));
587
588            call_generator = Some(RandomCallGenerator::new(
589                invariant_contract.address,
590                self.runner.clone(),
591                override_call_strat(
592                    fuzz_state.clone(),
593                    targeted_contracts.clone(),
594                    target_contract_ref.clone(),
595                    fuzz_fixtures.clone(),
596                ),
597                target_contract_ref,
598            ));
599        }
600
601        self.executor.inspector_mut().set_fuzzer(Fuzzer {
602            call_generator,
603            fuzz_state: fuzz_state.clone(),
604            collect: true,
605        });
606
607        // Let's make sure the invariant is sound before actually starting the run:
608        // We'll assert the invariant in its initial state, and if it fails, we'll
609        // already know if we can early exit the invariant run.
610        // This does not count as a fuzz run. It will just register the revert.
611        let mut failures = InvariantFailures::new();
612        let last_call_results = assert_invariants(
613            invariant_contract,
614            &self.config,
615            &targeted_contracts,
616            &self.executor,
617            &[],
618            &mut failures,
619        )?;
620        if let Some(error) = failures.error {
621            return Err(eyre!(error.revert_reason().unwrap_or_default()));
622        }
623
624        let corpus_manager = CorpusManager::new(
625            self.config.corpus.clone(),
626            strategy.boxed(),
627            &self.executor,
628            None,
629            Some(&targeted_contracts),
630        )?;
631        let invariant_test = InvariantTest::new(
632            fuzz_state,
633            targeted_contracts,
634            failures,
635            last_call_results,
636            self.runner.clone(),
637        );
638
639        Ok((invariant_test, corpus_manager))
640    }
641
642    /// Fills the `InvariantExecutor` with the artifact identifier filters (in `path:name` string
643    /// format). They will be used to filter contracts after the `setUp`, and more importantly,
644    /// during the runs.
645    ///
646    /// Also excludes any contract without any mutable functions.
647    ///
648    /// Priority:
649    ///
650    /// targetArtifactSelectors > excludeArtifacts > targetArtifacts
651    pub fn select_contract_artifacts(&mut self, invariant_address: Address) -> Result<()> {
652        let targeted_artifact_selectors = self
653            .executor
654            .call_sol_default(invariant_address, &IInvariantTest::targetArtifactSelectorsCall {});
655
656        // Insert them into the executor `targeted_abi`.
657        for IInvariantTest::FuzzArtifactSelector { artifact, selectors } in
658            targeted_artifact_selectors
659        {
660            let identifier = self.validate_selected_contract(artifact, &selectors)?;
661            self.artifact_filters.targeted.entry(identifier).or_default().extend(selectors);
662        }
663
664        let targeted_artifacts = self
665            .executor
666            .call_sol_default(invariant_address, &IInvariantTest::targetArtifactsCall {});
667        let excluded_artifacts = self
668            .executor
669            .call_sol_default(invariant_address, &IInvariantTest::excludeArtifactsCall {});
670
671        // Insert `excludeArtifacts` into the executor `excluded_abi`.
672        for contract in excluded_artifacts {
673            let identifier = self.validate_selected_contract(contract, &[])?;
674
675            if !self.artifact_filters.excluded.contains(&identifier) {
676                self.artifact_filters.excluded.push(identifier);
677            }
678        }
679
680        // Exclude any artifact without mutable functions.
681        for (artifact, contract) in self.project_contracts.iter() {
682            if contract
683                .abi
684                .functions()
685                .filter(|func| {
686                    !matches!(
687                        func.state_mutability,
688                        alloy_json_abi::StateMutability::Pure
689                            | alloy_json_abi::StateMutability::View
690                    )
691                })
692                .count()
693                == 0
694                && !self.artifact_filters.excluded.contains(&artifact.identifier())
695            {
696                self.artifact_filters.excluded.push(artifact.identifier());
697            }
698        }
699
700        // Insert `targetArtifacts` into the executor `targeted_abi`, if they have not been seen
701        // before.
702        for contract in targeted_artifacts {
703            let identifier = self.validate_selected_contract(contract, &[])?;
704
705            if !self.artifact_filters.targeted.contains_key(&identifier)
706                && !self.artifact_filters.excluded.contains(&identifier)
707            {
708                self.artifact_filters.targeted.insert(identifier, vec![]);
709            }
710        }
711        Ok(())
712    }
713
714    /// Makes sure that the contract exists in the project. If so, it returns its artifact
715    /// identifier.
716    fn validate_selected_contract(
717        &mut self,
718        contract: String,
719        selectors: &[FixedBytes<4>],
720    ) -> Result<String> {
721        if let Some((artifact, contract_data)) =
722            self.project_contracts.find_by_name_or_identifier(&contract)?
723        {
724            // Check that the selectors really exist for this contract.
725            for selector in selectors {
726                contract_data
727                    .abi
728                    .functions()
729                    .find(|func| func.selector().as_slice() == selector.as_slice())
730                    .wrap_err(format!("{contract} does not have the selector {selector:?}"))?;
731            }
732
733            return Ok(artifact.identifier());
734        }
735        eyre::bail!(
736            "{contract} not found in the project. Allowed format: `contract_name` or `contract_path:contract_name`."
737        );
738    }
739
740    /// Selects senders and contracts based on the contract methods `targetSenders() -> address[]`,
741    /// `targetContracts() -> address[]` and `excludeContracts() -> address[]`.
742    pub fn select_contracts_and_senders(
743        &self,
744        to: Address,
745    ) -> Result<(SenderFilters, FuzzRunIdentifiedContracts)> {
746        let targeted_senders =
747            self.executor.call_sol_default(to, &IInvariantTest::targetSendersCall {});
748        let mut excluded_senders =
749            self.executor.call_sol_default(to, &IInvariantTest::excludeSendersCall {});
750        // Extend with default excluded addresses - https://github.com/foundry-rs/foundry/issues/4163
751        excluded_senders.extend([
752            CHEATCODE_ADDRESS,
753            HARDHAT_CONSOLE_ADDRESS,
754            DEFAULT_CREATE2_DEPLOYER,
755        ]);
756        // Extend with precompiles - https://github.com/foundry-rs/foundry/issues/4287
757        excluded_senders.extend(PRECOMPILES);
758        let sender_filters = SenderFilters::new(targeted_senders, excluded_senders);
759
760        let selected = self.executor.call_sol_default(to, &IInvariantTest::targetContractsCall {});
761        let excluded = self.executor.call_sol_default(to, &IInvariantTest::excludeContractsCall {});
762
763        let contracts = self
764            .setup_contracts
765            .iter()
766            .filter(|&(addr, (identifier, _))| {
767                // Include to address if explicitly set as target.
768                if *addr == to && selected.contains(&to) {
769                    return true;
770                }
771
772                *addr != to
773                    && *addr != CHEATCODE_ADDRESS
774                    && *addr != HARDHAT_CONSOLE_ADDRESS
775                    && (selected.is_empty() || selected.contains(addr))
776                    && (excluded.is_empty() || !excluded.contains(addr))
777                    && self.artifact_filters.matches(identifier)
778            })
779            .map(|(addr, (identifier, abi))| {
780                (
781                    *addr,
782                    TargetedContract::new(identifier.clone(), abi.clone())
783                        .with_project_contracts(self.project_contracts),
784                )
785            })
786            .collect();
787        let mut contracts = TargetedContracts { inner: contracts };
788
789        self.target_interfaces(to, &mut contracts)?;
790
791        self.select_selectors(to, &mut contracts)?;
792
793        // There should be at least one contract identified as target for fuzz runs.
794        if contracts.is_empty() {
795            eyre::bail!("No contracts to fuzz.");
796        }
797
798        Ok((sender_filters, FuzzRunIdentifiedContracts::new(contracts, selected.is_empty())))
799    }
800
801    /// Extends the contracts and selectors to fuzz with the addresses and ABIs specified in
802    /// `targetInterfaces() -> (address, string[])[]`. Enables targeting of addresses that are
803    /// not deployed during `setUp` such as when fuzzing in a forked environment. Also enables
804    /// targeting of delegate proxies and contracts deployed with `create` or `create2`.
805    pub fn target_interfaces(
806        &self,
807        invariant_address: Address,
808        targeted_contracts: &mut TargetedContracts,
809    ) -> Result<()> {
810        let interfaces = self
811            .executor
812            .call_sol_default(invariant_address, &IInvariantTest::targetInterfacesCall {});
813
814        // Since `targetInterfaces` returns a tuple array there is no guarantee
815        // that the addresses are unique this map is used to merge functions of
816        // the specified interfaces for the same address. For example:
817        // `[(addr1, ["IERC20", "IOwnable"])]` and `[(addr1, ["IERC20"]), (addr1, ("IOwnable"))]`
818        // should be equivalent.
819        let mut combined = TargetedContracts::new();
820
821        // Loop through each address and its associated artifact identifiers.
822        // We're borrowing here to avoid taking full ownership.
823        for IInvariantTest::FuzzInterface { addr, artifacts } in &interfaces {
824            // Identifiers are specified as an array, so we loop through them.
825            for identifier in artifacts {
826                // Try to find the contract by name or identifier in the project's contracts.
827                if let Some((_, contract_data)) =
828                    self.project_contracts.iter().find(|(artifact, _)| {
829                        &artifact.name == identifier || &artifact.identifier() == identifier
830                    })
831                {
832                    let abi = &contract_data.abi;
833                    combined
834                        // Check if there's an entry for the given key in the 'combined' map.
835                        .entry(*addr)
836                        // If the entry exists, extends its ABI with the function list.
837                        .and_modify(|entry| {
838                            // Extend the ABI's function list with the new functions.
839                            entry.abi.functions.extend(abi.functions.clone());
840                        })
841                        // Otherwise insert it into the map.
842                        .or_insert_with(|| {
843                            let mut contract =
844                                TargetedContract::new(identifier.to_string(), abi.clone());
845                            contract.storage_layout =
846                                contract_data.storage_layout.as_ref().map(Arc::clone);
847                            contract
848                        });
849                }
850            }
851        }
852
853        targeted_contracts.extend(combined.inner);
854
855        Ok(())
856    }
857
858    /// Selects the functions to fuzz based on the contract method `targetSelectors()` and
859    /// `targetArtifactSelectors()`.
860    pub fn select_selectors(
861        &self,
862        address: Address,
863        targeted_contracts: &mut TargetedContracts,
864    ) -> Result<()> {
865        for (address, (identifier, _)) in self.setup_contracts {
866            if let Some(selectors) = self.artifact_filters.targeted.get(identifier) {
867                self.add_address_with_functions(*address, selectors, false, targeted_contracts)?;
868            }
869        }
870
871        let mut target_test_selectors = vec![];
872        let mut excluded_test_selectors = vec![];
873
874        // Collect contract functions marked as target for fuzzing campaign.
875        let selectors =
876            self.executor.call_sol_default(address, &IInvariantTest::targetSelectorsCall {});
877        for IInvariantTest::FuzzSelector { addr, selectors } in selectors {
878            if addr == address {
879                target_test_selectors = selectors.clone();
880            }
881            self.add_address_with_functions(addr, &selectors, false, targeted_contracts)?;
882        }
883
884        // Collect contract functions excluded from fuzzing campaign.
885        let excluded_selectors =
886            self.executor.call_sol_default(address, &IInvariantTest::excludeSelectorsCall {});
887        for IInvariantTest::FuzzSelector { addr, selectors } in excluded_selectors {
888            if addr == address {
889                // If fuzz selector address is the test contract, then record selectors to be
890                // later excluded if needed.
891                excluded_test_selectors = selectors.clone();
892            }
893            self.add_address_with_functions(addr, &selectors, true, targeted_contracts)?;
894        }
895
896        if target_test_selectors.is_empty()
897            && let Some(target) = targeted_contracts.get(&address)
898        {
899            // If test contract is marked as a target and no target selector explicitly set, then
900            // include only state-changing functions that are not reserved and selectors that are
901            // not explicitly excluded.
902            let selectors: Vec<_> = target
903                .abi
904                .functions()
905                .filter_map(|func| {
906                    if matches!(
907                        func.state_mutability,
908                        alloy_json_abi::StateMutability::Pure
909                            | alloy_json_abi::StateMutability::View
910                    ) || func.is_reserved()
911                        || excluded_test_selectors.contains(&func.selector())
912                    {
913                        None
914                    } else {
915                        Some(func.selector())
916                    }
917                })
918                .collect();
919            self.add_address_with_functions(address, &selectors, false, targeted_contracts)?;
920        }
921
922        Ok(())
923    }
924
925    /// Adds the address and fuzzed or excluded functions to `TargetedContracts`.
926    fn add_address_with_functions(
927        &self,
928        address: Address,
929        selectors: &[Selector],
930        should_exclude: bool,
931        targeted_contracts: &mut TargetedContracts,
932    ) -> eyre::Result<()> {
933        // Do not add address in target contracts if no function selected.
934        if selectors.is_empty() {
935            return Ok(());
936        }
937
938        let contract = match targeted_contracts.entry(address) {
939            Entry::Occupied(entry) => entry.into_mut(),
940            Entry::Vacant(entry) => {
941                let (identifier, abi) = self.setup_contracts.get(&address).ok_or_else(|| {
942                    eyre::eyre!(
943                        "[{}] address does not have an associated contract: {}",
944                        if should_exclude { "excludeSelectors" } else { "targetSelectors" },
945                        address
946                    )
947                })?;
948                entry.insert(
949                    TargetedContract::new(identifier.clone(), abi.clone())
950                        .with_project_contracts(self.project_contracts),
951                )
952            }
953        };
954        contract.add_selectors(selectors.iter().copied(), should_exclude)?;
955        Ok(())
956    }
957}
958
959/// Collects data from call for fuzzing. However, it first verifies that the sender is not an EOA
960/// before inserting it into the dictionary. Otherwise, we flood the dictionary with
961/// randomly generated addresses.
962fn collect_data(
963    invariant_test: &InvariantTest,
964    state_changeset: &mut HashMap<Address, Account>,
965    tx: &BasicTxDetails,
966    call_result: &RawCallResult,
967    run_depth: u32,
968) {
969    // Verify it has no code.
970    let mut has_code = false;
971    if let Some(Some(code)) =
972        state_changeset.get(&tx.sender).map(|account| account.info.code.as_ref())
973    {
974        has_code = !code.is_empty();
975    }
976
977    // We keep the nonce changes to apply later.
978    let mut sender_changeset = None;
979    if !has_code {
980        sender_changeset = state_changeset.remove(&tx.sender);
981    }
982
983    // Collect values from fuzzed call result and add them to fuzz dictionary.
984    invariant_test.fuzz_state.collect_values_from_call(
985        &invariant_test.targeted_contracts,
986        tx,
987        &call_result.result,
988        &call_result.logs,
989        &*state_changeset,
990        run_depth,
991    );
992
993    // Re-add changes
994    if let Some(changed) = sender_changeset {
995        state_changeset.insert(tx.sender, changed);
996    }
997}
998
999/// Calls the `afterInvariant()` function on a contract.
1000/// Returns call result and if call succeeded.
1001/// The state after the call is not persisted.
1002pub(crate) fn call_after_invariant_function(
1003    executor: &Executor,
1004    to: Address,
1005) -> Result<(RawCallResult, bool), EvmError> {
1006    let calldata = Bytes::from_static(&IInvariantTest::afterInvariantCall::SELECTOR);
1007    let mut call_result = executor.call_raw(CALLER, to, calldata, U256::ZERO)?;
1008    let success = executor.is_raw_call_mut_success(to, &mut call_result, false);
1009    Ok((call_result, success))
1010}
1011
1012/// Calls the invariant function and returns call result and if succeeded.
1013pub(crate) fn call_invariant_function(
1014    executor: &Executor,
1015    address: Address,
1016    calldata: Bytes,
1017) -> Result<(RawCallResult, bool)> {
1018    let mut call_result = executor.call_raw(CALLER, address, calldata, U256::ZERO)?;
1019    let success = executor.is_raw_call_mut_success(address, &mut call_result, false);
1020    Ok((call_result, success))
1021}