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