risc0_steel/
ethereum.rs

1// Copyright 2025 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,
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    ]),
42});
43
44/// The Ethereum Holešky [ChainSpec].
45pub static ETH_HOLESKY_CHAIN_SPEC: LazyLock<EthChainSpec> = LazyLock::new(|| ChainSpec {
46    chain_id: 17000,
47    forks: BTreeMap::from([
48        (SpecId::MERGE, ForkCondition::Block(0)),
49        (SpecId::SHANGHAI, ForkCondition::Timestamp(1696000704)),
50        (SpecId::CANCUN, ForkCondition::Timestamp(1707305664)),
51        (SpecId::PRAGUE, ForkCondition::Timestamp(1740434112)),
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    ]),
64});
65
66/// [ChainSpec] for a custom Steel Testnet using the Prague EVM.
67pub static STEEL_TEST_PRAGUE_CHAIN_SPEC: LazyLock<ChainSpec<SpecId>> =
68    LazyLock::new(|| ChainSpec::new_single(5733100018, SpecId::PRAGUE));
69
70/// [EvmFactory] for Ethereum.
71#[derive(Debug, Default, Clone, Copy, Serialize, Deserialize)]
72#[non_exhaustive]
73pub struct EthEvmFactory;
74
75impl EvmFactory for EthEvmFactory {
76    type Evm<DB: Database> = <AlloyEthEvmFactory as AlloyEvmFactory>::Evm<DB, NoOpInspector>;
77    type Tx = <AlloyEthEvmFactory as AlloyEvmFactory>::Tx;
78    type Error<DBError: Error + Send + Sync + 'static> =
79        <AlloyEthEvmFactory as AlloyEvmFactory>::Error<DBError>;
80    type HaltReason = <AlloyEthEvmFactory as AlloyEvmFactory>::HaltReason;
81    type Spec = <AlloyEthEvmFactory as AlloyEvmFactory>::Spec;
82    type Header = EthBlockHeader;
83
84    fn new_tx(address: Address, data: Bytes) -> Self::Tx {
85        TxEnv {
86            caller: address,
87            kind: TxKind::Call(address),
88            data,
89            chain_id: None,
90            ..Default::default()
91        }
92    }
93
94    fn create_evm<DB: Database>(
95        db: DB,
96        chain_id: u64,
97        spec: Self::Spec,
98        header: &Self::Header,
99    ) -> Self::Evm<DB> {
100        let mut cfg_env = CfgEnv::new_with_spec(spec).with_chain_id(chain_id);
101        cfg_env.disable_nonce_check = true;
102        cfg_env.disable_balance_check = true;
103        cfg_env.disable_block_gas_limit = true;
104        // Disabled because eth_call is sometimes used with eoa senders
105        cfg_env.disable_eip3607 = true;
106        // The basefee should be ignored for eth_call
107        cfg_env.disable_base_fee = true;
108
109        let block_env = header.to_block_env(spec);
110
111        AlloyEthEvmFactory::default().create_evm(db, (cfg_env, block_env).into())
112    }
113}
114
115/// [ChainSpec] for Ethereum.
116pub type EthChainSpec = ChainSpec<SpecId>;
117
118/// [EvmEnv] for Ethereum.
119pub type EthEvmEnv<D, C> = EvmEnv<D, EthEvmFactory, C>;
120
121/// [EvmInput] for Ethereum.
122pub type EthEvmInput = EvmInput<EthEvmFactory>;
123
124/// [EvmBlockHeader] for Ethereum.
125pub type EthBlockHeader = RlpHeader<alloy_consensus::Header>;
126
127impl EvmBlockHeader for EthBlockHeader {
128    type Spec = SpecId;
129
130    #[inline]
131    fn parent_hash(&self) -> &B256 {
132        &self.inner().parent_hash
133    }
134    #[inline]
135    fn number(&self) -> BlockNumber {
136        self.inner().number
137    }
138    #[inline]
139    fn timestamp(&self) -> u64 {
140        self.inner().timestamp
141    }
142    #[inline]
143    fn state_root(&self) -> &B256 {
144        &self.inner().state_root
145    }
146    #[inline]
147    fn receipts_root(&self) -> &B256 {
148        &self.inner().receipts_root
149    }
150    #[inline]
151    fn logs_bloom(&self) -> &alloy_primitives::Bloom {
152        &self.inner().logs_bloom
153    }
154
155    #[inline]
156    fn to_block_env(&self, spec: SpecId) -> BlockEnv {
157        let header = self.inner();
158
159        let blob_excess_gas_and_price = header.excess_blob_gas.map(|excess_blob_gas| match spec {
160            SpecId::CANCUN => BlobExcessGasAndPrice::new(
161                excess_blob_gas,
162                eip4844::BLOB_GASPRICE_UPDATE_FRACTION as u64,
163            ),
164            SpecId::PRAGUE => BlobExcessGasAndPrice::new(
165                excess_blob_gas,
166                eip7691::BLOB_GASPRICE_UPDATE_FRACTION_PECTRA as u64,
167            ),
168            SpecId::OSAKA => BlobExcessGasAndPrice::new(
169                excess_blob_gas,
170                eip7691::BLOB_GASPRICE_UPDATE_FRACTION_PECTRA as u64,
171            ),
172            _ => unimplemented!("unsupported spec with `excess_blob_gas`: {spec}"),
173        });
174
175        BlockEnv {
176            number: U256::from(header.number),
177            beneficiary: header.beneficiary,
178            timestamp: U256::from(header.timestamp),
179            gas_limit: header.gas_limit,
180            basefee: header.base_fee_per_gas.unwrap_or_default(),
181            difficulty: header.difficulty,
182            prevrandao: (spec >= SpecId::MERGE).then_some(header.mix_hash),
183            blob_excess_gas_and_price,
184        }
185    }
186}
187
188#[cfg(test)]
189mod tests {
190    use alloy::primitives::b256;
191
192    use super::{
193        ETH_HOLESKY_CHAIN_SPEC, ETH_MAINNET_CHAIN_SPEC, ETH_SEPOLIA_CHAIN_SPEC,
194        STEEL_TEST_PRAGUE_CHAIN_SPEC,
195    };
196
197    // NOTE: If these are updated here, make sure to update them in Steel.sol
198
199    #[test]
200    fn mainnet_spec_digest() {
201        assert_eq!(
202            ETH_MAINNET_CHAIN_SPEC.digest(),
203            b256!("0x9a223c7ca04c969f1cacbe5b8db44c308b2c53390505d3d48c834ed4469fc839")
204        );
205    }
206
207    #[test]
208    fn sepolia_spec_digest() {
209        assert_eq!(
210            ETH_SEPOLIA_CHAIN_SPEC.digest(),
211            b256!("0x5c9552dc9bfad8572ded4f818bb35b0f4260660c1554236986b768ae999b4b60")
212        );
213    }
214
215    #[test]
216    fn holesky_spec_digest() {
217        assert_eq!(
218            ETH_HOLESKY_CHAIN_SPEC.digest(),
219            b256!("0x8eae1ba5f877e6ad7007bf6985f5245be7d758457fb4eb7e6a72d47f49bea389")
220        );
221    }
222
223    #[test]
224    fn testnet_spec_digest() {
225        assert_eq!(
226            STEEL_TEST_PRAGUE_CHAIN_SPEC.digest(),
227            b256!("0x33e32d9590cd4b168773ca27de65d535f2e744274b1437acb712dd4264f2eb87")
228        );
229    }
230}