reth_optimism_chainspec/superchain/
chain_metadata.rs

1use alloy_chains::NamedChain;
2use alloy_genesis::ChainConfig;
3use alloy_primitives::{ChainId, U256};
4use serde::{Deserialize, Serialize};
5
6/// The chain metadata stored in a superchain toml config file.
7/// Referring here as `ChainMetadata` to avoid confusion with `ChainConfig`.
8/// Find configs here: `<https://github.com/ethereum-optimism/superchain-registry/tree/main/superchain/configs>`
9/// This struct is stripped down to only include the necessary fields. We use JSON instead of
10/// TOML to make it easier to work in a no-std environment.
11#[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// Helper struct to serialize field for extra fields in ChainConfig
68#[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
86/// Returns a [`ChainConfig`] filled from [`ChainMetadata`] with extra fields and handling
87/// special case for Optimism chain.
88// Mimic the behavior from https://github.com/ethereum-optimism/op-geth/blob/35e2c852/params/superchain.go#L26
89pub(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, // Shanghai activates with Canyon
109        cancun_time: chain_config.hardforks.ecotone_time,  // Cancun activates with Ecotone
110        prague_time: chain_config.hardforks.isthmus_time,  // Prague activates with Isthmus
111        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    // Special case for Optimism chain
120    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    // Add extra fields for ChainConfig from Genesis
129    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        // hardforks
181        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        // optimism
189        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}