1use alloy_chains::NamedChain;
2use alloy_genesis::ChainConfig;
3use alloy_primitives::{ChainId, U256};
4use serde::{Deserialize, Serialize};
5
6#[derive(Clone, Debug, Deserialize)]
12#[serde(rename_all = "snake_case")]
13pub(crate) struct ChainMetadata {
14 pub chain_id: ChainId,
15 pub hardforks: HardforkConfig,
16 pub optimism: Option<OptimismConfig>,
17}
18
19#[derive(Clone, Debug, Deserialize)]
20#[serde(rename_all = "snake_case")]
21pub(crate) struct HardforkConfig {
22 pub canyon_time: Option<u64>,
23 pub delta_time: Option<u64>,
24 pub ecotone_time: Option<u64>,
25 pub fjord_time: Option<u64>,
26 pub granite_time: Option<u64>,
27 pub holocene_time: Option<u64>,
28 pub isthmus_time: Option<u64>,
29 pub jovian_time: Option<u64>,
30}
31
32#[derive(Clone, Debug, Deserialize)]
33#[serde(rename_all = "snake_case")]
34pub(crate) struct OptimismConfig {
35 pub eip1559_elasticity: u64,
36 pub eip1559_denominator: u64,
37 pub eip1559_denominator_canyon: Option<u64>,
38}
39
40#[derive(Clone, Debug, Serialize)]
41#[serde(rename_all = "camelCase")]
42pub(crate) struct ChainConfigExtraFields {
43 #[serde(skip_serializing_if = "Option::is_none")]
44 pub bedrock_block: Option<u64>,
45 #[serde(skip_serializing_if = "Option::is_none")]
46 pub regolith_time: Option<u64>,
47 #[serde(skip_serializing_if = "Option::is_none")]
48 pub canyon_time: Option<u64>,
49 #[serde(skip_serializing_if = "Option::is_none")]
50 pub delta_time: Option<u64>,
51 #[serde(skip_serializing_if = "Option::is_none")]
52 pub ecotone_time: Option<u64>,
53 #[serde(skip_serializing_if = "Option::is_none")]
54 pub fjord_time: Option<u64>,
55 #[serde(skip_serializing_if = "Option::is_none")]
56 pub granite_time: Option<u64>,
57 #[serde(skip_serializing_if = "Option::is_none")]
58 pub holocene_time: Option<u64>,
59 #[serde(skip_serializing_if = "Option::is_none")]
60 pub isthmus_time: Option<u64>,
61 #[serde(skip_serializing_if = "Option::is_none")]
62 pub jovian_time: Option<u64>,
63 #[serde(skip_serializing_if = "Option::is_none")]
64 pub optimism: Option<ChainConfigExtraFieldsOptimism>,
65}
66
67#[derive(Clone, Debug, Serialize)]
69#[serde(rename_all = "camelCase")]
70pub(crate) struct ChainConfigExtraFieldsOptimism {
71 pub eip1559_elasticity: u64,
72 pub eip1559_denominator: u64,
73 pub eip1559_denominator_canyon: Option<u64>,
74}
75
76impl From<&OptimismConfig> for ChainConfigExtraFieldsOptimism {
77 fn from(value: &OptimismConfig) -> Self {
78 Self {
79 eip1559_elasticity: value.eip1559_elasticity,
80 eip1559_denominator: value.eip1559_denominator,
81 eip1559_denominator_canyon: value.eip1559_denominator_canyon,
82 }
83 }
84}
85
86pub(crate) fn to_genesis_chain_config(chain_config: &ChainMetadata) -> ChainConfig {
90 let mut res = ChainConfig {
91 chain_id: chain_config.chain_id,
92 homestead_block: Some(0),
93 dao_fork_block: None,
94 dao_fork_support: false,
95 eip150_block: Some(0),
96 eip155_block: Some(0),
97 eip158_block: Some(0),
98 byzantium_block: Some(0),
99 constantinople_block: Some(0),
100 petersburg_block: Some(0),
101 istanbul_block: Some(0),
102 muir_glacier_block: Some(0),
103 berlin_block: Some(0),
104 london_block: Some(0),
105 arrow_glacier_block: Some(0),
106 gray_glacier_block: Some(0),
107 merge_netsplit_block: Some(0),
108 shanghai_time: chain_config.hardforks.canyon_time, cancun_time: chain_config.hardforks.ecotone_time, prague_time: chain_config.hardforks.isthmus_time, osaka_time: None,
112 terminal_total_difficulty: Some(U256::ZERO),
113 terminal_total_difficulty_passed: true,
114 ethash: None,
115 clique: None,
116 ..Default::default()
117 };
118
119 if chain_config.chain_id == NamedChain::Optimism as ChainId {
121 res.berlin_block = Some(3950000);
122 res.london_block = Some(105235063);
123 res.arrow_glacier_block = Some(105235063);
124 res.gray_glacier_block = Some(105235063);
125 res.merge_netsplit_block = Some(105235063);
126 }
127
128 let extra_fields = ChainConfigExtraFields {
130 bedrock_block: if chain_config.chain_id == NamedChain::Optimism as ChainId {
131 Some(105235063)
132 } else {
133 Some(0)
134 },
135 regolith_time: Some(0),
136 canyon_time: chain_config.hardforks.canyon_time,
137 delta_time: chain_config.hardforks.delta_time,
138 ecotone_time: chain_config.hardforks.ecotone_time,
139 fjord_time: chain_config.hardforks.fjord_time,
140 granite_time: chain_config.hardforks.granite_time,
141 holocene_time: chain_config.hardforks.holocene_time,
142 isthmus_time: chain_config.hardforks.isthmus_time,
143 jovian_time: chain_config.hardforks.jovian_time,
144 optimism: chain_config.optimism.as_ref().map(|o| o.into()),
145 };
146 res.extra_fields =
147 serde_json::to_value(extra_fields).unwrap_or_default().try_into().unwrap_or_default();
148
149 res
150}
151
152#[cfg(test)]
153mod tests {
154 use super::*;
155
156 const BASE_CHAIN_METADATA: &str = r#"
157 {
158 "chain_id": 8453,
159 "hardforks": {
160 "canyon_time": 1704992401,
161 "delta_time": 1708560000,
162 "ecotone_time": 1710374401,
163 "fjord_time": 1720627201,
164 "granite_time": 1726070401,
165 "holocene_time": 1736445601,
166 "isthmus_time": 1746806401
167 },
168 "optimism": {
169 "eip1559_elasticity": 6,
170 "eip1559_denominator": 50,
171 "eip1559_denominator_canyon": 250
172 }
173 }
174 "#;
175
176 #[test]
177 fn test_deserialize_chain_config() {
178 let config: ChainMetadata = serde_json::from_str(BASE_CHAIN_METADATA).unwrap();
179 assert_eq!(config.chain_id, 8453);
180 assert_eq!(config.hardforks.canyon_time, Some(1704992401));
182 assert_eq!(config.hardforks.delta_time, Some(1708560000));
183 assert_eq!(config.hardforks.ecotone_time, Some(1710374401));
184 assert_eq!(config.hardforks.fjord_time, Some(1720627201));
185 assert_eq!(config.hardforks.granite_time, Some(1726070401));
186 assert_eq!(config.hardforks.holocene_time, Some(1736445601));
187 assert_eq!(config.hardforks.isthmus_time, Some(1746806401));
188 assert_eq!(config.optimism.as_ref().unwrap().eip1559_elasticity, 6);
190 assert_eq!(config.optimism.as_ref().unwrap().eip1559_denominator, 50);
191 assert_eq!(config.optimism.as_ref().unwrap().eip1559_denominator_canyon, Some(250));
192 }
193
194 #[test]
195 fn test_chain_config_extra_fields() {
196 let extra_fields = ChainConfigExtraFields {
197 bedrock_block: Some(105235063),
198 regolith_time: Some(0),
199 canyon_time: Some(1704992401),
200 delta_time: Some(1708560000),
201 ecotone_time: Some(1710374401),
202 fjord_time: Some(1720627201),
203 granite_time: Some(1726070401),
204 holocene_time: Some(1736445601),
205 isthmus_time: Some(1746806401),
206 jovian_time: None,
207 optimism: Option::from(ChainConfigExtraFieldsOptimism {
208 eip1559_elasticity: 6,
209 eip1559_denominator: 50,
210 eip1559_denominator_canyon: Some(250),
211 }),
212 };
213 let value = serde_json::to_value(extra_fields).unwrap();
214 assert_eq!(value.get("bedrockBlock").unwrap(), 105235063);
215 assert_eq!(value.get("regolithTime").unwrap(), 0);
216 assert_eq!(value.get("canyonTime").unwrap(), 1704992401);
217 assert_eq!(value.get("deltaTime").unwrap(), 1708560000);
218 assert_eq!(value.get("ecotoneTime").unwrap(), 1710374401);
219 assert_eq!(value.get("fjordTime").unwrap(), 1720627201);
220 assert_eq!(value.get("graniteTime").unwrap(), 1726070401);
221 assert_eq!(value.get("holoceneTime").unwrap(), 1736445601);
222 assert_eq!(value.get("isthmusTime").unwrap(), 1746806401);
223 assert_eq!(value.get("jovianTime"), None);
224 let optimism = value.get("optimism").unwrap();
225 assert_eq!(optimism.get("eip1559Elasticity").unwrap(), 6);
226 assert_eq!(optimism.get("eip1559Denominator").unwrap(), 50);
227 assert_eq!(optimism.get("eip1559DenominatorCanyon").unwrap(), 250);
228 }
229
230 #[test]
231 fn test_convert_to_genesis_chain_config() {
232 let config: ChainMetadata = serde_json::from_str(BASE_CHAIN_METADATA).unwrap();
233 let chain_config = to_genesis_chain_config(&config);
234 assert_eq!(chain_config.chain_id, 8453);
235 assert_eq!(chain_config.homestead_block, Some(0));
236 assert_eq!(chain_config.dao_fork_block, None);
237 assert!(!chain_config.dao_fork_support);
238 assert_eq!(chain_config.eip150_block, Some(0));
239 assert_eq!(chain_config.eip155_block, Some(0));
240 assert_eq!(chain_config.eip158_block, Some(0));
241 assert_eq!(chain_config.byzantium_block, Some(0));
242 assert_eq!(chain_config.constantinople_block, Some(0));
243 assert_eq!(chain_config.petersburg_block, Some(0));
244 assert_eq!(chain_config.istanbul_block, Some(0));
245 assert_eq!(chain_config.muir_glacier_block, Some(0));
246 assert_eq!(chain_config.berlin_block, Some(0));
247 assert_eq!(chain_config.london_block, Some(0));
248 assert_eq!(chain_config.arrow_glacier_block, Some(0));
249 assert_eq!(chain_config.gray_glacier_block, Some(0));
250 assert_eq!(chain_config.merge_netsplit_block, Some(0));
251 assert_eq!(chain_config.shanghai_time, Some(1704992401));
252 assert_eq!(chain_config.cancun_time, Some(1710374401));
253 assert_eq!(chain_config.prague_time, Some(1746806401));
254 assert_eq!(chain_config.osaka_time, None);
255 assert_eq!(chain_config.terminal_total_difficulty, Some(U256::ZERO));
256 assert!(chain_config.terminal_total_difficulty_passed);
257 assert_eq!(chain_config.ethash, None);
258 assert_eq!(chain_config.clique, None);
259 assert_eq!(chain_config.extra_fields.get("bedrockBlock").unwrap(), 0);
260 assert_eq!(chain_config.extra_fields.get("regolithTime").unwrap(), 0);
261 assert_eq!(chain_config.extra_fields.get("canyonTime").unwrap(), 1704992401);
262 assert_eq!(chain_config.extra_fields.get("deltaTime").unwrap(), 1708560000);
263 assert_eq!(chain_config.extra_fields.get("ecotoneTime").unwrap(), 1710374401);
264 assert_eq!(chain_config.extra_fields.get("fjordTime").unwrap(), 1720627201);
265 assert_eq!(chain_config.extra_fields.get("graniteTime").unwrap(), 1726070401);
266 assert_eq!(chain_config.extra_fields.get("holoceneTime").unwrap(), 1736445601);
267 assert_eq!(chain_config.extra_fields.get("isthmusTime").unwrap(), 1746806401);
268 assert_eq!(chain_config.extra_fields.get("jovianTime"), None);
269 let optimism = chain_config.extra_fields.get("optimism").unwrap();
270 assert_eq!(optimism.get("eip1559Elasticity").unwrap(), 6);
271 assert_eq!(optimism.get("eip1559Denominator").unwrap(), 50);
272 assert_eq!(optimism.get("eip1559DenominatorCanyon").unwrap(), 250);
273 }
274
275 #[test]
276 fn test_convert_to_genesis_chain_config_op() {
277 const OP_CHAIN_METADATA: &str = r#"
278 {
279 "chain_id": 10,
280 "hardforks": {
281 "canyon_time": 1704992401,
282 "delta_time": 1708560000,
283 "ecotone_time": 1710374401,
284 "fjord_time": 1720627201,
285 "granite_time": 1726070401,
286 "holocene_time": 1736445601,
287 "isthmus_time": 1746806401
288 },
289 "optimism": {
290 "eip1559_elasticity": 6,
291 "eip1559_denominator": 50,
292 "eip1559_denominator_canyon": 250
293 }
294 }
295 "#;
296 let config: ChainMetadata = serde_json::from_str(OP_CHAIN_METADATA).unwrap();
297 assert_eq!(config.hardforks.canyon_time, Some(1704992401));
298 let chain_config = to_genesis_chain_config(&config);
299 assert_eq!(chain_config.chain_id, 10);
300 assert_eq!(chain_config.shanghai_time, Some(1704992401));
301 assert_eq!(chain_config.cancun_time, Some(1710374401));
302 assert_eq!(chain_config.prague_time, Some(1746806401));
303 assert_eq!(chain_config.berlin_block, Some(3950000));
304 assert_eq!(chain_config.london_block, Some(105235063));
305 assert_eq!(chain_config.arrow_glacier_block, Some(105235063));
306 assert_eq!(chain_config.gray_glacier_block, Some(105235063));
307 assert_eq!(chain_config.merge_netsplit_block, Some(105235063));
308 assert_eq!(chain_config.extra_fields.get("bedrockBlock").unwrap(), 105235063);
309 assert_eq!(chain_config.extra_fields.get("regolithTime").unwrap(), 0);
310 assert_eq!(chain_config.extra_fields.get("canyonTime").unwrap(), 1704992401);
311 assert_eq!(chain_config.extra_fields.get("deltaTime").unwrap(), 1708560000);
312 assert_eq!(chain_config.extra_fields.get("ecotoneTime").unwrap(), 1710374401);
313 assert_eq!(chain_config.extra_fields.get("fjordTime").unwrap(), 1720627201);
314 assert_eq!(chain_config.extra_fields.get("graniteTime").unwrap(), 1726070401);
315 assert_eq!(chain_config.extra_fields.get("holoceneTime").unwrap(), 1736445601);
316 assert_eq!(chain_config.extra_fields.get("isthmusTime").unwrap(), 1746806401);
317 assert_eq!(chain_config.extra_fields.get("jovianTime"), None);
318
319 let optimism = chain_config.extra_fields.get("optimism").unwrap();
320 assert_eq!(optimism.get("eip1559Elasticity").unwrap(), 6);
321 assert_eq!(optimism.get("eip1559Denominator").unwrap(), 50);
322 assert_eq!(optimism.get("eip1559DenominatorCanyon").unwrap(), 250);
323 }
324}