reth_evm_ethereum/
lib.rs

1//! EVM config for vanilla ethereum.
2//!
3//! # Revm features
4//!
5//! This crate does __not__ enforce specific revm features such as `blst` or `c-kzg`, which are
6//! critical for revm's evm internals, it is the responsibility of the implementer to ensure the
7//! proper features are selected.
8
9#![doc(
10    html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png",
11    html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256",
12    issue_tracker_base_url = "https://github.com/SeismicSystems/seismic-reth/issues/"
13)]
14#![cfg_attr(not(test), warn(unused_crate_dependencies))]
15#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
16#![cfg_attr(not(feature = "std"), no_std)]
17
18extern crate alloc;
19
20use alloc::{borrow::Cow, sync::Arc, vec::Vec};
21use alloy_consensus::{BlockHeader, Header};
22pub use alloy_evm::EthEvm;
23use alloy_evm::{
24    eth::{EthBlockExecutionCtx, EthBlockExecutorFactory},
25    EthEvmFactory, FromRecoveredTx, FromTxWithEncoded,
26};
27use alloy_primitives::{Bytes, U256};
28use core::{convert::Infallible, fmt::Debug};
29use reth_chainspec::{ChainSpec, EthChainSpec, MAINNET};
30use reth_ethereum_primitives::{Block, EthPrimitives, TransactionSigned};
31use reth_evm::{ConfigureEvm, EvmEnv, EvmFactory, NextBlockEnvAttributes, TransactionEnv};
32use reth_primitives_traits::{SealedBlock, SealedHeader};
33use revm::{
34    context::{BlockEnv, CfgEnv},
35    context_interface::block::BlobExcessGasAndPrice,
36    primitives::hardfork::SpecId,
37};
38
39mod config;
40use alloy_eips::{eip1559::INITIAL_BASE_FEE, eip7840::BlobParams};
41pub use config::{revm_spec, revm_spec_by_timestamp_and_block_number};
42use reth_ethereum_forks::EthereumHardfork;
43
44pub mod execute;
45
46pub mod build;
47pub use build::EthBlockAssembler;
48
49mod receipt;
50pub use receipt::RethReceiptBuilder;
51
52#[cfg(feature = "test-utils")]
53mod test_utils;
54#[cfg(feature = "test-utils")]
55pub use test_utils::*;
56
57/// Ethereum-related EVM configuration.
58#[derive(Debug, Clone)]
59pub struct EthEvmConfig<EvmFactory = EthEvmFactory> {
60    /// Inner [`EthBlockExecutorFactory`].
61    pub executor_factory: EthBlockExecutorFactory<RethReceiptBuilder, Arc<ChainSpec>, EvmFactory>,
62    /// Ethereum block assembler.
63    pub block_assembler: EthBlockAssembler<ChainSpec>,
64}
65
66impl EthEvmConfig {
67    /// Creates a new Ethereum EVM configuration with the given chain spec.
68    pub fn new(chain_spec: Arc<ChainSpec>) -> Self {
69        Self::ethereum(chain_spec)
70    }
71
72    /// Creates a new Ethereum EVM configuration.
73    pub fn ethereum(chain_spec: Arc<ChainSpec>) -> Self {
74        Self::new_with_evm_factory(chain_spec, EthEvmFactory::default())
75    }
76
77    /// Creates a new Ethereum EVM configuration for the ethereum mainnet.
78    pub fn mainnet() -> Self {
79        Self::ethereum(MAINNET.clone())
80    }
81}
82
83impl<EvmFactory> EthEvmConfig<EvmFactory> {
84    /// Creates a new Ethereum EVM configuration with the given chain spec and EVM factory.
85    pub fn new_with_evm_factory(chain_spec: Arc<ChainSpec>, evm_factory: EvmFactory) -> Self {
86        Self {
87            block_assembler: EthBlockAssembler::new(chain_spec.clone()),
88            executor_factory: EthBlockExecutorFactory::new(
89                RethReceiptBuilder::default(),
90                chain_spec,
91                evm_factory,
92            ),
93        }
94    }
95
96    /// Returns the chain spec associated with this configuration.
97    pub const fn chain_spec(&self) -> &Arc<ChainSpec> {
98        self.executor_factory.spec()
99    }
100
101    /// Returns blob params by hard fork as specified in chain spec.
102    /// Blob params are in format `(spec id, target blob count, max blob count)`.
103    pub fn blob_max_and_target_count_by_hardfork(&self) -> Vec<(SpecId, u64, u64)> {
104        let cancun = self.chain_spec().blob_params.cancun();
105        let prague = self.chain_spec().blob_params.prague();
106        let osaka = self.chain_spec().blob_params.osaka();
107        Vec::from([
108            (SpecId::CANCUN, cancun.target_blob_count, cancun.max_blob_count),
109            (SpecId::PRAGUE, prague.target_blob_count, prague.max_blob_count),
110            (SpecId::OSAKA, osaka.target_blob_count, osaka.max_blob_count),
111        ])
112    }
113
114    /// Sets the extra data for the block assembler.
115    pub fn with_extra_data(mut self, extra_data: Bytes) -> Self {
116        self.block_assembler.extra_data = extra_data;
117        self
118    }
119}
120
121impl<EvmF> ConfigureEvm for EthEvmConfig<EvmF>
122where
123    EvmF: EvmFactory<
124            Tx: TransactionEnv
125                    + FromRecoveredTx<TransactionSigned>
126                    + FromTxWithEncoded<TransactionSigned>,
127            Spec = SpecId,
128            // Precompiles = PrecompilesMap,
129        > + Clone
130        + Debug
131        + Send
132        + Sync
133        + Unpin
134        + 'static,
135{
136    type Primitives = EthPrimitives;
137    type Error = Infallible;
138    type NextBlockEnvCtx = NextBlockEnvAttributes;
139    type BlockExecutorFactory = EthBlockExecutorFactory<RethReceiptBuilder, Arc<ChainSpec>, EvmF>;
140    type BlockAssembler = EthBlockAssembler<ChainSpec>;
141
142    fn block_executor_factory(&self) -> &Self::BlockExecutorFactory {
143        &self.executor_factory
144    }
145
146    fn block_assembler(&self) -> &Self::BlockAssembler {
147        &self.block_assembler
148    }
149
150    fn evm_env(&self, header: &Header) -> EvmEnv {
151        let spec = config::revm_spec(self.chain_spec(), header);
152
153        // configure evm env based on parent block
154        let cfg_env = CfgEnv::new()
155            .with_chain_id(self.chain_spec().chain().id())
156            .with_spec(spec)
157            .with_blob_max_and_target_count(self.blob_max_and_target_count_by_hardfork());
158
159        // derive the EIP-4844 blob fees from the header's `excess_blob_gas` and the current
160        // blobparams
161        let blob_excess_gas_and_price = header
162            .excess_blob_gas
163            .zip(self.chain_spec().blob_params_at_timestamp(header.timestamp))
164            .map(|(excess_blob_gas, params)| {
165                let blob_gasprice = params.calc_blob_fee(excess_blob_gas);
166                BlobExcessGasAndPrice { excess_blob_gas, blob_gasprice }
167            });
168
169        let block_env = BlockEnv {
170            number: header.number(),
171            beneficiary: header.beneficiary(),
172            timestamp: header.timestamp(),
173            difficulty: if spec >= SpecId::MERGE { U256::ZERO } else { header.difficulty() },
174            prevrandao: if spec >= SpecId::MERGE { header.mix_hash() } else { None },
175            gas_limit: header.gas_limit(),
176            basefee: header.base_fee_per_gas().unwrap_or_default(),
177            blob_excess_gas_and_price,
178        };
179
180        EvmEnv { cfg_env, block_env }
181    }
182
183    fn next_evm_env(
184        &self,
185        parent: &Header,
186        attributes: &NextBlockEnvAttributes,
187    ) -> Result<EvmEnv, Self::Error> {
188        // ensure we're not missing any timestamp based hardforks
189        let spec_id = revm_spec_by_timestamp_and_block_number(
190            self.chain_spec(),
191            attributes.timestamp,
192            parent.number() + 1,
193        );
194
195        // configure evm env based on parent block
196        let cfg = CfgEnv::new()
197            .with_chain_id(self.chain_spec().chain().id())
198            .with_spec(spec_id)
199            .with_blob_max_and_target_count(self.blob_max_and_target_count_by_hardfork());
200
201        let blob_params = self.chain_spec().blob_params_at_timestamp(attributes.timestamp);
202        // if the parent block did not have excess blob gas (i.e. it was pre-cancun), but it is
203        // cancun now, we need to set the excess blob gas to the default value(0)
204        let blob_excess_gas_and_price = parent
205            .maybe_next_block_excess_blob_gas(blob_params)
206            .or_else(|| (spec_id == SpecId::CANCUN).then_some(0))
207            .map(|excess_blob_gas| {
208                let blob_gasprice =
209                    blob_params.unwrap_or_else(BlobParams::cancun).calc_blob_fee(excess_blob_gas);
210                BlobExcessGasAndPrice { excess_blob_gas, blob_gasprice }
211            });
212
213        let mut basefee = parent.next_block_base_fee(
214            self.chain_spec().base_fee_params_at_timestamp(attributes.timestamp),
215        );
216
217        let mut gas_limit = attributes.gas_limit;
218
219        // If we are on the London fork boundary, we need to multiply the parent's gas limit by the
220        // elasticity multiplier to get the new gas limit.
221        if self.chain_spec().fork(EthereumHardfork::London).transitions_at_block(parent.number + 1)
222        {
223            let elasticity_multiplier = self
224                .chain_spec()
225                .base_fee_params_at_timestamp(attributes.timestamp)
226                .elasticity_multiplier;
227
228            // multiply the gas limit by the elasticity multiplier
229            gas_limit *= elasticity_multiplier as u64;
230
231            // set the base fee to the initial base fee from the EIP-1559 spec
232            basefee = Some(INITIAL_BASE_FEE)
233        }
234
235        let block_env = BlockEnv {
236            number: parent.number + 1,
237            beneficiary: attributes.suggested_fee_recipient,
238            timestamp: attributes.timestamp,
239            difficulty: U256::ZERO,
240            prevrandao: Some(attributes.prev_randao),
241            gas_limit,
242            // calculate basefee based on parent block's gas usage
243            basefee: basefee.unwrap_or_default(),
244            // calculate excess gas based on parent block's blob gas usage
245            blob_excess_gas_and_price,
246        };
247
248        Ok((cfg, block_env).into())
249    }
250
251    fn context_for_block<'a>(&self, block: &'a SealedBlock<Block>) -> EthBlockExecutionCtx<'a> {
252        EthBlockExecutionCtx {
253            parent_hash: block.header().parent_hash,
254            parent_beacon_block_root: block.header().parent_beacon_block_root,
255            ommers: &block.body().ommers,
256            withdrawals: block.body().withdrawals.as_ref().map(Cow::Borrowed),
257        }
258    }
259
260    fn context_for_next_block(
261        &self,
262        parent: &SealedHeader,
263        attributes: Self::NextBlockEnvCtx,
264    ) -> EthBlockExecutionCtx<'_> {
265        EthBlockExecutionCtx {
266            parent_hash: parent.hash(),
267            parent_beacon_block_root: attributes.parent_beacon_block_root,
268            ommers: &[],
269            withdrawals: attributes.withdrawals.map(Cow::Owned),
270        }
271    }
272}
273
274#[cfg(test)]
275mod tests {
276    use super::*;
277    use alloy_consensus::Header;
278    use alloy_genesis::Genesis;
279    use reth_chainspec::{Chain, ChainSpec};
280    use reth_evm::{execute::ProviderError, EvmEnv};
281    use revm::{
282        context::{BlockEnv, CfgEnv},
283        database::CacheDB,
284        database_interface::EmptyDBTyped,
285        inspector::NoOpInspector,
286    };
287
288    #[test]
289    fn test_fill_cfg_and_block_env() {
290        // Create a default header
291        let header = Header::default();
292
293        // Build the ChainSpec for Ethereum mainnet, activating London, Paris, and Shanghai
294        // hardforks
295        let chain_spec = ChainSpec::builder()
296            .chain(Chain::mainnet())
297            .genesis(Genesis::default())
298            .london_activated()
299            .paris_activated()
300            .shanghai_activated()
301            .build();
302
303        // Use the `EthEvmConfig` to fill the `cfg_env` and `block_env` based on the ChainSpec,
304        // Header, and total difficulty
305        let EvmEnv { cfg_env, .. } =
306            EthEvmConfig::new(Arc::new(chain_spec.clone())).evm_env(&header);
307
308        // Assert that the chain ID in the `cfg_env` is correctly set to the chain ID of the
309        // ChainSpec
310        assert_eq!(cfg_env.chain_id, chain_spec.chain().id());
311    }
312
313    #[test]
314    fn test_evm_with_env_default_spec() {
315        let evm_config = EthEvmConfig::mainnet();
316
317        let db = CacheDB::<EmptyDBTyped<ProviderError>>::default();
318
319        let evm_env = EvmEnv::default();
320
321        let evm = evm_config.evm_with_env(db, evm_env.clone());
322
323        // Check that the EVM environment
324        assert_eq!(evm.block, evm_env.block_env);
325        assert_eq!(evm.cfg, evm_env.cfg_env);
326    }
327
328    #[test]
329    fn test_evm_with_env_custom_cfg() {
330        let evm_config = EthEvmConfig::mainnet();
331
332        let db = CacheDB::<EmptyDBTyped<ProviderError>>::default();
333
334        // Create a custom configuration environment with a chain ID of 111
335        let cfg = CfgEnv::default().with_chain_id(111);
336
337        let evm_env = EvmEnv { cfg_env: cfg.clone(), ..Default::default() };
338
339        let evm = evm_config.evm_with_env(db, evm_env);
340
341        // Check that the EVM environment is initialized with the custom environment
342        assert_eq!(evm.cfg, cfg);
343    }
344
345    #[test]
346    fn test_evm_with_env_custom_block_and_tx() {
347        let evm_config = EthEvmConfig::mainnet();
348
349        let db = CacheDB::<EmptyDBTyped<ProviderError>>::default();
350
351        // Create customs block and tx env
352        let block =
353            BlockEnv { basefee: 1000, gas_limit: 10_000_000, number: 42, ..Default::default() };
354
355        let evm_env = EvmEnv { block_env: block, ..Default::default() };
356
357        let evm = evm_config.evm_with_env(db, evm_env.clone());
358
359        // Verify that the block and transaction environments are set correctly
360        assert_eq!(evm.block, evm_env.block_env);
361
362        // Default spec ID
363        assert_eq!(evm.cfg.spec, SpecId::default());
364    }
365
366    #[test]
367    fn test_evm_with_spec_id() {
368        let evm_config = EthEvmConfig::mainnet();
369
370        let db = CacheDB::<EmptyDBTyped<ProviderError>>::default();
371
372        let evm_env = EvmEnv {
373            cfg_env: CfgEnv::new().with_spec(SpecId::CONSTANTINOPLE),
374            ..Default::default()
375        };
376
377        let evm = evm_config.evm_with_env(db, evm_env);
378
379        // Check that the spec ID is setup properly
380        assert_eq!(evm.cfg.spec, SpecId::CONSTANTINOPLE);
381    }
382
383    #[test]
384    fn test_evm_with_env_and_default_inspector() {
385        let evm_config = EthEvmConfig::mainnet();
386        let db = CacheDB::<EmptyDBTyped<ProviderError>>::default();
387
388        let evm_env = EvmEnv::default();
389
390        let evm = evm_config.evm_with_env_and_inspector(db, evm_env.clone(), NoOpInspector {});
391
392        // Check that the EVM environment is set to default values
393        assert_eq!(evm.block, evm_env.block_env);
394        assert_eq!(evm.cfg, evm_env.cfg_env);
395    }
396
397    #[test]
398    fn test_evm_with_env_inspector_and_custom_cfg() {
399        let evm_config = EthEvmConfig::mainnet();
400        let db = CacheDB::<EmptyDBTyped<ProviderError>>::default();
401
402        let cfg_env = CfgEnv::default().with_chain_id(111);
403        let block = BlockEnv::default();
404        let evm_env = EvmEnv { cfg_env: cfg_env.clone(), block_env: block };
405
406        let evm = evm_config.evm_with_env_and_inspector(db, evm_env, NoOpInspector {});
407
408        // Check that the EVM environment is set with custom configuration
409        assert_eq!(evm.cfg, cfg_env);
410        assert_eq!(evm.cfg.spec, SpecId::default());
411    }
412
413    #[test]
414    fn test_evm_with_env_inspector_and_custom_block_tx() {
415        let evm_config = EthEvmConfig::mainnet();
416        let db = CacheDB::<EmptyDBTyped<ProviderError>>::default();
417
418        // Create custom block and tx environment
419        let block =
420            BlockEnv { basefee: 1000, gas_limit: 10_000_000, number: 42, ..Default::default() };
421        let evm_env = EvmEnv { block_env: block, ..Default::default() };
422
423        let evm = evm_config.evm_with_env_and_inspector(db, evm_env.clone(), NoOpInspector {});
424
425        // Verify that the block and transaction environments are set correctly
426        assert_eq!(evm.block, evm_env.block_env);
427        assert_eq!(evm.cfg.spec, SpecId::default());
428    }
429
430    #[test]
431    fn test_evm_with_env_inspector_and_spec_id() {
432        let evm_config = EthEvmConfig::mainnet();
433        let db = CacheDB::<EmptyDBTyped<ProviderError>>::default();
434
435        let evm_env = EvmEnv {
436            cfg_env: CfgEnv::new().with_spec(SpecId::CONSTANTINOPLE),
437            ..Default::default()
438        };
439
440        let evm = evm_config.evm_with_env_and_inspector(db, evm_env.clone(), NoOpInspector {});
441
442        // Check that the spec ID is set properly
443        assert_eq!(evm.block, evm_env.block_env);
444        assert_eq!(evm.cfg, evm_env.cfg_env);
445        assert_eq!(evm.tx, Default::default());
446    }
447}