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, 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.
211    pub const fn set_spec_id(&mut self, spec_id: SpecFor<FEN>) {
212        self.evm_env.cfg_env.spec = 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    /// Returns `true` if a test can be considered successful.
667    ///
668    /// If the call succeeded, we also have to check the global and local failure flags.
669    ///
670    /// These are set by the test contract itself when an assertion fails, using the internal `fail`
671    /// function. The global flag is located in [`CHEATCODE_ADDRESS`] at slot [`GLOBAL_FAIL_SLOT`],
672    /// and the local flag is located in the test contract at an unspecified slot.
673    ///
674    /// This behavior is inherited from Dapptools, where initially only a public
675    /// `failed` variable was used to track test failures, and later, a global failure flag was
676    /// introduced to track failures across multiple contracts in
677    /// [ds-test#30](https://github.com/dapphub/ds-test/pull/30).
678    ///
679    /// The assumption is that the test runner calls `failed` on the test contract to determine if
680    /// it failed. However, we want to avoid this as much as possible, as it is relatively
681    /// expensive to set up an EVM call just for checking a single boolean flag.
682    ///
683    /// See:
684    /// - Newer DSTest: <https://github.com/dapphub/ds-test/blob/e282159d5170298eb2455a6c05280ab5a73a4ef0/src/test.sol#L47-L63>
685    /// - Older DSTest: <https://github.com/dapphub/ds-test/blob/9ca4ecd48862b40d7b0197b600713f64d337af12/src/test.sol#L38-L49>
686    /// - forge-std: <https://github.com/foundry-rs/forge-std/blob/19891e6a0b5474b9ea6827ddb90bb9388f7acfc0/src/StdAssertions.sol#L38-L44>
687    pub fn is_success(
688        &self,
689        address: Address,
690        reverted: bool,
691        state_changeset: Cow<'_, StateChangeset>,
692        should_fail: bool,
693    ) -> bool {
694        let success = self.is_success_raw(address, reverted, state_changeset);
695        should_fail ^ success
696    }
697
698    #[instrument(name = "is_success", level = "debug", skip_all)]
699    fn is_success_raw(
700        &self,
701        address: Address,
702        reverted: bool,
703        state_changeset: Cow<'_, StateChangeset>,
704    ) -> bool {
705        // The call reverted.
706        if reverted {
707            return false;
708        }
709
710        // A failure occurred in a reverted snapshot, which is considered a failed test.
711        if self.backend().has_state_snapshot_failure() {
712            return false;
713        }
714
715        // Check the global failure slot.
716        if self.has_global_failure(&state_changeset) {
717            return false;
718        }
719
720        if !self.legacy_assertions {
721            return true;
722        }
723
724        // Finally, resort to calling `DSTest::failed`.
725        {
726            // Construct a new bare-bones backend to evaluate success.
727            let mut backend = self.backend().clone_empty();
728
729            // We only clone the test contract and cheatcode accounts,
730            // that's all we need to evaluate success.
731            for address in [address, CHEATCODE_ADDRESS] {
732                let Ok(acc) = self.backend().basic_ref(address) else { return false };
733                backend.insert_account_info(address, acc.unwrap_or_default());
734            }
735
736            // If this test failed any asserts, then this changeset will contain changes
737            // `false -> true` for the contract's `failed` variable and the `globalFailure` flag
738            // in the state of the cheatcode address,
739            // which are both read when we call `"failed()(bool)"` in the next step.
740            backend.commit(state_changeset.into_owned());
741
742            // Check if a DSTest assertion failed
743            let executor = self.clone_with_backend(backend);
744            let call = executor.call_sol(CALLER, address, &ITest::failedCall {}, U256::ZERO, None);
745            match call {
746                Ok(CallResult { raw: _, decoded_result: failed }) => {
747                    trace!(failed, "DSTest::failed()");
748                    !failed
749                }
750                Err(err) => {
751                    trace!(%err, "failed to call DSTest::failed()");
752                    true
753                }
754            }
755        }
756    }
757
758    /// Returns whether the in-flight state changeset for the current call sets the global
759    /// assertion failure flag.
760    pub fn has_pending_global_failure(state_changeset: &StateChangeset) -> bool {
761        if let Some(acc) = state_changeset.get(&CHEATCODE_ADDRESS)
762            && let Some(failed_slot) = acc.storage.get(&GLOBAL_FAIL_SLOT)
763            && !failed_slot.present_value().is_zero()
764        {
765            return true;
766        }
767
768        false
769    }
770
771    /// Returns whether the global assertion failure flag is set either in the in-flight state
772    /// changeset or in the committed backend state.
773    pub fn has_global_failure(&self, state_changeset: &StateChangeset) -> bool {
774        if Self::has_pending_global_failure(state_changeset) {
775            return true;
776        }
777
778        self.backend()
779            .storage_ref(CHEATCODE_ADDRESS, GLOBAL_FAIL_SLOT)
780            .is_ok_and(|failed_slot| !failed_slot.is_zero())
781    }
782
783    /// Clears the global assertion failure flag from both the committed backend state and, when
784    /// provided, the in-flight state changeset for the current call.
785    pub fn clear_global_failure(
786        &mut self,
787        state_changeset: Option<&mut StateChangeset>,
788    ) -> BackendResult<()> {
789        if let Some(state_changeset) = state_changeset
790            && let Some(acc) = state_changeset.get_mut(&CHEATCODE_ADDRESS)
791            && let Some(failed_slot) = acc.storage.get_mut(&GLOBAL_FAIL_SLOT)
792        {
793            failed_slot.present_value = U256::ZERO;
794        }
795
796        self.set_storage_slot(CHEATCODE_ADDRESS, GLOBAL_FAIL_SLOT, U256::ZERO)
797    }
798
799    /// Creates the environment to use when executing a transaction in a test context
800    ///
801    /// If using a backend with cheatcodes, `tx.gas_price` and `block.number` will be overwritten by
802    /// the cheatcode state in between calls.
803    fn build_test_env(
804        &self,
805        caller: Address,
806        kind: TxKind,
807        data: Bytes,
808        value: U256,
809    ) -> (EvmEnvFor<FEN>, TxEnvFor<FEN>) {
810        let mut cfg_env = self.evm_env.cfg_env.clone();
811        cfg_env.spec = self.spec_id();
812
813        // We always set the gas price to 0 so we can execute the transaction regardless of
814        // network conditions - the actual gas price is kept in `self.block` and is applied
815        // by the cheatcode handler if it is enabled
816        let mut block_env = self.evm_env.block_env.clone();
817        block_env.set_basefee(0);
818        block_env.set_gas_limit(self.gas_limit);
819
820        let mut tx_env = self.tx_env.clone();
821        tx_env.set_caller(caller);
822        tx_env.set_kind(kind);
823        tx_env.set_data(data);
824        tx_env.set_value(value);
825        // As above, we set the gas price to 0.
826        tx_env.set_gas_price(0);
827        tx_env.set_gas_priority_fee(None);
828        tx_env.set_gas_limit(self.gas_limit);
829        tx_env.set_chain_id(Some(self.evm_env.cfg_env.chain_id));
830
831        (EvmEnv { cfg_env, block_env }, tx_env)
832    }
833
834    pub fn call_sol_default<C: SolCall>(&self, to: Address, args: &C) -> C::Return
835    where
836        C::Return: Default,
837    {
838        self.call_sol(CALLER, to, args, U256::ZERO, None)
839            .map(|c| c.decoded_result)
840            .inspect_err(|e| warn!(target: "forge::test", "failed calling {:?}: {e}", C::SIGNATURE))
841            .unwrap_or_default()
842    }
843}
844
845/// Represents the context after an execution error occurred.
846#[derive(Debug, thiserror::Error)]
847#[error("execution reverted: {reason} (gas: {})", raw.gas_used)]
848pub struct ExecutionErr<FEN: FoundryEvmNetwork = EthEvmNetwork> {
849    /// The raw result of the call.
850    pub raw: RawCallResult<FEN>,
851    /// The revert reason.
852    pub reason: String,
853}
854
855impl<FEN: FoundryEvmNetwork> std::ops::Deref for ExecutionErr<FEN> {
856    type Target = RawCallResult<FEN>;
857
858    #[inline]
859    fn deref(&self) -> &Self::Target {
860        &self.raw
861    }
862}
863
864impl<FEN: FoundryEvmNetwork> std::ops::DerefMut for ExecutionErr<FEN> {
865    #[inline]
866    fn deref_mut(&mut self) -> &mut Self::Target {
867        &mut self.raw
868    }
869}
870
871#[derive(Debug, thiserror::Error)]
872pub enum EvmError<FEN: FoundryEvmNetwork = EthEvmNetwork> {
873    /// Error which occurred during execution of a transaction.
874    #[error(transparent)]
875    Execution(Box<ExecutionErr<FEN>>),
876    /// Error which occurred during ABI encoding/decoding.
877    #[error(transparent)]
878    Abi(#[from] alloy_dyn_abi::Error),
879    /// Error caused which occurred due to calling the `skip` cheatcode.
880    #[error("{0}")]
881    Skip(SkipReason),
882    /// Any other error.
883    #[error("{0}")]
884    Eyre(
885        #[from]
886        #[source]
887        eyre::Report,
888    ),
889}
890
891impl<FEN: FoundryEvmNetwork> From<ExecutionErr<FEN>> for EvmError<FEN> {
892    fn from(err: ExecutionErr<FEN>) -> Self {
893        Self::Execution(Box::new(err))
894    }
895}
896
897impl<FEN: FoundryEvmNetwork> From<alloy_sol_types::Error> for EvmError<FEN> {
898    fn from(err: alloy_sol_types::Error) -> Self {
899        Self::Abi(err.into())
900    }
901}
902
903/// The result of a deployment.
904#[derive(Debug)]
905pub struct DeployResult<FEN: FoundryEvmNetwork = EthEvmNetwork> {
906    /// The raw result of the deployment.
907    pub raw: RawCallResult<FEN>,
908    /// The address of the deployed contract
909    pub address: Address,
910}
911
912impl<FEN: FoundryEvmNetwork> std::ops::Deref for DeployResult<FEN> {
913    type Target = RawCallResult<FEN>;
914
915    #[inline]
916    fn deref(&self) -> &Self::Target {
917        &self.raw
918    }
919}
920
921impl<FEN: FoundryEvmNetwork> std::ops::DerefMut for DeployResult<FEN> {
922    #[inline]
923    fn deref_mut(&mut self) -> &mut Self::Target {
924        &mut self.raw
925    }
926}
927
928impl<FEN: FoundryEvmNetwork> From<DeployResult<FEN>> for RawCallResult<FEN> {
929    fn from(d: DeployResult<FEN>) -> Self {
930        d.raw
931    }
932}
933
934/// The result of a raw call.
935#[derive(Debug)]
936pub struct RawCallResult<FEN: FoundryEvmNetwork = EthEvmNetwork> {
937    /// The status of the call
938    pub exit_reason: Option<InstructionResult>,
939    /// Whether the call reverted or not
940    pub reverted: bool,
941    /// Whether the call includes a snapshot failure
942    ///
943    /// This is tracked separately from revert because a snapshot failure can occur without a
944    /// revert, since assert failures are stored in a global variable (ds-test legacy)
945    pub has_state_snapshot_failure: bool,
946    /// The raw result of the call.
947    pub result: Bytes,
948    /// The gas used for the call
949    pub gas_used: u64,
950    /// Refunded gas
951    pub gas_refunded: u64,
952    /// The initial gas stipend for the transaction
953    pub stipend: u64,
954    /// The logs emitted during the call
955    pub logs: Vec<Log>,
956    /// The labels assigned to addresses during the call
957    pub labels: AddressHashMap<String>,
958    /// The traces of the call
959    pub traces: Option<SparsedTraceArena>,
960    /// The line coverage info collected during the call
961    pub line_coverage: Option<HitMaps>,
962    /// The edge coverage info collected during the call
963    pub edge_coverage: Option<Vec<u8>>,
964    /// Sancov edge coverage from instrumented native Rust crates (e.g. precompiles).
965    /// Tracked separately from EVM edge coverage to avoid ID-space collisions.
966    pub sancov_coverage: Option<Vec<u8>>,
967    /// Comparison operands captured via sancov trace-cmp callbacks.
968    pub sancov_cmp_values: Option<Vec<foundry_evm_sancov::CmpSample>>,
969    /// Scripted transactions generated from this call
970    pub transactions: Option<BroadcastableTransactions<FEN::Network>>,
971    /// The changeset of the state.
972    pub state_changeset: StateChangeset,
973    /// The `EvmEnv` after the call
974    pub evm_env: EvmEnvFor<FEN>,
975    /// The `TxEnv` after the call
976    pub tx_env: TxEnvFor<FEN>,
977    /// The cheatcode states after execution
978    pub cheatcodes: Option<Box<Cheatcodes<FEN>>>,
979    /// The raw output of the execution
980    pub out: Option<Output>,
981    /// The chisel state
982    pub chisel_state: Option<(Vec<U256>, Vec<u8>)>,
983    pub reverter: Option<Address>,
984}
985
986impl<FEN: FoundryEvmNetwork> Default for RawCallResult<FEN> {
987    fn default() -> Self {
988        Self {
989            exit_reason: None,
990            reverted: false,
991            has_state_snapshot_failure: false,
992            result: Bytes::new(),
993            gas_used: 0,
994            gas_refunded: 0,
995            stipend: 0,
996            logs: Vec::new(),
997            labels: HashMap::default(),
998            traces: None,
999            line_coverage: None,
1000            edge_coverage: None,
1001            sancov_coverage: None,
1002            sancov_cmp_values: None,
1003            transactions: None,
1004            state_changeset: HashMap::default(),
1005            evm_env: EvmEnv::default(),
1006            tx_env: TxEnvFor::<FEN>::default(),
1007            cheatcodes: Default::default(),
1008            out: None,
1009            chisel_state: None,
1010            reverter: None,
1011        }
1012    }
1013}
1014
1015impl<FEN: FoundryEvmNetwork> RawCallResult<FEN> {
1016    /// Unpacks an EVM result.
1017    pub fn from_evm_result(r: Result<Self, EvmError<FEN>>) -> eyre::Result<(Self, Option<String>)> {
1018        match r {
1019            Ok(r) => Ok((r, None)),
1020            Err(EvmError::Execution(e)) => Ok((e.raw, Some(e.reason))),
1021            Err(e) => Err(e.into()),
1022        }
1023    }
1024
1025    /// Converts the result of the call into an `EvmError`.
1026    pub fn into_evm_error(self, rd: Option<&RevertDecoder>) -> EvmError<FEN> {
1027        if self.reverter == Some(CHEATCODE_ADDRESS)
1028            && let Some(reason) = SkipReason::decode(&self.result)
1029        {
1030            return EvmError::Skip(reason);
1031        }
1032        let reason = rd.unwrap_or_default().decode(&self.result, self.exit_reason);
1033        EvmError::Execution(Box::new(self.into_execution_error(reason)))
1034    }
1035
1036    /// Converts the result of the call into an `ExecutionErr`.
1037    pub const fn into_execution_error(self, reason: String) -> ExecutionErr<FEN> {
1038        ExecutionErr { raw: self, reason }
1039    }
1040
1041    /// Returns an `EvmError` if the call failed, otherwise returns `self`.
1042    pub fn into_result(self, rd: Option<&RevertDecoder>) -> Result<Self, EvmError<FEN>> {
1043        if let Some(reason) = self.exit_reason
1044            && reason.is_ok()
1045        {
1046            Ok(self)
1047        } else {
1048            Err(self.into_evm_error(rd))
1049        }
1050    }
1051
1052    /// Decodes the result of the call with the given function.
1053    pub fn into_decoded_result(
1054        mut self,
1055        func: &Function,
1056        rd: Option<&RevertDecoder>,
1057    ) -> Result<CallResult<DynSolValue, FEN>, EvmError<FEN>> {
1058        self = self.into_result(rd)?;
1059        let mut result = func.abi_decode_output(&self.result)?;
1060        let decoded_result =
1061            if result.len() == 1 { result.pop().unwrap() } else { DynSolValue::Tuple(result) };
1062        Ok(CallResult { raw: self, decoded_result })
1063    }
1064
1065    /// Returns the transactions generated from this call.
1066    pub fn transactions(&self) -> Option<&BroadcastableTransactions<FEN::Network>> {
1067        self.cheatcodes.as_ref().map(|c| &c.broadcastable_transactions)
1068    }
1069
1070    /// Update provided history map with edge coverage info collected during this call.
1071    /// Uses AFL binning algo <https://github.com/h0mbre/Lucid/blob/3026e7323c52b30b3cf12563954ac1eaa9c6981e/src/coverage.rs#L57-L85>
1072    pub fn merge_edge_coverage(&mut self, history_map: &mut [u8]) -> (bool, bool) {
1073        let mut new_coverage = false;
1074        let mut is_edge = false;
1075        if let Some(x) = &mut self.edge_coverage {
1076            // Iterate over the current map and the history map together and update
1077            // the history map, if we discover some new coverage, report true
1078            for (curr, hist) in std::iter::zip(x, history_map) {
1079                // If we got a hitcount of at least 1
1080                if *curr > 0 {
1081                    // Convert hitcount into bucket count
1082                    let bucket = match *curr {
1083                        0 => 0,
1084                        1 => 1,
1085                        2 => 2,
1086                        3 => 4,
1087                        4..=7 => 8,
1088                        8..=15 => 16,
1089                        16..=31 => 32,
1090                        32..=127 => 64,
1091                        128..=255 => 128,
1092                    };
1093
1094                    // If the old record for this edge pair is lower, update
1095                    if *hist < bucket {
1096                        if *hist == 0 {
1097                            // Counts as an edge the first time we see it, otherwise it's a feature.
1098                            is_edge = true;
1099                        }
1100                        *hist = bucket;
1101                        new_coverage = true;
1102                    }
1103
1104                    // Zero out the current map for next iteration.
1105                    *curr = 0;
1106                }
1107            }
1108        }
1109        (new_coverage, is_edge)
1110    }
1111
1112    /// Update provided history map with sancov coverage info collected during this call.
1113    /// Same AFL binning algo as [`Self::merge_edge_coverage`].
1114    pub fn merge_sancov_coverage(&mut self, history_map: &mut Vec<u8>) -> (bool, bool) {
1115        let mut new_coverage = false;
1116        let mut is_edge = false;
1117        if let Some(x) = &mut self.sancov_coverage {
1118            if history_map.len() < x.len() {
1119                history_map.resize(x.len(), 0);
1120            }
1121            for (curr, hist) in std::iter::zip(x.iter_mut(), history_map.iter_mut()) {
1122                if *curr > 0 {
1123                    let bucket = match *curr {
1124                        0 => 0,
1125                        1 => 1,
1126                        2 => 2,
1127                        3 => 4,
1128                        4..=7 => 8,
1129                        8..=15 => 16,
1130                        16..=31 => 32,
1131                        32..=127 => 64,
1132                        128..=255 => 128,
1133                    };
1134                    if *hist < bucket {
1135                        if *hist == 0 {
1136                            is_edge = true;
1137                        }
1138                        *hist = bucket;
1139                        new_coverage = true;
1140                    }
1141                    *curr = 0;
1142                }
1143            }
1144        }
1145        (new_coverage, is_edge)
1146    }
1147
1148    /// Merge both EVM and sancov coverage into their respective history maps.
1149    /// Returns `(new_coverage, is_edge)` — true if either domain produced new coverage.
1150    pub fn merge_all_coverage(
1151        &mut self,
1152        evm_history: &mut [u8],
1153        sancov_history: &mut Vec<u8>,
1154    ) -> (bool, bool) {
1155        let (new_evm, edge_evm) = self.merge_edge_coverage(evm_history);
1156        let (new_san, edge_san) = self.merge_sancov_coverage(sancov_history);
1157        (new_evm || new_san, edge_evm || edge_san)
1158    }
1159}
1160
1161/// The result of a call.
1162pub struct CallResult<T = DynSolValue, FEN: FoundryEvmNetwork = EthEvmNetwork> {
1163    /// The raw result of the call.
1164    pub raw: RawCallResult<FEN>,
1165    /// The decoded result of the call.
1166    pub decoded_result: T,
1167}
1168
1169impl<T, FEN: FoundryEvmNetwork> std::ops::Deref for CallResult<T, FEN> {
1170    type Target = RawCallResult<FEN>;
1171
1172    #[inline]
1173    fn deref(&self) -> &Self::Target {
1174        &self.raw
1175    }
1176}
1177
1178impl<T, FEN: FoundryEvmNetwork> std::ops::DerefMut for CallResult<T, FEN> {
1179    #[inline]
1180    fn deref_mut(&mut self) -> &mut Self::Target {
1181        &mut self.raw
1182    }
1183}
1184
1185/// Converts the data aggregated in the `inspector` and `call` to a `RawCallResult`
1186fn convert_executed_result<FEN: FoundryEvmNetwork>(
1187    evm_env: EvmEnvFor<FEN>,
1188    tx_env: TxEnvFor<FEN>,
1189    inspector: InspectorStack<FEN>,
1190    ResultAndState { result, state: state_changeset }: ResultAndState<HaltReasonFor<FEN>>,
1191    has_state_snapshot_failure: bool,
1192) -> eyre::Result<RawCallResult<FEN>> {
1193    let (exit_reason, gas_refunded, gas_used, out, exec_logs) = match result {
1194        ExecutionResult::Success { reason, gas, output, logs } => {
1195            (reason.into(), gas.final_refunded(), gas.tx_gas_used(), Some(output), logs)
1196        }
1197        ExecutionResult::Revert { gas, output, logs } => {
1198            (InstructionResult::Revert, 0_u64, gas.tx_gas_used(), Some(Output::Call(output)), logs)
1199        }
1200        ExecutionResult::Halt { reason, gas, logs } => {
1201            (reason.into_instruction_result(), 0_u64, gas.tx_gas_used(), None, logs)
1202        }
1203    };
1204    let gas = revm::interpreter::gas::calculate_initial_tx_gas_for_tx(
1205        &tx_env,
1206        evm_env.cfg_env.spec.into(),
1207    );
1208
1209    let result = match &out {
1210        Some(Output::Call(data)) => data.clone(),
1211        _ => Bytes::new(),
1212    };
1213
1214    let InspectorData {
1215        mut logs,
1216        labels,
1217        traces,
1218        line_coverage,
1219        edge_coverage,
1220        cheatcodes,
1221        chisel_state,
1222        reverter,
1223    } = inspector.collect();
1224
1225    if logs.is_empty() {
1226        logs = exec_logs;
1227    }
1228
1229    let transactions = cheatcodes
1230        .as_ref()
1231        .map(|c| c.broadcastable_transactions.clone())
1232        .filter(|txs| !txs.is_empty());
1233
1234    Ok(RawCallResult {
1235        exit_reason: Some(exit_reason),
1236        reverted: !matches!(exit_reason, return_ok!()),
1237        has_state_snapshot_failure,
1238        result,
1239        gas_used,
1240        gas_refunded,
1241        stipend: gas.initial_total_gas,
1242        logs,
1243        labels,
1244        traces,
1245        line_coverage,
1246        edge_coverage,
1247        sancov_coverage: None,
1248        sancov_cmp_values: None,
1249        transactions,
1250        state_changeset,
1251        evm_env,
1252        tx_env,
1253        cheatcodes,
1254        out,
1255        chisel_state,
1256        reverter,
1257    })
1258}
1259
1260/// Timer for a fuzz test.
1261pub struct FuzzTestTimer {
1262    /// Inner fuzz test timer - (test start time, test duration).
1263    inner: Option<(Instant, Duration)>,
1264}
1265
1266impl FuzzTestTimer {
1267    pub fn new(timeout: Option<u32>) -> Self {
1268        Self { inner: timeout.map(|timeout| (Instant::now(), Duration::from_secs(timeout.into()))) }
1269    }
1270
1271    /// Whether the fuzz test timer is enabled.
1272    pub const fn is_enabled(&self) -> bool {
1273        self.inner.is_some()
1274    }
1275
1276    /// Whether the current fuzz test timed out and should be stopped.
1277    pub fn is_timed_out(&self) -> bool {
1278        self.inner.is_some_and(|(start, duration)| start.elapsed() > duration)
1279    }
1280}
1281
1282/// Helper struct to enable early exit behavior: when one test fails or run is interrupted,
1283/// all other tests stop early.
1284#[derive(Clone, Debug)]
1285pub struct EarlyExit {
1286    /// Shared atomic flag set to `true` when a failure occurs or ctrl-c received.
1287    inner: Arc<AtomicBool>,
1288    /// Whether to exit early on test failure (fail-fast mode).
1289    fail_fast: bool,
1290}
1291
1292impl EarlyExit {
1293    pub fn new(fail_fast: bool) -> Self {
1294        Self { inner: Arc::new(AtomicBool::new(false)), fail_fast }
1295    }
1296
1297    /// Records a test failure. Only triggers early exit if fail-fast mode is enabled.
1298    pub fn record_failure(&self) {
1299        if self.fail_fast {
1300            self.inner.store(true, Ordering::Relaxed);
1301        }
1302    }
1303
1304    /// Records a Ctrl-C interrupt. Always triggers early exit.
1305    pub fn record_ctrl_c(&self) {
1306        self.inner.store(true, Ordering::Relaxed);
1307    }
1308
1309    /// Whether tests should stop and exit early.
1310    pub fn should_stop(&self) -> bool {
1311        self.inner.load(Ordering::Relaxed)
1312    }
1313}
1314
1315#[cfg(test)]
1316mod tests {
1317    use super::*;
1318    use foundry_evm_core::constants::MAGIC_SKIP;
1319
1320    #[test]
1321    fn cheatcode_skip_payload_is_classified_as_skip() {
1322        let raw = RawCallResult::<EthEvmNetwork> {
1323            result: Bytes::from_static(b"FOUNDRY::SKIPwith reason"),
1324            reverter: Some(CHEATCODE_ADDRESS),
1325            ..Default::default()
1326        };
1327
1328        let err = raw.into_evm_error(None);
1329        assert!(matches!(err, EvmError::Skip(_)));
1330    }
1331
1332    #[test]
1333    fn forged_skip_payload_from_non_cheatcode_is_execution_error() {
1334        let raw = RawCallResult::<EthEvmNetwork> {
1335            result: Bytes::from_static(MAGIC_SKIP),
1336            reverter: Some(CALLER),
1337            ..Default::default()
1338        };
1339
1340        let err = raw.into_evm_error(None);
1341        assert!(matches!(err, EvmError::Execution(_)));
1342    }
1343
1344    #[test]
1345    fn skip_payload_without_reverter_is_execution_error() {
1346        let raw = RawCallResult::<EthEvmNetwork> {
1347            result: Bytes::from_static(MAGIC_SKIP),
1348            reverter: None,
1349            ..Default::default()
1350        };
1351
1352        let err = raw.into_evm_error(None);
1353        assert!(matches!(err, EvmError::Execution(_)));
1354    }
1355}