Skip to main content

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