1use 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#[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 pub current: EthForkConfig,
15 pub next: Option<EthForkConfig>,
17 pub last: Option<EthForkConfig>,
19}
20
21#[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 pub activation_time: u64,
32 pub blob_schedule: BlobParams,
36 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
42 pub chain_id: u64,
43 pub fork_id: Bytes,
47 pub precompiles: BTreeMap<String, Address>,
61 pub system_contracts: BTreeMap<SystemContract, Address>,
75}
76
77#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Debug)]
79#[cfg_attr(feature = "serde", derive(serde_with::SerializeDisplay, serde_with::DeserializeFromStr))]
80pub enum SystemContract {
81 BeaconRoots,
83 ConsolidationRequestPredeploy,
85 DepositContract,
87 HistoryStorage,
89 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 pub const ALL: [Self; 5] = [
125 Self::BeaconRoots,
126 Self::ConsolidationRequestPredeploy,
127 Self::DepositContract,
128 Self::HistoryStorage,
129 Self::WithdrawalRequestPredeploy,
130 ];
131
132 pub const fn cancun() -> [(Self, Address); 1] {
134 [(Self::BeaconRoots, eip4788::BEACON_ROOTS_ADDRESS)]
135 }
136
137 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#[derive(Debug, thiserror::Error)]
155pub enum ParseSystemContractError {
156 #[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}