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