Skip to main content

foundry_evm/executors/
mod.rs

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