alloy_evm/eth/
mod.rs

1//! Ethereum EVM implementation.
2
3pub use env::NextEvmEnvAttributes;
4
5#[cfg(feature = "op")]
6pub(crate) use env::EvmEnvInput;
7
8use crate::{env::EvmEnv, evm::EvmFactory, precompiles::PrecompilesMap, Database, Evm};
9use alloy_primitives::{Address, Bytes};
10use core::{
11    fmt::Debug,
12    ops::{Deref, DerefMut},
13};
14use revm::{
15    context::{BlockEnv, CfgEnv, Evm as RevmEvm, TxEnv},
16    context_interface::result::{EVMError, HaltReason, ResultAndState},
17    handler::{instructions::EthInstructions, EthFrame, EthPrecompiles, PrecompileProvider},
18    inspector::NoOpInspector,
19    interpreter::{interpreter::EthInterpreter, InterpreterResult},
20    precompile::{PrecompileSpecId, Precompiles},
21    primitives::hardfork::SpecId,
22    Context, ExecuteEvm, InspectEvm, Inspector, MainBuilder, MainContext, SystemCallEvm,
23};
24
25mod block;
26pub use block::*;
27
28pub mod dao_fork;
29pub mod eip6110;
30pub mod receipt_builder;
31pub mod spec;
32
33mod env;
34pub(crate) mod spec_id;
35
36/// The Ethereum EVM context type.
37pub type EthEvmContext<DB> = Context<BlockEnv, TxEnv, CfgEnv, DB>;
38
39/// Helper builder to construct `EthEvm` instances in a unified way.
40#[derive(Debug)]
41pub struct EthEvmBuilder<DB: Database, I = NoOpInspector> {
42    db: DB,
43    block_env: BlockEnv,
44    cfg_env: CfgEnv,
45    inspector: I,
46    inspect: bool,
47    precompiles: Option<PrecompilesMap>,
48}
49
50impl<DB: Database> EthEvmBuilder<DB, NoOpInspector> {
51    /// Creates a builder from the provided `EvmEnv` and database.
52    pub const fn new(db: DB, env: EvmEnv) -> Self {
53        Self {
54            db,
55            block_env: env.block_env,
56            cfg_env: env.cfg_env,
57            inspector: NoOpInspector {},
58            inspect: false,
59            precompiles: None,
60        }
61    }
62}
63
64impl<DB: Database, I> EthEvmBuilder<DB, I> {
65    /// Sets a custom inspector
66    pub fn inspector<J>(self, inspector: J) -> EthEvmBuilder<DB, J> {
67        EthEvmBuilder {
68            db: self.db,
69            block_env: self.block_env,
70            cfg_env: self.cfg_env,
71            inspector,
72            inspect: self.inspect,
73            precompiles: self.precompiles,
74        }
75    }
76
77    /// Sets a custom inspector and enables invoking it during transaction execution.
78    pub fn activate_inspector<J>(self, inspector: J) -> EthEvmBuilder<DB, J> {
79        self.inspector(inspector).inspect()
80    }
81
82    /// Sets whether to invoke the inspector during transaction execution.
83    pub const fn set_inspect(mut self, inspect: bool) -> Self {
84        self.inspect = inspect;
85        self
86    }
87
88    /// Enables invoking the inspector during transaction execution.
89    pub const fn inspect(self) -> Self {
90        self.set_inspect(true)
91    }
92
93    /// Overrides the precompiles map. If not provided, it will be derived from the `SpecId` in
94    /// `CfgEnv`.
95    pub fn precompiles(mut self, precompiles: PrecompilesMap) -> Self {
96        self.precompiles = Some(precompiles);
97        self
98    }
99
100    /// Builds the `EthEvm` instance.
101    pub fn build(self) -> EthEvm<DB, I, PrecompilesMap>
102    where
103        I: Inspector<EthEvmContext<DB>>,
104    {
105        let precompiles = match self.precompiles {
106            Some(p) => p,
107            None => PrecompilesMap::from_static(Precompiles::new(PrecompileSpecId::from_spec_id(
108                self.cfg_env.spec,
109            ))),
110        };
111
112        let inner = Context::mainnet()
113            .with_block(self.block_env)
114            .with_cfg(self.cfg_env)
115            .with_db(self.db)
116            .build_mainnet_with_inspector(self.inspector)
117            .with_precompiles(precompiles);
118
119        EthEvm { inner, inspect: self.inspect }
120    }
121}
122
123/// Ethereum EVM implementation.
124///
125/// This is a wrapper type around the `revm` ethereum evm with optional [`Inspector`] (tracing)
126/// support. [`Inspector`] support is configurable at runtime because it's part of the underlying
127/// [`RevmEvm`] type.
128#[expect(missing_debug_implementations)]
129pub struct EthEvm<DB: Database, I, PRECOMPILE = EthPrecompiles> {
130    inner: RevmEvm<
131        EthEvmContext<DB>,
132        I,
133        EthInstructions<EthInterpreter, EthEvmContext<DB>>,
134        PRECOMPILE,
135        EthFrame,
136    >,
137    inspect: bool,
138}
139
140impl<DB: Database, I, PRECOMPILE> EthEvm<DB, I, PRECOMPILE> {
141    /// Creates a new Ethereum EVM instance.
142    ///
143    /// The `inspect` argument determines whether the configured [`Inspector`] of the given
144    /// [`RevmEvm`] should be invoked on [`Evm::transact`].
145    pub const fn new(
146        evm: RevmEvm<
147            EthEvmContext<DB>,
148            I,
149            EthInstructions<EthInterpreter, EthEvmContext<DB>>,
150            PRECOMPILE,
151            EthFrame,
152        >,
153        inspect: bool,
154    ) -> Self {
155        Self { inner: evm, inspect }
156    }
157
158    /// Consumes self and return the inner EVM instance.
159    pub fn into_inner(
160        self,
161    ) -> RevmEvm<
162        EthEvmContext<DB>,
163        I,
164        EthInstructions<EthInterpreter, EthEvmContext<DB>>,
165        PRECOMPILE,
166        EthFrame,
167    > {
168        self.inner
169    }
170
171    /// Provides a reference to the EVM context.
172    pub const fn ctx(&self) -> &EthEvmContext<DB> {
173        &self.inner.ctx
174    }
175
176    /// Provides a mutable reference to the EVM context.
177    pub const fn ctx_mut(&mut self) -> &mut EthEvmContext<DB> {
178        &mut self.inner.ctx
179    }
180}
181
182impl<DB: Database, I, PRECOMPILE> Deref for EthEvm<DB, I, PRECOMPILE> {
183    type Target = EthEvmContext<DB>;
184
185    #[inline]
186    fn deref(&self) -> &Self::Target {
187        self.ctx()
188    }
189}
190
191impl<DB: Database, I, PRECOMPILE> DerefMut for EthEvm<DB, I, PRECOMPILE> {
192    #[inline]
193    fn deref_mut(&mut self) -> &mut Self::Target {
194        self.ctx_mut()
195    }
196}
197
198impl<DB, I, PRECOMPILE> Evm for EthEvm<DB, I, PRECOMPILE>
199where
200    DB: Database,
201    I: Inspector<EthEvmContext<DB>>,
202    PRECOMPILE: PrecompileProvider<EthEvmContext<DB>, Output = InterpreterResult>,
203{
204    type DB = DB;
205    type Tx = TxEnv;
206    type Error = EVMError<DB::Error>;
207    type HaltReason = HaltReason;
208    type Spec = SpecId;
209    type BlockEnv = BlockEnv;
210    type Precompiles = PRECOMPILE;
211    type Inspector = I;
212
213    fn block(&self) -> &BlockEnv {
214        &self.block
215    }
216
217    fn chain_id(&self) -> u64 {
218        self.cfg.chain_id
219    }
220
221    fn transact_raw(
222        &mut self,
223        tx: Self::Tx,
224    ) -> Result<ResultAndState<Self::HaltReason>, Self::Error> {
225        if self.inspect {
226            self.inner.inspect_tx(tx)
227        } else {
228            self.inner.transact(tx)
229        }
230    }
231
232    fn transact_system_call(
233        &mut self,
234        caller: Address,
235        contract: Address,
236        data: Bytes,
237    ) -> Result<ResultAndState<Self::HaltReason>, Self::Error> {
238        self.inner.system_call_with_caller(caller, contract, data)
239    }
240
241    fn finish(self) -> (Self::DB, EvmEnv<Self::Spec>) {
242        let Context { block: block_env, cfg: cfg_env, journaled_state, .. } = self.inner.ctx;
243
244        (journaled_state.database, EvmEnv { block_env, cfg_env })
245    }
246
247    fn set_inspector_enabled(&mut self, enabled: bool) {
248        self.inspect = enabled;
249    }
250
251    fn components(&self) -> (&Self::DB, &Self::Inspector, &Self::Precompiles) {
252        (&self.inner.ctx.journaled_state.database, &self.inner.inspector, &self.inner.precompiles)
253    }
254
255    fn components_mut(&mut self) -> (&mut Self::DB, &mut Self::Inspector, &mut Self::Precompiles) {
256        (
257            &mut self.inner.ctx.journaled_state.database,
258            &mut self.inner.inspector,
259            &mut self.inner.precompiles,
260        )
261    }
262}
263
264/// Factory producing [`EthEvm`].
265#[derive(Debug, Default, Clone, Copy)]
266#[non_exhaustive]
267pub struct EthEvmFactory;
268
269impl EvmFactory for EthEvmFactory {
270    type Evm<DB: Database, I: Inspector<EthEvmContext<DB>>> = EthEvm<DB, I, Self::Precompiles>;
271    type Context<DB: Database> = Context<BlockEnv, TxEnv, CfgEnv, DB>;
272    type Tx = TxEnv;
273    type Error<DBError: core::error::Error + Send + Sync + 'static> = EVMError<DBError>;
274    type HaltReason = HaltReason;
275    type Spec = SpecId;
276    type BlockEnv = BlockEnv;
277    type Precompiles = PrecompilesMap;
278
279    fn create_evm<DB: Database>(&self, db: DB, input: EvmEnv) -> Self::Evm<DB, NoOpInspector> {
280        EthEvmBuilder::new(db, input).build()
281    }
282
283    fn create_evm_with_inspector<DB: Database, I: Inspector<Self::Context<DB>>>(
284        &self,
285        db: DB,
286        input: EvmEnv,
287        inspector: I,
288    ) -> Self::Evm<DB, I> {
289        EthEvmBuilder::new(db, input).activate_inspector(inspector).build()
290    }
291}
292
293#[cfg(test)]
294mod tests {
295    use super::*;
296    use alloy_primitives::address;
297    use revm::{database_interface::EmptyDB, primitives::hardfork::SpecId};
298
299    #[test]
300    fn test_precompiles_with_correct_spec() {
301        // create tests where precompile should be available for later specs but not earlier ones
302        let specs_to_test = [
303            // MODEXP (0x05) was added in Byzantium, should not exist in Frontier
304            (
305                address!("0x0000000000000000000000000000000000000005"),
306                SpecId::FRONTIER,  // Early spec - should NOT have this precompile
307                SpecId::BYZANTIUM, // Later spec - should have this precompile
308                "MODEXP",
309            ),
310            // BLAKE2F (0x09) was added in Istanbul, should not exist in Byzantium
311            (
312                address!("0x0000000000000000000000000000000000000009"),
313                SpecId::BYZANTIUM, // Early spec - should NOT have this precompile
314                SpecId::ISTANBUL,  // Later spec - should have this precompile
315                "BLAKE2F",
316            ),
317        ];
318
319        for (precompile_addr, early_spec, later_spec, name) in specs_to_test {
320            let mut early_cfg_env = CfgEnv::default();
321            early_cfg_env.spec = early_spec;
322            early_cfg_env.chain_id = 1;
323
324            let early_env = EvmEnv { block_env: BlockEnv::default(), cfg_env: early_cfg_env };
325            let factory = EthEvmFactory;
326            let mut early_evm = factory.create_evm(EmptyDB::default(), early_env);
327
328            // precompile should NOT be available in early spec
329            assert!(
330                early_evm.precompiles_mut().get(&precompile_addr).is_none(),
331                "{name} precompile at {precompile_addr:?} should NOT be available for early spec {early_spec:?}"
332            );
333
334            let mut later_cfg_env = CfgEnv::default();
335            later_cfg_env.spec = later_spec;
336            later_cfg_env.chain_id = 1;
337
338            let later_env = EvmEnv { block_env: BlockEnv::default(), cfg_env: later_cfg_env };
339            let mut later_evm = factory.create_evm(EmptyDB::default(), later_env);
340
341            // precompile should be available in later spec
342            assert!(
343                later_evm.precompiles_mut().get(&precompile_addr).is_some(),
344                "{name} precompile at {precompile_addr:?} should be available for later spec {later_spec:?}"
345            );
346        }
347    }
348}