Skip to main content

foundry_evm/executors/
mod.rs

1//! EVM executor abstractions, which can execute calls.
2//!
3//! Used for running tests, scripts, and interacting with the inner backend which holds the state.
4
5// TODO: The individual executors in this module should be moved into the respective crates, and the
6// `Executor` struct should be accessed using a trait defined in `foundry-evm-core` instead of
7// the concrete `Executor` type.
8
9use crate::inspectors::{
10    Cheatcodes, CmpOperands, EdgeCoverage, EdgeIndexMap, InspectorData, InspectorStack,
11    cheatcodes::BroadcastableTransactions,
12};
13use alloy_dyn_abi::{DynSolValue, FunctionExt, JsonAbiExt};
14use alloy_eips::eip4788::{BEACON_ROOTS_ADDRESS, SYSTEM_ADDRESS};
15use alloy_evm::Evm;
16use alloy_json_abi::Function;
17use alloy_primitives::{
18    Address, Bytes, Log, TxKind, U256, keccak256,
19    map::{AddressHashMap, HashMap},
20};
21use alloy_sol_types::{SolCall, sol};
22use foundry_evm_core::{
23    EvmEnv, FoundryBlock, FoundryChain, FoundryTransaction,
24    backend::{
25        Backend, BackendError, BackendResult, CowBackend, DatabaseError, DatabaseExt,
26        GLOBAL_FAIL_SLOT,
27    },
28    constants::{
29        CALLER, CHEATCODE_ADDRESS, CHEATCODE_CONTRACT_HASH, DEFAULT_CREATE2_DEPLOYER,
30        DEFAULT_CREATE2_DEPLOYER_CODE, DEFAULT_CREATE2_DEPLOYER_DEPLOYER,
31    },
32    decode::{RevertDecoder, SkipReason},
33    eip2935::{
34        HISTORY_STORAGE_ADDRESS, HISTORY_STORAGE_CODE, history_storage_slot, history_storage_value,
35        history_window_start,
36    },
37    evm::{
38        BlockContext, ChainFor, EthEvmNetwork, EvmEnvFor, FoundryEvmFactory, FoundryEvmNetwork,
39        IntoInstructionResult, SpecFor, TxEnvFor,
40    },
41    utils::StateChangeset,
42};
43use foundry_evm_coverage::HitMaps;
44use foundry_evm_fuzz::ObservedCall;
45use foundry_evm_networks::NetworkConfigs;
46use foundry_evm_traces::{SparsedTraceArena, TraceRequirements};
47use revm::{
48    bytecode::Bytecode,
49    context::{Block, Cfg, Transaction},
50    context_interface::{
51        cfg::gas_params::Eip2780TxInfo,
52        result::{ExecutionResult, Output, ResultAndState},
53        transaction::SignedAuthorization,
54    },
55    database::{Database, DatabaseCommit, DatabaseRef},
56    interpreter::{InstructionResult, return_ok},
57    primitives::hardfork::SpecId,
58};
59use sancov::SancovGuard;
60use std::{
61    borrow::Cow,
62    sync::{
63        Arc,
64        atomic::{AtomicBool, Ordering},
65    },
66    time::{Duration, Instant},
67};
68
69mod builder;
70pub use builder::ExecutorBuilder;
71
72mod campaign;
73
74pub mod fuzz;
75pub use fuzz::FuzzedExecutor;
76
77pub mod invariant;
78pub use invariant::InvariantExecutor;
79
80mod corpus;
81mod corpus_io;
82mod sancov;
83mod showmap;
84mod trace;
85
86pub use corpus::{DynamicTargetCtx, StatelessReplayTarget, persist_corpus_seed};
87pub use corpus_io::{
88    CorpusDirEntry, canonical_replay_dirs, parse_corpus_filename, read_corpus_dir, read_corpus_tree,
89};
90pub use showmap::{
91    InvariantReplayOptions, MinimizationReplayInput, ReplayFailure, ReplayObservation,
92    ShowmapDomain, ShowmapOpts, ShowmapReplayTarget, ShowmapStats, replay_corpus_to_showmap,
93    replay_sequence_for_minimization,
94};
95pub use trace::TracingExecutor;
96
97const DURATION_BETWEEN_METRICS_REPORT: Duration = Duration::from_secs(5);
98
99sol! {
100    interface ITest {
101        function setUp() external;
102        function failed() external view returns (bool failed);
103
104        #[derive(Default)]
105        function beforeTestSetup(bytes4 testSelector) public view returns (bytes[] memory beforeTestCalldata);
106    }
107}
108
109/// EVM executor.
110///
111/// The executor can be configured with various `revm::Inspector`s, like `Cheatcodes`.
112///
113/// There are multiple ways of interacting the EVM:
114/// - `call`: executes a transaction, but does not persist any state changes; similar to `eth_call`,
115///   where the EVM state is unchanged after the call.
116/// - `transact`: executes a transaction and persists the state changes
117/// - `deploy`: a special case of `transact`, specialized for persisting the state of a contract
118///   deployment
119/// - `setup`: a special case of `transact`, used to set up the environment for a test
120#[derive(Clone, Debug)]
121pub struct Executor<FEN: FoundryEvmNetwork> {
122    /// The underlying `revm::Database` that contains the EVM storage.
123    ///
124    /// Wrapped in `Arc` for efficient cloning during parallel fuzzing. Use [`Arc::make_mut`]
125    /// for copy-on-write semantics when mutation is needed.
126    // Note: We do not store an EVM here, since we are really
127    // only interested in the database. REVM's `EVM` is a thin
128    // wrapper around spawning a new EVM on every call anyway,
129    // so the performance difference should be negligible.
130    backend: Arc<Backend<FEN>>,
131    /// The EVM environment (block and cfg).
132    evm_env: EvmEnvFor<FEN>,
133    /// The transaction environment.
134    tx_env: TxEnvFor<FEN>,
135    /// The Revm inspector stack.
136    inspector: InspectorStack<FEN>,
137    /// The gas limit for calls and deployments.
138    gas_limit: u64,
139    /// Whether `failed()` should be called on the test contract to determine if the test failed.
140    legacy_assertions: bool,
141    /// Opt-in cursor for transactions simulated sequentially against one fork.
142    block_context: Option<BlockContext<FEN>>,
143}
144
145impl<FEN: FoundryEvmNetwork> Executor<FEN> {
146    /// Creates a new `Executor` with the given arguments.
147    #[inline]
148    pub fn new(
149        mut backend: Backend<FEN>,
150        evm_env: EvmEnvFor<FEN>,
151        tx_env: TxEnvFor<FEN>,
152        mut inspector: InspectorStack<FEN>,
153        networks: NetworkConfigs,
154        gas_limit: u64,
155        legacy_assertions: bool,
156    ) -> Self {
157        inspector.networks(networks);
158        backend.set_networks(networks);
159        let extra_cheatcode_addresses = networks.extra_cheatcode_addresses();
160        backend.extend_persistent_accounts(extra_cheatcode_addresses.iter().copied());
161
162        // Need to create a non-empty contract on the cheatcodes address so `extcodesize` checks
163        // do not fail.
164        backend.insert_account_info(
165            CHEATCODE_ADDRESS,
166            revm::state::AccountInfo {
167                code: Some(Bytecode::new_raw(Bytes::from_static(&[0]))),
168                // Also set the code hash manually so that it's not computed later.
169                // The code hash value does not matter, as long as it's not zero or `KECCAK_EMPTY`.
170                code_hash: CHEATCODE_CONTRACT_HASH,
171                ..Default::default()
172            },
173        );
174
175        for &address in extra_cheatcode_addresses {
176            backend.insert_account_info(
177                address,
178                revm::state::AccountInfo {
179                    code: Some(Bytecode::new_raw(Bytes::from_static(&[0]))),
180                    code_hash: keccak256(address),
181                    ..Default::default()
182                },
183            );
184        }
185
186        if !backend.is_in_forking_mode() && evm_env.cfg_env.spec.into() >= SpecId::PRAGUE {
187            let mut account =
188                backend.basic_ref(HISTORY_STORAGE_ADDRESS).unwrap_or_default().unwrap_or_default();
189            account.code_hash = keccak256(&HISTORY_STORAGE_CODE);
190            account.code = Some(Bytecode::new_raw(HISTORY_STORAGE_CODE.clone()));
191            backend.insert_account_info(HISTORY_STORAGE_ADDRESS, account);
192
193            let current_block = evm_env.block_env.number();
194            let mut block_number = history_window_start(current_block);
195            while block_number < current_block {
196                let block_hash =
197                    backend.block_hash(block_number.saturating_to()).unwrap_or_default();
198                let slot = history_storage_slot(block_number);
199                let value = history_storage_value(block_hash);
200                let _ = backend.insert_account_storage(HISTORY_STORAGE_ADDRESS, slot, value);
201                block_number += U256::from(1);
202            }
203        }
204
205        Self {
206            backend: Arc::new(backend),
207            evm_env,
208            tx_env,
209            inspector,
210            gas_limit,
211            legacy_assertions,
212            block_context: None,
213        }
214    }
215
216    fn clone_with_backend(&self, backend: Backend<FEN>) -> Self {
217        let evm_env = self.evm_env.clone();
218        Self {
219            backend: Arc::new(backend),
220            evm_env,
221            tx_env: self.tx_env.clone(),
222            inspector: self.inspector().clone(),
223            gas_limit: self.gas_limit,
224            legacy_assertions: self.legacy_assertions,
225            block_context: self.block_context.clone(),
226        }
227    }
228
229    /// Returns a reference to the EVM backend.
230    pub fn backend(&self) -> &Backend<FEN> {
231        &self.backend
232    }
233
234    /// Returns a mutable reference to the EVM backend.
235    ///
236    /// Uses copy-on-write semantics: if other clones of this executor share the backend,
237    /// this will clone the backend first.
238    pub fn backend_mut(&mut self) -> &mut Backend<FEN> {
239        Arc::make_mut(&mut self.backend)
240    }
241
242    /// Enables exact block-context progression for sequential committed transactions.
243    ///
244    /// This is opt-in because test and setup calls are execution phases rather than transactions
245    /// that should automatically become part of one simulated block.
246    pub fn enable_block_context_progression(&mut self) -> eyre::Result<()> {
247        self.block_context = self.backend().block_context_for_synthetic_transaction()?;
248        Ok(())
249    }
250
251    /// Advances an enabled block-context cursor to the start of the next block.
252    pub fn advance_block_context(&mut self) {
253        if let Some(context) = &mut self.block_context {
254            context.advance_block();
255        }
256    }
257
258    fn chain_context_for_synthetic_transaction(
259        &self,
260        tx: &TxEnvFor<FEN>,
261    ) -> eyre::Result<ChainFor<FEN>> {
262        self.block_context.as_ref().map_or_else(
263            || self.backend().chain_context_for_synthetic_transaction(tx),
264            |context| Ok(context.next_transaction(tx)),
265        )
266    }
267
268    fn record_block_transaction(&mut self, tx: TxEnvFor<FEN>) {
269        if let Some(context) = &mut self.block_context {
270            context.record_transaction(tx);
271        }
272    }
273
274    /// Returns a reference to the EVM environment (block and cfg).
275    pub const fn evm_env(&self) -> &EvmEnvFor<FEN> {
276        &self.evm_env
277    }
278
279    /// Returns a mutable reference to the EVM environment (block and cfg).
280    pub const fn evm_env_mut(&mut self) -> &mut EvmEnvFor<FEN> {
281        &mut self.evm_env
282    }
283
284    /// Returns a reference to the transaction environment.
285    pub const fn tx_env(&self) -> &TxEnvFor<FEN> {
286        &self.tx_env
287    }
288
289    /// Returns a mutable reference to the transaction environment.
290    pub const fn tx_env_mut(&mut self) -> &mut TxEnvFor<FEN> {
291        &mut self.tx_env
292    }
293
294    /// Returns a reference to the EVM inspector.
295    pub const fn inspector(&self) -> &InspectorStack<FEN> {
296        &self.inspector
297    }
298
299    /// Returns a mutable reference to the EVM inspector.
300    pub const fn inspector_mut(&mut self) -> &mut InspectorStack<FEN> {
301        &mut self.inspector
302    }
303
304    /// Returns the EVM spec.
305    pub const fn spec_id(&self) -> SpecFor<FEN> {
306        self.evm_env.cfg_env.spec
307    }
308
309    /// Sets the EVM spec and updates spec-dependent gas parameters.
310    pub fn set_spec_id(&mut self, spec_id: SpecFor<FEN>) {
311        self.evm_env.cfg_env.set_spec_and_mainnet_gas_params(spec_id);
312    }
313
314    /// Returns the gas limit for calls and deployments.
315    ///
316    /// This is different from the gas limit imposed by the passed in environment, as those limits
317    /// are used by the EVM for certain opcodes like `gaslimit`.
318    pub const fn gas_limit(&self) -> u64 {
319        self.gas_limit
320    }
321
322    /// Sets the gas limit for calls and deployments.
323    pub const fn set_gas_limit(&mut self, gas_limit: u64) {
324        self.gas_limit = gas_limit;
325    }
326
327    /// Returns whether `failed()` should be called on the test contract to determine if the test
328    /// failed.
329    pub const fn legacy_assertions(&self) -> bool {
330        self.legacy_assertions
331    }
332
333    /// Sets whether `failed()` should be called on the test contract to determine if the test
334    /// failed.
335    pub const fn set_legacy_assertions(&mut self, legacy_assertions: bool) {
336        self.legacy_assertions = legacy_assertions;
337    }
338
339    /// Creates the default CREATE2 Contract Deployer for local tests and scripts.
340    pub fn deploy_create2_deployer(&mut self) -> eyre::Result<()> {
341        trace!("deploying local create2 deployer");
342        let create2_deployer_account = self
343            .backend()
344            .basic_ref(DEFAULT_CREATE2_DEPLOYER)?
345            .ok_or_else(|| BackendError::MissingAccount(DEFAULT_CREATE2_DEPLOYER))?;
346
347        // If the deployer is not currently deployed, deploy the default one.
348        if create2_deployer_account.code.is_none_or(|code| code.is_empty()) {
349            let creator = DEFAULT_CREATE2_DEPLOYER_DEPLOYER;
350
351            // Probably 0, but just in case.
352            let initial_balance = self.get_balance(creator)?;
353            self.set_balance(creator, U256::MAX)?;
354
355            let res =
356                self.deploy(creator, DEFAULT_CREATE2_DEPLOYER_CODE.into(), U256::ZERO, None)?;
357            trace!(create2=?res.address, "deployed local create2 deployer");
358
359            self.set_balance(creator, initial_balance)?;
360        }
361        Ok(())
362    }
363
364    /// Set the balance of an account.
365    pub fn set_balance(&mut self, address: Address, amount: U256) -> BackendResult<()> {
366        trace!(?address, ?amount, "setting account balance");
367        let mut account = self.backend().basic_ref(address)?.unwrap_or_default();
368        account.balance = amount;
369        self.backend_mut().insert_account_info(address, account);
370        Ok(())
371    }
372
373    /// Gets the balance of an account
374    pub fn get_balance(&self, address: Address) -> BackendResult<U256> {
375        Ok(self.backend().basic_ref(address)?.map(|acc| acc.balance).unwrap_or_default())
376    }
377
378    /// Sets the nonce of an account without modifying the transaction environment.
379    pub fn set_account_nonce(&mut self, address: Address, nonce: u64) -> BackendResult<()> {
380        let mut account = self.backend().basic_ref(address)?.unwrap_or_default();
381        account.nonce = nonce;
382        self.backend_mut().insert_account_info(address, account);
383        Ok(())
384    }
385
386    /// Sets the nonce of an account and the transaction environment.
387    pub fn set_nonce(&mut self, address: Address, nonce: u64) -> BackendResult<()> {
388        self.set_account_nonce(address, nonce)?;
389        self.tx_env_mut().set_nonce(nonce);
390        Ok(())
391    }
392
393    /// Returns the nonce of an account.
394    pub fn get_nonce(&self, address: Address) -> BackendResult<u64> {
395        Ok(self.backend().basic_ref(address)?.map(|acc| acc.nonce).unwrap_or_default())
396    }
397
398    /// Set the code of an account.
399    pub fn set_code(&mut self, address: Address, code: Bytecode) -> BackendResult<()> {
400        let mut account = self.backend().basic_ref(address)?.unwrap_or_default();
401        account.code_hash = keccak256(code.original_byte_slice());
402        account.code = Some(code);
403        self.backend_mut().insert_account_info(address, account);
404        Ok(())
405    }
406
407    /// Set the storage of an account.
408    pub fn set_storage(
409        &mut self,
410        address: Address,
411        storage: HashMap<U256, U256>,
412    ) -> BackendResult<()> {
413        self.backend_mut().replace_account_storage(address, storage)?;
414        Ok(())
415    }
416
417    /// Set a storage slot of an account.
418    pub fn set_storage_slot(
419        &mut self,
420        address: Address,
421        slot: U256,
422        value: U256,
423    ) -> BackendResult<()> {
424        self.backend_mut().insert_account_storage(address, slot, value)?;
425        Ok(())
426    }
427
428    /// Apply prestate trace data to the executor's backend.
429    ///
430    /// This is used to set up the EVM state based on the prestate trace from
431    /// `debug_traceTransaction`, which provides all accounts and storage slots
432    /// that will be accessed during transaction execution.
433    pub fn apply_prestate_trace(
434        &mut self,
435        prestate: std::collections::BTreeMap<Address, alloy_rpc_types::trace::geth::AccountState>,
436    ) -> eyre::Result<()> {
437        let backend = self.backend_mut();
438        for (address, account_state) in prestate {
439            let code = account_state.code.map(Bytecode::new_raw).unwrap_or_default();
440            let info = revm::state::AccountInfo {
441                nonce: account_state.nonce.unwrap_or_default(),
442                balance: account_state.balance.unwrap_or_default(),
443                code_hash: keccak256(code.original_byte_slice()),
444                code: Some(code),
445                account_id: Default::default(),
446            };
447            backend.insert_account_info(address, info);
448
449            for (slot, value) in account_state.storage {
450                let slot = U256::from_be_bytes(slot.0);
451                let value = U256::from_be_bytes(value.0);
452                backend.insert_account_storage(address, slot, value)?;
453            }
454        }
455        Ok(())
456    }
457
458    /// Returns `true` if the account has no code.
459    pub fn is_empty_code(&self, address: Address) -> BackendResult<bool> {
460        Ok(self.backend().basic_ref(address)?.map(|acc| acc.is_empty_code_hash()).unwrap_or(true))
461    }
462
463    #[inline]
464    pub fn set_trace_requirements(&mut self, requirements: TraceRequirements) -> &mut Self {
465        self.inspector_mut().tracing_requirements(requirements);
466        self
467    }
468
469    #[inline]
470    pub fn set_script_execution(&mut self, script_address: Address) {
471        self.inspector_mut().script(script_address);
472    }
473
474    #[inline]
475    pub fn set_trace_printer(&mut self, trace_printer: bool) -> &mut Self {
476        self.inspector_mut().print(trace_printer);
477        self
478    }
479
480    #[inline]
481    pub fn create2_deployer(&self) -> Address {
482        self.inspector().create2_deployer
483    }
484
485    /// Deploys a contract and commits the new state to the underlying database.
486    ///
487    /// Executes a CREATE transaction with the contract `code` and persistent database state
488    /// modifications.
489    pub fn deploy(
490        &mut self,
491        from: Address,
492        code: Bytes,
493        value: U256,
494        rd: Option<&RevertDecoder>,
495    ) -> Result<DeployResult<FEN>, EvmError<FEN>> {
496        let (evm_env, tx_env) = self.build_test_env(from, TxKind::Create, code, value);
497        self.deploy_with_env(evm_env, tx_env, rd)
498    }
499
500    /// Deploys a contract with explicit network-specific context.
501    pub fn deploy_with_context(
502        &mut self,
503        from: Address,
504        code: Bytes,
505        value: U256,
506        chain_context: ChainFor<FEN>,
507        rd: Option<&RevertDecoder>,
508    ) -> Result<DeployResult<FEN>, EvmError<FEN>> {
509        let (evm_env, tx_env) = self.build_test_env(from, TxKind::Create, code, value);
510        self.deploy_with_env_and_context(evm_env, tx_env, chain_context, rd)
511    }
512
513    /// Deploys a contract using the given `env` and commits the new state to the underlying
514    /// database.
515    ///
516    /// # Panics
517    ///
518    /// Panics if `tx_env.kind` is not `TxKind::Create(_)`.
519    #[instrument(name = "deploy", level = "debug", skip_all)]
520    pub fn deploy_with_env(
521        &mut self,
522        evm_env: EvmEnvFor<FEN>,
523        tx_env: TxEnvFor<FEN>,
524        rd: Option<&RevertDecoder>,
525    ) -> Result<DeployResult<FEN>, EvmError<FEN>> {
526        let chain_context = self.chain_context_for_synthetic_transaction(&tx_env)?;
527        self.deploy_with_env_and_context(evm_env, tx_env, chain_context, rd)
528    }
529
530    /// Deploys a contract with explicit network-specific context and commits its state changes.
531    ///
532    /// # Panics
533    ///
534    /// Panics if `tx_env.kind` is not `TxKind::Create(_)`.
535    #[instrument(name = "deploy", level = "debug", skip_all)]
536    pub fn deploy_with_env_and_context(
537        &mut self,
538        evm_env: EvmEnvFor<FEN>,
539        tx_env: TxEnvFor<FEN>,
540        chain_context: ChainFor<FEN>,
541        rd: Option<&RevertDecoder>,
542    ) -> Result<DeployResult<FEN>, EvmError<FEN>> {
543        assert!(
544            matches!(tx_env.kind(), TxKind::Create),
545            "Expected create transaction, got {:?}",
546            tx_env.kind()
547        );
548        trace!(sender=%tx_env.caller(), "deploying contract");
549
550        let mut result = self.transact_with_env_and_context(evm_env, tx_env, chain_context)?;
551        result = result.into_result(rd)?;
552        let Some(Output::Create(_, Some(address))) = result.out else {
553            panic!("Deployment succeeded, but no address was returned: {result:#?}");
554        };
555
556        // also mark this library as persistent, this will ensure that the state of the library is
557        // persistent across fork swaps in forking mode
558        self.backend_mut().add_persistent_account(address);
559
560        trace!(%address, "deployed contract");
561
562        Ok(DeployResult { raw: result, address })
563    }
564
565    /// Calls the `setUp()` function on a contract.
566    ///
567    /// This will commit any state changes to the underlying database.
568    ///
569    /// Ayn changes made during the setup call to env's block environment are persistent, for
570    /// example `vm.chainId()` will change the `block.chainId` for all subsequent test calls.
571    #[instrument(name = "setup", level = "debug", skip_all)]
572    pub fn setup(
573        &mut self,
574        from: Option<Address>,
575        to: Address,
576        rd: Option<&RevertDecoder>,
577    ) -> Result<RawCallResult<FEN>, EvmError<FEN>> {
578        trace!(?from, ?to, "setting up contract");
579
580        let from = from.unwrap_or(CALLER);
581        self.backend_mut().set_test_contract(to).set_caller(from);
582        let calldata = Bytes::from_static(&ITest::setUpCall::SELECTOR);
583        let mut res = self.transact_raw(from, to, calldata, U256::ZERO)?;
584        res = res.into_result(rd)?;
585
586        // record any changes made to the block's environment during setup
587        self.evm_env_mut().block_env = res.evm_env.block_env.clone();
588        // and also the chainid, which can be set manually
589        self.evm_env_mut().cfg_env.chain_id = res.evm_env.cfg_env.chain_id;
590
591        let success =
592            self.is_raw_call_success(to, Cow::Borrowed(&res.state_changeset), &res, false);
593        if !success {
594            return Err(res.into_execution_error("execution error".to_string()).into());
595        }
596
597        Ok(res)
598    }
599
600    /// Performs a call to an account on the current state of the VM.
601    pub fn call(
602        &self,
603        from: Address,
604        to: Address,
605        func: &Function,
606        args: &[DynSolValue],
607        value: U256,
608        rd: Option<&RevertDecoder>,
609    ) -> Result<CallResult<DynSolValue, FEN>, EvmError<FEN>> {
610        let calldata = Bytes::from(func.abi_encode_input(args)?);
611        let result = self.call_raw(from, to, calldata, value)?;
612        result.into_decoded_result(func, rd)
613    }
614
615    /// Performs a call to an account on the current state of the VM.
616    pub fn call_sol<C: SolCall>(
617        &self,
618        from: Address,
619        to: Address,
620        args: &C,
621        value: U256,
622        rd: Option<&RevertDecoder>,
623    ) -> Result<CallResult<C::Return, FEN>, EvmError<FEN>> {
624        let calldata = Bytes::from(args.abi_encode());
625        let mut raw = self.call_raw(from, to, calldata, value)?;
626        raw = raw.into_result(rd)?;
627        Ok(CallResult { decoded_result: C::abi_decode_returns(&raw.result)?, raw })
628    }
629
630    /// Performs a call to an account on the current state of the VM.
631    pub fn transact(
632        &mut self,
633        from: Address,
634        to: Address,
635        func: &Function,
636        args: &[DynSolValue],
637        value: U256,
638        rd: Option<&RevertDecoder>,
639    ) -> Result<CallResult<DynSolValue, FEN>, EvmError<FEN>> {
640        let calldata = Bytes::from(func.abi_encode_input(args)?);
641        let result = self.transact_raw(from, to, calldata, value)?;
642        result.into_decoded_result(func, rd)
643    }
644
645    /// Performs a raw call to an account on the current state of the VM.
646    pub fn call_raw(
647        &self,
648        from: Address,
649        to: Address,
650        calldata: Bytes,
651        value: U256,
652    ) -> eyre::Result<RawCallResult<FEN>> {
653        let (evm_env, tx_env) = self.build_test_env(from, TxKind::Call(to), calldata, value);
654        self.call_with_env(evm_env, tx_env)
655    }
656
657    /// Performs a raw call to an account on the current state of the VM with an EIP-7702
658    /// authorization list.
659    pub fn call_raw_with_authorization(
660        &mut self,
661        from: Address,
662        to: Address,
663        calldata: Bytes,
664        value: U256,
665        authorization_list: Vec<SignedAuthorization>,
666    ) -> eyre::Result<RawCallResult<FEN>> {
667        let (evm_env, mut tx_env) = self.build_test_env(from, to.into(), calldata, value);
668        tx_env.set_signed_authorization(authorization_list);
669        tx_env.set_tx_type(4);
670        self.call_with_env(evm_env, tx_env)
671    }
672
673    /// Performs a raw call to an account on the current state of the VM.
674    pub fn transact_raw(
675        &mut self,
676        from: Address,
677        to: Address,
678        calldata: Bytes,
679        value: U256,
680    ) -> eyre::Result<RawCallResult<FEN>> {
681        let (evm_env, tx_env) = self.build_test_env(from, TxKind::Call(to), calldata, value);
682        self.transact_with_env(evm_env, tx_env)
683    }
684
685    /// Performs a raw call with explicit network-specific context.
686    pub fn transact_raw_with_context(
687        &mut self,
688        from: Address,
689        to: Address,
690        calldata: Bytes,
691        value: U256,
692        chain_context: ChainFor<FEN>,
693    ) -> eyre::Result<RawCallResult<FEN>> {
694        let (evm_env, tx_env) = self.build_test_env(from, TxKind::Call(to), calldata, value);
695        self.transact_with_env_and_context(evm_env, tx_env, chain_context)
696    }
697
698    /// Performs a raw call to an account on the current state of the VM with an EIP-7702
699    /// authorization last.
700    pub fn transact_raw_with_authorization(
701        &mut self,
702        from: Address,
703        to: Address,
704        calldata: Bytes,
705        value: U256,
706        authorization_list: Vec<SignedAuthorization>,
707    ) -> eyre::Result<RawCallResult<FEN>> {
708        let (evm_env, mut tx_env) = self.build_test_env(from, TxKind::Call(to), calldata, value);
709        tx_env.set_signed_authorization(authorization_list);
710        tx_env.set_tx_type(4);
711        self.transact_with_env(evm_env, tx_env)
712    }
713
714    /// Applies the EIP-4788 beacon roots system call (Cancun+).
715    /// <https://eips.ethereum.org/EIPS/eip-4788>
716    pub fn apply_beacon_root(
717        &mut self,
718        parent_beacon_block_root: alloy_primitives::B256,
719    ) -> eyre::Result<()> {
720        let calldata = Bytes::copy_from_slice(parent_beacon_block_root.as_slice());
721        let mut evm_env = self.evm_env.clone();
722        let inspector = self.inspector().clone();
723        let mut state = {
724            let mut backend = CowBackend::new_borrowed(self.backend());
725            let mut evm = FEN::EvmFactory::default().create_foundry_evm_with_inspector(
726                &mut backend,
727                evm_env.clone(),
728                ChainFor::<FEN>::for_transaction(&TxEnvFor::<FEN>::default()),
729                inspector,
730            );
731            let result =
732                evm.transact_system_call(SYSTEM_ADDRESS, BEACON_ROOTS_ADDRESS, calldata)?;
733            evm_env = evm.finish().1;
734            result.state
735        };
736        state.retain(|address, _| *address == BEACON_ROOTS_ADDRESS);
737
738        self.backend_mut().commit(state);
739        self.inspector_mut().set_block(evm_env.block_env);
740
741        Ok(())
742    }
743
744    /// Execute the transaction configured in `tx_env`.
745    ///
746    /// The state after the call is **not** persisted.
747    #[instrument(name = "call", level = "debug", skip_all)]
748    pub fn call_with_env(
749        &self,
750        evm_env: EvmEnvFor<FEN>,
751        tx_env: TxEnvFor<FEN>,
752    ) -> eyre::Result<RawCallResult<FEN>> {
753        let chain_context = self.chain_context_for_synthetic_transaction(&tx_env)?;
754        self.call_with_env_and_context(evm_env, tx_env, chain_context)
755    }
756
757    /// Executes the transaction with explicit network-specific context without committing state.
758    #[instrument(name = "call", level = "debug", skip_all)]
759    pub fn call_with_env_and_context(
760        &self,
761        mut evm_env: EvmEnvFor<FEN>,
762        mut tx_env: TxEnvFor<FEN>,
763        chain_context: ChainFor<FEN>,
764    ) -> eyre::Result<RawCallResult<FEN>> {
765        let mut stack = self.inspector().clone();
766        let sancov_edges = stack.inner.sancov_edges;
767        let sancov_trace_cmp = stack.inner.sancov_trace_cmp;
768        let sancov_active = sancov_edges || sancov_trace_cmp;
769        let mut backend = CowBackend::new_borrowed(self.backend());
770        let result = {
771            let _guard = sancov_active.then(|| SancovGuard::new(sancov_edges, sancov_trace_cmp));
772            backend.inspect_with_context(&mut evm_env, &mut tx_env, chain_context, &mut stack)?
773        };
774        let has_state_snapshot_failure = backend.has_state_snapshot_failure();
775        let fork_block_number = backend.active_fork_block_number();
776        let mut result = convert_executed_result(
777            evm_env,
778            tx_env,
779            stack,
780            result,
781            &backend,
782            has_state_snapshot_failure,
783            fork_block_number,
784        )?;
785        if sancov_edges {
786            SancovGuard::append_edges_into(&mut result);
787        }
788        if sancov_trace_cmp {
789            SancovGuard::drain_cmp_into(&mut result);
790        }
791        Ok(result)
792    }
793
794    /// Execute the transaction configured in `tx_env`.
795    #[instrument(name = "transact", level = "debug", skip_all)]
796    pub fn transact_with_env(
797        &mut self,
798        evm_env: EvmEnvFor<FEN>,
799        tx_env: TxEnvFor<FEN>,
800    ) -> eyre::Result<RawCallResult<FEN>> {
801        let chain_context = self.chain_context_for_synthetic_transaction(&tx_env)?;
802        self.transact_with_env_and_context(evm_env, tx_env, chain_context)
803    }
804
805    /// Executes and commits the transaction with explicit network-specific context.
806    #[instrument(name = "transact", level = "debug", skip_all)]
807    pub fn transact_with_env_and_context(
808        &mut self,
809        mut evm_env: EvmEnvFor<FEN>,
810        mut tx_env: TxEnvFor<FEN>,
811        chain_context: ChainFor<FEN>,
812    ) -> eyre::Result<RawCallResult<FEN>> {
813        let mut stack = self.inspector().clone();
814        let sancov_edges = stack.inner.sancov_edges;
815        let sancov_trace_cmp = stack.inner.sancov_trace_cmp;
816        let sancov_active = sancov_edges || sancov_trace_cmp;
817        let backend = self.backend_mut();
818        let result = {
819            let _guard = sancov_active.then(|| SancovGuard::new(sancov_edges, sancov_trace_cmp));
820            backend.inspect_with_context(&mut evm_env, &mut tx_env, chain_context, &mut stack)?
821        };
822        let has_state_snapshot_failure = backend.has_state_snapshot_failure();
823        let fork_block_number = backend.active_fork_block_number();
824        let mut result = convert_executed_result(
825            evm_env,
826            tx_env,
827            stack,
828            result,
829            &*backend,
830            has_state_snapshot_failure,
831            fork_block_number,
832        )?;
833        if sancov_edges {
834            SancovGuard::append_edges_into(&mut result);
835        }
836        if sancov_trace_cmp {
837            SancovGuard::drain_cmp_into(&mut result);
838        }
839        let committed_tx = result.tx_env.clone();
840        self.commit(&mut result);
841        self.record_block_transaction(committed_tx);
842        Ok(result)
843    }
844
845    /// Tries to execute and commit a canonical system transaction during replay.
846    #[cfg(feature = "monad")]
847    #[instrument(name = "transact_system_replay", level = "debug", skip_all)]
848    pub fn try_transact_system_replay_with_env_and_context(
849        &mut self,
850        mut evm_env: EvmEnvFor<FEN>,
851        mut tx_env: TxEnvFor<FEN>,
852        chain_context: ChainFor<FEN>,
853    ) -> eyre::Result<Option<RawCallResult<FEN>>> {
854        let mut stack = self.inspector().clone();
855        let mut backend = CowBackend::new_borrowed(self.backend());
856        let Some(result) = backend.try_inspect_system_replay_with_context(
857            &mut evm_env,
858            &mut tx_env,
859            chain_context,
860            &mut stack,
861        )?
862        else {
863            return Ok(None);
864        };
865        let has_state_snapshot_failure = backend.has_state_snapshot_failure();
866        let fork_block_number = backend.active_fork_block_number();
867        let mut result = convert_executed_result(
868            evm_env,
869            tx_env,
870            stack,
871            result,
872            &backend,
873            has_state_snapshot_failure,
874            fork_block_number,
875        )?;
876        let committed_tx = result.tx_env.clone();
877        self.commit(&mut result);
878        self.record_block_transaction(committed_tx);
879        Ok(Some(result))
880    }
881
882    /// Commit the changeset to the database and adjust `self.inspector_config` values according to
883    /// the executed call result.
884    ///
885    /// This should not be exposed to the user, as it should be called only by `transact*`.
886    #[instrument(name = "commit", level = "debug", skip_all)]
887    fn commit(&mut self, result: &mut RawCallResult<FEN>) {
888        // Persist changes to db.
889        self.backend_mut().commit(result.state_changeset.clone());
890
891        // Persist cheatcode state.
892        self.inspector_mut().cheatcodes = result.cheatcodes.take();
893        if let Some(cheats) = self.inspector_mut().cheatcodes.as_mut() {
894            // Clear broadcastable transactions
895            cheats.broadcastable_transactions.clear();
896            cheats.ignored_traces.ignored.clear();
897            // if tracing was paused but never unpaused, we should begin next frame with tracing
898            // still paused
899            if let Some(last_pause_call) = cheats.ignored_traces.last_pause_call.as_mut() {
900                *last_pause_call = (0, 0);
901            }
902        }
903
904        // Persist the changed environment.
905        self.inspector_mut().set_block(result.evm_env.block_env.clone());
906        self.inspector_mut().set_gas_price(result.tx_env.gas_price());
907    }
908
909    /// Returns `true` if a test can be considered successful.
910    ///
911    /// This is the same as [`Self::is_success`], but will consume the `state_changeset` map to use
912    /// internally when calling `failed()`.
913    pub fn is_raw_call_mut_success(
914        &self,
915        address: Address,
916        call_result: &mut RawCallResult<FEN>,
917        should_fail: bool,
918    ) -> bool {
919        self.is_raw_call_success(
920            address,
921            Cow::Owned(std::mem::take(&mut call_result.state_changeset)),
922            call_result,
923            should_fail,
924        )
925    }
926
927    /// Returns `true` if a test can be considered successful.
928    ///
929    /// This is the same as [`Self::is_success`], but intended for outcomes of [`Self::call_raw`].
930    pub fn is_raw_call_success(
931        &self,
932        address: Address,
933        state_changeset: Cow<'_, StateChangeset>,
934        call_result: &RawCallResult<FEN>,
935        should_fail: bool,
936    ) -> bool {
937        if call_result.has_state_snapshot_failure {
938            // a failure occurred in a reverted snapshot, which is considered a failed test
939            return should_fail;
940        }
941        self.is_success(address, call_result.reverted, state_changeset, should_fail)
942    }
943
944    /// Like [`Self::is_raw_call_mut_success`] but uses [`Self::is_success_handler_gate`] under
945    /// the hood. Intended for invariant view-call success checks during a campaign where the
946    /// committed `GLOBAL_FAIL_SLOT` may be stale poison from a previously-recorded handler bug.
947    pub fn is_raw_call_mut_success_handler_gate(
948        &self,
949        address: Address,
950        call_result: &mut RawCallResult<FEN>,
951    ) -> bool {
952        if call_result.has_state_snapshot_failure {
953            return false;
954        }
955        let state_changeset = std::mem::take(&mut call_result.state_changeset);
956        self.is_success_handler_gate(address, call_result.reverted, Cow::Owned(state_changeset))
957    }
958
959    /// Returns `true` if a test can be considered successful.
960    ///
961    /// If the call succeeded, we also have to check the global and local failure flags.
962    ///
963    /// These are set by the test contract itself when an assertion fails, using the internal `fail`
964    /// function. The global flag is located in [`CHEATCODE_ADDRESS`] at slot [`GLOBAL_FAIL_SLOT`],
965    /// and the local flag is located in the test contract at an unspecified slot.
966    ///
967    /// This behavior is inherited from Dapptools, where initially only a public
968    /// `failed` variable was used to track test failures, and later, a global failure flag was
969    /// introduced to track failures across multiple contracts in
970    /// [ds-test#30](https://github.com/dapphub/ds-test/pull/30).
971    ///
972    /// The assumption is that the test runner calls `failed` on the test contract to determine if
973    /// it failed. However, we want to avoid this as much as possible, as it is relatively
974    /// expensive to set up an EVM call just for checking a single boolean flag.
975    ///
976    /// See:
977    /// - Newer DSTest: <https://github.com/dapphub/ds-test/blob/e282159d5170298eb2455a6c05280ab5a73a4ef0/src/test.sol#L47-L63>
978    /// - Older DSTest: <https://github.com/dapphub/ds-test/blob/9ca4ecd48862b40d7b0197b600713f64d337af12/src/test.sol#L38-L49>
979    /// - forge-std: <https://github.com/foundry-rs/forge-std/blob/19891e6a0b5474b9ea6827ddb90bb9388f7acfc0/src/StdAssertions.sol#L38-L44>
980    pub fn is_success(
981        &self,
982        address: Address,
983        reverted: bool,
984        state_changeset: Cow<'_, StateChangeset>,
985        should_fail: bool,
986    ) -> bool {
987        let success = self.is_success_raw(address, reverted, state_changeset, false);
988        should_fail ^ success
989    }
990
991    /// Like [`Self::is_success`] but ignores the *committed* `GLOBAL_FAIL_SLOT` and only treats
992    /// the slot as failed when this call's in-flight changeset writes it. Used by the invariant
993    /// runner's per-call handler-success gate, where a `1` already in committed storage is just
994    /// stale poison from a previously-recorded handler bug (separately tracked) and must not
995    /// suppress later `assert_invariants` / `afterInvariant` evaluations.
996    pub fn is_success_handler_gate(
997        &self,
998        address: Address,
999        reverted: bool,
1000        state_changeset: Cow<'_, StateChangeset>,
1001    ) -> bool {
1002        self.is_success_raw(address, reverted, state_changeset, true)
1003    }
1004
1005    #[instrument(name = "is_success", level = "debug", skip_all)]
1006    fn is_success_raw(
1007        &self,
1008        address: Address,
1009        reverted: bool,
1010        state_changeset: Cow<'_, StateChangeset>,
1011        pending_global_failure_only: bool,
1012    ) -> bool {
1013        // The call reverted.
1014        if reverted {
1015            return false;
1016        }
1017
1018        // A failure occurred in a reverted snapshot, which is considered a failed test.
1019        if self.backend().has_state_snapshot_failure() {
1020            return false;
1021        }
1022
1023        // Check the global failure slot. Callers that already track recorded handler bugs
1024        // out-of-band can pass `pending_global_failure_only = true` to ignore the committed
1025        // slot (which would otherwise stay `1` for the rest of the run after a non-reverting
1026        // `vm.assert*` under `assertions_revert = false`).
1027        let global_failed = if pending_global_failure_only {
1028            Self::has_pending_global_failure(&state_changeset)
1029        } else {
1030            self.has_global_failure(&state_changeset)
1031        };
1032        if global_failed {
1033            return false;
1034        }
1035
1036        if !self.legacy_assertions {
1037            return true;
1038        }
1039
1040        // Finally, resort to calling `DSTest::failed`.
1041        {
1042            // Construct a new bare-bones backend to evaluate success.
1043            let mut backend = self.backend().clone_empty();
1044
1045            // We only clone the test contract and cheatcode accounts,
1046            // that's all we need to evaluate success.
1047            for address in [address, CHEATCODE_ADDRESS] {
1048                let Ok(acc) = self.backend().basic_ref(address) else { return false };
1049                backend.insert_account_info(address, acc.unwrap_or_default());
1050            }
1051
1052            // If this test failed any asserts, then this changeset will contain changes
1053            // `false -> true` for the contract's `failed` variable and the `globalFailure` flag
1054            // in the state of the cheatcode address,
1055            // which are both read when we call `"failed()(bool)"` in the next step.
1056            backend.commit(state_changeset.into_owned());
1057
1058            // Check if a DSTest assertion failed
1059            let executor = self.clone_with_backend(backend);
1060            let call = executor.call_sol(CALLER, address, &ITest::failedCall {}, U256::ZERO, None);
1061            match call {
1062                Ok(CallResult { raw: _, decoded_result: failed }) => {
1063                    trace!(failed, "DSTest::failed()");
1064                    !failed
1065                }
1066                Err(err) => {
1067                    trace!(%err, "failed to call DSTest::failed()");
1068                    true
1069                }
1070            }
1071        }
1072    }
1073
1074    /// Returns whether the in-flight state changeset for the current call sets the global
1075    /// assertion failure flag.
1076    pub fn has_pending_global_failure(state_changeset: &StateChangeset) -> bool {
1077        if let Some(acc) = state_changeset.get(&CHEATCODE_ADDRESS)
1078            && let Some(failed_slot) = acc.storage.get(&GLOBAL_FAIL_SLOT)
1079            && !failed_slot.present_value().is_zero()
1080        {
1081            return true;
1082        }
1083
1084        false
1085    }
1086
1087    /// Returns whether the global assertion failure flag is set either in the in-flight state
1088    /// changeset or in the committed backend state.
1089    pub fn has_global_failure(&self, state_changeset: &StateChangeset) -> bool {
1090        if Self::has_pending_global_failure(state_changeset) {
1091            return true;
1092        }
1093
1094        self.backend()
1095            .storage_ref(CHEATCODE_ADDRESS, GLOBAL_FAIL_SLOT)
1096            .is_ok_and(|failed_slot| !failed_slot.is_zero())
1097    }
1098
1099    /// Creates the environment to use when executing a transaction in a test context
1100    ///
1101    /// If using a backend with cheatcodes, `tx.gas_price` and `block.number` will be overwritten by
1102    /// the cheatcode state in between calls.
1103    fn build_test_env(
1104        &self,
1105        caller: Address,
1106        kind: TxKind,
1107        data: Bytes,
1108        value: U256,
1109    ) -> (EvmEnvFor<FEN>, TxEnvFor<FEN>) {
1110        let mut cfg_env = self.evm_env.cfg_env.clone();
1111        cfg_env.spec = self.spec_id();
1112
1113        // We always set the gas price to 0 so we can execute the transaction regardless of
1114        // network conditions - the actual gas price is kept in `self.block` and is applied
1115        // by the cheatcode handler if it is enabled
1116        let mut block_env = self.evm_env.block_env.clone();
1117        block_env.set_basefee(0);
1118        block_env.set_gas_limit(self.gas_limit);
1119
1120        let mut tx_env = self.tx_env.clone();
1121        tx_env.set_caller(caller);
1122        tx_env.set_kind(kind);
1123        tx_env.set_data(data);
1124        tx_env.set_value(value);
1125        // As above, we set the gas price to 0.
1126        tx_env.set_gas_price(0);
1127        tx_env.set_gas_priority_fee(None);
1128        tx_env.set_gas_limit(self.gas_limit);
1129        tx_env.set_chain_id(Some(self.evm_env.cfg_env.chain_id));
1130
1131        (EvmEnv { cfg_env, block_env }, tx_env)
1132    }
1133
1134    pub fn call_sol_default<C: SolCall>(&self, to: Address, args: &C) -> C::Return
1135    where
1136        C::Return: Default,
1137    {
1138        self.call_sol(CALLER, to, args, U256::ZERO, None)
1139            .map(|c| c.decoded_result)
1140            .inspect_err(|e| warn!(target: "forge::test", "failed calling {:?}: {e}", C::SIGNATURE))
1141            .unwrap_or_default()
1142    }
1143}
1144
1145/// Represents the context after an execution error occurred.
1146#[derive(Debug, thiserror::Error)]
1147#[error("execution reverted: {reason} (gas: {})", raw.gas_used)]
1148pub struct ExecutionErr<FEN: FoundryEvmNetwork = EthEvmNetwork> {
1149    /// The raw result of the call.
1150    pub raw: RawCallResult<FEN>,
1151    /// The revert reason.
1152    pub reason: String,
1153}
1154
1155impl<FEN: FoundryEvmNetwork> std::ops::Deref for ExecutionErr<FEN> {
1156    type Target = RawCallResult<FEN>;
1157
1158    #[inline]
1159    fn deref(&self) -> &Self::Target {
1160        &self.raw
1161    }
1162}
1163
1164impl<FEN: FoundryEvmNetwork> std::ops::DerefMut for ExecutionErr<FEN> {
1165    #[inline]
1166    fn deref_mut(&mut self) -> &mut Self::Target {
1167        &mut self.raw
1168    }
1169}
1170
1171#[derive(Debug, thiserror::Error)]
1172pub enum EvmError<FEN: FoundryEvmNetwork = EthEvmNetwork> {
1173    /// Error which occurred during execution of a transaction.
1174    #[error(transparent)]
1175    Execution(Box<ExecutionErr<FEN>>),
1176    /// Error which occurred during ABI encoding/decoding.
1177    #[error(transparent)]
1178    Abi(#[from] alloy_dyn_abi::Error),
1179    /// Error caused which occurred due to calling the `skip` cheatcode.
1180    #[error("{0}")]
1181    Skip(SkipReason),
1182    /// Any other error.
1183    #[error("{0}")]
1184    Eyre(
1185        #[from]
1186        #[source]
1187        eyre::Report,
1188    ),
1189}
1190
1191impl<FEN: FoundryEvmNetwork> From<ExecutionErr<FEN>> for EvmError<FEN> {
1192    fn from(err: ExecutionErr<FEN>) -> Self {
1193        Self::Execution(Box::new(err))
1194    }
1195}
1196
1197impl<FEN: FoundryEvmNetwork> From<alloy_sol_types::Error> for EvmError<FEN> {
1198    fn from(err: alloy_sol_types::Error) -> Self {
1199        Self::Abi(err.into())
1200    }
1201}
1202
1203/// The result of a deployment.
1204#[derive(Debug)]
1205pub struct DeployResult<FEN: FoundryEvmNetwork = EthEvmNetwork> {
1206    /// The raw result of the deployment.
1207    pub raw: RawCallResult<FEN>,
1208    /// The address of the deployed contract
1209    pub address: Address,
1210}
1211
1212impl<FEN: FoundryEvmNetwork> std::ops::Deref for DeployResult<FEN> {
1213    type Target = RawCallResult<FEN>;
1214
1215    #[inline]
1216    fn deref(&self) -> &Self::Target {
1217        &self.raw
1218    }
1219}
1220
1221impl<FEN: FoundryEvmNetwork> std::ops::DerefMut for DeployResult<FEN> {
1222    #[inline]
1223    fn deref_mut(&mut self) -> &mut Self::Target {
1224        &mut self.raw
1225    }
1226}
1227
1228impl<FEN: FoundryEvmNetwork> From<DeployResult<FEN>> for RawCallResult<FEN> {
1229    fn from(d: DeployResult<FEN>) -> Self {
1230        d.raw
1231    }
1232}
1233
1234/// The result of a raw call.
1235#[derive(Debug)]
1236pub struct RawCallResult<FEN: FoundryEvmNetwork = EthEvmNetwork> {
1237    /// The status of the call
1238    pub exit_reason: Option<InstructionResult>,
1239    /// Whether the call was halted by the execution cancellation inspector.
1240    pub execution_cancelled: bool,
1241    /// Whether the call reverted or not
1242    pub reverted: bool,
1243    /// Whether the call includes a snapshot failure
1244    ///
1245    /// This is tracked separately from revert because a snapshot failure can occur without a
1246    /// revert, since assert failures are stored in a global variable (ds-test legacy)
1247    pub has_state_snapshot_failure: bool,
1248    /// The raw result of the call.
1249    pub result: Bytes,
1250    /// The gas used for the call
1251    pub gas_used: u64,
1252    /// Refunded gas
1253    pub gas_refunded: u64,
1254    /// The initial gas stipend for the transaction
1255    pub stipend: u64,
1256    /// The logs emitted during the call
1257    pub logs: Vec<Log>,
1258    /// The labels assigned to addresses during the call
1259    pub labels: AddressHashMap<String>,
1260    /// The traces of the call
1261    pub traces: Option<SparsedTraceArena>,
1262    /// Runtime bytecodes for contracts seen in the trace, used by debug source mapping.
1263    pub debug_bytecodes: AddressHashMap<Bytes>,
1264    /// The line coverage info collected during the call
1265    pub line_coverage: Option<HitMaps>,
1266    /// The edge coverage info collected during the call
1267    pub edge_coverage: Option<EdgeCoverage>,
1268    /// EVM comparison operands collected during the call.
1269    pub evm_cmp_values: Option<Vec<CmpOperands>>,
1270    /// Observed sub-calls collected during the call.
1271    pub observed_calls: Vec<ObservedCall>,
1272    /// Sancov edge coverage from instrumented native Rust crates (e.g. precompiles).
1273    /// Tracked separately from EVM edge coverage to avoid ID-space collisions.
1274    pub sancov_coverage: Option<Vec<u8>>,
1275    /// Comparison operands captured via sancov trace-cmp callbacks.
1276    pub sancov_cmp_values: Option<Vec<foundry_evm_sancov::CmpSample>>,
1277    /// Scripted transactions generated from this call
1278    pub transactions: Option<BroadcastableTransactions<FEN::Network>>,
1279    /// The changeset of the state.
1280    pub state_changeset: StateChangeset,
1281    /// The `EvmEnv` after the call
1282    pub evm_env: EvmEnvFor<FEN>,
1283    /// The `TxEnv` after the call
1284    pub tx_env: TxEnvFor<FEN>,
1285    /// The cheatcode states after execution
1286    pub cheatcodes: Option<Box<Cheatcodes<FEN>>>,
1287    /// The raw output of the execution
1288    pub out: Option<Output>,
1289    /// The active fork's block number after execution, if any.
1290    pub fork_block_number: Option<u64>,
1291    /// The chisel state
1292    pub chisel_state: Option<(Vec<U256>, Vec<u8>)>,
1293    pub reverter: Option<Address>,
1294    /// Revert payloads minted by the `skip` cheatcode during this call.
1295    ///
1296    /// Moved out of the cheatcode state on conversion since `commit` moves that state back into
1297    /// the executor before results are classified.
1298    pub skip_payloads: Vec<Bytes>,
1299}
1300
1301impl<FEN: FoundryEvmNetwork> Default for RawCallResult<FEN> {
1302    fn default() -> Self {
1303        Self {
1304            exit_reason: None,
1305            execution_cancelled: false,
1306            reverted: false,
1307            has_state_snapshot_failure: false,
1308            result: Bytes::new(),
1309            gas_used: 0,
1310            gas_refunded: 0,
1311            stipend: 0,
1312            logs: Vec::new(),
1313            labels: HashMap::default(),
1314            traces: None,
1315            debug_bytecodes: HashMap::default(),
1316            line_coverage: None,
1317            edge_coverage: None,
1318            evm_cmp_values: None,
1319            observed_calls: Vec::new(),
1320            sancov_coverage: None,
1321            sancov_cmp_values: None,
1322            transactions: None,
1323            state_changeset: HashMap::default(),
1324            evm_env: EvmEnv::default(),
1325            tx_env: TxEnvFor::<FEN>::default(),
1326            cheatcodes: Default::default(),
1327            out: None,
1328            fork_block_number: None,
1329            chisel_state: None,
1330            reverter: None,
1331            skip_payloads: Vec::new(),
1332        }
1333    }
1334}
1335
1336impl<FEN: FoundryEvmNetwork> RawCallResult<FEN> {
1337    /// Unpacks an EVM result.
1338    pub fn from_evm_result(r: Result<Self, EvmError<FEN>>) -> eyre::Result<(Self, Option<String>)> {
1339        match r {
1340            Ok(r) => Ok((r, None)),
1341            Err(EvmError::Execution(e)) => Ok((e.raw, Some(e.reason))),
1342            Err(e) => Err(e.into()),
1343        }
1344    }
1345
1346    /// Returns the skip reason if this call reverted with a genuine `vm.skip` payload.
1347    ///
1348    /// The revert data must byte-equal a payload recorded by the skip cheatcode during this call;
1349    /// a matching `FOUNDRY::SKIP` prefix alone (user-crafted revert data) does not count.
1350    pub fn skip_reason(&self) -> Option<SkipReason> {
1351        if !self.reverted || !self.skip_payloads.contains(&self.result) {
1352            return None;
1353        }
1354        SkipReason::decode(&self.result)
1355    }
1356
1357    /// Converts the result of the call into an `EvmError`.
1358    pub fn into_evm_error(self, rd: Option<&RevertDecoder>) -> EvmError<FEN> {
1359        if let Some(reason) = self.skip_reason() {
1360            return EvmError::Skip(reason);
1361        }
1362        let reason = rd.unwrap_or_default().decode(&self.result, self.exit_reason);
1363        EvmError::Execution(Box::new(self.into_execution_error(reason)))
1364    }
1365
1366    /// Converts the result of the call into an `ExecutionErr`.
1367    pub const fn into_execution_error(self, reason: String) -> ExecutionErr<FEN> {
1368        ExecutionErr { raw: self, reason }
1369    }
1370
1371    /// Returns an `EvmError` if the call failed, otherwise returns `self`.
1372    pub fn into_result(self, rd: Option<&RevertDecoder>) -> Result<Self, EvmError<FEN>> {
1373        if let Some(reason) = self.exit_reason
1374            && reason.is_ok()
1375        {
1376            Ok(self)
1377        } else {
1378            Err(self.into_evm_error(rd))
1379        }
1380    }
1381
1382    /// Decodes the result of the call with the given function.
1383    pub fn into_decoded_result(
1384        mut self,
1385        func: &Function,
1386        rd: Option<&RevertDecoder>,
1387    ) -> Result<CallResult<DynSolValue, FEN>, EvmError<FEN>> {
1388        self = self.into_result(rd)?;
1389        let mut result = func.abi_decode_output(&self.result)?;
1390        let decoded_result =
1391            if result.len() == 1 { result.pop().unwrap() } else { DynSolValue::Tuple(result) };
1392        Ok(CallResult { raw: self, decoded_result })
1393    }
1394
1395    /// Returns the transactions generated from this call.
1396    pub fn transactions(&self) -> Option<&BroadcastableTransactions<FEN::Network>> {
1397        self.cheatcodes.as_ref().map(|c| &c.broadcastable_transactions)
1398    }
1399
1400    /// Update provided history map with edge coverage info collected during this call.
1401    pub fn merge_edge_coverage(
1402        &mut self,
1403        history_map: &mut Vec<u8>,
1404        edge_indices: &mut EdgeIndexMap,
1405    ) -> (bool, bool) {
1406        let mut new_coverage = false;
1407        let mut is_edge = false;
1408        if let Some(x) = &mut self.edge_coverage {
1409            match x {
1410                EdgeCoverage::Hash(x) => {
1411                    if history_map.len() < x.len() {
1412                        history_map.resize(x.len(), 0);
1413                    }
1414                    // Iterate over the current map and the history map together and update
1415                    // the history map, if we discover some new coverage, report true
1416                    for (curr, hist) in std::iter::zip(x.iter_mut(), history_map.iter_mut()) {
1417                        Self::merge_edge_count(*curr, hist, &mut new_coverage, &mut is_edge);
1418
1419                        // Hash reuses its map; collision-free drains hits.
1420                        *curr = 0;
1421                    }
1422                }
1423                EdgeCoverage::CollisionFree(hits) => {
1424                    for hit in hits.drain(..) {
1425                        let edge_index = edge_indices.edge_index(hit.edge);
1426                        if history_map.len() <= edge_index {
1427                            history_map.resize(edge_index + 1, 0);
1428                        }
1429                        Self::merge_edge_count(
1430                            hit.count,
1431                            &mut history_map[edge_index],
1432                            &mut new_coverage,
1433                            &mut is_edge,
1434                        );
1435                    }
1436                }
1437            }
1438        }
1439        (new_coverage, is_edge)
1440    }
1441
1442    const fn merge_edge_count(
1443        curr: u8,
1444        hist: &mut u8,
1445        new_coverage: &mut bool,
1446        is_edge: &mut bool,
1447    ) {
1448        let Some(bucket) = Self::bin_count(curr) else {
1449            return;
1450        };
1451
1452        // If the old record for this edge pair is lower, update
1453        if *hist < bucket {
1454            if *hist == 0 {
1455                // Counts as an edge the first time we see it, otherwise it's a feature.
1456                *is_edge = true;
1457            }
1458            *hist = bucket;
1459            *new_coverage = true;
1460        }
1461    }
1462
1463    /// Convert a hitcount into an AFL-style bucket.
1464    /// <https://github.com/h0mbre/Lucid/blob/3026e7323c52b30b3cf12563954ac1eaa9c6981e/src/coverage.rs#L57-L85>
1465    const fn bin_count(count: u8) -> Option<u8> {
1466        match count {
1467            0 => None,
1468            1 => Some(1),
1469            2 => Some(2),
1470            3 => Some(4),
1471            4..=7 => Some(8),
1472            8..=15 => Some(16),
1473            16..=31 => Some(32),
1474            32..=127 => Some(64),
1475            128..=255 => Some(128),
1476        }
1477    }
1478
1479    /// Update provided history map with sancov coverage info collected during this call.
1480    /// Uses AFL-style hitcount binning.
1481    pub fn merge_sancov_coverage(&mut self, history_map: &mut Vec<u8>) -> (bool, bool) {
1482        let mut new_coverage = false;
1483        let mut is_edge = false;
1484        if let Some(x) = &mut self.sancov_coverage {
1485            if history_map.len() < x.len() {
1486                history_map.resize(x.len(), 0);
1487            }
1488            for (curr, hist) in std::iter::zip(x.iter_mut(), history_map.iter_mut()) {
1489                if *curr > 0 {
1490                    if let Some(bucket) = Self::bin_count(*curr)
1491                        && *hist < bucket
1492                    {
1493                        if *hist == 0 {
1494                            is_edge = true;
1495                        }
1496                        *hist = bucket;
1497                        new_coverage = true;
1498                    }
1499                    *curr = 0;
1500                }
1501            }
1502        }
1503        (new_coverage, is_edge)
1504    }
1505
1506    /// Merge both EVM and sancov coverage into their respective history maps.
1507    /// Returns `(new_coverage, is_edge)` — true if either domain produced new coverage.
1508    pub fn merge_all_coverage(
1509        &mut self,
1510        evm_history: &mut Vec<u8>,
1511        evm_edge_indices: &mut EdgeIndexMap,
1512        sancov_history: &mut Vec<u8>,
1513    ) -> (bool, bool) {
1514        let (new_evm, edge_evm) = self.merge_edge_coverage(evm_history, evm_edge_indices);
1515        let (new_san, edge_san) = self.merge_sancov_coverage(sancov_history);
1516        (new_evm || new_san, edge_evm || edge_san)
1517    }
1518}
1519
1520/// The result of a call.
1521pub struct CallResult<T = DynSolValue, FEN: FoundryEvmNetwork = EthEvmNetwork> {
1522    /// The raw result of the call.
1523    pub raw: RawCallResult<FEN>,
1524    /// The decoded result of the call.
1525    pub decoded_result: T,
1526}
1527
1528impl<T, FEN: FoundryEvmNetwork> std::ops::Deref for CallResult<T, FEN> {
1529    type Target = RawCallResult<FEN>;
1530
1531    #[inline]
1532    fn deref(&self) -> &Self::Target {
1533        &self.raw
1534    }
1535}
1536
1537impl<T, FEN: FoundryEvmNetwork> std::ops::DerefMut for CallResult<T, FEN> {
1538    #[inline]
1539    fn deref_mut(&mut self) -> &mut Self::Target {
1540        &mut self.raw
1541    }
1542}
1543
1544fn calculate_stipend(tx_env: &impl Transaction, spec: SpecId, eip2780_enabled: bool) -> u64 {
1545    let eip2780 = eip2780_enabled.then(|| Eip2780TxInfo {
1546        value: tx_env.value(),
1547        is_self_transfer: matches!(tx_env.kind(), TxKind::Call(to) if to == tx_env.caller()),
1548    });
1549    revm::interpreter::gas::calculate_initial_tx_gas_for_tx(tx_env, spec, eip2780)
1550        .initial_total_gas()
1551}
1552
1553/// Converts the data aggregated in the `inspector` and `call` to a `RawCallResult`.
1554fn convert_executed_result<FEN: FoundryEvmNetwork, H: IntoInstructionResult>(
1555    evm_env: EvmEnvFor<FEN>,
1556    tx_env: TxEnvFor<FEN>,
1557    mut inspector: InspectorStack<FEN>,
1558    ResultAndState { result, state: state_changeset }: ResultAndState<H>,
1559    db: &dyn DatabaseRef<Error = DatabaseError>,
1560    has_state_snapshot_failure: bool,
1561    fork_block_number: Option<u64>,
1562) -> eyre::Result<RawCallResult<FEN>> {
1563    let execution_cancelled = inspector.execution_cancelled();
1564    let (exit_reason, gas_refunded, gas_used, out, exec_logs) = match result {
1565        ExecutionResult::Success { reason, gas, output, logs } => {
1566            (reason.into(), gas.final_refunded(), gas.tx_gas_used(), Some(output), logs)
1567        }
1568        ExecutionResult::Revert { gas, output, logs } => {
1569            (InstructionResult::Revert, 0_u64, gas.tx_gas_used(), Some(Output::Call(output)), logs)
1570        }
1571        ExecutionResult::Halt { reason, gas, logs } => {
1572            (reason.into_instruction_result(), 0_u64, gas.tx_gas_used(), None, logs)
1573        }
1574    };
1575    let stipend = calculate_stipend(
1576        &tx_env,
1577        evm_env.cfg_env.spec.into(),
1578        evm_env.cfg_env.is_amsterdam_eip2780_enabled(),
1579    );
1580
1581    let result = match &out {
1582        Some(Output::Call(data)) => data.clone(),
1583        _ => Bytes::new(),
1584    };
1585    let observed_calls = inspector
1586        .inner
1587        .fuzzer
1588        .as_mut()
1589        .map(|fuzzer| fuzzer.take_observed_calls())
1590        .unwrap_or_default();
1591
1592    let InspectorData {
1593        mut logs,
1594        labels,
1595        traces,
1596        line_coverage,
1597        edge_coverage,
1598        evm_cmp_values,
1599        mut cheatcodes,
1600        chisel_state,
1601        reverter,
1602    } = inspector.collect();
1603    let fork_block_number = cheatcodes
1604        .as_ref()
1605        .and_then(|cheats| cheats.fork_block_number_override)
1606        .or(fork_block_number);
1607    let debug_bytecodes = collect_debug_bytecodes(traces.as_ref(), db);
1608
1609    if logs.is_empty() {
1610        logs = exec_logs;
1611    }
1612
1613    let transactions = cheatcodes
1614        .as_ref()
1615        .map(|c| c.broadcastable_transactions.clone())
1616        .filter(|txs| !txs.is_empty());
1617    let skip_payloads =
1618        cheatcodes.as_mut().map(|c| std::mem::take(&mut c.skip_payloads)).unwrap_or_default();
1619
1620    Ok(RawCallResult {
1621        exit_reason: Some(exit_reason),
1622        execution_cancelled,
1623        reverted: !matches!(exit_reason, return_ok!()),
1624        has_state_snapshot_failure,
1625        result,
1626        gas_used,
1627        gas_refunded,
1628        stipend,
1629        logs,
1630        labels,
1631        traces,
1632        debug_bytecodes,
1633        line_coverage,
1634        edge_coverage,
1635        evm_cmp_values,
1636        observed_calls,
1637        sancov_coverage: None,
1638        sancov_cmp_values: None,
1639        transactions,
1640        state_changeset,
1641        evm_env,
1642        tx_env,
1643        cheatcodes,
1644        out,
1645        fork_block_number,
1646        chisel_state,
1647        reverter,
1648        skip_payloads,
1649    })
1650}
1651
1652fn collect_debug_bytecodes(
1653    traces: Option<&SparsedTraceArena>,
1654    db: &dyn DatabaseRef<Error = DatabaseError>,
1655) -> AddressHashMap<Bytes> {
1656    let mut bytecodes = HashMap::default();
1657    let Some(traces) = traces else { return bytecodes };
1658
1659    for node in traces.arena.nodes() {
1660        let address = node.trace.address;
1661        if bytecodes.contains_key(&address) {
1662            continue;
1663        }
1664
1665        let Ok(Some(account)) = db.basic_ref(address) else { continue };
1666        let code: Option<Bytecode> =
1667            account.code.or_else(|| db.code_by_hash_ref(account.code_hash).ok());
1668        let code: Bytes = code.map(|code| code.original_bytes()).unwrap_or_default();
1669
1670        if !code.is_empty() {
1671            bytecodes.insert(address, code);
1672        }
1673    }
1674
1675    bytecodes
1676}
1677
1678/// Timer for a fuzz test.
1679pub struct FuzzTestTimer {
1680    /// Inner fuzz test timer - (test start time, test duration).
1681    inner: Option<(Instant, Duration)>,
1682}
1683
1684impl FuzzTestTimer {
1685    pub fn new(timeout: Option<u32>) -> Self {
1686        Self { inner: timeout.map(|timeout| (Instant::now(), Duration::from_secs(timeout.into()))) }
1687    }
1688
1689    /// Whether the fuzz test timer is enabled.
1690    pub const fn is_enabled(&self) -> bool {
1691        self.inner.is_some()
1692    }
1693
1694    /// Whether the current fuzz test timed out and should be stopped.
1695    pub fn is_timed_out(&self) -> bool {
1696        self.inner.is_some_and(|(start, duration)| start.elapsed() > duration)
1697    }
1698}
1699
1700/// Helper struct to enable early exit behavior: when one test fails or run is interrupted,
1701/// all other tests stop early.
1702#[derive(Clone, Debug)]
1703pub struct EarlyExit {
1704    /// Shared atomic flag set to `true` when a failure occurs or ctrl-c received.
1705    inner: Arc<AtomicBool>,
1706    /// Whether to exit early on test failure (fail-fast mode).
1707    fail_fast: bool,
1708}
1709
1710impl EarlyExit {
1711    pub fn new(fail_fast: bool) -> Self {
1712        Self { inner: Arc::new(AtomicBool::new(false)), fail_fast }
1713    }
1714
1715    /// Records a test failure. Only triggers early exit if fail-fast mode is enabled.
1716    pub fn record_failure(&self) {
1717        if self.fail_fast {
1718            self.inner.store(true, Ordering::Relaxed);
1719        }
1720    }
1721
1722    /// Records a Ctrl-C interrupt. Always triggers early exit.
1723    pub fn record_ctrl_c(&self) {
1724        self.inner.store(true, Ordering::Relaxed);
1725    }
1726
1727    /// Whether tests should stop and exit early.
1728    pub fn should_stop(&self) -> bool {
1729        self.inner.load(Ordering::Relaxed)
1730    }
1731}
1732
1733/// Shared cancellation state for an active EVM execution.
1734#[derive(Clone, Debug)]
1735pub(crate) enum EvmExecutionCancellation {
1736    /// Cancellation driven only by the process-wide early-exit signal.
1737    EarlyExit(EarlyExit),
1738    /// Cancellation driven by the complete invariant campaign stop condition.
1739    Campaign { early_exit: EarlyExit, stop: Arc<AtomicBool>, deadline: Option<Instant> },
1740}
1741
1742impl EvmExecutionCancellation {
1743    pub(crate) const fn early_exit(early_exit: EarlyExit) -> Self {
1744        Self::EarlyExit(early_exit)
1745    }
1746
1747    pub(crate) const fn campaign(
1748        early_exit: EarlyExit,
1749        stop: Arc<AtomicBool>,
1750        deadline: Option<Instant>,
1751    ) -> Self {
1752        Self::Campaign { early_exit, stop, deadline }
1753    }
1754
1755    /// Returns whether execution should stop, optionally polling a campaign deadline.
1756    pub(crate) fn should_stop(&self, poll_deadline: bool) -> bool {
1757        match self {
1758            Self::EarlyExit(early_exit) => early_exit.should_stop(),
1759            Self::Campaign { early_exit, stop, deadline } => {
1760                if early_exit.should_stop() || stop.load(Ordering::Relaxed) {
1761                    return true;
1762                }
1763                if poll_deadline && deadline.is_some_and(|deadline| Instant::now() > deadline) {
1764                    stop.store(true, Ordering::Relaxed);
1765                    return true;
1766                }
1767                false
1768            }
1769        }
1770    }
1771
1772    pub(crate) fn request_stop(&self) {
1773        if let Self::Campaign { stop, .. } = self {
1774            stop.store(true, Ordering::Relaxed);
1775        }
1776    }
1777
1778    pub(crate) const fn early_exit_ref(&self) -> &EarlyExit {
1779        match self {
1780            Self::EarlyExit(early_exit) | Self::Campaign { early_exit, .. } => early_exit,
1781        }
1782    }
1783}
1784
1785/// Returns whether a nested revert can be ignored when fail-on-revert is disabled.
1786#[inline]
1787pub fn should_ignore_revert(
1788    fail_on_revert: bool,
1789    target: Address,
1790    reverter: Option<Address>,
1791    extra_cheatcode_addresses: &[Address],
1792) -> bool {
1793    !fail_on_revert
1794        && reverter.is_some_and(|reverter| {
1795            reverter != target
1796                && reverter != CHEATCODE_ADDRESS
1797                && !extra_cheatcode_addresses.contains(&reverter)
1798        })
1799}
1800
1801#[cfg(test)]
1802mod tests {
1803    use super::*;
1804    use crate::inspectors::{EdgeCovHit, EdgeKey};
1805    use alloy_primitives::B256;
1806    use foundry_cheatcodes::{
1807        CheatsConfig,
1808        Vm::{blobhashesCall, mockCallRevert_1Call, revertToStateCall, snapshotStateCall},
1809    };
1810    use foundry_config::Config;
1811    use foundry_evm_core::{constants::MAGIC_SKIP, opts::EvmOpts};
1812    use foundry_evm_traces::InternalTraceMode;
1813    use revm::context::TxEnv;
1814    use std::{sync::mpsc, thread};
1815
1816    fn dense_call(edge: EdgeKey) -> RawCallResult {
1817        RawCallResult {
1818            edge_coverage: Some(EdgeCoverage::CollisionFree(vec![EdgeCovHit { edge, count: 1 }])),
1819            ..Default::default()
1820        }
1821    }
1822
1823    #[test]
1824    fn nested_revert_is_ignored_only_when_allowed() {
1825        let target = Address::from([0x11; 20]);
1826        let nested = Address::from([0x22; 20]);
1827
1828        assert!(should_ignore_revert(false, target, Some(nested), &[]));
1829        assert!(!should_ignore_revert(true, target, Some(nested), &[]));
1830        assert!(!should_ignore_revert(false, target, Some(target), &[]));
1831        assert!(!should_ignore_revert(false, target, Some(CHEATCODE_ADDRESS), &[]));
1832        assert!(!should_ignore_revert(false, target, None, &[]));
1833    }
1834
1835    #[cfg(feature = "monad")]
1836    #[test]
1837    fn network_cheatcode_revert_handling_is_monad_specific() {
1838        let target = Address::from([0x11; 20]);
1839
1840        assert!(should_ignore_revert(
1841            false,
1842            target,
1843            Some(foundry_evm_core::constants::MONAD_CHEATCODE_ADDRESS),
1844            &[]
1845        ));
1846        assert!(!should_ignore_revert(
1847            false,
1848            target,
1849            Some(foundry_evm_core::constants::MONAD_CHEATCODE_ADDRESS),
1850            NetworkConfigs::with_monad().extra_cheatcode_addresses(),
1851        ));
1852    }
1853
1854    #[cfg(feature = "monad")]
1855    #[test]
1856    fn executor_networks_follow_explicit_configuration() {
1857        let ethereum = ExecutorBuilder::<EthEvmNetwork>::default().build(
1858            EvmEnvFor::<EthEvmNetwork>::default(),
1859            TxEnvFor::<EthEvmNetwork>::default(),
1860            Backend::spawn(None).unwrap(),
1861            NetworkConfigs::default(),
1862        );
1863        assert!(!ethereum.backend().networks().is_monad());
1864        assert!(
1865            !ethereum
1866                .backend()
1867                .is_persistent(&foundry_evm_core::constants::MONAD_CHEATCODE_ADDRESS)
1868        );
1869
1870        let monad = ExecutorBuilder::<foundry_evm_core::evm::MonadEvmNetwork>::default()
1871            .inspectors(|stack| stack.networks(NetworkConfigs::default()))
1872            .build(
1873                EvmEnvFor::<foundry_evm_core::evm::MonadEvmNetwork>::default(),
1874                TxEnvFor::<foundry_evm_core::evm::MonadEvmNetwork>::default(),
1875                Backend::spawn(None).unwrap(),
1876                NetworkConfigs::with_monad(),
1877            );
1878        assert!(monad.inspector().networks.is_monad());
1879        assert!(monad.backend().networks().is_monad());
1880        assert!(
1881            monad.backend().is_persistent(&foundry_evm_core::constants::MONAD_CHEATCODE_ADDRESS)
1882        );
1883    }
1884
1885    #[test]
1886    fn collision_free_edge_merge_uses_stable_indices() {
1887        let first =
1888            EdgeKey { address: Address::ZERO, depth: None, pc: 0, jump_dest: U256::from(10) };
1889        let second =
1890            EdgeKey { address: Address::ZERO, depth: None, pc: 0, jump_dest: U256::from(20) };
1891        let mut history = Vec::new();
1892        let mut edge_indices = EdgeIndexMap::default();
1893
1894        assert_eq!(
1895            dense_call(first).merge_edge_coverage(&mut history, &mut edge_indices),
1896            (true, true)
1897        );
1898        assert_eq!(history, [1]);
1899
1900        assert_eq!(
1901            dense_call(second).merge_edge_coverage(&mut history, &mut edge_indices),
1902            (true, true)
1903        );
1904        assert_eq!(history, [1, 1]);
1905
1906        assert_eq!(
1907            dense_call(first).merge_edge_coverage(&mut history, &mut edge_indices),
1908            (false, false)
1909        );
1910        assert_eq!(history, [1, 1]);
1911    }
1912
1913    #[test]
1914    fn collision_free_edge_merge_handles_sparse_observation_indices() {
1915        let first =
1916            EdgeKey { address: Address::ZERO, depth: None, pc: 0, jump_dest: U256::from(10) };
1917        let second =
1918            EdgeKey { address: Address::ZERO, depth: None, pc: 0, jump_dest: U256::from(20) };
1919        let mut edge_indices = EdgeIndexMap::default();
1920        edge_indices.edge_index(first);
1921        edge_indices.edge_index(second);
1922        let mut history = Vec::new();
1923
1924        assert_eq!(
1925            dense_call(second).merge_edge_coverage(&mut history, &mut edge_indices),
1926            (true, true)
1927        );
1928        assert_eq!(history, [0, 1]);
1929    }
1930
1931    #[test]
1932    fn cheatcode_skip_payload_is_classified_as_skip() {
1933        let raw = RawCallResult::<EthEvmNetwork> {
1934            reverted: true,
1935            result: Bytes::from_static(b"FOUNDRY::SKIPwith reason"),
1936            skip_payloads: vec![Bytes::from_static(b"FOUNDRY::SKIPwith reason")],
1937            ..Default::default()
1938        };
1939
1940        let err = raw.into_evm_error(None);
1941        assert!(matches!(err, EvmError::Skip(_)));
1942    }
1943
1944    #[test]
1945    fn forged_skip_payload_is_execution_error() {
1946        let raw = RawCallResult::<EthEvmNetwork> {
1947            reverted: true,
1948            result: Bytes::from_static(MAGIC_SKIP),
1949            reverter: Some(CHEATCODE_ADDRESS),
1950            ..Default::default()
1951        };
1952
1953        let err = raw.into_evm_error(None);
1954        assert!(matches!(err, EvmError::Execution(_)));
1955    }
1956
1957    #[test]
1958    fn mismatched_skip_payload_is_execution_error() {
1959        let raw = RawCallResult::<EthEvmNetwork> {
1960            reverted: true,
1961            result: Bytes::from_static(b"FOUNDRY::SKIPforged"),
1962            skip_payloads: vec![Bytes::from_static(b"FOUNDRY::SKIPgenuine")],
1963            ..Default::default()
1964        };
1965
1966        let err = raw.into_evm_error(None);
1967        assert!(matches!(err, EvmError::Execution(_)));
1968    }
1969
1970    #[test]
1971    fn set_spec_id_updates_spec_dependent_cfg_state() {
1972        let backend = Backend::<EthEvmNetwork>::spawn(None).unwrap();
1973        let mut executor = ExecutorBuilder::default().build(
1974            EvmEnvFor::<EthEvmNetwork>::default(),
1975            TxEnvFor::<EthEvmNetwork>::default(),
1976            backend,
1977            NetworkConfigs::default(),
1978        );
1979
1980        executor.evm_env_mut().cfg_env.set_spec_and_mainnet_gas_params(SpecId::HOMESTEAD);
1981        assert_eq!(
1982            executor.evm_env().cfg_env.gas_params(),
1983            &revm::context_interface::cfg::GasParams::new_spec(SpecId::HOMESTEAD),
1984        );
1985        assert!(!executor.evm_env().cfg_env.is_amsterdam_eip8037_enabled());
1986
1987        executor.set_spec_id(SpecId::AMSTERDAM);
1988
1989        assert_eq!(executor.spec_id(), SpecId::AMSTERDAM);
1990        assert_eq!(
1991            executor.evm_env().cfg_env.gas_params(),
1992            &revm::context_interface::cfg::GasParams::new_spec(SpecId::AMSTERDAM),
1993        );
1994        assert!(executor.evm_env().cfg_env.is_amsterdam_eip8037_enabled());
1995    }
1996
1997    #[test]
1998    fn calculate_stipend_uses_eip2780_transaction_context() {
1999        let caller = Address::repeat_byte(0x11);
2000        let recipient = Address::repeat_byte(0x22);
2001        let mut tx = TxEnv { caller, kind: TxKind::Call(recipient), ..Default::default() };
2002
2003        assert_eq!(
2004            calculate_stipend(&tx, SpecId::AMSTERDAM, true),
2005            revm::primitives::eip2780::TX_BASE_COST
2006                + revm::primitives::eip8038::COLD_ACCOUNT_ACCESS
2007        );
2008        assert_eq!(
2009            calculate_stipend(&tx, SpecId::AMSTERDAM, false),
2010            revm::context_interface::cfg::GasParams::new_spec(SpecId::AMSTERDAM).tx_base_stipend()
2011        );
2012
2013        tx.kind = TxKind::Call(caller);
2014        assert_eq!(
2015            calculate_stipend(&tx, SpecId::AMSTERDAM, true),
2016            revm::primitives::eip2780::TX_BASE_COST
2017        );
2018    }
2019
2020    #[test]
2021    fn amsterdam_intercepted_create_refunds_state_gas() {
2022        let cheats_config =
2023            Arc::new(CheatsConfig::new(&Config::default(), EvmOpts::default(), None, None, false));
2024        let backend = Backend::<EthEvmNetwork>::spawn(None).unwrap();
2025        let mut executor = ExecutorBuilder::default()
2026            .inspectors(|stack| stack.cheatcodes(cheats_config))
2027            .spec_id(SpecId::AMSTERDAM)
2028            .gas_limit(1_000_000)
2029            .build(EvmEnv::default(), TxEnv::default(), backend, NetworkConfigs::default());
2030
2031        let target = Address::repeat_byte(0x11);
2032        // PUSH0; PUSH0; PUSH0; CREATE; POP; STOP.
2033        executor
2034            .set_code(
2035                target,
2036                Bytecode::new_raw(Bytes::from_static(&[0x5f, 0x5f, 0x5f, 0xf0, 0x50, 0x00])),
2037            )
2038            .unwrap();
2039        executor.inspector_mut().cheatcodes.as_mut().unwrap().intercept_next_create_call = true;
2040
2041        let result = executor.transact_raw(CALLER, target, Bytes::new(), U256::ZERO).unwrap();
2042
2043        assert!(!result.reverted);
2044        assert!(
2045            result.gas_used
2046                < revm::context_interface::cfg::GasParams::new_spec(SpecId::AMSTERDAM)
2047                    .create_state_gas(),
2048            "failed CREATE retained its conditional state-gas charge"
2049        );
2050    }
2051
2052    #[test]
2053    fn amsterdam_mocked_call_revert_refunds_state_gas() {
2054        let cheats_config =
2055            Arc::new(CheatsConfig::new(&Config::default(), EvmOpts::default(), None, None, false));
2056        let backend = Backend::<EthEvmNetwork>::spawn(None).unwrap();
2057        let mut executor = ExecutorBuilder::default()
2058            .inspectors(|stack| stack.cheatcodes(cheats_config))
2059            .spec_id(SpecId::AMSTERDAM)
2060            .gas_limit(1_000_000)
2061            .build(EvmEnv::default(), TxEnv::default(), backend, NetworkConfigs::default());
2062
2063        let target = Address::repeat_byte(0x11);
2064        let mocked = Address::repeat_byte(0x22);
2065        executor
2066            .transact_raw(
2067                CALLER,
2068                CHEATCODE_ADDRESS,
2069                mockCallRevert_1Call {
2070                    callee: mocked,
2071                    msgValue: U256::from(1),
2072                    data: Bytes::new(),
2073                    revertData: Bytes::new(),
2074                }
2075                .abi_encode()
2076                .into(),
2077                U256::ZERO,
2078            )
2079            .unwrap();
2080        executor.set_code(mocked, Bytecode::default()).unwrap();
2081        executor.set_balance(target, U256::from(1)).unwrap();
2082
2083        // PUSH0 x4; PUSH1 1; PUSH20 <mocked>; GAS; CALL; POP; STOP.
2084        let mut code = vec![0x5f, 0x5f, 0x5f, 0x5f, 0x60, 0x01, 0x73];
2085        code.extend_from_slice(mocked.as_slice());
2086        code.extend_from_slice(&[0x5a, 0xf1, 0x50, 0x00]);
2087        executor.set_code(target, Bytecode::new_raw(code.into())).unwrap();
2088
2089        let result = executor.transact_raw(CALLER, target, Bytes::new(), U256::ZERO).unwrap();
2090
2091        assert!(!result.reverted);
2092        assert!(
2093            result.gas_used
2094                < revm::context_interface::cfg::GasParams::new_spec(SpecId::AMSTERDAM)
2095                    .new_account_state_gas(),
2096            "reverted mocked CALL retained its conditional state-gas charge"
2097        );
2098    }
2099
2100    #[test]
2101    fn set_trace_requirements_replaces_trace_mode_between_transactions() {
2102        let backend = Backend::<EthEvmNetwork>::spawn(None).unwrap();
2103        let mut executor = ExecutorBuilder::default().gas_limit(1 << 20).build(
2104            EvmEnvFor::<EthEvmNetwork>::default(),
2105            TxEnvFor::<EthEvmNetwork>::default(),
2106            backend,
2107            NetworkConfigs::default(),
2108        );
2109        executor.evm_env_mut().cfg_env.disable_nonce_check = true;
2110        let target = Address::repeat_byte(0x11);
2111        // PUSH1 4; JUMP; STOP; JUMPDEST; PUSH1 1; PUSH1 0; SSTORE; STOP.
2112        executor
2113            .set_code(
2114                target,
2115                Bytecode::new_raw(Bytes::from_static(&[
2116                    0x60, 0x04, 0x56, 0x00, 0x5b, 0x60, 0x01, 0x60, 0x00, 0x55, 0x00,
2117                ])),
2118            )
2119            .unwrap();
2120
2121        let untraced = executor.transact_raw(CALLER, target, Bytes::new(), U256::ZERO).unwrap();
2122        assert!(untraced.traces.is_none());
2123
2124        executor.set_trace_requirements(TraceRequirements::none().with_debug(true));
2125        let debug = executor.transact_raw(CALLER, target, Bytes::new(), U256::ZERO).unwrap();
2126        let debug_steps = &debug.traces.as_ref().unwrap().nodes()[0].trace.steps;
2127        assert_eq!(debug_steps.len(), 7);
2128        assert!(debug_steps.iter().all(|step| step.stack.is_some() && step.memory.is_some()));
2129
2130        executor.set_trace_requirements(
2131            TraceRequirements::none().with_decode_internal(InternalTraceMode::Full),
2132        );
2133        let internal = executor.transact_raw(CALLER, target, Bytes::new(), U256::ZERO).unwrap();
2134        let internal_steps = &internal.traces.as_ref().unwrap().nodes()[0].trace.steps;
2135        assert_eq!(internal_steps.len(), 2);
2136        assert!(internal_steps.iter().all(|step| step.stack.is_some() && step.memory.is_some()));
2137
2138        executor.set_trace_requirements(TraceRequirements::none());
2139        let untraced = executor.transact_raw(CALLER, target, Bytes::new(), U256::ZERO).unwrap();
2140        assert!(untraced.traces.is_none());
2141    }
2142
2143    #[test]
2144    fn early_exit_interrupts_active_evm_execution() {
2145        const GAS_LIMIT: u64 = 1 << 24;
2146        let backend = Backend::<EthEvmNetwork>::spawn(None).unwrap();
2147        let mut executor = ExecutorBuilder::default().gas_limit(GAS_LIMIT).build(
2148            EvmEnvFor::<EthEvmNetwork>::default(),
2149            TxEnvFor::<EthEvmNetwork>::default(),
2150            backend,
2151            NetworkConfigs::default(),
2152        );
2153        let early_exit = EarlyExit::new(false);
2154        executor.inspector_mut().set_early_exit(early_exit.clone());
2155
2156        let target = Address::repeat_byte(0x11);
2157        // JUMPDEST; PUSH1 0; JUMP loops until the inspector observes the interrupt.
2158        executor
2159            .set_code(target, Bytecode::new_raw(Bytes::from_static(&[0x5b, 0x60, 0x00, 0x56])))
2160            .unwrap();
2161
2162        let (started_tx, started_rx) = mpsc::channel();
2163        let (result_tx, result_rx) = mpsc::channel();
2164        let handle = thread::spawn(move || {
2165            started_tx.send(()).unwrap();
2166            let result = executor.transact_raw(CALLER, target, Bytes::new(), U256::ZERO);
2167            let _ = result_tx.send(result);
2168        });
2169
2170        started_rx.recv().unwrap();
2171        thread::sleep(Duration::from_millis(1));
2172        early_exit.record_ctrl_c();
2173
2174        let result = result_rx.recv_timeout(Duration::from_secs(1));
2175        handle.join().unwrap();
2176        let result = result.expect("active EVM execution did not observe early exit").unwrap();
2177        assert!(result.execution_cancelled);
2178        assert!(!result.reverted);
2179        assert_eq!(result.exit_reason, Some(InstructionResult::Stop));
2180        assert!(result.gas_used > 21_000, "interrupt fired before EVM execution started");
2181        assert!(result.gas_used < GAS_LIMIT, "execution ran out of gas instead of exiting");
2182    }
2183
2184    #[test]
2185    fn completed_execution_is_not_retroactively_cancelled() {
2186        let backend = Backend::<EthEvmNetwork>::spawn(None).unwrap();
2187        let mut executor = ExecutorBuilder::default().gas_limit(1 << 24).build(
2188            EvmEnvFor::<EthEvmNetwork>::default(),
2189            TxEnvFor::<EthEvmNetwork>::default(),
2190            backend,
2191            NetworkConfigs::default(),
2192        );
2193        let early_exit = EarlyExit::new(false);
2194        executor.inspector_mut().set_early_exit(early_exit.clone());
2195
2196        let target = Address::repeat_byte(0x11);
2197        executor.set_code(target, Bytecode::new_raw(Bytes::from_static(&[0x00]))).unwrap();
2198        let result = executor.transact_raw(CALLER, target, Bytes::new(), U256::ZERO).unwrap();
2199        early_exit.record_ctrl_c();
2200
2201        assert!(!result.execution_cancelled);
2202        assert!(!result.reverted);
2203    }
2204
2205    #[test]
2206    fn campaign_deadline_interrupts_active_evm_execution() {
2207        const GAS_LIMIT: u64 = 1 << 24;
2208        let backend = Backend::<EthEvmNetwork>::spawn(None).unwrap();
2209        let mut executor = ExecutorBuilder::default().gas_limit(GAS_LIMIT).build(
2210            EvmEnvFor::<EthEvmNetwork>::default(),
2211            TxEnvFor::<EthEvmNetwork>::default(),
2212            backend,
2213            NetworkConfigs::default(),
2214        );
2215        let cancellation = EvmExecutionCancellation::campaign(
2216            EarlyExit::new(false),
2217            Arc::new(AtomicBool::new(false)),
2218            Some(Instant::now()),
2219        );
2220        executor.inspector_mut().set_execution_cancellation(cancellation);
2221
2222        let target = Address::repeat_byte(0x11);
2223        executor
2224            .set_code(target, Bytecode::new_raw(Bytes::from_static(&[0x5b, 0x60, 0x00, 0x56])))
2225            .unwrap();
2226
2227        let result = executor.transact_raw(CALLER, target, Bytes::new(), U256::ZERO).unwrap();
2228        assert!(result.execution_cancelled);
2229        assert!(!result.reverted);
2230        assert_eq!(result.exit_reason, Some(InstructionResult::Stop));
2231        assert!(result.gas_used < GAS_LIMIT, "execution ran out of gas instead of timing out");
2232    }
2233
2234    #[test]
2235    fn beacon_root_system_call_does_not_persist_system_address() {
2236        let backend = Backend::<EthEvmNetwork>::spawn(None).unwrap();
2237        let mut executor = ExecutorBuilder::default().spec_id(SpecId::CANCUN).build(
2238            EvmEnvFor::<EthEvmNetwork>::default(),
2239            TxEnvFor::<EthEvmNetwork>::default(),
2240            backend,
2241            NetworkConfigs::default(),
2242        );
2243        let before = executor.backend().basic_ref(SYSTEM_ADDRESS).unwrap();
2244
2245        executor.apply_beacon_root(B256::repeat_byte(0x11)).unwrap();
2246
2247        assert_eq!(
2248            executor.backend().basic_ref(SYSTEM_ADDRESS).unwrap(),
2249            before,
2250            "EIP-4788 system calls must not persist the system caller account",
2251        );
2252    }
2253
2254    /// Regression test for `pre_override_blob_hashes` restoration.
2255    ///
2256    /// Exercises the `None` arm of `sync_tx_after_env_override_restore` with
2257    /// *non-empty* native blob hashes, the case that cannot be reached from
2258    /// Solidity because no cheatcode sets `tx.blob_hashes` without also setting
2259    /// `env_overrides.blob_hashes`.
2260    ///
2261    /// Steps:
2262    /// 1. Seed `tx.blob_hashes = original` directly (no cheatcode -> override stays `None`).
2263    /// 2. `vm.snapshotState()` -> `inner_snapshot_state` captures `pre_override_blob_hashes =
2264    ///    Some(original)`.
2265    /// 3. `vm.blobhashes(new)` -> sets override (`Some`) AND real tx hashes.
2266    /// 4. `vm.revertToState(id)` -> restores override to `None`,
2267    ///    `sync_tx_after_env_override_restore` must restore `tx.blob_hashes = original`.
2268    #[test]
2269    fn pre_override_blob_hashes_restored_on_revert_to_state() {
2270        let cheats_config =
2271            Arc::new(CheatsConfig::new(&Config::default(), EvmOpts::default(), None, None, false));
2272
2273        let backend = Backend::<EthEvmNetwork>::spawn(None).unwrap();
2274        let mut executor = ExecutorBuilder::default()
2275            .inspectors(|stack| stack.cheatcodes(cheats_config))
2276            .spec_id(SpecId::CANCUN)
2277            .build(EvmEnv::default(), TxEnv::default(), backend, NetworkConfigs::default());
2278
2279        let original: Vec<B256> = vec![B256::repeat_byte(0x11), B256::repeat_byte(0x22)];
2280        executor.tx_env_mut().set_blob_hashes(original.clone());
2281
2282        let snap_result = executor
2283            .transact_raw(
2284                CALLER,
2285                CHEATCODE_ADDRESS,
2286                snapshotStateCall {}.abi_encode().into(),
2287                U256::ZERO,
2288            )
2289            .expect("snapshotState failed");
2290        assert!(!snap_result.reverted, "snapshotState reverted unexpectedly");
2291        let snapshot_id = U256::from_be_slice(&snap_result.result[..32]);
2292
2293        let new_hashes = vec![B256::repeat_byte(0x33)];
2294        let blob_result = executor
2295            .transact_raw(
2296                CALLER,
2297                CHEATCODE_ADDRESS,
2298                blobhashesCall { hashes: new_hashes }.abi_encode().into(),
2299                U256::ZERO,
2300            )
2301            .expect("blobhashes failed");
2302        assert!(!blob_result.reverted, "blobhashes reverted unexpectedly");
2303
2304        let revert_result = executor
2305            .transact_raw(
2306                CALLER,
2307                CHEATCODE_ADDRESS,
2308                revertToStateCall { snapshotId: snapshot_id }.abi_encode().into(),
2309                U256::ZERO,
2310            )
2311            .expect("revertToState failed");
2312        assert!(!revert_result.reverted, "revertToState reverted unexpectedly");
2313
2314        assert_eq!(
2315            revert_result.tx_env.blob_hashes, original,
2316            "pre_override_blob_hashes must be restored to original non-empty hashes, not []",
2317        );
2318        assert!(
2319            executor.inspector().cheatcodes.as_ref().unwrap().env_overrides.is_empty(),
2320            "inactive env overrides must be removed after restoring their metadata",
2321        );
2322    }
2323}