foundry_evm/executors/invariant/
mod.rs

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/// Contains invariant metrics for a single fuzzed selector.
114#[derive(Default, Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
115pub struct InvariantMetrics {
116    // Count of fuzzed selector calls.
117    pub calls: usize,
118    // Count of fuzzed selector reverts.
119    pub reverts: usize,
120    // Count of fuzzed selector discards (through assume cheatcodes).
121    pub discards: usize,
122}
123
124/// Contains data collected during invariant test runs.
125struct InvariantTestData {
126    // Consumed gas and calldata of every successful fuzz call.
127    fuzz_cases: Vec<FuzzedCases>,
128    // Data related to reverts or failed assertions of the test.
129    failures: InvariantFailures,
130    // Calldata in the last invariant run.
131    last_run_inputs: Vec<BasicTxDetails>,
132    // Additional traces for gas report.
133    gas_report_traces: Vec<Vec<CallTraceArena>>,
134    // Last call results of the invariant test.
135    last_call_results: Option<RawCallResult>,
136    // Line coverage information collected from all fuzzed calls.
137    line_coverage: Option<HitMaps>,
138    // Metrics for each fuzzed selector.
139    metrics: Map<String, InvariantMetrics>,
140
141    // Proptest runner to query for random values.
142    // The strategy only comes with the first `input`. We fill the rest of the `inputs`
143    // until the desired `depth` so we can use the evolving fuzz dictionary
144    // during the run.
145    branch_runner: TestRunner,
146}
147
148/// Contains invariant test data.
149struct InvariantTest {
150    // Fuzz state of invariant test.
151    fuzz_state: EvmFuzzState,
152    // Contracts fuzzed by the invariant test.
153    targeted_contracts: FuzzRunIdentifiedContracts,
154    // Data collected during invariant runs.
155    test_data: InvariantTestData,
156}
157
158impl InvariantTest {
159    /// Instantiates an invariant test.
160    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    /// Returns number of invariant test reverts.
185    fn reverts(&self) -> usize {
186        self.test_data.failures.reverts
187    }
188
189    /// Whether invariant test has errors or not.
190    fn has_errors(&self) -> bool {
191        self.test_data.failures.error.is_some()
192    }
193
194    /// Set invariant test error.
195    fn set_error(&mut self, error: InvariantFuzzError) {
196        self.test_data.failures.error = Some(error);
197    }
198
199    /// Set last invariant test call results.
200    fn set_last_call_results(&mut self, call_result: Option<RawCallResult>) {
201        self.test_data.last_call_results = call_result;
202    }
203
204    /// Set last invariant run call sequence.
205    fn set_last_run_inputs(&mut self, inputs: &Vec<BasicTxDetails>) {
206        self.test_data.last_run_inputs.clone_from(inputs);
207    }
208
209    /// Merge current collected line coverage with the new coverage from last fuzzed call.
210    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    /// Update metrics for a fuzzed selector, extracted from tx details.
215    /// Always increments number of calls; discarded runs (through assume cheatcodes) are tracked
216    /// separated from reverts.
217    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    /// End invariant test run by collecting results, cleaning collected artifacts and reverting
233    /// created fuzz state.
234    fn end_run(&mut self, run: InvariantTestRun, gas_samples: usize) {
235        // We clear all the targeted contracts created during this run.
236        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        // Revert state to not persist values between runs.
246        self.fuzz_state.revert();
247    }
248}
249
250/// Contains data for an invariant test run.
251struct InvariantTestRun {
252    // Invariant run call sequence.
253    inputs: Vec<BasicTxDetails>,
254    // Current invariant run executor.
255    executor: Executor,
256    // Invariant run stat reports (eg. gas usage).
257    fuzz_runs: Vec<FuzzCase>,
258    // Contracts created during current invariant run.
259    created_contracts: Vec<Address>,
260    // Traces of each call of the invariant run call sequence.
261    run_traces: Vec<SparsedTraceArena>,
262    // Current depth of invariant run.
263    depth: u32,
264    // Current assume rejects of the invariant run.
265    rejects: u32,
266    // Whether new coverage was discovered during this run.
267    new_coverage: bool,
268}
269
270impl InvariantTestRun {
271    /// Instantiates an invariant test run.
272    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
286/// Wrapper around any [`Executor`] implementer which provides fuzzing support using [`proptest`].
287///
288/// After instantiation, calling `invariant_fuzz` will proceed to hammer the deployed smart
289/// contracts with inputs, until it finds a counterexample sequence. The provided [`TestRunner`]
290/// contains all the configuration which can be overridden via [environment
291/// variables](proptest::test_runner::Config)
292pub struct InvariantExecutor<'a> {
293    pub executor: Executor,
294    /// Proptest runner.
295    runner: TestRunner,
296    /// The invariant configuration
297    config: InvariantConfig,
298    /// Contracts deployed with `setUp()`
299    setup_contracts: &'a ContractsByAddress,
300    /// Contracts that are part of the project but have not been deployed yet. We need the bytecode
301    /// to identify them from the stateset changes.
302    project_contracts: &'a ContractsByArtifact,
303    /// Filters contracts to be fuzzed through their artifact identifiers.
304    artifact_filters: ArtifactFilters,
305}
306
307impl<'a> InvariantExecutor<'a> {
308    /// Instantiates a fuzzed executor EVM given a testrunner
309    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    /// Fuzzes any deployed contract and checks any broken invariant at `invariant_address`.
327    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        // Throw an error to abort test run if the invariant function accepts input params
336        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        // Start timer for this invariant test.
344        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        // Invariant runs with edge coverage if corpus dir is set or showing edge coverage.
356        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            // Create current invariant run data.
366            let mut current_run = InvariantTestRun::new(
367                initial_seq[0].clone(),
368                // Before each run, we must reset the backend state.
369                self.executor.clone(),
370                self.config.depth as usize,
371            );
372
373            // We stop the run immediately if we have reverted, and `fail_on_revert` is set.
374            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                // Check if the timeout has been reached.
380                if timer.is_timed_out() {
381                    // Since we never record a revert here the test is still considered
382                    // successful even though it timed out. We *want*
383                    // this behavior for now, so that's ok, but
384                    // future developers should be aware of this.
385                    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                // Execute call from the randomly generated sequence without committing state.
394                // State is committed only if call is not a magic assume.
395                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                // Collect line coverage from last fuzzed call.
411                invariant_test.merge_line_coverage(call_result.line_coverage.clone());
412                // Collect edge coverage and set the flag in the current run.
413                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                    // Commit executed call result.
428                    current_run.executor.commit(&mut call_result);
429
430                    // Collect data for fuzzing from the state changeset.
431                    // This step updates the state dictionary and therefore invalidates the
432                    // ValueTree in use by the current run. This manifestsitself in proptest
433                    // observing a different input case than what it was called with, and creates
434                    // inconsistencies whenever proptest tries to use the input case after test
435                    // execution.
436                    // See <https://github.com/foundry-rs/foundry/issues/9764>.
437                    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                    // Collect created contracts and add to fuzz targets only if targeted contracts
449                    // are updatable.
450                    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                    // Determine if test can continue or should exit.
468                    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(&current_run.inputs);
479                    }
480                    // If test cannot continue then stop current run and exit test suite.
481                    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            // Extend corpus with current run data.
498            corpus_manager.process_inputs(&current_run.inputs, current_run.new_coverage);
499
500            // Call `afterInvariant` only if it is declared and test didn't fail already.
501            if invariant_contract.call_after_invariant && !invariant_test.has_errors() {
502                assert_after_invariant(
503                    &invariant_contract,
504                    &mut invariant_test,
505                    &current_run,
506                    &self.config,
507                )
508                .map_err(|_| eyre!("Failed to call afterInvariant"))?;
509            }
510
511            // End current invariant test run.
512            invariant_test.end_run(current_run, self.config.gas_report_samples as usize);
513            if let Some(progress) = progress {
514                // If running with progress then increment completed runs.
515                progress.inc(1);
516                // Display metrics in progress bar.
517                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                // Display metrics inline if corpus dir set.
524                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    /// Prepares certain structures to execute the invariant tests:
555    /// * Invariant Fuzz Test.
556    /// * Invariant Corpus Manager.
557    fn prepare_test(
558        &mut self,
559        invariant_contract: &InvariantContract<'_>,
560        fuzz_fixtures: &FuzzFixtures,
561        deployed_libs: &[Address],
562    ) -> Result<(InvariantTest, CorpusManager)> {
563        // Finds out the chosen deployed contracts and/or senders.
564        self.select_contract_artifacts(invariant_contract.address)?;
565        let (targeted_senders, targeted_contracts) =
566            self.select_contracts_and_senders(invariant_contract.address)?;
567
568        // Stores fuzz state for use with [fuzz_calldata_from_state].
569        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        // Creates the invariant strategy.
579        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        // Allows `override_call_strat` to use the address given by the Fuzzer inspector during
589        // EVM execution.
590        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        // If any of the targeted contracts have the storage layout enabled then we can sample
608        // mapping values. To accomplish, we need to record the mapping storage slots and keys.
609        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's make sure the invariant is sound before actually starting the run:
623        // We'll assert the invariant in its initial state, and if it fails, we'll
624        // already know if we can early exit the invariant run.
625        // This does not count as a fuzz run. It will just register the revert.
626        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    /// Fills the `InvariantExecutor` with the artifact identifier filters (in `path:name` string
658    /// format). They will be used to filter contracts after the `setUp`, and more importantly,
659    /// during the runs.
660    ///
661    /// Also excludes any contract without any mutable functions.
662    ///
663    /// Priority:
664    ///
665    /// targetArtifactSelectors > excludeArtifacts > targetArtifacts
666    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        // Insert them into the executor `targeted_abi`.
672        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        // Insert `excludeArtifacts` into the executor `excluded_abi`.
687        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        // Exclude any artifact without mutable functions.
696        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        // Insert `targetArtifacts` into the executor `targeted_abi`, if they have not been seen
716        // before.
717        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    /// Makes sure that the contract exists in the project. If so, it returns its artifact
730    /// identifier.
731    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            // Check that the selectors really exist for this contract.
740            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    /// Selects senders and contracts based on the contract methods `targetSenders() -> address[]`,
756    /// `targetContracts() -> address[]` and `excludeContracts() -> address[]`.
757    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        // Extend with default excluded addresses - https://github.com/foundry-rs/foundry/issues/4163
766        excluded_senders.extend([
767            CHEATCODE_ADDRESS,
768            HARDHAT_CONSOLE_ADDRESS,
769            DEFAULT_CREATE2_DEPLOYER,
770        ]);
771        // Extend with precompiles - https://github.com/foundry-rs/foundry/issues/4287
772        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                // Include to address if explicitly set as target.
783                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        // There should be at least one contract identified as target for fuzz runs.
809        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    /// Extends the contracts and selectors to fuzz with the addresses and ABIs specified in
817    /// `targetInterfaces() -> (address, string[])[]`. Enables targeting of addresses that are
818    /// not deployed during `setUp` such as when fuzzing in a forked environment. Also enables
819    /// targeting of delegate proxies and contracts deployed with `create` or `create2`.
820    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        // Since `targetInterfaces` returns a tuple array there is no guarantee
830        // that the addresses are unique this map is used to merge functions of
831        // the specified interfaces for the same address. For example:
832        // `[(addr1, ["IERC20", "IOwnable"])]` and `[(addr1, ["IERC20"]), (addr1, ("IOwnable"))]`
833        // should be equivalent.
834        let mut combined = TargetedContracts::new();
835
836        // Loop through each address and its associated artifact identifiers.
837        // We're borrowing here to avoid taking full ownership.
838        for IInvariantTest::FuzzInterface { addr, artifacts } in &interfaces {
839            // Identifiers are specified as an array, so we loop through them.
840            for identifier in artifacts {
841                // Try to find the contract by name or identifier in the project's contracts.
842                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                        // Check if there's an entry for the given key in the 'combined' map.
850                        .entry(*addr)
851                        // If the entry exists, extends its ABI with the function list.
852                        .and_modify(|entry| {
853                            // Extend the ABI's function list with the new functions.
854                            entry.abi.functions.extend(abi.functions.clone());
855                        })
856                        // Otherwise insert it into the map.
857                        .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    /// Selects the functions to fuzz based on the contract method `targetSelectors()` and
874    /// `targetArtifactSelectors()`.
875    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        // Collect contract functions marked as target for fuzzing campaign.
890        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        // Collect contract functions excluded from fuzzing campaign.
900        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                // If fuzz selector address is the test contract, then record selectors to be
905                // later excluded if needed.
906                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            // If test contract is marked as a target and no target selector explicitly set, then
915            // include only state-changing functions that are not reserved and selectors that are
916            // not explicitly excluded.
917            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    /// Adds the address and fuzzed or excluded functions to `TargetedContracts`.
941    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        // Do not add address in target contracts if no function selected.
949        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
974/// Collects data from call for fuzzing. However, it first verifies that the sender is not an EOA
975/// before inserting it into the dictionary. Otherwise, we flood the dictionary with
976/// randomly generated addresses.
977fn 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    // Verify it has no code.
985    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    // We keep the nonce changes to apply later.
993    let mut sender_changeset = None;
994    if !has_code {
995        sender_changeset = state_changeset.remove(&tx.sender);
996    }
997
998    // Collect values from fuzzed call result and add them to fuzz dictionary.
999    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    // Re-add changes
1009    if let Some(changed) = sender_changeset {
1010        state_changeset.insert(tx.sender, changed);
1011    }
1012}
1013
1014/// Calls the `afterInvariant()` function on a contract.
1015/// Returns call result and if call succeeded.
1016/// The state after the call is not persisted.
1017pub(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
1027/// Calls the invariant function and returns call result and if succeeded.
1028pub(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}