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};
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    /// Future forks MUST define the list of system contracts in their meta-EIPs.
77    pub system_contracts: BTreeMap<SystemContract, Address>,
78}
79
80/// System-level contracts for [`EthForkConfig`].
81#[derive(PartialEq, Eq, Clone, Debug)]
82#[cfg_attr(feature = "serde", derive(serde_with::SerializeDisplay, serde_with::DeserializeFromStr))]
83pub enum SystemContract {
84    /// Beacon roots system contract.
85    BeaconRoots,
86    /// Consolidation requests predeploy system contract.
87    ConsolidationRequestPredeploy,
88    /// Deposit system contract.
89    DepositContract,
90    /// History storage system contract.
91    HistoryStorage,
92    /// Withdrawal requests predeploy system contract.
93    WithdrawalRequestPredeploy,
94    /// A custom system contract not defined by a known EIP.
95    Other(String),
96}
97
98impl PartialOrd for SystemContract {
99    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
100        Some(self.cmp(other))
101    }
102}
103
104impl Ord for SystemContract {
105    fn cmp(&self, other: &Self) -> Ordering {
106        self.to_string().cmp(&other.to_string())
107    }
108}
109
110impl fmt::Display for SystemContract {
111    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
112        let str = match self {
113            Self::BeaconRoots => "BEACON_ROOTS",
114            Self::ConsolidationRequestPredeploy => "CONSOLIDATION_REQUEST_PREDEPLOY",
115            Self::DepositContract => "DEPOSIT_CONTRACT",
116            Self::HistoryStorage => "HISTORY_STORAGE",
117            Self::WithdrawalRequestPredeploy => "WITHDRAWAL_REQUEST_PREDEPLOY",
118            Self::Other(name) => return write!(f, "{name}"),
119        };
120        write!(f, "{str}_ADDRESS")
121    }
122}
123
124impl str::FromStr for SystemContract {
125    type Err = Infallible;
126
127    fn from_str(s: &str) -> Result<Self, Self::Err> {
128        let system_contract = match s {
129            "BEACON_ROOTS_ADDRESS" => Self::BeaconRoots,
130            "CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS" => Self::ConsolidationRequestPredeploy,
131            "DEPOSIT_CONTRACT_ADDRESS" => Self::DepositContract,
132            "HISTORY_STORAGE_ADDRESS" => Self::HistoryStorage,
133            "WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS" => Self::WithdrawalRequestPredeploy,
134            _ => Self::Other(s.into()),
135        };
136        Ok(system_contract)
137    }
138}
139
140impl SystemContract {
141    /// Enumeration of all [`SystemContract`] variants.
142    pub const ALL: [Self; 5] = [
143        Self::BeaconRoots,
144        Self::ConsolidationRequestPredeploy,
145        Self::DepositContract,
146        Self::HistoryStorage,
147        Self::WithdrawalRequestPredeploy,
148    ];
149
150    /// Returns Cancun system contracts.
151    pub const fn cancun() -> [(Self, Address); 1] {
152        [(Self::BeaconRoots, eip4788::BEACON_ROOTS_ADDRESS)]
153    }
154
155    /// Returns Prague system contracts.
156    /// Takes an optional deposit contract address. If it's `None`, mainnet deposit contract address
157    /// will be used instead.
158    pub fn prague(deposit_contract: Option<Address>) -> [(Self, Address); 4] {
159        [
160            (Self::HistoryStorage, eip2935::HISTORY_STORAGE_ADDRESS),
161            (Self::ConsolidationRequestPredeploy, eip7251::CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS),
162            (Self::WithdrawalRequestPredeploy, eip7002::WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS),
163            (
164                Self::DepositContract,
165                deposit_contract.unwrap_or(eip6110::MAINNET_DEPOSIT_CONTRACT_ADDRESS),
166            ),
167        ]
168    }
169}
170
171#[cfg(test)]
172mod tests {
173    use super::*;
174
175    #[test]
176    fn system_contract_str() {
177        assert_eq!(SystemContract::BeaconRoots.to_string(), "BEACON_ROOTS_ADDRESS");
178        assert_eq!(
179            SystemContract::ConsolidationRequestPredeploy.to_string(),
180            "CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS"
181        );
182        assert_eq!(SystemContract::DepositContract.to_string(), "DEPOSIT_CONTRACT_ADDRESS");
183        assert_eq!(SystemContract::HistoryStorage.to_string(), "HISTORY_STORAGE_ADDRESS");
184        assert_eq!(
185            SystemContract::WithdrawalRequestPredeploy.to_string(),
186            "WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS"
187        );
188    }
189
190    #[cfg(feature = "serde")]
191    #[test]
192    fn system_contract_serde_roundtrip() {
193        for contract in &SystemContract::ALL {
194            assert_eq!(
195                *contract,
196                serde_json::from_value::<SystemContract>(serde_json::to_value(contract).unwrap())
197                    .unwrap()
198            );
199        }
200    }
201
202    #[cfg(feature = "serde")]
203    #[test]
204    fn hoodie_prague_eth_config() {
205        let raw = r#"
206            {
207                "activationTime": 1742999832,
208                "blobSchedule": {
209                    "baseFeeUpdateFraction": 5007716,
210                    "max": 9,
211                    "target": 6
212                },
213                "chainId": "0x88bb0",
214                "forkId": "0x0929e24e",
215                "precompiles": {
216                    "BLAKE2F": "0x0000000000000000000000000000000000000009",
217                    "BLS12_G1ADD": "0x000000000000000000000000000000000000000b",
218                    "BLS12_G1MSM": "0x000000000000000000000000000000000000000c",
219                    "BLS12_G2ADD": "0x000000000000000000000000000000000000000d",
220                    "BLS12_G2MSM": "0x000000000000000000000000000000000000000e",
221                    "BLS12_MAP_FP2_TO_G2": "0x0000000000000000000000000000000000000011",
222                    "BLS12_MAP_FP_TO_G1": "0x0000000000000000000000000000000000000010",
223                    "BLS12_PAIRING_CHECK": "0x000000000000000000000000000000000000000f",
224                    "BN254_ADD": "0x0000000000000000000000000000000000000006",
225                    "BN254_MUL": "0x0000000000000000000000000000000000000007",
226                    "BN254_PAIRING": "0x0000000000000000000000000000000000000008",
227                    "ECREC": "0x0000000000000000000000000000000000000001",
228                    "ID": "0x0000000000000000000000000000000000000004",
229                    "KZG_POINT_EVALUATION": "0x000000000000000000000000000000000000000a",
230                    "MODEXP": "0x0000000000000000000000000000000000000005",
231                    "RIPEMD160": "0x0000000000000000000000000000000000000003",
232                    "SHA256": "0x0000000000000000000000000000000000000002"
233                },
234                "systemContracts": {
235                    "BEACON_ROOTS_ADDRESS": "0x000f3df6d732807ef1319fb7b8bb8522d0beac02",
236                    "CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS": "0x0000bbddc7ce488642fb579f8b00f3a590007251",
237                    "DEPOSIT_CONTRACT_ADDRESS": "0x00000000219ab540356cbb839cbe05303d7705fa",
238                    "HISTORY_STORAGE_ADDRESS": "0x0000f90827f1c53a10cb7a02335b175320002935",
239                    "WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS": "0x00000961ef480eb55e80d19ad83579a64c007002"
240                }
241            }
242        "#;
243
244        let fork_config = serde_json::from_str::<EthForkConfig>(raw).unwrap();
245        assert_eq!(
246            serde_json::to_string(&fork_config).unwrap(),
247            raw.chars().filter(|c| !c.is_whitespace()).collect::<String>()
248        );
249    }
250}