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