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