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