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