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, InspectorData, InspectorStack, cheatcodes::BroadcastableTransactions,
11};
12use alloy_dyn_abi::{DynSolValue, FunctionExt, JsonAbiExt};
13use alloy_json_abi::Function;
14use alloy_primitives::{
15    Address, Bytes, Log, TxKind, U256, keccak256,
16    map::{AddressHashMap, HashMap},
17};
18use alloy_sol_types::{SolCall, sol};
19use foundry_evm_core::{
20    EvmEnv, FoundryBlock, FoundryTransaction,
21    backend::{Backend, BackendError, BackendResult, CowBackend, DatabaseExt, GLOBAL_FAIL_SLOT},
22    constants::{
23        CALLER, CHEATCODE_ADDRESS, CHEATCODE_CONTRACT_HASH, DEFAULT_CREATE2_DEPLOYER,
24        DEFAULT_CREATE2_DEPLOYER_CODE, DEFAULT_CREATE2_DEPLOYER_DEPLOYER,
25    },
26    decode::{RevertDecoder, SkipReason},
27    evm::{
28        EthEvmNetwork, EvmEnvFor, FoundryEvmNetwork, HaltReasonFor, IntoInstructionResult, SpecFor,
29        TxEnvFor,
30    },
31    utils::StateChangeset,
32};
33use foundry_evm_coverage::HitMaps;
34use foundry_evm_traces::{SparsedTraceArena, TraceMode};
35use revm::{
36    bytecode::Bytecode,
37    context::Transaction,
38    context_interface::{
39        result::{ExecutionResult, Output, ResultAndState},
40        transaction::SignedAuthorization,
41    },
42    database::{DatabaseCommit, DatabaseRef},
43    interpreter::{InstructionResult, return_ok},
44};
45use sancov::SancovGuard;
46use std::{
47    borrow::Cow,
48    sync::{
49        Arc,
50        atomic::{AtomicBool, Ordering},
51    },
52    time::{Duration, Instant},
53};
54
55mod builder;
56pub use builder::ExecutorBuilder;
57
58pub mod fuzz;
59pub use fuzz::FuzzedExecutor;
60
61pub mod invariant;
62pub use invariant::InvariantExecutor;
63
64mod corpus;
65mod sancov;
66mod trace;
67
68pub use trace::TracingExecutor;
69
70const DURATION_BETWEEN_METRICS_REPORT: Duration = Duration::from_secs(5);
71
72sol! {
73    interface ITest {
74        function setUp() external;
75        function failed() external view returns (bool failed);
76
77        #[derive(Default)]
78        function beforeTestSetup(bytes4 testSelector) public view returns (bytes[] memory beforeTestCalldata);
79    }
80}
81
82/// EVM executor.
83///
84/// The executor can be configured with various `revm::Inspector`s, like `Cheatcodes`.
85///
86/// There are multiple ways of interacting the EVM:
87/// - `call`: executes a transaction, but does not persist any state changes; similar to `eth_call`,
88///   where the EVM state is unchanged after the call.
89/// - `transact`: executes a transaction and persists the state changes
90/// - `deploy`: a special case of `transact`, specialized for persisting the state of a contract
91///   deployment
92/// - `setup`: a special case of `transact`, used to set up the environment for a test
93#[derive(Clone, Debug)]
94pub struct Executor<FEN: FoundryEvmNetwork> {
95    /// The underlying `revm::Database` that contains the EVM storage.
96    ///
97    /// Wrapped in `Arc` for efficient cloning during parallel fuzzing. Use [`Arc::make_mut`]
98    /// for copy-on-write semantics when mutation is needed.
99    // Note: We do not store an EVM here, since we are really
100    // only interested in the database. REVM's `EVM` is a thin
101    // wrapper around spawning a new EVM on every call anyway,
102    // so the performance difference should be negligible.
103    backend: Arc<Backend<FEN>>,
104    /// The EVM environment (block and cfg).
105    evm_env: EvmEnvFor<FEN>,
106    /// The transaction environment.
107    tx_env: TxEnvFor<FEN>,
108    /// The Revm inspector stack.
109    inspector: InspectorStack<FEN>,
110    /// The gas limit for calls and deployments.
111    gas_limit: u64,
112    /// Whether `failed()` should be called on the test contract to determine if the test failed.
113    legacy_assertions: bool,
114}
115
116impl<FEN: FoundryEvmNetwork> Executor<FEN> {
117    /// Creates a new `Executor` with the given arguments.
118    #[inline]
119    pub fn new(
120        mut backend: Backend<FEN>,
121        evm_env: EvmEnvFor<FEN>,
122        tx_env: TxEnvFor<FEN>,
123        inspector: InspectorStack<FEN>,
124        gas_limit: u64,
125        legacy_assertions: bool,
126    ) -> Self {
127        // Need to create a non-empty contract on the cheatcodes address so `extcodesize` checks
128        // do not fail.
129        backend.insert_account_info(
130            CHEATCODE_ADDRESS,
131            revm::state::AccountInfo {
132                code: Some(Bytecode::new_raw(Bytes::from_static(&[0]))),
133                // Also set the code hash manually so that it's not computed later.
134                // The code hash value does not matter, as long as it's not zero or `KECCAK_EMPTY`.
135                code_hash: CHEATCODE_CONTRACT_HASH,
136                ..Default::default()
137            },
138        );
139
140        Self {
141            backend: Arc::new(backend),
142            evm_env,
143            tx_env,
144            inspector,
145            gas_limit,
146            legacy_assertions,
147        }
148    }
149
150    fn clone_with_backend(&self, backend: Backend<FEN>) -> Self {
151        let evm_env = self.evm_env.clone();
152        Self {
153            backend: Arc::new(backend),
154            evm_env,
155            tx_env: self.tx_env.clone(),
156            inspector: self.inspector().clone(),
157            gas_limit: self.gas_limit,
158            legacy_assertions: self.legacy_assertions,
159        }
160    }
161
162    /// Returns a reference to the EVM backend.
163    pub fn backend(&self) -> &Backend<FEN> {
164        &self.backend
165    }
166
167    /// Returns a mutable reference to the EVM backend.
168    ///
169    /// Uses copy-on-write semantics: if other clones of this executor share the backend,
170    /// this will clone the backend first.
171    pub fn backend_mut(&mut self) -> &mut Backend<FEN> {
172        Arc::make_mut(&mut self.backend)
173    }
174
175    /// Returns a reference to the EVM environment (block and cfg).
176    pub const fn evm_env(&self) -> &EvmEnvFor<FEN> {
177        &self.evm_env
178    }
179
180    /// Returns a mutable reference to the EVM environment (block and cfg).
181    pub const fn evm_env_mut(&mut self) -> &mut EvmEnvFor<FEN> {
182        &mut self.evm_env
183    }
184
185    /// Returns a reference to the transaction environment.
186    pub const fn tx_env(&self) -> &TxEnvFor<FEN> {
187        &self.tx_env
188    }
189
190    /// Returns a mutable reference to the transaction environment.
191    pub const fn tx_env_mut(&mut self) -> &mut TxEnvFor<FEN> {
192        &mut self.tx_env
193    }
194
195    /// Returns a reference to the EVM inspector.
196    pub const fn inspector(&self) -> &InspectorStack<FEN> {
197        &self.inspector
198    }
199
200    /// Returns a mutable reference to the EVM inspector.
201    pub const fn inspector_mut(&mut self) -> &mut InspectorStack<FEN> {
202        &mut self.inspector
203    }
204
205    /// Returns the EVM spec.
206    pub const fn spec_id(&self) -> SpecFor<FEN> {
207        self.evm_env.cfg_env.spec
208    }
209
210    /// Sets the EVM spec and updates spec-dependent gas parameters.
211    pub fn set_spec_id(&mut self, spec_id: SpecFor<FEN>) {
212        self.evm_env.cfg_env.set_spec_and_mainnet_gas_params(spec_id);
213    }
214
215    /// Returns the gas limit for calls and deployments.
216    ///
217    /// This is different from the gas limit imposed by the passed in environment, as those limits
218    /// are used by the EVM for certain opcodes like `gaslimit`.
219    pub const fn gas_limit(&self) -> u64 {
220        self.gas_limit
221    }
222
223    /// Sets the gas limit for calls and deployments.
224    pub const fn set_gas_limit(&mut self, gas_limit: u64) {
225        self.gas_limit = gas_limit;
226    }
227
228    /// Returns whether `failed()` should be called on the test contract to determine if the test
229    /// failed.
230    pub const fn legacy_assertions(&self) -> bool {
231        self.legacy_assertions
232    }
233
234    /// Sets whether `failed()` should be called on the test contract to determine if the test
235    /// failed.
236    pub const fn set_legacy_assertions(&mut self, legacy_assertions: bool) {
237        self.legacy_assertions = legacy_assertions;
238    }
239
240    /// Creates the default CREATE2 Contract Deployer for local tests and scripts.
241    pub fn deploy_create2_deployer(&mut self) -> eyre::Result<()> {
242        trace!("deploying local create2 deployer");
243        let create2_deployer_account = self
244            .backend()
245            .basic_ref(DEFAULT_CREATE2_DEPLOYER)?
246            .ok_or_else(|| BackendError::MissingAccount(DEFAULT_CREATE2_DEPLOYER))?;
247
248        // If the deployer is not currently deployed, deploy the default one.
249        if create2_deployer_account.code.is_none_or(|code| code.is_empty()) {
250            let creator = DEFAULT_CREATE2_DEPLOYER_DEPLOYER;
251
252            // Probably 0, but just in case.
253            let initial_balance = self.get_balance(creator)?;
254            self.set_balance(creator, U256::MAX)?;
255
256            let res =
257                self.deploy(creator, DEFAULT_CREATE2_DEPLOYER_CODE.into(), U256::ZERO, None)?;
258            trace!(create2=?res.address, "deployed local create2 deployer");
259
260            self.set_balance(creator, initial_balance)?;
261        }
262        Ok(())
263    }
264
265    /// Set the balance of an account.
266    pub fn set_balance(&mut self, address: Address, amount: U256) -> BackendResult<()> {
267        trace!(?address, ?amount, "setting account balance");
268        let mut account = self.backend().basic_ref(address)?.unwrap_or_default();
269        account.balance = amount;
270        self.backend_mut().insert_account_info(address, account);
271        Ok(())
272    }
273
274    /// Gets the balance of an account
275    pub fn get_balance(&self, address: Address) -> BackendResult<U256> {
276        Ok(self.backend().basic_ref(address)?.map(|acc| acc.balance).unwrap_or_default())
277    }
278
279    /// Set the nonce of an account.
280    pub fn set_nonce(&mut self, address: Address, nonce: u64) -> BackendResult<()> {
281        let mut account = self.backend().basic_ref(address)?.unwrap_or_default();
282        account.nonce = nonce;
283        self.backend_mut().insert_account_info(address, account);
284        self.tx_env_mut().set_nonce(nonce);
285        Ok(())
286    }
287
288    /// Returns the nonce of an account.
289    pub fn get_nonce(&self, address: Address) -> BackendResult<u64> {
290        Ok(self.backend().basic_ref(address)?.map(|acc| acc.nonce).unwrap_or_default())
291    }
292
293    /// Set the code of an account.
294    pub fn set_code(&mut self, address: Address, code: Bytecode) -> BackendResult<()> {
295        let mut account = self.backend().basic_ref(address)?.unwrap_or_default();
296        account.code_hash = keccak256(code.original_byte_slice());
297        account.code = Some(code);
298        self.backend_mut().insert_account_info(address, account);
299        Ok(())
300    }
301
302    /// Set the storage of an account.
303    pub fn set_storage(
304        &mut self,
305        address: Address,
306        storage: HashMap<U256, U256>,
307    ) -> BackendResult<()> {
308        self.backend_mut().replace_account_storage(address, storage)?;
309        Ok(())
310    }
311
312    /// Set a storage slot of an account.
313    pub fn set_storage_slot(
314        &mut self,
315        address: Address,
316        slot: U256,
317        value: U256,
318    ) -> BackendResult<()> {
319        self.backend_mut().insert_account_storage(address, slot, value)?;
320        Ok(())
321    }
322
323    /// Returns `true` if the account has no code.
324    pub fn is_empty_code(&self, address: Address) -> BackendResult<bool> {
325        Ok(self.backend().basic_ref(address)?.map(|acc| acc.is_empty_code_hash()).unwrap_or(true))
326    }
327
328    #[inline]
329    pub fn set_tracing(&mut self, mode: TraceMode) -> &mut Self {
330        self.inspector_mut().tracing(mode);
331        self
332    }
333
334    #[inline]
335    pub fn set_script_execution(&mut self, script_address: Address) {
336        self.inspector_mut().script(script_address);
337    }
338
339    #[inline]
340    pub fn set_trace_printer(&mut self, trace_printer: bool) -> &mut Self {
341        self.inspector_mut().print(trace_printer);
342        self
343    }
344
345    #[inline]
346    pub fn create2_deployer(&self) -> Address {
347        self.inspector().create2_deployer
348    }
349
350    /// Deploys a contract and commits the new state to the underlying database.
351    ///
352    /// Executes a CREATE transaction with the contract `code` and persistent database state
353    /// modifications.
354    pub fn deploy(
355        &mut self,
356        from: Address,
357        code: Bytes,
358        value: U256,
359        rd: Option<&RevertDecoder>,
360    ) -> Result<DeployResult<FEN>, EvmError<FEN>> {
361        let (evm_env, tx_env) = self.build_test_env(from, TxKind::Create, code, value);
362        self.deploy_with_env(evm_env, tx_env, rd)
363    }
364
365    /// Deploys a contract using the given `env` and commits the new state to the underlying
366    /// database.
367    ///
368    /// # Panics
369    ///
370    /// Panics if `tx_env.kind` is not `TxKind::Create(_)`.
371    #[instrument(name = "deploy", level = "debug", skip_all)]
372    pub fn deploy_with_env(
373        &mut self,
374        evm_env: EvmEnvFor<FEN>,
375        tx_env: TxEnvFor<FEN>,
376        rd: Option<&RevertDecoder>,
377    ) -> Result<DeployResult<FEN>, EvmError<FEN>> {
378        assert!(
379            matches!(tx_env.kind(), TxKind::Create),
380            "Expected create transaction, got {:?}",
381            tx_env.kind()
382        );
383        trace!(sender=%tx_env.caller(), "deploying contract");
384
385        let mut result = self.transact_with_env(evm_env, tx_env)?;
386        result = result.into_result(rd)?;
387        let Some(Output::Create(_, Some(address))) = result.out else {
388            panic!("Deployment succeeded, but no address was returned: {result:#?}");
389        };
390
391        // also mark this library as persistent, this will ensure that the state of the library is
392        // persistent across fork swaps in forking mode
393        self.backend_mut().add_persistent_account(address);
394
395        trace!(%address, "deployed contract");
396
397        Ok(DeployResult { raw: result, address })
398    }
399
400    /// Calls the `setUp()` function on a contract.
401    ///
402    /// This will commit any state changes to the underlying database.
403    ///
404    /// Ayn changes made during the setup call to env's block environment are persistent, for
405    /// example `vm.chainId()` will change the `block.chainId` for all subsequent test calls.
406    #[instrument(name = "setup", level = "debug", skip_all)]
407    pub fn setup(
408        &mut self,
409        from: Option<Address>,
410        to: Address,
411        rd: Option<&RevertDecoder>,
412    ) -> Result<RawCallResult<FEN>, EvmError<FEN>> {
413        trace!(?from, ?to, "setting up contract");
414
415        let from = from.unwrap_or(CALLER);
416        self.backend_mut().set_test_contract(to).set_caller(from);
417        let calldata = Bytes::from_static(&ITest::setUpCall::SELECTOR);
418        let mut res = self.transact_raw(from, to, calldata, U256::ZERO)?;
419        res = res.into_result(rd)?;
420
421        // record any changes made to the block's environment during setup
422        self.evm_env_mut().block_env = res.evm_env.block_env.clone();
423        // and also the chainid, which can be set manually
424        self.evm_env_mut().cfg_env.chain_id = res.evm_env.cfg_env.chain_id;
425
426        let success =
427            self.is_raw_call_success(to, Cow::Borrowed(&res.state_changeset), &res, false);
428        if !success {
429            return Err(res.into_execution_error("execution error".to_string()).into());
430        }
431
432        Ok(res)
433    }
434
435    /// Performs a call to an account on the current state of the VM.
436    pub fn call(
437        &self,
438        from: Address,
439        to: Address,
440        func: &Function,
441        args: &[DynSolValue],
442        value: U256,
443        rd: Option<&RevertDecoder>,
444    ) -> Result<CallResult<DynSolValue, FEN>, EvmError<FEN>> {
445        let calldata = Bytes::from(func.abi_encode_input(args)?);
446        let result = self.call_raw(from, to, calldata, value)?;
447        result.into_decoded_result(func, rd)
448    }
449
450    /// Performs a call to an account on the current state of the VM.
451    pub fn call_sol<C: SolCall>(
452        &self,
453        from: Address,
454        to: Address,
455        args: &C,
456        value: U256,
457        rd: Option<&RevertDecoder>,
458    ) -> Result<CallResult<C::Return, FEN>, EvmError<FEN>> {
459        let calldata = Bytes::from(args.abi_encode());
460        let mut raw = self.call_raw(from, to, calldata, value)?;
461        raw = raw.into_result(rd)?;
462        Ok(CallResult { decoded_result: C::abi_decode_returns(&raw.result)?, raw })
463    }
464
465    /// Performs a call to an account on the current state of the VM.
466    pub fn transact(
467        &mut self,
468        from: Address,
469        to: Address,
470        func: &Function,
471        args: &[DynSolValue],
472        value: U256,
473        rd: Option<&RevertDecoder>,
474    ) -> Result<CallResult<DynSolValue, FEN>, EvmError<FEN>> {
475        let calldata = Bytes::from(func.abi_encode_input(args)?);
476        let result = self.transact_raw(from, to, calldata, value)?;
477        result.into_decoded_result(func, rd)
478    }
479
480    /// Performs a raw call to an account on the current state of the VM.
481    pub fn call_raw(
482        &self,
483        from: Address,
484        to: Address,
485        calldata: Bytes,
486        value: U256,
487    ) -> eyre::Result<RawCallResult<FEN>> {
488        let (evm_env, tx_env) = self.build_test_env(from, TxKind::Call(to), calldata, value);
489        self.call_with_env(evm_env, tx_env)
490    }
491
492    /// Performs a raw call to an account on the current state of the VM with an EIP-7702
493    /// authorization list.
494    pub fn call_raw_with_authorization(
495        &mut self,
496        from: Address,
497        to: Address,
498        calldata: Bytes,
499        value: U256,
500        authorization_list: Vec<SignedAuthorization>,
501    ) -> eyre::Result<RawCallResult<FEN>> {
502        let (evm_env, mut tx_env) = self.build_test_env(from, to.into(), calldata, value);
503        tx_env.set_signed_authorization(authorization_list);
504        tx_env.set_tx_type(4);
505        self.call_with_env(evm_env, tx_env)
506    }
507
508    /// Performs a raw call to an account on the current state of the VM.
509    pub fn transact_raw(
510        &mut self,
511        from: Address,
512        to: Address,
513        calldata: Bytes,
514        value: U256,
515    ) -> eyre::Result<RawCallResult<FEN>> {
516        let (evm_env, tx_env) = self.build_test_env(from, TxKind::Call(to), calldata, value);
517        self.transact_with_env(evm_env, tx_env)
518    }
519
520    /// Performs a raw call to an account on the current state of the VM with an EIP-7702
521    /// authorization last.
522    pub fn transact_raw_with_authorization(
523        &mut self,
524        from: Address,
525        to: Address,
526        calldata: Bytes,
527        value: U256,
528        authorization_list: Vec<SignedAuthorization>,
529    ) -> eyre::Result<RawCallResult<FEN>> {
530        let (evm_env, mut tx_env) = self.build_test_env(from, TxKind::Call(to), calldata, value);
531        tx_env.set_signed_authorization(authorization_list);
532        tx_env.set_tx_type(4);
533        self.transact_with_env(evm_env, tx_env)
534    }
535
536    /// Execute the transaction configured in `tx_env`.
537    ///
538    /// The state after the call is **not** persisted.
539    #[instrument(name = "call", level = "debug", skip_all)]
540    pub fn call_with_env(
541        &self,
542        mut evm_env: EvmEnvFor<FEN>,
543        mut tx_env: TxEnvFor<FEN>,
544    ) -> eyre::Result<RawCallResult<FEN>> {
545        let mut stack = self.inspector().clone();
546        let sancov_edges = stack.inner.sancov_edges;
547        let sancov_trace_cmp = stack.inner.sancov_trace_cmp;
548        let sancov_active = sancov_edges || sancov_trace_cmp;
549        let mut backend = CowBackend::new_borrowed(self.backend());
550        let result = {
551            let _guard = sancov_active.then(|| SancovGuard::new(sancov_edges, sancov_trace_cmp));
552            backend.inspect(&mut evm_env, &mut tx_env, &mut stack)?
553        };
554        let mut result = convert_executed_result(
555            evm_env,
556            tx_env,
557            stack,
558            result,
559            backend.has_state_snapshot_failure(),
560        )?;
561        if sancov_edges {
562            SancovGuard::append_edges_into(&mut result);
563        }
564        if sancov_trace_cmp {
565            SancovGuard::drain_cmp_into(&mut result);
566        }
567        Ok(result)
568    }
569
570    /// Execute the transaction configured in `tx_env`.
571    #[instrument(name = "transact", level = "debug", skip_all)]
572    pub fn transact_with_env(
573        &mut self,
574        mut evm_env: EvmEnvFor<FEN>,
575        mut tx_env: TxEnvFor<FEN>,
576    ) -> eyre::Result<RawCallResult<FEN>> {
577        let mut stack = self.inspector().clone();
578        let sancov_edges = stack.inner.sancov_edges;
579        let sancov_trace_cmp = stack.inner.sancov_trace_cmp;
580        let sancov_active = sancov_edges || sancov_trace_cmp;
581        let backend = self.backend_mut();
582        let result = {
583            let _guard = sancov_active.then(|| SancovGuard::new(sancov_edges, sancov_trace_cmp));
584            backend.inspect(&mut evm_env, &mut tx_env, &mut stack)?
585        };
586        let mut result = convert_executed_result(
587            evm_env,
588            tx_env,
589            stack,
590            result,
591            backend.has_state_snapshot_failure(),
592        )?;
593        if sancov_edges {
594            SancovGuard::append_edges_into(&mut result);
595        }
596        if sancov_trace_cmp {
597            SancovGuard::drain_cmp_into(&mut result);
598        }
599        self.commit(&mut result);
600        Ok(result)
601    }
602
603    /// Commit the changeset to the database and adjust `self.inspector_config` values according to
604    /// the executed call result.
605    ///
606    /// This should not be exposed to the user, as it should be called only by `transact*`.
607    #[instrument(name = "commit", level = "debug", skip_all)]
608    fn commit(&mut self, result: &mut RawCallResult<FEN>) {
609        // Persist changes to db.
610        self.backend_mut().commit(result.state_changeset.clone());
611
612        // Persist cheatcode state.
613        self.inspector_mut().cheatcodes = result.cheatcodes.take();
614        if let Some(cheats) = self.inspector_mut().cheatcodes.as_mut() {
615            // Clear broadcastable transactions
616            cheats.broadcastable_transactions.clear();
617            cheats.ignored_traces.ignored.clear();
618
619            // if tracing was paused but never unpaused, we should begin next frame with tracing
620            // still paused
621            if let Some(last_pause_call) = cheats.ignored_traces.last_pause_call.as_mut() {
622                *last_pause_call = (0, 0);
623            }
624        }
625
626        // Persist the changed environment.
627        self.inspector_mut().set_block(result.evm_env.block_env.clone());
628        self.inspector_mut().set_gas_price(result.tx_env.gas_price());
629    }
630
631    /// Returns `true` if a test can be considered successful.
632    ///
633    /// This is the same as [`Self::is_success`], but will consume the `state_changeset` map to use
634    /// internally when calling `failed()`.
635    pub fn is_raw_call_mut_success(
636        &self,
637        address: Address,
638        call_result: &mut RawCallResult<FEN>,
639        should_fail: bool,
640    ) -> bool {
641        self.is_raw_call_success(
642            address,
643            Cow::Owned(std::mem::take(&mut call_result.state_changeset)),
644            call_result,
645            should_fail,
646        )
647    }
648
649    /// Returns `true` if a test can be considered successful.
650    ///
651    /// This is the same as [`Self::is_success`], but intended for outcomes of [`Self::call_raw`].
652    pub fn is_raw_call_success(
653        &self,
654        address: Address,
655        state_changeset: Cow<'_, StateChangeset>,
656        call_result: &RawCallResult<FEN>,
657        should_fail: bool,
658    ) -> bool {
659        if call_result.has_state_snapshot_failure {
660            // a failure occurred in a reverted snapshot, which is considered a failed test
661            return should_fail;
662        }
663        self.is_success(address, call_result.reverted, state_changeset, should_fail)
664    }
665
666    /// Like [`Self::is_raw_call_mut_success`] but uses [`Self::is_success_handler_gate`] under
667    /// the hood. Intended for invariant view-call success checks during a campaign where the
668    /// committed `GLOBAL_FAIL_SLOT` may be stale poison from a previously-recorded handler bug.
669    pub fn is_raw_call_mut_success_handler_gate(
670        &self,
671        address: Address,
672        call_result: &mut RawCallResult<FEN>,
673    ) -> bool {
674        if call_result.has_state_snapshot_failure {
675            return false;
676        }
677        let state_changeset = std::mem::take(&mut call_result.state_changeset);
678        self.is_success_handler_gate(address, call_result.reverted, Cow::Owned(state_changeset))
679    }
680
681    /// Returns `true` if a test can be considered successful.
682    ///
683    /// If the call succeeded, we also have to check the global and local failure flags.
684    ///
685    /// These are set by the test contract itself when an assertion fails, using the internal `fail`
686    /// function. The global flag is located in [`CHEATCODE_ADDRESS`] at slot [`GLOBAL_FAIL_SLOT`],
687    /// and the local flag is located in the test contract at an unspecified slot.
688    ///
689    /// This behavior is inherited from Dapptools, where initially only a public
690    /// `failed` variable was used to track test failures, and later, a global failure flag was
691    /// introduced to track failures across multiple contracts in
692    /// [ds-test#30](https://github.com/dapphub/ds-test/pull/30).
693    ///
694    /// The assumption is that the test runner calls `failed` on the test contract to determine if
695    /// it failed. However, we want to avoid this as much as possible, as it is relatively
696    /// expensive to set up an EVM call just for checking a single boolean flag.
697    ///
698    /// See:
699    /// - Newer DSTest: <https://github.com/dapphub/ds-test/blob/e282159d5170298eb2455a6c05280ab5a73a4ef0/src/test.sol#L47-L63>
700    /// - Older DSTest: <https://github.com/dapphub/ds-test/blob/9ca4ecd48862b40d7b0197b600713f64d337af12/src/test.sol#L38-L49>
701    /// - forge-std: <https://github.com/foundry-rs/forge-std/blob/19891e6a0b5474b9ea6827ddb90bb9388f7acfc0/src/StdAssertions.sol#L38-L44>
702    pub fn is_success(
703        &self,
704        address: Address,
705        reverted: bool,
706        state_changeset: Cow<'_, StateChangeset>,
707        should_fail: bool,
708    ) -> bool {
709        let success = self.is_success_raw(address, reverted, state_changeset, false);
710        should_fail ^ success
711    }
712
713    /// Like [`Self::is_success`] but ignores the *committed* `GLOBAL_FAIL_SLOT` and only treats
714    /// the slot as failed when this call's in-flight changeset writes it. Used by the invariant
715    /// runner's per-call handler-success gate, where a `1` already in committed storage is just
716    /// stale poison from a previously-recorded handler bug (separately tracked) and must not
717    /// suppress later `assert_invariants` / `afterInvariant` evaluations.
718    pub fn is_success_handler_gate(
719        &self,
720        address: Address,
721        reverted: bool,
722        state_changeset: Cow<'_, StateChangeset>,
723    ) -> bool {
724        self.is_success_raw(address, reverted, state_changeset, true)
725    }
726
727    #[instrument(name = "is_success", level = "debug", skip_all)]
728    fn is_success_raw(
729        &self,
730        address: Address,
731        reverted: bool,
732        state_changeset: Cow<'_, StateChangeset>,
733        pending_global_failure_only: bool,
734    ) -> bool {
735        // The call reverted.
736        if reverted {
737            return false;
738        }
739
740        // A failure occurred in a reverted snapshot, which is considered a failed test.
741        if self.backend().has_state_snapshot_failure() {
742            return false;
743        }
744
745        // Check the global failure slot. Callers that already track recorded handler bugs
746        // out-of-band can pass `pending_global_failure_only = true` to ignore the committed
747        // slot (which would otherwise stay `1` for the rest of the run after a non-reverting
748        // `vm.assert*` under `assertions_revert = false`).
749        let global_failed = if pending_global_failure_only {
750            Self::has_pending_global_failure(&state_changeset)
751        } else {
752            self.has_global_failure(&state_changeset)
753        };
754        if global_failed {
755            return false;
756        }
757
758        if !self.legacy_assertions {
759            return true;
760        }
761
762        // Finally, resort to calling `DSTest::failed`.
763        {
764            // Construct a new bare-bones backend to evaluate success.
765            let mut backend = self.backend().clone_empty();
766
767            // We only clone the test contract and cheatcode accounts,
768            // that's all we need to evaluate success.
769            for address in [address, CHEATCODE_ADDRESS] {
770                let Ok(acc) = self.backend().basic_ref(address) else { return false };
771                backend.insert_account_info(address, acc.unwrap_or_default());
772            }
773
774            // If this test failed any asserts, then this changeset will contain changes
775            // `false -> true` for the contract's `failed` variable and the `globalFailure` flag
776            // in the state of the cheatcode address,
777            // which are both read when we call `"failed()(bool)"` in the next step.
778            backend.commit(state_changeset.into_owned());
779
780            // Check if a DSTest assertion failed
781            let executor = self.clone_with_backend(backend);
782            let call = executor.call_sol(CALLER, address, &ITest::failedCall {}, U256::ZERO, None);
783            match call {
784                Ok(CallResult { raw: _, decoded_result: failed }) => {
785                    trace!(failed, "DSTest::failed()");
786                    !failed
787                }
788                Err(err) => {
789                    trace!(%err, "failed to call DSTest::failed()");
790                    true
791                }
792            }
793        }
794    }
795
796    /// Returns whether the in-flight state changeset for the current call sets the global
797    /// assertion failure flag.
798    pub fn has_pending_global_failure(state_changeset: &StateChangeset) -> bool {
799        if let Some(acc) = state_changeset.get(&CHEATCODE_ADDRESS)
800            && let Some(failed_slot) = acc.storage.get(&GLOBAL_FAIL_SLOT)
801            && !failed_slot.present_value().is_zero()
802        {
803            return true;
804        }
805
806        false
807    }
808
809    /// Returns whether the global assertion failure flag is set either in the in-flight state
810    /// changeset or in the committed backend state.
811    pub fn has_global_failure(&self, state_changeset: &StateChangeset) -> bool {
812        if Self::has_pending_global_failure(state_changeset) {
813            return true;
814        }
815
816        self.backend()
817            .storage_ref(CHEATCODE_ADDRESS, GLOBAL_FAIL_SLOT)
818            .is_ok_and(|failed_slot| !failed_slot.is_zero())
819    }
820
821    /// Creates the environment to use when executing a transaction in a test context
822    ///
823    /// If using a backend with cheatcodes, `tx.gas_price` and `block.number` will be overwritten by
824    /// the cheatcode state in between calls.
825    fn build_test_env(
826        &self,
827        caller: Address,
828        kind: TxKind,
829        data: Bytes,
830        value: U256,
831    ) -> (EvmEnvFor<FEN>, TxEnvFor<FEN>) {
832        let mut cfg_env = self.evm_env.cfg_env.clone();
833        cfg_env.spec = self.spec_id();
834
835        // We always set the gas price to 0 so we can execute the transaction regardless of
836        // network conditions - the actual gas price is kept in `self.block` and is applied
837        // by the cheatcode handler if it is enabled
838        let mut block_env = self.evm_env.block_env.clone();
839        block_env.set_basefee(0);
840        block_env.set_gas_limit(self.gas_limit);
841
842        let mut tx_env = self.tx_env.clone();
843        tx_env.set_caller(caller);
844        tx_env.set_kind(kind);
845        tx_env.set_data(data);
846        tx_env.set_value(value);
847        // As above, we set the gas price to 0.
848        tx_env.set_gas_price(0);
849        tx_env.set_gas_priority_fee(None);
850        tx_env.set_gas_limit(self.gas_limit);
851        tx_env.set_chain_id(Some(self.evm_env.cfg_env.chain_id));
852
853        (EvmEnv { cfg_env, block_env }, tx_env)
854    }
855
856    pub fn call_sol_default<C: SolCall>(&self, to: Address, args: &C) -> C::Return
857    where
858        C::Return: Default,
859    {
860        self.call_sol(CALLER, to, args, U256::ZERO, None)
861            .map(|c| c.decoded_result)
862            .inspect_err(|e| warn!(target: "forge::test", "failed calling {:?}: {e}", C::SIGNATURE))
863            .unwrap_or_default()
864    }
865}
866
867/// Represents the context after an execution error occurred.
868#[derive(Debug, thiserror::Error)]
869#[error("execution reverted: {reason} (gas: {})", raw.gas_used)]
870pub struct ExecutionErr<FEN: FoundryEvmNetwork = EthEvmNetwork> {
871    /// The raw result of the call.
872    pub raw: RawCallResult<FEN>,
873    /// The revert reason.
874    pub reason: String,
875}
876
877impl<FEN: FoundryEvmNetwork> std::ops::Deref for ExecutionErr<FEN> {
878    type Target = RawCallResult<FEN>;
879
880    #[inline]
881    fn deref(&self) -> &Self::Target {
882        &self.raw
883    }
884}
885
886impl<FEN: FoundryEvmNetwork> std::ops::DerefMut for ExecutionErr<FEN> {
887    #[inline]
888    fn deref_mut(&mut self) -> &mut Self::Target {
889        &mut self.raw
890    }
891}
892
893#[derive(Debug, thiserror::Error)]
894pub enum EvmError<FEN: FoundryEvmNetwork = EthEvmNetwork> {
895    /// Error which occurred during execution of a transaction.
896    #[error(transparent)]
897    Execution(Box<ExecutionErr<FEN>>),
898    /// Error which occurred during ABI encoding/decoding.
899    #[error(transparent)]
900    Abi(#[from] alloy_dyn_abi::Error),
901    /// Error caused which occurred due to calling the `skip` cheatcode.
902    #[error("{0}")]
903    Skip(SkipReason),
904    /// Any other error.
905    #[error("{0}")]
906    Eyre(
907        #[from]
908        #[source]
909        eyre::Report,
910    ),
911}
912
913impl<FEN: FoundryEvmNetwork> From<ExecutionErr<FEN>> for EvmError<FEN> {
914    fn from(err: ExecutionErr<FEN>) -> Self {
915        Self::Execution(Box::new(err))
916    }
917}
918
919impl<FEN: FoundryEvmNetwork> From<alloy_sol_types::Error> for EvmError<FEN> {
920    fn from(err: alloy_sol_types::Error) -> Self {
921        Self::Abi(err.into())
922    }
923}
924
925/// The result of a deployment.
926#[derive(Debug)]
927pub struct DeployResult<FEN: FoundryEvmNetwork = EthEvmNetwork> {
928    /// The raw result of the deployment.
929    pub raw: RawCallResult<FEN>,
930    /// The address of the deployed contract
931    pub address: Address,
932}
933
934impl<FEN: FoundryEvmNetwork> std::ops::Deref for DeployResult<FEN> {
935    type Target = RawCallResult<FEN>;
936
937    #[inline]
938    fn deref(&self) -> &Self::Target {
939        &self.raw
940    }
941}
942
943impl<FEN: FoundryEvmNetwork> std::ops::DerefMut for DeployResult<FEN> {
944    #[inline]
945    fn deref_mut(&mut self) -> &mut Self::Target {
946        &mut self.raw
947    }
948}
949
950impl<FEN: FoundryEvmNetwork> From<DeployResult<FEN>> for RawCallResult<FEN> {
951    fn from(d: DeployResult<FEN>) -> Self {
952        d.raw
953    }
954}
955
956/// The result of a raw call.
957#[derive(Debug)]
958pub struct RawCallResult<FEN: FoundryEvmNetwork = EthEvmNetwork> {
959    /// The status of the call
960    pub exit_reason: Option<InstructionResult>,
961    /// Whether the call reverted or not
962    pub reverted: bool,
963    /// Whether the call includes a snapshot failure
964    ///
965    /// This is tracked separately from revert because a snapshot failure can occur without a
966    /// revert, since assert failures are stored in a global variable (ds-test legacy)
967    pub has_state_snapshot_failure: bool,
968    /// The raw result of the call.
969    pub result: Bytes,
970    /// The gas used for the call
971    pub gas_used: u64,
972    /// Refunded gas
973    pub gas_refunded: u64,
974    /// The initial gas stipend for the transaction
975    pub stipend: u64,
976    /// The logs emitted during the call
977    pub logs: Vec<Log>,
978    /// The labels assigned to addresses during the call
979    pub labels: AddressHashMap<String>,
980    /// The traces of the call
981    pub traces: Option<SparsedTraceArena>,
982    /// The line coverage info collected during the call
983    pub line_coverage: Option<HitMaps>,
984    /// The edge coverage info collected during the call
985    pub edge_coverage: Option<Vec<u8>>,
986    /// EVM comparison operands collected during the call.
987    pub evm_cmp_values: Option<Vec<CmpOperands>>,
988    /// Sancov edge coverage from instrumented native Rust crates (e.g. precompiles).
989    /// Tracked separately from EVM edge coverage to avoid ID-space collisions.
990    pub sancov_coverage: Option<Vec<u8>>,
991    /// Comparison operands captured via sancov trace-cmp callbacks.
992    pub sancov_cmp_values: Option<Vec<foundry_evm_sancov::CmpSample>>,
993    /// Scripted transactions generated from this call
994    pub transactions: Option<BroadcastableTransactions<FEN::Network>>,
995    /// The changeset of the state.
996    pub state_changeset: StateChangeset,
997    /// The `EvmEnv` after the call
998    pub evm_env: EvmEnvFor<FEN>,
999    /// The `TxEnv` after the call
1000    pub tx_env: TxEnvFor<FEN>,
1001    /// The cheatcode states after execution
1002    pub cheatcodes: Option<Box<Cheatcodes<FEN>>>,
1003    /// The raw output of the execution
1004    pub out: Option<Output>,
1005    /// The chisel state
1006    pub chisel_state: Option<(Vec<U256>, Vec<u8>)>,
1007    pub reverter: Option<Address>,
1008}
1009
1010impl<FEN: FoundryEvmNetwork> Default for RawCallResult<FEN> {
1011    fn default() -> Self {
1012        Self {
1013            exit_reason: None,
1014            reverted: false,
1015            has_state_snapshot_failure: false,
1016            result: Bytes::new(),
1017            gas_used: 0,
1018            gas_refunded: 0,
1019            stipend: 0,
1020            logs: Vec::new(),
1021            labels: HashMap::default(),
1022            traces: None,
1023            line_coverage: None,
1024            edge_coverage: None,
1025            evm_cmp_values: None,
1026            sancov_coverage: None,
1027            sancov_cmp_values: None,
1028            transactions: None,
1029            state_changeset: HashMap::default(),
1030            evm_env: EvmEnv::default(),
1031            tx_env: TxEnvFor::<FEN>::default(),
1032            cheatcodes: Default::default(),
1033            out: None,
1034            chisel_state: None,
1035            reverter: None,
1036        }
1037    }
1038}
1039
1040impl<FEN: FoundryEvmNetwork> RawCallResult<FEN> {
1041    /// Unpacks an EVM result.
1042    pub fn from_evm_result(r: Result<Self, EvmError<FEN>>) -> eyre::Result<(Self, Option<String>)> {
1043        match r {
1044            Ok(r) => Ok((r, None)),
1045            Err(EvmError::Execution(e)) => Ok((e.raw, Some(e.reason))),
1046            Err(e) => Err(e.into()),
1047        }
1048    }
1049
1050    /// Converts the result of the call into an `EvmError`.
1051    pub fn into_evm_error(self, rd: Option<&RevertDecoder>) -> EvmError<FEN> {
1052        if self.reverter == Some(CHEATCODE_ADDRESS)
1053            && let Some(reason) = SkipReason::decode(&self.result)
1054        {
1055            return EvmError::Skip(reason);
1056        }
1057        let reason = rd.unwrap_or_default().decode(&self.result, self.exit_reason);
1058        EvmError::Execution(Box::new(self.into_execution_error(reason)))
1059    }
1060
1061    /// Converts the result of the call into an `ExecutionErr`.
1062    pub const fn into_execution_error(self, reason: String) -> ExecutionErr<FEN> {
1063        ExecutionErr { raw: self, reason }
1064    }
1065
1066    /// Returns an `EvmError` if the call failed, otherwise returns `self`.
1067    pub fn into_result(self, rd: Option<&RevertDecoder>) -> Result<Self, EvmError<FEN>> {
1068        if let Some(reason) = self.exit_reason
1069            && reason.is_ok()
1070        {
1071            Ok(self)
1072        } else {
1073            Err(self.into_evm_error(rd))
1074        }
1075    }
1076
1077    /// Decodes the result of the call with the given function.
1078    pub fn into_decoded_result(
1079        mut self,
1080        func: &Function,
1081        rd: Option<&RevertDecoder>,
1082    ) -> Result<CallResult<DynSolValue, FEN>, EvmError<FEN>> {
1083        self = self.into_result(rd)?;
1084        let mut result = func.abi_decode_output(&self.result)?;
1085        let decoded_result =
1086            if result.len() == 1 { result.pop().unwrap() } else { DynSolValue::Tuple(result) };
1087        Ok(CallResult { raw: self, decoded_result })
1088    }
1089
1090    /// Returns the transactions generated from this call.
1091    pub fn transactions(&self) -> Option<&BroadcastableTransactions<FEN::Network>> {
1092        self.cheatcodes.as_ref().map(|c| &c.broadcastable_transactions)
1093    }
1094
1095    /// Update provided history map with edge coverage info collected during this call.
1096    /// Uses AFL binning algo <https://github.com/h0mbre/Lucid/blob/3026e7323c52b30b3cf12563954ac1eaa9c6981e/src/coverage.rs#L57-L85>
1097    pub fn merge_edge_coverage(&mut self, history_map: &mut [u8]) -> (bool, bool) {
1098        let mut new_coverage = false;
1099        let mut is_edge = false;
1100        if let Some(x) = &mut self.edge_coverage {
1101            // Iterate over the current map and the history map together and update
1102            // the history map, if we discover some new coverage, report true
1103            for (curr, hist) in std::iter::zip(x, history_map) {
1104                // If we got a hitcount of at least 1
1105                if *curr > 0 {
1106                    // Convert hitcount into bucket count
1107                    let bucket = match *curr {
1108                        0 => 0,
1109                        1 => 1,
1110                        2 => 2,
1111                        3 => 4,
1112                        4..=7 => 8,
1113                        8..=15 => 16,
1114                        16..=31 => 32,
1115                        32..=127 => 64,
1116                        128..=255 => 128,
1117                    };
1118
1119                    // If the old record for this edge pair is lower, update
1120                    if *hist < bucket {
1121                        if *hist == 0 {
1122                            // Counts as an edge the first time we see it, otherwise it's a feature.
1123                            is_edge = true;
1124                        }
1125                        *hist = bucket;
1126                        new_coverage = true;
1127                    }
1128
1129                    // Zero out the current map for next iteration.
1130                    *curr = 0;
1131                }
1132            }
1133        }
1134        (new_coverage, is_edge)
1135    }
1136
1137    /// Update provided history map with sancov coverage info collected during this call.
1138    /// Same AFL binning algo as [`Self::merge_edge_coverage`].
1139    pub fn merge_sancov_coverage(&mut self, history_map: &mut Vec<u8>) -> (bool, bool) {
1140        let mut new_coverage = false;
1141        let mut is_edge = false;
1142        if let Some(x) = &mut self.sancov_coverage {
1143            if history_map.len() < x.len() {
1144                history_map.resize(x.len(), 0);
1145            }
1146            for (curr, hist) in std::iter::zip(x.iter_mut(), history_map.iter_mut()) {
1147                if *curr > 0 {
1148                    let bucket = match *curr {
1149                        0 => 0,
1150                        1 => 1,
1151                        2 => 2,
1152                        3 => 4,
1153                        4..=7 => 8,
1154                        8..=15 => 16,
1155                        16..=31 => 32,
1156                        32..=127 => 64,
1157                        128..=255 => 128,
1158                    };
1159                    if *hist < bucket {
1160                        if *hist == 0 {
1161                            is_edge = true;
1162                        }
1163                        *hist = bucket;
1164                        new_coverage = true;
1165                    }
1166                    *curr = 0;
1167                }
1168            }
1169        }
1170        (new_coverage, is_edge)
1171    }
1172
1173    /// Merge both EVM and sancov coverage into their respective history maps.
1174    /// Returns `(new_coverage, is_edge)` — true if either domain produced new coverage.
1175    pub fn merge_all_coverage(
1176        &mut self,
1177        evm_history: &mut [u8],
1178        sancov_history: &mut Vec<u8>,
1179    ) -> (bool, bool) {
1180        let (new_evm, edge_evm) = self.merge_edge_coverage(evm_history);
1181        let (new_san, edge_san) = self.merge_sancov_coverage(sancov_history);
1182        (new_evm || new_san, edge_evm || edge_san)
1183    }
1184}
1185
1186/// The result of a call.
1187pub struct CallResult<T = DynSolValue, FEN: FoundryEvmNetwork = EthEvmNetwork> {
1188    /// The raw result of the call.
1189    pub raw: RawCallResult<FEN>,
1190    /// The decoded result of the call.
1191    pub decoded_result: T,
1192}
1193
1194impl<T, FEN: FoundryEvmNetwork> std::ops::Deref for CallResult<T, FEN> {
1195    type Target = RawCallResult<FEN>;
1196
1197    #[inline]
1198    fn deref(&self) -> &Self::Target {
1199        &self.raw
1200    }
1201}
1202
1203impl<T, FEN: FoundryEvmNetwork> std::ops::DerefMut for CallResult<T, FEN> {
1204    #[inline]
1205    fn deref_mut(&mut self) -> &mut Self::Target {
1206        &mut self.raw
1207    }
1208}
1209
1210/// Converts the data aggregated in the `inspector` and `call` to a `RawCallResult`
1211fn convert_executed_result<FEN: FoundryEvmNetwork>(
1212    evm_env: EvmEnvFor<FEN>,
1213    tx_env: TxEnvFor<FEN>,
1214    inspector: InspectorStack<FEN>,
1215    ResultAndState { result, state: state_changeset }: ResultAndState<HaltReasonFor<FEN>>,
1216    has_state_snapshot_failure: bool,
1217) -> eyre::Result<RawCallResult<FEN>> {
1218    let (exit_reason, gas_refunded, gas_used, out, exec_logs) = match result {
1219        ExecutionResult::Success { reason, gas, output, logs } => {
1220            (reason.into(), gas.final_refunded(), gas.tx_gas_used(), Some(output), logs)
1221        }
1222        ExecutionResult::Revert { gas, output, logs } => {
1223            (InstructionResult::Revert, 0_u64, gas.tx_gas_used(), Some(Output::Call(output)), logs)
1224        }
1225        ExecutionResult::Halt { reason, gas, logs } => {
1226            (reason.into_instruction_result(), 0_u64, gas.tx_gas_used(), None, logs)
1227        }
1228    };
1229    let gas = revm::interpreter::gas::calculate_initial_tx_gas_for_tx(
1230        &tx_env,
1231        evm_env.cfg_env.spec.into(),
1232    );
1233
1234    let result = match &out {
1235        Some(Output::Call(data)) => data.clone(),
1236        _ => Bytes::new(),
1237    };
1238
1239    let InspectorData {
1240        mut logs,
1241        labels,
1242        traces,
1243        line_coverage,
1244        edge_coverage,
1245        evm_cmp_values,
1246        cheatcodes,
1247        chisel_state,
1248        reverter,
1249    } = inspector.collect();
1250
1251    if logs.is_empty() {
1252        logs = exec_logs;
1253    }
1254
1255    let transactions = cheatcodes
1256        .as_ref()
1257        .map(|c| c.broadcastable_transactions.clone())
1258        .filter(|txs| !txs.is_empty());
1259
1260    Ok(RawCallResult {
1261        exit_reason: Some(exit_reason),
1262        reverted: !matches!(exit_reason, return_ok!()),
1263        has_state_snapshot_failure,
1264        result,
1265        gas_used,
1266        gas_refunded,
1267        stipend: gas.initial_total_gas,
1268        logs,
1269        labels,
1270        traces,
1271        line_coverage,
1272        edge_coverage,
1273        evm_cmp_values,
1274        sancov_coverage: None,
1275        sancov_cmp_values: None,
1276        transactions,
1277        state_changeset,
1278        evm_env,
1279        tx_env,
1280        cheatcodes,
1281        out,
1282        chisel_state,
1283        reverter,
1284    })
1285}
1286
1287/// Timer for a fuzz test.
1288pub struct FuzzTestTimer {
1289    /// Inner fuzz test timer - (test start time, test duration).
1290    inner: Option<(Instant, Duration)>,
1291}
1292
1293impl FuzzTestTimer {
1294    pub fn new(timeout: Option<u32>) -> Self {
1295        Self { inner: timeout.map(|timeout| (Instant::now(), Duration::from_secs(timeout.into()))) }
1296    }
1297
1298    /// Whether the fuzz test timer is enabled.
1299    pub const fn is_enabled(&self) -> bool {
1300        self.inner.is_some()
1301    }
1302
1303    /// Whether the current fuzz test timed out and should be stopped.
1304    pub fn is_timed_out(&self) -> bool {
1305        self.inner.is_some_and(|(start, duration)| start.elapsed() > duration)
1306    }
1307}
1308
1309/// Helper struct to enable early exit behavior: when one test fails or run is interrupted,
1310/// all other tests stop early.
1311#[derive(Clone, Debug)]
1312pub struct EarlyExit {
1313    /// Shared atomic flag set to `true` when a failure occurs or ctrl-c received.
1314    inner: Arc<AtomicBool>,
1315    /// Whether to exit early on test failure (fail-fast mode).
1316    fail_fast: bool,
1317}
1318
1319impl EarlyExit {
1320    pub fn new(fail_fast: bool) -> Self {
1321        Self { inner: Arc::new(AtomicBool::new(false)), fail_fast }
1322    }
1323
1324    /// Records a test failure. Only triggers early exit if fail-fast mode is enabled.
1325    pub fn record_failure(&self) {
1326        if self.fail_fast {
1327            self.inner.store(true, Ordering::Relaxed);
1328        }
1329    }
1330
1331    /// Records a Ctrl-C interrupt. Always triggers early exit.
1332    pub fn record_ctrl_c(&self) {
1333        self.inner.store(true, Ordering::Relaxed);
1334    }
1335
1336    /// Whether tests should stop and exit early.
1337    pub fn should_stop(&self) -> bool {
1338        self.inner.load(Ordering::Relaxed)
1339    }
1340}
1341
1342#[cfg(test)]
1343mod tests {
1344    use super::*;
1345    use foundry_evm_core::constants::MAGIC_SKIP;
1346    use revm::{context::Cfg, primitives::hardfork::SpecId};
1347
1348    #[test]
1349    fn cheatcode_skip_payload_is_classified_as_skip() {
1350        let raw = RawCallResult::<EthEvmNetwork> {
1351            result: Bytes::from_static(b"FOUNDRY::SKIPwith reason"),
1352            reverter: Some(CHEATCODE_ADDRESS),
1353            ..Default::default()
1354        };
1355
1356        let err = raw.into_evm_error(None);
1357        assert!(matches!(err, EvmError::Skip(_)));
1358    }
1359
1360    #[test]
1361    fn forged_skip_payload_from_non_cheatcode_is_execution_error() {
1362        let raw = RawCallResult::<EthEvmNetwork> {
1363            result: Bytes::from_static(MAGIC_SKIP),
1364            reverter: Some(CALLER),
1365            ..Default::default()
1366        };
1367
1368        let err = raw.into_evm_error(None);
1369        assert!(matches!(err, EvmError::Execution(_)));
1370    }
1371
1372    #[test]
1373    fn skip_payload_without_reverter_is_execution_error() {
1374        let raw = RawCallResult::<EthEvmNetwork> {
1375            result: Bytes::from_static(MAGIC_SKIP),
1376            reverter: None,
1377            ..Default::default()
1378        };
1379
1380        let err = raw.into_evm_error(None);
1381        assert!(matches!(err, EvmError::Execution(_)));
1382    }
1383
1384    #[test]
1385    fn set_spec_id_updates_spec_dependent_cfg_state() {
1386        let backend = Backend::<EthEvmNetwork>::spawn(None).unwrap();
1387        let mut executor = ExecutorBuilder::default().build(
1388            EvmEnvFor::<EthEvmNetwork>::default(),
1389            TxEnvFor::<EthEvmNetwork>::default(),
1390            backend,
1391        );
1392
1393        executor.evm_env_mut().cfg_env.set_spec_and_mainnet_gas_params(SpecId::HOMESTEAD);
1394        assert_eq!(
1395            executor.evm_env().cfg_env.gas_params(),
1396            &revm::context_interface::cfg::GasParams::new_spec(SpecId::HOMESTEAD),
1397        );
1398        assert!(!executor.evm_env().cfg_env.is_amsterdam_eip8037_enabled());
1399
1400        executor.set_spec_id(SpecId::AMSTERDAM);
1401
1402        assert_eq!(executor.spec_id(), SpecId::AMSTERDAM);
1403        assert_eq!(
1404            executor.evm_env().cfg_env.gas_params(),
1405            &revm::context_interface::cfg::GasParams::new_spec(SpecId::AMSTERDAM),
1406        );
1407        assert!(executor.evm_env().cfg_env.is_amsterdam_eip8037_enabled());
1408    }
1409}