alloy_eips/
eip7910.rs

1//! Implementation of [`EIP-7910`](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-7910.md).
2
3use crate::{eip2935, eip4788, eip6110, eip7002, eip7251, eip7840::BlobParams};
4use alloc::{borrow::ToOwned, collections::BTreeMap, string::String};
5use alloy_primitives::{Address, Bytes};
6use core::{fmt, str};
7
8/// Response type for `eth_config`
9#[derive(Clone, Debug, PartialEq)]
10#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
11#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
12pub struct EthConfig {
13    /// Fork configuration of the current active fork.
14    pub current: EthForkConfig,
15    /// Fork configuration of the next scheduled fork.
16    pub next: Option<EthForkConfig>,
17    /// Fork configuration of the last fork (before current).
18    pub last: Option<EthForkConfig>,
19}
20
21/// The fork configuration object as defined by [`EIP-7910`](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-7910.md).
22#[derive(Clone, Debug, PartialEq)]
23#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
24#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
25pub struct EthForkConfig {
26    /// The fork activation timestamp, represented as a JSON number in Unix epoch seconds (UTC).
27    /// For the "current" configuration, this reflects the actual activation time; for "next," it
28    /// is the scheduled time. Activation time is required. If a fork is activated at genesis
29    /// the value `0` is used. If the fork is not scheduled to be activated or its activation time
30    /// is unknown it should not be in the rpc results.
31    pub activation_time: u64,
32    /// The blob configuration parameters for the specific fork, as defined in the genesis file.
33    /// This is a JSON object with three members — `baseFeeUpdateFraction`, `max`, and `target` —
34    /// all represented as JSON numbers.
35    pub blob_schedule: BlobParams,
36    /// The chain ID of the current network, presented as a string with an unsigned 0x-prefixed
37    /// hexadecimal number, with all leading zeros removed. This specification does not support
38    /// chains without a chain ID or with a chain ID of zero.
39    ///
40    /// For purposes of canonicalization this value must always be a string.
41    #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
42    pub chain_id: u64,
43    /// The `FORK_HASH` value as specified in [EIP-6122](https://eips.ethereum.org/EIPS/eip-6122) of the specific fork,
44    /// presented as an unsigned 0x-prefixed hexadecimal numbers, with zeros left padded to a four
45    /// byte length, in lower case.
46    pub fork_id: Bytes,
47    /// A representation of the active precompile contracts for the fork. If a precompile is
48    /// replaced by an on-chain contract, or removed, then it is not included.
49    ///
50    /// This is a JSON object where the members are the 20-byte 0x-prefixed hexadecimal addresses
51    /// of the precompiles (with zeros preserved), and the values are agreed-upon names for each
52    /// contract, typically specified in the EIP defining that contract.
53    ///
54    /// For Cancun, the contract names are (in order): `ECREC`, `SHA256`, `RIPEMD160`, `ID`,
55    /// `MODEXP`, `BN256_ADD`, `BN256_MUL`, `BN256_PAIRING`, `BLAKE2F`, `KZG_POINT_EVALUATION`.
56    ///
57    /// For Prague, the added contracts are (in order): `BLS12_G1ADD`, `BLS12_G1MSM`,
58    /// `BLS12_G2ADD`, `BLS12_G2MSM`, `BLS12_PAIRING_CHECK`, `BLS12_MAP_FP_TO_G1`,
59    /// `BLS12_MAP_FP2_TO_G2`.
60    pub precompiles: BTreeMap<String, Address>,
61    /// A JSON object representing system-level contracts relevant to the fork, as introduced in
62    /// their defining EIPs. Keys are the contract names (e.g., BEACON_ROOTS_ADDRESS) from the
63    /// first EIP where they appeared, sorted alphabetically. Values are 20-byte addresses in
64    /// 0x-prefixed hexadecimal form, with leading zeros preserved. Omitted for forks before
65    /// Cancun.
66    ///
67    /// For Cancun the only system contract is `BEACON_ROOTS_ADDRESS`.
68    ///
69    /// For Prague the system contracts are (in order) `BEACON_ROOTS_ADDRESS`,
70    /// `CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS`, `DEPOSIT_CONTRACT_ADDRESS`,
71    /// `HISTORY_STORAGE_ADDRESS`, and `WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS`.
72    ///
73    /// Future forks MUST define the list of system contracts in their meta-EIPs.
74    pub system_contracts: BTreeMap<SystemContract, Address>,
75}
76
77/// System-level contracts for [`EthForkConfig`].
78#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Debug)]
79#[cfg_attr(feature = "serde", derive(serde_with::SerializeDisplay, serde_with::DeserializeFromStr))]
80pub enum SystemContract {
81    /// Beacon roots system contract.
82    BeaconRoots,
83    /// Consolidation requests predeploy system contract.
84    ConsolidationRequestPredeploy,
85    /// Deposit system contract.
86    DepositContract,
87    /// History storage system contract.
88    HistoryStorage,
89    /// Withdrawal requests predeploy system contract.
90    WithdrawalRequestPredeploy,
91}
92
93impl fmt::Display for SystemContract {
94    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
95        let str = match self {
96            Self::BeaconRoots => "BEACON_ROOTS",
97            Self::ConsolidationRequestPredeploy => "CONSOLIDATION_REQUEST_PREDEPLOY",
98            Self::DepositContract => "DEPOSIT_CONTRACT",
99            Self::HistoryStorage => "HISTORY_STORAGE",
100            Self::WithdrawalRequestPredeploy => "WITHDRAWAL_REQUEST_PREDEPLOY",
101        };
102        write!(f, "{str}_ADDRESS")
103    }
104}
105
106impl str::FromStr for SystemContract {
107    type Err = ParseSystemContractError;
108
109    fn from_str(s: &str) -> Result<Self, Self::Err> {
110        let system_contract = match s {
111            "BEACON_ROOTS_ADDRESS" => Self::BeaconRoots,
112            "CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS" => Self::ConsolidationRequestPredeploy,
113            "DEPOSIT_CONTRACT_ADDRESS" => Self::DepositContract,
114            "HISTORY_STORAGE_ADDRESS" => Self::HistoryStorage,
115            "WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS" => Self::WithdrawalRequestPredeploy,
116            _ => return Err(ParseSystemContractError::Unknown(s.to_owned())),
117        };
118        Ok(system_contract)
119    }
120}
121
122impl SystemContract {
123    /// Enumeration of all [`SystemContract`] variants.
124    pub const ALL: [Self; 5] = [
125        Self::BeaconRoots,
126        Self::ConsolidationRequestPredeploy,
127        Self::DepositContract,
128        Self::HistoryStorage,
129        Self::WithdrawalRequestPredeploy,
130    ];
131
132    /// Returns Cancun system contracts.
133    pub const fn cancun() -> [(Self, Address); 1] {
134        [(Self::BeaconRoots, eip4788::BEACON_ROOTS_ADDRESS)]
135    }
136
137    /// Returns Prague system contracts.
138    /// Takes an optional deposit contract address. If it's `None`, mainnet deposit contract address
139    /// will be used instead.
140    pub fn prague(deposit_contract: Option<Address>) -> [(Self, Address); 4] {
141        [
142            (Self::HistoryStorage, eip2935::HISTORY_STORAGE_ADDRESS),
143            (Self::ConsolidationRequestPredeploy, eip7251::CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS),
144            (Self::WithdrawalRequestPredeploy, eip7002::WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS),
145            (
146                Self::DepositContract,
147                deposit_contract.unwrap_or(eip6110::MAINNET_DEPOSIT_CONTRACT_ADDRESS),
148            ),
149        ]
150    }
151}
152
153/// Parse error for [`SystemContract`].
154#[derive(Debug, thiserror::Error)]
155pub enum ParseSystemContractError {
156    /// System contract unknown.
157    #[error("unknown system contract: {0}")]
158    Unknown(String),
159}
160
161#[cfg(test)]
162mod tests {
163    use super::*;
164
165    #[test]
166    fn system_contract_str() {
167        assert_eq!(SystemContract::BeaconRoots.to_string(), "BEACON_ROOTS_ADDRESS");
168        assert_eq!(
169            SystemContract::ConsolidationRequestPredeploy.to_string(),
170            "CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS"
171        );
172        assert_eq!(SystemContract::DepositContract.to_string(), "DEPOSIT_CONTRACT_ADDRESS");
173        assert_eq!(SystemContract::HistoryStorage.to_string(), "HISTORY_STORAGE_ADDRESS");
174        assert_eq!(
175            SystemContract::WithdrawalRequestPredeploy.to_string(),
176            "WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS"
177        );
178    }
179
180    #[cfg(feature = "serde")]
181    #[test]
182    fn system_contract_serde_roundtrip() {
183        for contract in SystemContract::ALL {
184            assert_eq!(
185                contract,
186                serde_json::from_value(serde_json::to_value(contract).unwrap()).unwrap()
187            );
188        }
189    }
190
191    #[cfg(feature = "serde")]
192    #[test]
193    fn hoodie_prague_eth_config() {
194        let raw = r#"
195            {
196                "activationTime": 1742999832,
197                "blobSchedule": {
198                    "baseFeeUpdateFraction": 5007716,
199                    "max": 9,
200                    "target": 6
201                },
202                "chainId": "0x88bb0",
203                "forkId": "0x0929e24e",
204                "precompiles": {
205                    "BLAKE2F": "0x0000000000000000000000000000000000000009",
206                    "BLS12_G1ADD": "0x000000000000000000000000000000000000000b",
207                    "BLS12_G1MSM": "0x000000000000000000000000000000000000000c",
208                    "BLS12_G2ADD": "0x000000000000000000000000000000000000000d",
209                    "BLS12_G2MSM": "0x000000000000000000000000000000000000000e",
210                    "BLS12_MAP_FP2_TO_G2": "0x0000000000000000000000000000000000000011",
211                    "BLS12_MAP_FP_TO_G1": "0x0000000000000000000000000000000000000010",
212                    "BLS12_PAIRING_CHECK": "0x000000000000000000000000000000000000000f",
213                    "BN254_ADD": "0x0000000000000000000000000000000000000006",
214                    "BN254_MUL": "0x0000000000000000000000000000000000000007",
215                    "BN254_PAIRING": "0x0000000000000000000000000000000000000008",
216                    "ECREC": "0x0000000000000000000000000000000000000001",
217                    "ID": "0x0000000000000000000000000000000000000004",
218                    "KZG_POINT_EVALUATION": "0x000000000000000000000000000000000000000a",
219                    "MODEXP": "0x0000000000000000000000000000000000000005",
220                    "RIPEMD160": "0x0000000000000000000000000000000000000003",
221                    "SHA256": "0x0000000000000000000000000000000000000002"
222                },
223                "systemContracts": {
224                    "BEACON_ROOTS_ADDRESS": "0x000f3df6d732807ef1319fb7b8bb8522d0beac02",
225                    "CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS": "0x0000bbddc7ce488642fb579f8b00f3a590007251",
226                    "DEPOSIT_CONTRACT_ADDRESS": "0x00000000219ab540356cbb839cbe05303d7705fa",
227                    "HISTORY_STORAGE_ADDRESS": "0x0000f90827f1c53a10cb7a02335b175320002935",
228                    "WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS": "0x00000961ef480eb55e80d19ad83579a64c007002"
229                }
230            }
231        "#;
232
233        let fork_config = serde_json::from_str::<EthForkConfig>(raw).unwrap();
234        assert_eq!(
235            serde_json::to_string(&fork_config).unwrap(),
236            raw.chars().filter(|c| !c.is_whitespace()).collect::<String>()
237        );
238    }
239}