Skip to main content

risc0_steel/
ethereum.rs

1// Copyright 2026 RISC Zero, Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Type aliases and specifications for Ethereum.
16use crate::{
17    config::{ChainSpec, ForkCondition},
18    serde::RlpHeader,
19    EvmBlockHeader, EvmEnv, EvmFactory, EvmInput, EvmSpecId,
20};
21use alloy_eips::{eip4844, eip7691};
22use alloy_evm::{Database, EthEvmFactory as AlloyEthEvmFactory, EvmFactory as AlloyEvmFactory};
23use alloy_primitives::{Address, BlockNumber, Bytes, TxKind, B256, U256};
24use revm::{
25    context::{BlockEnv, CfgEnv, TxEnv},
26    context_interface::block::BlobExcessGasAndPrice,
27    inspector::NoOpInspector,
28    primitives::hardfork::SpecId,
29};
30use serde::{Deserialize, Serialize};
31use std::{collections::BTreeMap, error::Error, sync::LazyLock};
32
33/// The Ethereum Sepolia [ChainSpec].
34pub static ETH_SEPOLIA_CHAIN_SPEC: LazyLock<EthChainSpec> = LazyLock::new(|| ChainSpec {
35    chain_id: 11155111,
36    forks: BTreeMap::from([
37        (SpecId::MERGE, ForkCondition::Block(1735371)),
38        (SpecId::SHANGHAI, ForkCondition::Timestamp(1677557088)),
39        (SpecId::CANCUN, ForkCondition::Timestamp(1706655072)),
40        (SpecId::PRAGUE, ForkCondition::Timestamp(1741159776)),
41        (SpecId::OSAKA, ForkCondition::Timestamp(1760427360)),
42    ]),
43});
44
45/// The Ethereum Hoodi [ChainSpec].
46pub static ETH_HOODI_CHAIN_SPEC: LazyLock<EthChainSpec> = LazyLock::new(|| ChainSpec {
47    chain_id: 560048,
48    forks: BTreeMap::from([
49        (SpecId::CANCUN, ForkCondition::Block(0)),
50        (SpecId::PRAGUE, ForkCondition::Timestamp(1742999832)),
51        (SpecId::OSAKA, ForkCondition::Timestamp(1761677592)),
52    ]),
53});
54
55/// The Ethereum Mainnet [ChainSpec].
56pub static ETH_MAINNET_CHAIN_SPEC: LazyLock<EthChainSpec> = LazyLock::new(|| ChainSpec {
57    chain_id: 1,
58    forks: BTreeMap::from([
59        (SpecId::MERGE, ForkCondition::Block(15537394)),
60        (SpecId::SHANGHAI, ForkCondition::Timestamp(1681338455)),
61        (SpecId::CANCUN, ForkCondition::Timestamp(1710338135)),
62        (SpecId::PRAGUE, ForkCondition::Timestamp(1746612311)),
63        (SpecId::OSAKA, ForkCondition::Timestamp(1764798551)),
64    ]),
65});
66
67/// [ChainSpec] for a custom Steel Testnet using the Prague EVM.
68pub static STEEL_TEST_PRAGUE_CHAIN_SPEC: LazyLock<ChainSpec<SpecId>> =
69    LazyLock::new(|| ChainSpec::new_single(5733100018, SpecId::PRAGUE));
70
71/// [EvmFactory] for Ethereum.
72#[derive(Debug, Default, Clone, Copy, Serialize, Deserialize)]
73#[non_exhaustive]
74pub struct EthEvmFactory;
75
76impl EvmFactory for EthEvmFactory {
77    type Evm<DB: Database> = <AlloyEthEvmFactory as AlloyEvmFactory>::Evm<DB, NoOpInspector>;
78    type Tx = <AlloyEthEvmFactory as AlloyEvmFactory>::Tx;
79    type Error<DBError: Error + Send + Sync + 'static> =
80        <AlloyEthEvmFactory as AlloyEvmFactory>::Error<DBError>;
81    type HaltReason = <AlloyEthEvmFactory as AlloyEvmFactory>::HaltReason;
82    type Spec = <AlloyEthEvmFactory as AlloyEvmFactory>::Spec;
83    type SpecId = SpecId;
84    type Header = EthBlockHeader;
85
86    fn new_tx(address: Address, data: Bytes) -> Self::Tx {
87        TxEnv {
88            caller: address,
89            kind: TxKind::Call(address),
90            data,
91            chain_id: None,
92            ..Default::default()
93        }
94    }
95
96    fn create_evm<DB: Database>(
97        db: DB,
98        chain_id: u64,
99        spec_id: Self::SpecId,
100        header: &Self::Header,
101    ) -> Self::Evm<DB> {
102        let mut cfg_env = CfgEnv::new_with_spec(spec_id).with_chain_id(chain_id);
103        cfg_env.disable_nonce_check = true;
104        cfg_env.disable_balance_check = true;
105        cfg_env.disable_block_gas_limit = true;
106        // Disabled because eth_call is sometimes used with eoa senders
107        cfg_env.disable_eip3607 = true;
108        // The basefee should be ignored for eth_call
109        cfg_env.disable_base_fee = true;
110
111        let block_env = header.to_block_env(spec_id);
112
113        AlloyEthEvmFactory::default().create_evm(db, (cfg_env, block_env).into())
114    }
115}
116
117/// [ChainSpec] for Ethereum.
118pub type EthChainSpec = ChainSpec<SpecId>;
119
120/// [EvmEnv] for Ethereum.
121pub type EthEvmEnv<D, C> = EvmEnv<D, EthEvmFactory, C>;
122
123/// [EvmInput] for Ethereum.
124pub type EthEvmInput = EvmInput<EthEvmFactory>;
125
126/// [EvmBlockHeader] for Ethereum.
127pub type EthBlockHeader = RlpHeader<alloy_consensus::Header>;
128
129impl EvmSpecId for SpecId {
130    #[inline]
131    fn has_eip4788(&self) -> bool {
132        self >= &SpecId::CANCUN
133    }
134    #[inline]
135    fn has_eip2935(&self) -> bool {
136        self >= &SpecId::PRAGUE
137    }
138    #[inline]
139    fn to_u32(&self) -> u32 {
140        *self as u32
141    }
142}
143
144impl EvmBlockHeader for EthBlockHeader {
145    type SpecId = SpecId;
146
147    #[inline]
148    fn parent_hash(&self) -> &B256 {
149        &self.inner().parent_hash
150    }
151    #[inline]
152    fn number(&self) -> BlockNumber {
153        self.inner().number
154    }
155    #[inline]
156    fn timestamp(&self) -> u64 {
157        self.inner().timestamp
158    }
159    #[inline]
160    fn state_root(&self) -> &B256 {
161        &self.inner().state_root
162    }
163    #[inline]
164    fn receipts_root(&self) -> &B256 {
165        &self.inner().receipts_root
166    }
167    #[inline]
168    fn logs_bloom(&self) -> &alloy_primitives::Bloom {
169        &self.inner().logs_bloom
170    }
171
172    #[inline]
173    fn to_block_env(&self, spec: SpecId) -> BlockEnv {
174        let header = self.inner();
175
176        let blob_excess_gas_and_price = header.excess_blob_gas.map(|excess_blob_gas| match spec {
177            SpecId::CANCUN => BlobExcessGasAndPrice::new(
178                excess_blob_gas,
179                eip4844::BLOB_GASPRICE_UPDATE_FRACTION as u64,
180            ),
181            SpecId::PRAGUE => BlobExcessGasAndPrice::new(
182                excess_blob_gas,
183                eip7691::BLOB_GASPRICE_UPDATE_FRACTION_PECTRA as u64,
184            ),
185            SpecId::OSAKA => BlobExcessGasAndPrice::new(
186                excess_blob_gas,
187                eip7691::BLOB_GASPRICE_UPDATE_FRACTION_PECTRA as u64,
188            ),
189            _ => unimplemented!("unsupported spec with `excess_blob_gas`: {spec}"),
190        });
191
192        BlockEnv {
193            number: U256::from(header.number),
194            beneficiary: header.beneficiary,
195            timestamp: U256::from(header.timestamp),
196            gas_limit: header.gas_limit,
197            basefee: header.base_fee_per_gas.unwrap_or_default(),
198            difficulty: header.difficulty,
199            prevrandao: (spec >= SpecId::MERGE).then_some(header.mix_hash),
200            blob_excess_gas_and_price,
201            slot_num: header.slot_number.unwrap_or_default(),
202        }
203    }
204}
205
206#[cfg(test)]
207mod tests {
208    use alloy::primitives::b256;
209
210    use super::{
211        ETH_HOODI_CHAIN_SPEC, ETH_MAINNET_CHAIN_SPEC, ETH_SEPOLIA_CHAIN_SPEC,
212        STEEL_TEST_PRAGUE_CHAIN_SPEC,
213    };
214
215    // NOTE: If these are updated here, make sure to update them in Steel.sol
216
217    #[test]
218    fn mainnet_spec_digest() {
219        assert_eq!(
220            ETH_MAINNET_CHAIN_SPEC.digest(),
221            b256!("0x47dc59f84afd2e9e7a48c4012004ab7c77fbd9acf822bf1143b8442c6c8851d4")
222        );
223    }
224
225    #[test]
226    fn sepolia_spec_digest() {
227        assert_eq!(
228            ETH_SEPOLIA_CHAIN_SPEC.digest(),
229            b256!("0x90c1e882b1f0fda4dc7f1c66c07ed3d2a74e443834905faa9f32f583b71f459d")
230        );
231    }
232
233    #[test]
234    fn hoodi_spec_digest() {
235        assert_eq!(
236            ETH_HOODI_CHAIN_SPEC.digest(),
237            b256!("0x34cb1defd939572b00439d2c13f93c033b82227067371c910ad104d527c78860")
238        );
239    }
240
241    #[test]
242    fn testnet_spec_digest() {
243        assert_eq!(
244            STEEL_TEST_PRAGUE_CHAIN_SPEC.digest(),
245            b256!("0x33e32d9590cd4b168773ca27de65d535f2e744274b1437acb712dd4264f2eb87")
246        );
247    }
248}