foundry_evm/executors/invariant/
mod.rs

1use crate::{
2    executors::{Executor, RawCallResult},
3    inspectors::Fuzzer,
4};
5use alloy_primitives::{Address, Bytes, FixedBytes, Selector, U256};
6use alloy_sol_types::{sol, SolCall};
7use eyre::{eyre, ContextCompat, Result};
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        TEST_TIMEOUT,
14    },
15    precompiles::PRECOMPILES,
16};
17use foundry_evm_fuzz::{
18    invariant::{
19        ArtifactFilters, BasicTxDetails, FuzzRunIdentifiedContracts, InvariantContract,
20        RandomCallGenerator, SenderFilters, TargetedContract, TargetedContracts,
21    },
22    strategies::{invariant_strat, override_call_strat, EvmFuzzState},
23    FuzzCase, FuzzFixtures, FuzzedCases,
24};
25use foundry_evm_traces::{CallTraceArena, SparsedTraceArena};
26use indicatif::ProgressBar;
27use parking_lot::RwLock;
28use proptest::{
29    strategy::{Strategy, ValueTree},
30    test_runner::{TestCaseError, TestRunner},
31};
32use result::{assert_after_invariant, assert_invariants, can_continue};
33use revm::primitives::HashMap;
34use shrink::shrink_sequence;
35use std::{
36    cell::RefCell,
37    collections::{btree_map::Entry, HashMap as Map},
38    sync::Arc,
39};
40
41mod error;
42pub use error::{InvariantFailures, InvariantFuzzError};
43use foundry_evm_coverage::HitMaps;
44
45mod replay;
46pub use replay::{replay_error, replay_run};
47
48mod result;
49pub use result::InvariantFuzzTestResult;
50use serde::{Deserialize, Serialize};
51
52mod shrink;
53use crate::executors::{EvmError, FuzzTestTimer};
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.
122pub struct InvariantTestData {
123    // Consumed gas and calldata of every successful fuzz call.
124    pub fuzz_cases: Vec<FuzzedCases>,
125    // Data related to reverts or failed assertions of the test.
126    pub failures: InvariantFailures,
127    // Calldata in the last invariant run.
128    pub last_run_inputs: Vec<BasicTxDetails>,
129    // Additional traces for gas report.
130    pub gas_report_traces: Vec<Vec<CallTraceArena>>,
131    // Last call results of the invariant test.
132    pub last_call_results: Option<RawCallResult>,
133    // Coverage information collected from all fuzzed calls.
134    pub coverage: Option<HitMaps>,
135    // Metrics for each fuzzed selector.
136    pub 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    pub branch_runner: TestRunner,
143}
144
145/// Contains invariant test data.
146pub struct InvariantTest {
147    // Fuzz state of invariant test.
148    pub fuzz_state: EvmFuzzState,
149    // Contracts fuzzed by the invariant test.
150    pub targeted_contracts: FuzzRunIdentifiedContracts,
151    // Data collected during invariant runs.
152    pub execution_data: RefCell<InvariantTestData>,
153}
154
155impl InvariantTest {
156    /// Instantiates an invariant test.
157    pub 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 execution_data = RefCell::new(InvariantTestData {
169            fuzz_cases,
170            failures,
171            last_run_inputs: vec![],
172            gas_report_traces: vec![],
173            last_call_results,
174            coverage: None,
175            metrics: Map::default(),
176            branch_runner,
177        });
178        Self { fuzz_state, targeted_contracts, execution_data }
179    }
180
181    /// Returns number of invariant test reverts.
182    pub fn reverts(&self) -> usize {
183        self.execution_data.borrow().failures.reverts
184    }
185
186    /// Whether invariant test has errors or not.
187    pub fn has_errors(&self) -> bool {
188        self.execution_data.borrow().failures.error.is_some()
189    }
190
191    /// Set invariant test error.
192    pub fn set_error(&self, error: InvariantFuzzError) {
193        self.execution_data.borrow_mut().failures.error = Some(error);
194    }
195
196    /// Set last invariant test call results.
197    pub fn set_last_call_results(&self, call_result: Option<RawCallResult>) {
198        self.execution_data.borrow_mut().last_call_results = call_result;
199    }
200
201    /// Set last invariant run call sequence.
202    pub fn set_last_run_inputs(&self, inputs: &Vec<BasicTxDetails>) {
203        self.execution_data.borrow_mut().last_run_inputs.clone_from(inputs);
204    }
205
206    /// Merge current collected coverage with the new coverage from last fuzzed call.
207    pub fn merge_coverage(&self, new_coverage: Option<HitMaps>) {
208        HitMaps::merge_opt(&mut self.execution_data.borrow_mut().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    pub fn record_metrics(&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.execution_data.borrow_mut().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    pub fn end_run(&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        let mut invariant_data = self.execution_data.borrow_mut();
236        if invariant_data.gas_report_traces.len() < gas_samples {
237            invariant_data
238                .gas_report_traces
239                .push(run.run_traces.into_iter().map(|arena| arena.arena).collect());
240        }
241        invariant_data.fuzz_cases.push(FuzzedCases::new(run.fuzz_runs));
242
243        // Revert state to not persist values between runs.
244        self.fuzz_state.revert();
245    }
246}
247
248/// Contains data for an invariant test run.
249pub struct InvariantTestRun {
250    // Invariant run call sequence.
251    pub inputs: Vec<BasicTxDetails>,
252    // Current invariant run executor.
253    pub executor: Executor,
254    // Invariant run stat reports (eg. gas usage).
255    pub fuzz_runs: Vec<FuzzCase>,
256    // Contracts created during current invariant run.
257    pub created_contracts: Vec<Address>,
258    // Traces of each call of the invariant run call sequence.
259    pub run_traces: Vec<SparsedTraceArena>,
260    // Current depth of invariant run.
261    pub depth: u32,
262    // Current assume rejects of the invariant run.
263    pub assume_rejects_counter: u32,
264}
265
266impl InvariantTestRun {
267    /// Instantiates an invariant test run.
268    pub fn new(first_input: BasicTxDetails, executor: Executor, depth: usize) -> Self {
269        Self {
270            inputs: vec![first_input],
271            executor,
272            fuzz_runs: Vec::with_capacity(depth),
273            created_contracts: vec![],
274            run_traces: vec![],
275            depth: 0,
276            assume_rejects_counter: 0,
277        }
278    }
279}
280
281/// Wrapper around any [`Executor`] implementer which provides fuzzing support using [`proptest`].
282///
283/// After instantiation, calling `invariant_fuzz` will proceed to hammer the deployed smart
284/// contracts with inputs, until it finds a counterexample sequence. The provided [`TestRunner`]
285/// contains all the configuration which can be overridden via [environment
286/// variables](proptest::test_runner::Config)
287pub struct InvariantExecutor<'a> {
288    pub executor: Executor,
289    /// Proptest runner.
290    runner: TestRunner,
291    /// The invariant configuration
292    config: InvariantConfig,
293    /// Contracts deployed with `setUp()`
294    setup_contracts: &'a ContractsByAddress,
295    /// Contracts that are part of the project but have not been deployed yet. We need the bytecode
296    /// to identify them from the stateset changes.
297    project_contracts: &'a ContractsByArtifact,
298    /// Filters contracts to be fuzzed through their artifact identifiers.
299    artifact_filters: ArtifactFilters,
300}
301
302impl<'a> InvariantExecutor<'a> {
303    /// Instantiates a fuzzed executor EVM given a testrunner
304    pub fn new(
305        executor: Executor,
306        runner: TestRunner,
307        config: InvariantConfig,
308        setup_contracts: &'a ContractsByAddress,
309        project_contracts: &'a ContractsByArtifact,
310    ) -> Self {
311        Self {
312            executor,
313            runner,
314            config,
315            setup_contracts,
316            project_contracts,
317            artifact_filters: ArtifactFilters::default(),
318        }
319    }
320
321    /// Fuzzes any deployed contract and checks any broken invariant at `invariant_address`.
322    pub fn invariant_fuzz(
323        &mut self,
324        invariant_contract: InvariantContract<'_>,
325        fuzz_fixtures: &FuzzFixtures,
326        deployed_libs: &[Address],
327        progress: Option<&ProgressBar>,
328    ) -> Result<InvariantFuzzTestResult> {
329        // Throw an error to abort test run if the invariant function accepts input params
330        if !invariant_contract.invariant_function.inputs.is_empty() {
331            return Err(eyre!("Invariant test function should have no inputs"))
332        }
333
334        let (invariant_test, invariant_strategy) =
335            self.prepare_test(&invariant_contract, fuzz_fixtures, deployed_libs)?;
336
337        // Start timer for this invariant test.
338        let timer = FuzzTestTimer::new(self.config.timeout);
339
340        let _ = self.runner.run(&invariant_strategy, |first_input| {
341            // Create current invariant run data.
342            let mut current_run = InvariantTestRun::new(
343                first_input,
344                // Before each run, we must reset the backend state.
345                self.executor.clone(),
346                self.config.depth as usize,
347            );
348
349            // We stop the run immediately if we have reverted, and `fail_on_revert` is set.
350            if self.config.fail_on_revert && invariant_test.reverts() > 0 {
351                return Err(TestCaseError::fail("call reverted"))
352            }
353
354            while current_run.depth < self.config.depth {
355                // Check if the timeout has been reached.
356                if timer.is_timed_out() {
357                    // Since we never record a revert here the test is still considered
358                    // successful even though it timed out. We *want*
359                    // this behavior for now, so that's ok, but
360                    // future developers should be aware of this.
361                    return Err(TestCaseError::fail(TEST_TIMEOUT));
362                }
363
364                let tx = current_run.inputs.last().ok_or_else(|| {
365                    TestCaseError::fail("no input generated to called fuzz target")
366                })?;
367
368                // Execute call from the randomly generated sequence without committing state.
369                // State is committed only if call is not a magic assume.
370                let mut call_result = current_run
371                    .executor
372                    .call_raw(
373                        tx.sender,
374                        tx.call_details.target,
375                        tx.call_details.calldata.clone(),
376                        U256::ZERO,
377                    )
378                    .map_err(|e| TestCaseError::fail(e.to_string()))?;
379
380                let discarded = call_result.result.as_ref() == MAGIC_ASSUME;
381                if self.config.show_metrics {
382                    invariant_test.record_metrics(tx, call_result.reverted, discarded);
383                }
384
385                // Collect coverage from last fuzzed call.
386                invariant_test.merge_coverage(call_result.coverage.clone());
387
388                if discarded {
389                    current_run.inputs.pop();
390                    current_run.assume_rejects_counter += 1;
391                    if current_run.assume_rejects_counter > self.config.max_assume_rejects {
392                        invariant_test.set_error(InvariantFuzzError::MaxAssumeRejects(
393                            self.config.max_assume_rejects,
394                        ));
395                        return Err(TestCaseError::fail(
396                            "reached maximum number of `vm.assume` rejects",
397                        ));
398                    }
399                } else {
400                    // Commit executed call result.
401                    current_run.executor.commit(&mut call_result);
402
403                    // Collect data for fuzzing from the state changeset.
404                    // This step updates the state dictionary and therefore invalidates the
405                    // ValueTree in use by the current run. This manifestsitself in proptest
406                    // observing a different input case than what it was called with, and creates
407                    // inconsistencies whenever proptest tries to use the input case after test
408                    // execution.
409                    // See <https://github.com/foundry-rs/foundry/issues/9764>.
410                    let mut state_changeset = call_result.state_changeset.clone();
411                    if !call_result.reverted {
412                        collect_data(
413                            &invariant_test,
414                            &mut state_changeset,
415                            tx,
416                            &call_result,
417                            self.config.depth,
418                        );
419                    }
420
421                    // Collect created contracts and add to fuzz targets only if targeted contracts
422                    // are updatable.
423                    if let Err(error) =
424                        &invariant_test.targeted_contracts.collect_created_contracts(
425                            &state_changeset,
426                            self.project_contracts,
427                            self.setup_contracts,
428                            &self.artifact_filters,
429                            &mut current_run.created_contracts,
430                        )
431                    {
432                        warn!(target: "forge::test", "{error}");
433                    }
434                    current_run.fuzz_runs.push(FuzzCase {
435                        calldata: tx.call_details.calldata.clone(),
436                        gas: call_result.gas_used,
437                        stipend: call_result.stipend,
438                    });
439
440                    // Determine if test can continue or should exit.
441                    let result = can_continue(
442                        &invariant_contract,
443                        &invariant_test,
444                        &mut current_run,
445                        &self.config,
446                        call_result,
447                        &state_changeset,
448                    )
449                    .map_err(|e| TestCaseError::fail(e.to_string()))?;
450                    if !result.can_continue || current_run.depth == self.config.depth - 1 {
451                        invariant_test.set_last_run_inputs(&current_run.inputs);
452                    }
453                    // If test cannot continue then stop current run and exit test suite.
454                    if !result.can_continue {
455                        return Err(TestCaseError::fail("test cannot continue"))
456                    }
457
458                    invariant_test.set_last_call_results(result.call_result);
459                    current_run.depth += 1;
460                }
461
462                // Generates the next call from the run using the recently updated
463                // dictionary.
464                current_run.inputs.push(
465                    invariant_strategy
466                        .new_tree(&mut invariant_test.execution_data.borrow_mut().branch_runner)
467                        .map_err(|_| TestCaseError::Fail("Could not generate case".into()))?
468                        .current(),
469                );
470            }
471
472            // Call `afterInvariant` only if it is declared and test didn't fail already.
473            if invariant_contract.call_after_invariant && !invariant_test.has_errors() {
474                assert_after_invariant(
475                    &invariant_contract,
476                    &invariant_test,
477                    &current_run,
478                    &self.config,
479                )
480                .map_err(|_| TestCaseError::Fail("Failed to call afterInvariant".into()))?;
481            }
482
483            // End current invariant test run.
484            invariant_test.end_run(current_run, self.config.gas_report_samples as usize);
485
486            // If running with progress then increment completed runs.
487            if let Some(progress) = progress {
488                progress.inc(1);
489            }
490
491            Ok(())
492        });
493
494        trace!(?fuzz_fixtures);
495        invariant_test.fuzz_state.log_stats();
496
497        let result = invariant_test.execution_data.into_inner();
498        Ok(InvariantFuzzTestResult {
499            error: result.failures.error,
500            cases: result.fuzz_cases,
501            reverts: result.failures.reverts,
502            last_run_inputs: result.last_run_inputs,
503            gas_report_traces: result.gas_report_traces,
504            coverage: result.coverage,
505            metrics: result.metrics,
506        })
507    }
508
509    /// Prepares certain structures to execute the invariant tests:
510    /// * Invariant Fuzz Test.
511    /// * Invariant Strategy
512    fn prepare_test(
513        &mut self,
514        invariant_contract: &InvariantContract<'_>,
515        fuzz_fixtures: &FuzzFixtures,
516        deployed_libs: &[Address],
517    ) -> Result<(InvariantTest, impl Strategy<Value = BasicTxDetails>)> {
518        // Finds out the chosen deployed contracts and/or senders.
519        self.select_contract_artifacts(invariant_contract.address)?;
520        let (targeted_senders, targeted_contracts) =
521            self.select_contracts_and_senders(invariant_contract.address)?;
522
523        // Stores fuzz state for use with [fuzz_calldata_from_state].
524        let fuzz_state = EvmFuzzState::new(
525            self.executor.backend().mem_db(),
526            self.config.dictionary,
527            deployed_libs,
528        );
529
530        // Creates the invariant strategy.
531        let strategy = invariant_strat(
532            fuzz_state.clone(),
533            targeted_senders,
534            targeted_contracts.clone(),
535            self.config.dictionary.dictionary_weight,
536            fuzz_fixtures.clone(),
537        )
538        .no_shrink();
539
540        // Allows `override_call_strat` to use the address given by the Fuzzer inspector during
541        // EVM execution.
542        let mut call_generator = None;
543        if self.config.call_override {
544            let target_contract_ref = Arc::new(RwLock::new(Address::ZERO));
545
546            call_generator = Some(RandomCallGenerator::new(
547                invariant_contract.address,
548                self.runner.clone(),
549                override_call_strat(
550                    fuzz_state.clone(),
551                    targeted_contracts.clone(),
552                    target_contract_ref.clone(),
553                    fuzz_fixtures.clone(),
554                ),
555                target_contract_ref,
556            ));
557        }
558
559        self.executor.inspector_mut().fuzzer =
560            Some(Fuzzer { call_generator, fuzz_state: fuzz_state.clone(), collect: true });
561
562        // Let's make sure the invariant is sound before actually starting the run:
563        // We'll assert the invariant in its initial state, and if it fails, we'll
564        // already know if we can early exit the invariant run.
565        // This does not count as a fuzz run. It will just register the revert.
566        let mut failures = InvariantFailures::new();
567        let last_call_results = assert_invariants(
568            invariant_contract,
569            &self.config,
570            &targeted_contracts,
571            &self.executor,
572            &[],
573            &mut failures,
574        )?;
575        if let Some(error) = failures.error {
576            return Err(eyre!(error.revert_reason().unwrap_or_default()))
577        }
578
579        Ok((
580            InvariantTest::new(
581                fuzz_state,
582                targeted_contracts,
583                failures,
584                last_call_results,
585                self.runner.clone(),
586            ),
587            strategy,
588        ))
589    }
590
591    /// Fills the `InvariantExecutor` with the artifact identifier filters (in `path:name` string
592    /// format). They will be used to filter contracts after the `setUp`, and more importantly,
593    /// during the runs.
594    ///
595    /// Also excludes any contract without any mutable functions.
596    ///
597    /// Priority:
598    ///
599    /// targetArtifactSelectors > excludeArtifacts > targetArtifacts
600    pub fn select_contract_artifacts(&mut self, invariant_address: Address) -> Result<()> {
601        let result = self
602            .executor
603            .call_sol_default(invariant_address, &IInvariantTest::targetArtifactSelectorsCall {});
604
605        // Insert them into the executor `targeted_abi`.
606        for IInvariantTest::FuzzArtifactSelector { artifact, selectors } in
607            result.targetedArtifactSelectors
608        {
609            let identifier = self.validate_selected_contract(artifact, &selectors)?;
610            self.artifact_filters.targeted.entry(identifier).or_default().extend(selectors);
611        }
612
613        let selected = self
614            .executor
615            .call_sol_default(invariant_address, &IInvariantTest::targetArtifactsCall {});
616        let excluded = self
617            .executor
618            .call_sol_default(invariant_address, &IInvariantTest::excludeArtifactsCall {});
619
620        // Insert `excludeArtifacts` into the executor `excluded_abi`.
621        for contract in excluded.excludedArtifacts {
622            let identifier = self.validate_selected_contract(contract, &[])?;
623
624            if !self.artifact_filters.excluded.contains(&identifier) {
625                self.artifact_filters.excluded.push(identifier);
626            }
627        }
628
629        // Exclude any artifact without mutable functions.
630        for (artifact, contract) in self.project_contracts.iter() {
631            if contract
632                .abi
633                .functions()
634                .filter(|func| {
635                    !matches!(
636                        func.state_mutability,
637                        alloy_json_abi::StateMutability::Pure |
638                            alloy_json_abi::StateMutability::View
639                    )
640                })
641                .count() ==
642                0 &&
643                !self.artifact_filters.excluded.contains(&artifact.identifier())
644            {
645                self.artifact_filters.excluded.push(artifact.identifier());
646            }
647        }
648
649        // Insert `targetArtifacts` into the executor `targeted_abi`, if they have not been seen
650        // before.
651        for contract in selected.targetedArtifacts {
652            let identifier = self.validate_selected_contract(contract, &[])?;
653
654            if !self.artifact_filters.targeted.contains_key(&identifier) &&
655                !self.artifact_filters.excluded.contains(&identifier)
656            {
657                self.artifact_filters.targeted.insert(identifier, vec![]);
658            }
659        }
660        Ok(())
661    }
662
663    /// Makes sure that the contract exists in the project. If so, it returns its artifact
664    /// identifier.
665    fn validate_selected_contract(
666        &mut self,
667        contract: String,
668        selectors: &[FixedBytes<4>],
669    ) -> Result<String> {
670        if let Some((artifact, contract_data)) =
671            self.project_contracts.find_by_name_or_identifier(&contract)?
672        {
673            // Check that the selectors really exist for this contract.
674            for selector in selectors {
675                contract_data
676                    .abi
677                    .functions()
678                    .find(|func| func.selector().as_slice() == selector.as_slice())
679                    .wrap_err(format!("{contract} does not have the selector {selector:?}"))?;
680            }
681
682            return Ok(artifact.identifier())
683        }
684        eyre::bail!("{contract} not found in the project. Allowed format: `contract_name` or `contract_path:contract_name`.");
685    }
686
687    /// Selects senders and contracts based on the contract methods `targetSenders() -> address[]`,
688    /// `targetContracts() -> address[]` and `excludeContracts() -> address[]`.
689    pub fn select_contracts_and_senders(
690        &self,
691        to: Address,
692    ) -> Result<(SenderFilters, FuzzRunIdentifiedContracts)> {
693        let targeted_senders = self
694            .executor
695            .call_sol_default(to, &IInvariantTest::targetSendersCall {})
696            .targetedSenders;
697        let mut excluded_senders = self
698            .executor
699            .call_sol_default(to, &IInvariantTest::excludeSendersCall {})
700            .excludedSenders;
701        // Extend with default excluded addresses - https://github.com/foundry-rs/foundry/issues/4163
702        excluded_senders.extend([
703            CHEATCODE_ADDRESS,
704            HARDHAT_CONSOLE_ADDRESS,
705            DEFAULT_CREATE2_DEPLOYER,
706        ]);
707        // Extend with precompiles - https://github.com/foundry-rs/foundry/issues/4287
708        excluded_senders.extend(PRECOMPILES);
709        let sender_filters = SenderFilters::new(targeted_senders, excluded_senders);
710
711        let selected = self
712            .executor
713            .call_sol_default(to, &IInvariantTest::targetContractsCall {})
714            .targetedContracts;
715        let excluded = self
716            .executor
717            .call_sol_default(to, &IInvariantTest::excludeContractsCall {})
718            .excludedContracts;
719
720        let contracts = self
721            .setup_contracts
722            .iter()
723            .filter(|&(addr, (identifier, _))| {
724                // Include to address if explicitly set as target.
725                if *addr == to && selected.contains(&to) {
726                    return true;
727                }
728
729                *addr != to &&
730                    *addr != CHEATCODE_ADDRESS &&
731                    *addr != HARDHAT_CONSOLE_ADDRESS &&
732                    (selected.is_empty() || selected.contains(addr)) &&
733                    (excluded.is_empty() || !excluded.contains(addr)) &&
734                    self.artifact_filters.matches(identifier)
735            })
736            .map(|(addr, (identifier, abi))| {
737                (*addr, TargetedContract::new(identifier.clone(), abi.clone()))
738            })
739            .collect();
740        let mut contracts = TargetedContracts { inner: contracts };
741
742        self.target_interfaces(to, &mut contracts)?;
743
744        self.select_selectors(to, &mut contracts)?;
745
746        // There should be at least one contract identified as target for fuzz runs.
747        if contracts.is_empty() {
748            eyre::bail!("No contracts to fuzz.");
749        }
750
751        Ok((sender_filters, FuzzRunIdentifiedContracts::new(contracts, selected.is_empty())))
752    }
753
754    /// Extends the contracts and selectors to fuzz with the addresses and ABIs specified in
755    /// `targetInterfaces() -> (address, string[])[]`. Enables targeting of addresses that are
756    /// not deployed during `setUp` such as when fuzzing in a forked environment. Also enables
757    /// targeting of delegate proxies and contracts deployed with `create` or `create2`.
758    pub fn target_interfaces(
759        &self,
760        invariant_address: Address,
761        targeted_contracts: &mut TargetedContracts,
762    ) -> Result<()> {
763        let interfaces = self
764            .executor
765            .call_sol_default(invariant_address, &IInvariantTest::targetInterfacesCall {})
766            .targetedInterfaces;
767
768        // Since `targetInterfaces` returns a tuple array there is no guarantee
769        // that the addresses are unique this map is used to merge functions of
770        // the specified interfaces for the same address. For example:
771        // `[(addr1, ["IERC20", "IOwnable"])]` and `[(addr1, ["IERC20"]), (addr1, ("IOwnable"))]`
772        // should be equivalent.
773        let mut combined = TargetedContracts::new();
774
775        // Loop through each address and its associated artifact identifiers.
776        // We're borrowing here to avoid taking full ownership.
777        for IInvariantTest::FuzzInterface { addr, artifacts } in &interfaces {
778            // Identifiers are specified as an array, so we loop through them.
779            for identifier in artifacts {
780                // Try to find the contract by name or identifier in the project's contracts.
781                if let Some(abi) = self.project_contracts.find_abi_by_name_or_identifier(identifier)
782                {
783                    combined
784                        // Check if there's an entry for the given key in the 'combined' map.
785                        .entry(*addr)
786                        // If the entry exists, extends its ABI with the function list.
787                        .and_modify(|entry| {
788                            // Extend the ABI's function list with the new functions.
789                            entry.abi.functions.extend(abi.functions.clone());
790                        })
791                        // Otherwise insert it into the map.
792                        .or_insert_with(|| TargetedContract::new(identifier.to_string(), abi));
793                }
794            }
795        }
796
797        targeted_contracts.extend(combined.inner);
798
799        Ok(())
800    }
801
802    /// Selects the functions to fuzz based on the contract method `targetSelectors()` and
803    /// `targetArtifactSelectors()`.
804    pub fn select_selectors(
805        &self,
806        address: Address,
807        targeted_contracts: &mut TargetedContracts,
808    ) -> Result<()> {
809        for (address, (identifier, _)) in self.setup_contracts {
810            if let Some(selectors) = self.artifact_filters.targeted.get(identifier) {
811                self.add_address_with_functions(*address, selectors, false, targeted_contracts)?;
812            }
813        }
814
815        // Collect contract functions marked as target for fuzzing campaign.
816        let selectors =
817            self.executor.call_sol_default(address, &IInvariantTest::targetSelectorsCall {});
818        for IInvariantTest::FuzzSelector { addr, selectors } in selectors.targetedSelectors {
819            self.add_address_with_functions(addr, &selectors, false, targeted_contracts)?;
820        }
821
822        // Collect contract functions excluded from fuzzing campaign.
823        let selectors =
824            self.executor.call_sol_default(address, &IInvariantTest::excludeSelectorsCall {});
825        for IInvariantTest::FuzzSelector { addr, selectors } in selectors.excludedSelectors {
826            self.add_address_with_functions(addr, &selectors, true, targeted_contracts)?;
827        }
828
829        Ok(())
830    }
831
832    /// Adds the address and fuzzed or excluded functions to `TargetedContracts`.
833    fn add_address_with_functions(
834        &self,
835        address: Address,
836        selectors: &[Selector],
837        should_exclude: bool,
838        targeted_contracts: &mut TargetedContracts,
839    ) -> eyre::Result<()> {
840        // Do not add address in target contracts if no function selected.
841        if selectors.is_empty() {
842            return Ok(())
843        }
844
845        let contract = match targeted_contracts.entry(address) {
846            Entry::Occupied(entry) => entry.into_mut(),
847            Entry::Vacant(entry) => {
848                let (identifier, abi) = self.setup_contracts.get(&address).ok_or_else(|| {
849                    eyre::eyre!(
850                        "[{}] address does not have an associated contract: {}",
851                        if should_exclude { "excludeSelectors" } else { "targetSelectors" },
852                        address
853                    )
854                })?;
855                entry.insert(TargetedContract::new(identifier.clone(), abi.clone()))
856            }
857        };
858        contract.add_selectors(selectors.iter().copied(), should_exclude)?;
859        Ok(())
860    }
861}
862
863/// Collects data from call for fuzzing. However, it first verifies that the sender is not an EOA
864/// before inserting it into the dictionary. Otherwise, we flood the dictionary with
865/// randomly generated addresses.
866fn collect_data(
867    invariant_test: &InvariantTest,
868    state_changeset: &mut HashMap<Address, revm::primitives::Account>,
869    tx: &BasicTxDetails,
870    call_result: &RawCallResult,
871    run_depth: u32,
872) {
873    // Verify it has no code.
874    let mut has_code = false;
875    if let Some(Some(code)) =
876        state_changeset.get(&tx.sender).map(|account| account.info.code.as_ref())
877    {
878        has_code = !code.is_empty();
879    }
880
881    // We keep the nonce changes to apply later.
882    let mut sender_changeset = None;
883    if !has_code {
884        sender_changeset = state_changeset.remove(&tx.sender);
885    }
886
887    // Collect values from fuzzed call result and add them to fuzz dictionary.
888    invariant_test.fuzz_state.collect_values_from_call(
889        &invariant_test.targeted_contracts,
890        tx,
891        &call_result.result,
892        &call_result.logs,
893        &*state_changeset,
894        run_depth,
895    );
896
897    // Re-add changes
898    if let Some(changed) = sender_changeset {
899        state_changeset.insert(tx.sender, changed);
900    }
901}
902
903/// Calls the `afterInvariant()` function on a contract.
904/// Returns call result and if call succeeded.
905/// The state after the call is not persisted.
906pub(crate) fn call_after_invariant_function(
907    executor: &Executor,
908    to: Address,
909) -> std::result::Result<(RawCallResult, bool), EvmError> {
910    let calldata = Bytes::from_static(&IInvariantTest::afterInvariantCall::SELECTOR);
911    let mut call_result = executor.call_raw(CALLER, to, calldata, U256::ZERO)?;
912    let success = executor.is_raw_call_mut_success(to, &mut call_result, false);
913    Ok((call_result, success))
914}
915
916/// Calls the invariant function and returns call result and if succeeded.
917pub(crate) fn call_invariant_function(
918    executor: &Executor,
919    address: Address,
920    calldata: Bytes,
921) -> Result<(RawCallResult, bool)> {
922    let mut call_result = executor.call_raw(CALLER, address, calldata, U256::ZERO)?;
923    let success = executor.is_raw_call_mut_success(address, &mut call_result, false);
924    Ok((call_result, success))
925}