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/// [EvmFactory] for Ethereum.
67#[derive(Debug, Default, Clone, Copy, Serialize, Deserialize)]
68#[non_exhaustive]
69pub struct EthEvmFactory;
70
71impl EvmFactory for EthEvmFactory {
72    type Evm<DB: Database> = <AlloyEthEvmFactory as AlloyEvmFactory>::Evm<DB, NoOpInspector>;
73    type Tx = <AlloyEthEvmFactory as AlloyEvmFactory>::Tx;
74    type Error<DBError: Error + Send + Sync + 'static> =
75        <AlloyEthEvmFactory as AlloyEvmFactory>::Error<DBError>;
76    type HaltReason = <AlloyEthEvmFactory as AlloyEvmFactory>::HaltReason;
77    type Spec = <AlloyEthEvmFactory as AlloyEvmFactory>::Spec;
78    type Header = EthBlockHeader;
79
80    fn new_tx(address: Address, data: Bytes) -> Self::Tx {
81        TxEnv {
82            caller: address,
83            kind: TxKind::Call(address),
84            data,
85            chain_id: None,
86            ..Default::default()
87        }
88    }
89
90    fn create_evm<DB: Database>(
91        db: DB,
92        chain_id: u64,
93        spec: Self::Spec,
94        header: &Self::Header,
95    ) -> Self::Evm<DB> {
96        let mut cfg_env = CfgEnv::new_with_spec(spec).with_chain_id(chain_id);
97        cfg_env.disable_nonce_check = true;
98        cfg_env.disable_balance_check = true;
99        cfg_env.disable_block_gas_limit = true;
100        // Disabled because eth_call is sometimes used with eoa senders
101        cfg_env.disable_eip3607 = true;
102        // The basefee should be ignored for eth_call
103        cfg_env.disable_base_fee = true;
104
105        let block_env = header.to_block_env(spec);
106
107        AlloyEthEvmFactory::default().create_evm(db, (cfg_env, block_env).into())
108    }
109}
110
111/// [ChainSpec] for Ethereum.
112pub type EthChainSpec = ChainSpec<SpecId>;
113
114/// [EvmEnv] for Ethereum.
115pub type EthEvmEnv<D, C> = EvmEnv<D, EthEvmFactory, C>;
116
117/// [EvmInput] for Ethereum.
118pub type EthEvmInput = EvmInput<EthEvmFactory>;
119
120/// [EvmBlockHeader] for Ethereum.
121pub type EthBlockHeader = RlpHeader<alloy_consensus::Header>;
122
123impl EvmBlockHeader for EthBlockHeader {
124    type Spec = SpecId;
125
126    #[inline]
127    fn parent_hash(&self) -> &B256 {
128        &self.inner().parent_hash
129    }
130    #[inline]
131    fn number(&self) -> BlockNumber {
132        self.inner().number
133    }
134    #[inline]
135    fn timestamp(&self) -> u64 {
136        self.inner().timestamp
137    }
138    #[inline]
139    fn state_root(&self) -> &B256 {
140        &self.inner().state_root
141    }
142    #[inline]
143    fn receipts_root(&self) -> &B256 {
144        &self.inner().receipts_root
145    }
146    #[inline]
147    fn logs_bloom(&self) -> &alloy_primitives::Bloom {
148        &self.inner().logs_bloom
149    }
150
151    #[inline]
152    fn to_block_env(&self, spec: SpecId) -> BlockEnv {
153        let header = self.inner();
154
155        let blob_excess_gas_and_price = header.excess_blob_gas.map(|excess_blob_gas| match spec {
156            SpecId::CANCUN => BlobExcessGasAndPrice::new(
157                excess_blob_gas,
158                eip4844::BLOB_GASPRICE_UPDATE_FRACTION as u64,
159            ),
160            SpecId::PRAGUE => BlobExcessGasAndPrice::new(
161                excess_blob_gas,
162                eip7691::BLOB_GASPRICE_UPDATE_FRACTION_PECTRA as u64,
163            ),
164            SpecId::OSAKA => BlobExcessGasAndPrice::new(
165                excess_blob_gas,
166                eip7691::BLOB_GASPRICE_UPDATE_FRACTION_PECTRA as u64,
167            ),
168            _ => unimplemented!("unsupported spec with `excess_blob_gas`: {spec}"),
169        });
170
171        BlockEnv {
172            number: U256::from(header.number),
173            beneficiary: header.beneficiary,
174            timestamp: U256::from(header.timestamp),
175            gas_limit: header.gas_limit,
176            basefee: header.base_fee_per_gas.unwrap_or_default(),
177            difficulty: header.difficulty,
178            prevrandao: (spec >= SpecId::MERGE).then_some(header.mix_hash),
179            blob_excess_gas_and_price,
180        }
181    }
182}