reth_optimism_chainspec/
lib.rs

1//! OP-Reth chain specs.
2
3#![doc(
4    html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png",
5    html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256",
6    issue_tracker_base_url = "https://github.com/paradigmxyz/reth/issues/"
7)]
8#![cfg_attr(docsrs, feature(doc_cfg))]
9#![cfg_attr(not(test), warn(unused_crate_dependencies))]
10#![cfg_attr(not(feature = "std"), no_std)]
11
12// About the provided chain specs from `res/superchain-configs.tar`:
13// The provided `OpChainSpec` structs are built from config files read from
14// `superchain-configs.tar`. This `superchain-configs.tar` file contains the chain configs and
15// genesis files for all chains. It is created by the `fetch_superchain_config.sh` script in
16// the `res` directory. Where all configs are where initial loaded from
17// <https://github.com/ethereum-optimism/superchain-registry>. See the script for more details.
18//
19// The file is a tar archive containing the following files:
20// - `genesis/<environment>/<chain_name>.json.zz`: The genesis file compressed with deflate. It
21//   contains the initial accounts, etc.
22// - `configs/<environment>/<chain_name>.json`: The chain metadata file containing the chain id,
23//   hard forks, etc.
24//
25// For example, for `UNICHAIN_MAINNET`, the `genesis/mainnet/unichain.json.zz` and
26// `configs/mainnet/base.json` is loaded and combined into the `OpChainSpec` struct.
27// See `read_superchain_genesis` in `configs.rs` for more details.
28//
29// To update the chain specs, run the `fetch_superchain_config.sh` script in the `res` directory.
30// This will fetch the latest chain configs from the superchain registry and create a new
31// `superchain-configs.tar` file. See the script for more details.
32
33extern crate alloc;
34
35mod base;
36mod base_sepolia;
37mod basefee;
38
39pub mod constants;
40mod dev;
41mod op;
42mod op_sepolia;
43
44#[cfg(feature = "superchain-configs")]
45mod superchain;
46#[cfg(feature = "superchain-configs")]
47pub use superchain::*;
48
49pub use base::BASE_MAINNET;
50pub use base_sepolia::BASE_SEPOLIA;
51pub use basefee::*;
52pub use dev::OP_DEV;
53pub use op::OP_MAINNET;
54pub use op_sepolia::OP_SEPOLIA;
55
56/// Re-export for convenience
57pub use reth_optimism_forks::*;
58
59use alloc::{boxed::Box, vec, vec::Vec};
60use alloy_chains::Chain;
61use alloy_consensus::{proofs::storage_root_unhashed, BlockHeader, Header};
62use alloy_eips::eip7840::BlobParams;
63use alloy_genesis::Genesis;
64use alloy_hardforks::Hardfork;
65use alloy_primitives::{B256, U256};
66use derive_more::{Constructor, Deref, From, Into};
67use reth_chainspec::{
68    BaseFeeParams, BaseFeeParamsKind, ChainSpec, ChainSpecBuilder, DepositContract,
69    DisplayHardforks, EthChainSpec, EthereumHardforks, ForkFilter, ForkId, Hardforks, Head,
70};
71use reth_ethereum_forks::{ChainHardforks, EthereumHardfork, ForkCondition};
72use reth_network_peers::NodeRecord;
73use reth_optimism_primitives::ADDRESS_L2_TO_L1_MESSAGE_PASSER;
74use reth_primitives_traits::{sync::LazyLock, SealedHeader};
75
76/// Chain spec builder for a OP stack chain.
77#[derive(Debug, Default, From)]
78pub struct OpChainSpecBuilder {
79    /// [`ChainSpecBuilder`]
80    inner: ChainSpecBuilder,
81}
82
83impl OpChainSpecBuilder {
84    /// Construct a new builder from the base mainnet chain spec.
85    pub fn base_mainnet() -> Self {
86        let mut inner = ChainSpecBuilder::default()
87            .chain(BASE_MAINNET.chain)
88            .genesis(BASE_MAINNET.genesis.clone());
89        let forks = BASE_MAINNET.hardforks.clone();
90        inner = inner.with_forks(forks);
91
92        Self { inner }
93    }
94
95    /// Construct a new builder from the optimism mainnet chain spec.
96    pub fn optimism_mainnet() -> Self {
97        let mut inner =
98            ChainSpecBuilder::default().chain(OP_MAINNET.chain).genesis(OP_MAINNET.genesis.clone());
99        let forks = OP_MAINNET.hardforks.clone();
100        inner = inner.with_forks(forks);
101
102        Self { inner }
103    }
104}
105
106impl OpChainSpecBuilder {
107    /// Set the chain ID
108    pub fn chain(mut self, chain: Chain) -> Self {
109        self.inner = self.inner.chain(chain);
110        self
111    }
112
113    /// Set the genesis block.
114    pub fn genesis(mut self, genesis: Genesis) -> Self {
115        self.inner = self.inner.genesis(genesis);
116        self
117    }
118
119    /// Add the given fork with the given activation condition to the spec.
120    pub fn with_fork<H: Hardfork>(mut self, fork: H, condition: ForkCondition) -> Self {
121        self.inner = self.inner.with_fork(fork, condition);
122        self
123    }
124
125    /// Add the given forks with the given activation condition to the spec.
126    pub fn with_forks(mut self, forks: ChainHardforks) -> Self {
127        self.inner = self.inner.with_forks(forks);
128        self
129    }
130
131    /// Remove the given fork from the spec.
132    pub fn without_fork(mut self, fork: OpHardfork) -> Self {
133        self.inner = self.inner.without_fork(fork);
134        self
135    }
136
137    /// Enable Bedrock at genesis
138    pub fn bedrock_activated(mut self) -> Self {
139        self.inner = self.inner.paris_activated();
140        self.inner = self.inner.with_fork(OpHardfork::Bedrock, ForkCondition::Block(0));
141        self
142    }
143
144    /// Enable Regolith at genesis
145    pub fn regolith_activated(mut self) -> Self {
146        self = self.bedrock_activated();
147        self.inner = self.inner.with_fork(OpHardfork::Regolith, ForkCondition::Timestamp(0));
148        self
149    }
150
151    /// Enable Canyon at genesis
152    pub fn canyon_activated(mut self) -> Self {
153        self = self.regolith_activated();
154        // Canyon also activates changes from L1's Shanghai hardfork
155        self.inner = self.inner.with_fork(EthereumHardfork::Shanghai, ForkCondition::Timestamp(0));
156        self.inner = self.inner.with_fork(OpHardfork::Canyon, ForkCondition::Timestamp(0));
157        self
158    }
159
160    /// Enable Ecotone at genesis
161    pub fn ecotone_activated(mut self) -> Self {
162        self = self.canyon_activated();
163        self.inner = self.inner.with_fork(EthereumHardfork::Cancun, ForkCondition::Timestamp(0));
164        self.inner = self.inner.with_fork(OpHardfork::Ecotone, ForkCondition::Timestamp(0));
165        self
166    }
167
168    /// Enable Fjord at genesis
169    pub fn fjord_activated(mut self) -> Self {
170        self = self.ecotone_activated();
171        self.inner = self.inner.with_fork(OpHardfork::Fjord, ForkCondition::Timestamp(0));
172        self
173    }
174
175    /// Enable Granite at genesis
176    pub fn granite_activated(mut self) -> Self {
177        self = self.fjord_activated();
178        self.inner = self.inner.with_fork(OpHardfork::Granite, ForkCondition::Timestamp(0));
179        self
180    }
181
182    /// Enable Holocene at genesis
183    pub fn holocene_activated(mut self) -> Self {
184        self = self.granite_activated();
185        self.inner = self.inner.with_fork(OpHardfork::Holocene, ForkCondition::Timestamp(0));
186        self
187    }
188
189    /// Enable Isthmus at genesis
190    pub fn isthmus_activated(mut self) -> Self {
191        self = self.holocene_activated();
192        self.inner = self.inner.with_fork(OpHardfork::Isthmus, ForkCondition::Timestamp(0));
193        self
194    }
195
196    /// Enable Jovian at genesis
197    pub fn jovian_activated(mut self) -> Self {
198        self = self.isthmus_activated();
199        self.inner = self.inner.with_fork(OpHardfork::Jovian, ForkCondition::Timestamp(0));
200        self
201    }
202
203    /// Enable Interop at genesis
204    pub fn interop_activated(mut self) -> Self {
205        self = self.jovian_activated();
206        self.inner = self.inner.with_fork(OpHardfork::Interop, ForkCondition::Timestamp(0));
207        self
208    }
209
210    /// Build the resulting [`OpChainSpec`].
211    ///
212    /// # Panics
213    ///
214    /// This function panics if the chain ID and genesis is not set ([`Self::chain`] and
215    /// [`Self::genesis`])
216    pub fn build(self) -> OpChainSpec {
217        let mut inner = self.inner.build();
218        inner.genesis_header =
219            SealedHeader::seal_slow(make_op_genesis_header(&inner.genesis, &inner.hardforks));
220
221        OpChainSpec { inner }
222    }
223}
224
225/// OP stack chain spec type.
226#[derive(Debug, Clone, Deref, Into, Constructor, PartialEq, Eq)]
227pub struct OpChainSpec {
228    /// [`ChainSpec`].
229    pub inner: ChainSpec,
230}
231
232impl OpChainSpec {
233    /// Converts the given [`Genesis`] into a [`OpChainSpec`].
234    pub fn from_genesis(genesis: Genesis) -> Self {
235        genesis.into()
236    }
237}
238
239impl EthChainSpec for OpChainSpec {
240    type Header = Header;
241
242    fn chain(&self) -> Chain {
243        self.inner.chain()
244    }
245
246    fn base_fee_params_at_timestamp(&self, timestamp: u64) -> BaseFeeParams {
247        self.inner.base_fee_params_at_timestamp(timestamp)
248    }
249
250    fn blob_params_at_timestamp(&self, timestamp: u64) -> Option<BlobParams> {
251        self.inner.blob_params_at_timestamp(timestamp)
252    }
253
254    fn deposit_contract(&self) -> Option<&DepositContract> {
255        self.inner.deposit_contract()
256    }
257
258    fn genesis_hash(&self) -> B256 {
259        self.inner.genesis_hash()
260    }
261
262    fn prune_delete_limit(&self) -> usize {
263        self.inner.prune_delete_limit()
264    }
265
266    fn display_hardforks(&self) -> Box<dyn core::fmt::Display> {
267        // filter only op hardforks
268        let op_forks = self.inner.hardforks.forks_iter().filter(|(fork, _)| {
269            !EthereumHardfork::VARIANTS.iter().any(|h| h.name() == (*fork).name())
270        });
271
272        Box::new(DisplayHardforks::new(op_forks))
273    }
274
275    fn genesis_header(&self) -> &Self::Header {
276        self.inner.genesis_header()
277    }
278
279    fn genesis(&self) -> &Genesis {
280        self.inner.genesis()
281    }
282
283    fn bootnodes(&self) -> Option<Vec<NodeRecord>> {
284        self.inner.bootnodes()
285    }
286
287    fn is_optimism(&self) -> bool {
288        true
289    }
290
291    fn final_paris_total_difficulty(&self) -> Option<U256> {
292        self.inner.final_paris_total_difficulty()
293    }
294
295    fn next_block_base_fee(&self, parent: &Header, target_timestamp: u64) -> Option<u64> {
296        if self.is_jovian_active_at_timestamp(parent.timestamp()) {
297            compute_jovian_base_fee(self, parent, target_timestamp).ok()
298        } else if self.is_holocene_active_at_timestamp(parent.timestamp()) {
299            decode_holocene_base_fee(self, parent, target_timestamp).ok()
300        } else {
301            self.inner.next_block_base_fee(parent, target_timestamp)
302        }
303    }
304}
305
306impl Hardforks for OpChainSpec {
307    fn fork<H: Hardfork>(&self, fork: H) -> ForkCondition {
308        self.inner.fork(fork)
309    }
310
311    fn forks_iter(&self) -> impl Iterator<Item = (&dyn Hardfork, ForkCondition)> {
312        self.inner.forks_iter()
313    }
314
315    fn fork_id(&self, head: &Head) -> ForkId {
316        self.inner.fork_id(head)
317    }
318
319    fn latest_fork_id(&self) -> ForkId {
320        self.inner.latest_fork_id()
321    }
322
323    fn fork_filter(&self, head: Head) -> ForkFilter {
324        self.inner.fork_filter(head)
325    }
326}
327
328impl EthereumHardforks for OpChainSpec {
329    fn ethereum_fork_activation(&self, fork: EthereumHardfork) -> ForkCondition {
330        self.fork(fork)
331    }
332}
333
334impl OpHardforks for OpChainSpec {
335    fn op_fork_activation(&self, fork: OpHardfork) -> ForkCondition {
336        self.fork(fork)
337    }
338}
339
340impl From<Genesis> for OpChainSpec {
341    fn from(genesis: Genesis) -> Self {
342        use reth_optimism_forks::OpHardfork;
343        let optimism_genesis_info = OpGenesisInfo::extract_from(&genesis);
344        let genesis_info =
345            optimism_genesis_info.optimism_chain_info.genesis_info.unwrap_or_default();
346
347        // Block-based hardforks
348        let hardfork_opts = [
349            (EthereumHardfork::Frontier.boxed(), Some(0)),
350            (EthereumHardfork::Homestead.boxed(), genesis.config.homestead_block),
351            (EthereumHardfork::Tangerine.boxed(), genesis.config.eip150_block),
352            (EthereumHardfork::SpuriousDragon.boxed(), genesis.config.eip155_block),
353            (EthereumHardfork::Byzantium.boxed(), genesis.config.byzantium_block),
354            (EthereumHardfork::Constantinople.boxed(), genesis.config.constantinople_block),
355            (EthereumHardfork::Petersburg.boxed(), genesis.config.petersburg_block),
356            (EthereumHardfork::Istanbul.boxed(), genesis.config.istanbul_block),
357            (EthereumHardfork::MuirGlacier.boxed(), genesis.config.muir_glacier_block),
358            (EthereumHardfork::Berlin.boxed(), genesis.config.berlin_block),
359            (EthereumHardfork::London.boxed(), genesis.config.london_block),
360            (EthereumHardfork::ArrowGlacier.boxed(), genesis.config.arrow_glacier_block),
361            (EthereumHardfork::GrayGlacier.boxed(), genesis.config.gray_glacier_block),
362            (OpHardfork::Bedrock.boxed(), genesis_info.bedrock_block),
363        ];
364        let mut block_hardforks = hardfork_opts
365            .into_iter()
366            .filter_map(|(hardfork, opt)| opt.map(|block| (hardfork, ForkCondition::Block(block))))
367            .collect::<Vec<_>>();
368
369        // We set the paris hardfork for OP networks to zero
370        block_hardforks.push((
371            EthereumHardfork::Paris.boxed(),
372            ForkCondition::TTD {
373                activation_block_number: 0,
374                total_difficulty: U256::ZERO,
375                fork_block: genesis.config.merge_netsplit_block,
376            },
377        ));
378
379        // Time-based hardforks
380        let time_hardfork_opts = [
381            // L1
382            // we need to map the L1 hardforks to the activation timestamps of the correspondong op
383            // hardforks
384            (EthereumHardfork::Shanghai.boxed(), genesis_info.canyon_time),
385            (EthereumHardfork::Cancun.boxed(), genesis_info.ecotone_time),
386            (EthereumHardfork::Prague.boxed(), genesis_info.isthmus_time),
387            // OP
388            (OpHardfork::Regolith.boxed(), genesis_info.regolith_time),
389            (OpHardfork::Canyon.boxed(), genesis_info.canyon_time),
390            (OpHardfork::Ecotone.boxed(), genesis_info.ecotone_time),
391            (OpHardfork::Fjord.boxed(), genesis_info.fjord_time),
392            (OpHardfork::Granite.boxed(), genesis_info.granite_time),
393            (OpHardfork::Holocene.boxed(), genesis_info.holocene_time),
394            (OpHardfork::Isthmus.boxed(), genesis_info.isthmus_time),
395            (OpHardfork::Jovian.boxed(), genesis_info.jovian_time),
396            (OpHardfork::Interop.boxed(), genesis_info.interop_time),
397        ];
398
399        let mut time_hardforks = time_hardfork_opts
400            .into_iter()
401            .filter_map(|(hardfork, opt)| {
402                opt.map(|time| (hardfork, ForkCondition::Timestamp(time)))
403            })
404            .collect::<Vec<_>>();
405
406        block_hardforks.append(&mut time_hardforks);
407
408        // Ordered Hardforks
409        let mainnet_hardforks = OP_MAINNET_HARDFORKS.clone();
410        let mainnet_order = mainnet_hardforks.forks_iter();
411
412        let mut ordered_hardforks = Vec::with_capacity(block_hardforks.len());
413        for (hardfork, _) in mainnet_order {
414            if let Some(pos) = block_hardforks.iter().position(|(e, _)| **e == *hardfork) {
415                ordered_hardforks.push(block_hardforks.remove(pos));
416            }
417        }
418
419        // append the remaining unknown hardforks to ensure we don't filter any out
420        ordered_hardforks.append(&mut block_hardforks);
421
422        let hardforks = ChainHardforks::new(ordered_hardforks);
423        let genesis_header = SealedHeader::seal_slow(make_op_genesis_header(&genesis, &hardforks));
424
425        Self {
426            inner: ChainSpec {
427                chain: genesis.config.chain_id.into(),
428                genesis_header,
429                genesis,
430                hardforks,
431                // We assume no OP network merges, and set the paris block and total difficulty to
432                // zero
433                paris_block_and_final_difficulty: Some((0, U256::ZERO)),
434                base_fee_params: optimism_genesis_info.base_fee_params,
435                ..Default::default()
436            },
437        }
438    }
439}
440
441impl From<ChainSpec> for OpChainSpec {
442    fn from(value: ChainSpec) -> Self {
443        Self { inner: value }
444    }
445}
446
447#[derive(Default, Debug)]
448struct OpGenesisInfo {
449    optimism_chain_info: op_alloy_rpc_types::OpChainInfo,
450    base_fee_params: BaseFeeParamsKind,
451}
452
453impl OpGenesisInfo {
454    fn extract_from(genesis: &Genesis) -> Self {
455        let mut info = Self {
456            optimism_chain_info: op_alloy_rpc_types::OpChainInfo::extract_from(
457                &genesis.config.extra_fields,
458            )
459            .unwrap_or_default(),
460            ..Default::default()
461        };
462        if let Some(optimism_base_fee_info) = &info.optimism_chain_info.base_fee_info &&
463            let (Some(elasticity), Some(denominator)) = (
464                optimism_base_fee_info.eip1559_elasticity,
465                optimism_base_fee_info.eip1559_denominator,
466            )
467        {
468            let base_fee_params = if let Some(canyon_denominator) =
469                optimism_base_fee_info.eip1559_denominator_canyon
470            {
471                BaseFeeParamsKind::Variable(
472                    vec![
473                        (
474                            EthereumHardfork::London.boxed(),
475                            BaseFeeParams::new(denominator as u128, elasticity as u128),
476                        ),
477                        (
478                            OpHardfork::Canyon.boxed(),
479                            BaseFeeParams::new(canyon_denominator as u128, elasticity as u128),
480                        ),
481                    ]
482                    .into(),
483                )
484            } else {
485                BaseFeeParams::new(denominator as u128, elasticity as u128).into()
486            };
487
488            info.base_fee_params = base_fee_params;
489        }
490
491        info
492    }
493}
494
495/// Helper method building a [`Header`] given [`Genesis`] and [`ChainHardforks`].
496pub fn make_op_genesis_header(genesis: &Genesis, hardforks: &ChainHardforks) -> Header {
497    let mut header = reth_chainspec::make_genesis_header(genesis, hardforks);
498
499    // If Isthmus is active, overwrite the withdrawals root with the storage root of predeploy
500    // `L2ToL1MessagePasser.sol`
501    if hardforks.fork(OpHardfork::Isthmus).active_at_timestamp(header.timestamp) &&
502        let Some(predeploy) = genesis.alloc.get(&ADDRESS_L2_TO_L1_MESSAGE_PASSER) &&
503        let Some(storage) = &predeploy.storage
504    {
505        header.withdrawals_root =
506            Some(storage_root_unhashed(storage.iter().filter_map(|(k, v)| {
507                if v.is_zero() {
508                    None
509                } else {
510                    Some((*k, (*v).into()))
511                }
512            })));
513    }
514
515    header
516}
517
518#[cfg(test)]
519mod tests {
520    use alloc::string::{String, ToString};
521    use alloy_genesis::{ChainConfig, Genesis};
522    use alloy_op_hardforks::{
523        BASE_MAINNET_JOVIAN_TIMESTAMP, OP_MAINNET_JOVIAN_TIMESTAMP, OP_SEPOLIA_JOVIAN_TIMESTAMP,
524    };
525    use alloy_primitives::b256;
526    use reth_chainspec::{test_fork_ids, BaseFeeParams, BaseFeeParamsKind};
527    use reth_ethereum_forks::{EthereumHardfork, ForkCondition, ForkHash, ForkId, Head};
528    use reth_optimism_forks::{OpHardfork, OpHardforks};
529
530    use crate::*;
531
532    #[test]
533    fn test_storage_root_consistency() {
534        use alloy_primitives::{B256, U256};
535        use core::str::FromStr;
536
537        let k1 =
538            B256::from_str("0x0000000000000000000000000000000000000000000000000000000000000001")
539                .unwrap();
540        let v1 =
541            U256::from_str("0x0000000000000000000000000000000000000000000000000000000000000000")
542                .unwrap();
543        let k2 =
544            B256::from_str("0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc")
545                .unwrap();
546        let v2 =
547            U256::from_str("0x000000000000000000000000c0d3c0d3c0d3c0d3c0d3c0d3c0d3c0d3c0d30016")
548                .unwrap();
549        let k3 =
550            B256::from_str("0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103")
551                .unwrap();
552        let v3 =
553            U256::from_str("0x0000000000000000000000004200000000000000000000000000000000000018")
554                .unwrap();
555        let origin_root =
556            B256::from_str("0x5d5ba3a8093ede3901ad7a569edfb7b9aecafa54730ba0bf069147cbcc00e345")
557                .unwrap();
558        let expected_root =
559            B256::from_str("0x8ed4baae3a927be3dea54996b4d5899f8c01e7594bf50b17dc1e741388ce3d12")
560                .unwrap();
561
562        let storage_origin = vec![(k1, v1), (k2, v2), (k3, v3)];
563        let storage_fix = vec![(k2, v2), (k3, v3)];
564        let root_origin = storage_root_unhashed(storage_origin);
565        let root_fix = storage_root_unhashed(storage_fix);
566        assert_ne!(root_origin, root_fix);
567        assert_eq!(root_origin, origin_root);
568        assert_eq!(root_fix, expected_root);
569    }
570
571    #[test]
572    fn base_mainnet_forkids() {
573        let mut base_mainnet = OpChainSpecBuilder::base_mainnet().build();
574        base_mainnet.inner.genesis_header.set_hash(BASE_MAINNET.genesis_hash());
575        test_fork_ids(
576            &BASE_MAINNET,
577            &[
578                (
579                    Head { number: 0, ..Default::default() },
580                    ForkId { hash: ForkHash([0x67, 0xda, 0x02, 0x60]), next: 1704992401 },
581                ),
582                (
583                    Head { number: 0, timestamp: 1704992400, ..Default::default() },
584                    ForkId { hash: ForkHash([0x67, 0xda, 0x02, 0x60]), next: 1704992401 },
585                ),
586                (
587                    Head { number: 0, timestamp: 1704992401, ..Default::default() },
588                    ForkId { hash: ForkHash([0x3c, 0x28, 0x3c, 0xb3]), next: 1710374401 },
589                ),
590                (
591                    Head { number: 0, timestamp: 1710374400, ..Default::default() },
592                    ForkId { hash: ForkHash([0x3c, 0x28, 0x3c, 0xb3]), next: 1710374401 },
593                ),
594                (
595                    Head { number: 0, timestamp: 1710374401, ..Default::default() },
596                    ForkId { hash: ForkHash([0x51, 0xcc, 0x98, 0xb3]), next: 1720627201 },
597                ),
598                (
599                    Head { number: 0, timestamp: 1720627200, ..Default::default() },
600                    ForkId { hash: ForkHash([0x51, 0xcc, 0x98, 0xb3]), next: 1720627201 },
601                ),
602                (
603                    Head { number: 0, timestamp: 1720627201, ..Default::default() },
604                    ForkId { hash: ForkHash([0xe4, 0x01, 0x0e, 0xb9]), next: 1726070401 },
605                ),
606                (
607                    Head { number: 0, timestamp: 1726070401, ..Default::default() },
608                    ForkId { hash: ForkHash([0xbc, 0x38, 0xf9, 0xca]), next: 1736445601 },
609                ),
610                (
611                    Head { number: 0, timestamp: 1736445601, ..Default::default() },
612                    ForkId { hash: ForkHash([0x3a, 0x2a, 0xf1, 0x83]), next: 1746806401 },
613                ),
614                // Isthmus
615                (
616                    Head { number: 0, timestamp: 1746806401, ..Default::default() },
617                    ForkId {
618                        hash: ForkHash([0x86, 0x72, 0x8b, 0x4e]),
619                        next: BASE_MAINNET_JOVIAN_TIMESTAMP,
620                    },
621                ),
622                // Jovian
623                (
624                    Head {
625                        number: 0,
626                        timestamp: BASE_MAINNET_JOVIAN_TIMESTAMP,
627                        ..Default::default()
628                    },
629                    BASE_MAINNET.hardfork_fork_id(OpHardfork::Jovian).unwrap(),
630                ),
631            ],
632        );
633    }
634
635    #[test]
636    fn op_sepolia_forkids() {
637        test_fork_ids(
638            &OP_SEPOLIA,
639            &[
640                (
641                    Head { number: 0, ..Default::default() },
642                    ForkId { hash: ForkHash([0x67, 0xa4, 0x03, 0x28]), next: 1699981200 },
643                ),
644                (
645                    Head { number: 0, timestamp: 1699981199, ..Default::default() },
646                    ForkId { hash: ForkHash([0x67, 0xa4, 0x03, 0x28]), next: 1699981200 },
647                ),
648                (
649                    Head { number: 0, timestamp: 1699981200, ..Default::default() },
650                    ForkId { hash: ForkHash([0xa4, 0x8d, 0x6a, 0x00]), next: 1708534800 },
651                ),
652                (
653                    Head { number: 0, timestamp: 1708534799, ..Default::default() },
654                    ForkId { hash: ForkHash([0xa4, 0x8d, 0x6a, 0x00]), next: 1708534800 },
655                ),
656                (
657                    Head { number: 0, timestamp: 1708534800, ..Default::default() },
658                    ForkId { hash: ForkHash([0xcc, 0x17, 0xc7, 0xeb]), next: 1716998400 },
659                ),
660                (
661                    Head { number: 0, timestamp: 1716998399, ..Default::default() },
662                    ForkId { hash: ForkHash([0xcc, 0x17, 0xc7, 0xeb]), next: 1716998400 },
663                ),
664                (
665                    Head { number: 0, timestamp: 1716998400, ..Default::default() },
666                    ForkId { hash: ForkHash([0x54, 0x0a, 0x8c, 0x5d]), next: 1723478400 },
667                ),
668                (
669                    Head { number: 0, timestamp: 1723478399, ..Default::default() },
670                    ForkId { hash: ForkHash([0x54, 0x0a, 0x8c, 0x5d]), next: 1723478400 },
671                ),
672                (
673                    Head { number: 0, timestamp: 1723478400, ..Default::default() },
674                    ForkId { hash: ForkHash([0x75, 0xde, 0xa4, 0x1e]), next: 1732633200 },
675                ),
676                (
677                    Head { number: 0, timestamp: 1732633200, ..Default::default() },
678                    ForkId { hash: ForkHash([0x4a, 0x1c, 0x79, 0x2e]), next: 1744905600 },
679                ),
680                // Isthmus
681                (
682                    Head { number: 0, timestamp: 1744905600, ..Default::default() },
683                    ForkId {
684                        hash: ForkHash([0x6c, 0x62, 0x5e, 0xe1]),
685                        next: OP_SEPOLIA_JOVIAN_TIMESTAMP,
686                    },
687                ),
688                // Jovian
689                (
690                    Head {
691                        number: 0,
692                        timestamp: OP_SEPOLIA_JOVIAN_TIMESTAMP,
693                        ..Default::default()
694                    },
695                    OP_SEPOLIA.hardfork_fork_id(OpHardfork::Jovian).unwrap(),
696                ),
697            ],
698        );
699    }
700
701    #[test]
702    fn op_mainnet_forkids() {
703        let mut op_mainnet = OpChainSpecBuilder::optimism_mainnet().build();
704        // for OP mainnet we have to do this because the genesis header can't be properly computed
705        // from the genesis.json file
706        op_mainnet.inner.genesis_header.set_hash(OP_MAINNET.genesis_hash());
707        test_fork_ids(
708            &op_mainnet,
709            &[
710                (
711                    Head { number: 0, ..Default::default() },
712                    ForkId { hash: ForkHash([0xca, 0xf5, 0x17, 0xed]), next: 3950000 },
713                ),
714                // London
715                (
716                    Head { number: 105235063, ..Default::default() },
717                    ForkId { hash: ForkHash([0xe3, 0x39, 0x8d, 0x7c]), next: 1704992401 },
718                ),
719                // Bedrock
720                (
721                    Head { number: 105235063, ..Default::default() },
722                    ForkId { hash: ForkHash([0xe3, 0x39, 0x8d, 0x7c]), next: 1704992401 },
723                ),
724                // Shanghai
725                (
726                    Head { number: 105235063, timestamp: 1704992401, ..Default::default() },
727                    ForkId { hash: ForkHash([0xbd, 0xd4, 0xfd, 0xb2]), next: 1710374401 },
728                ),
729                // OP activation timestamps
730                // https://specs.optimism.io/protocol/superchain-upgrades.html#activation-timestamps
731                // Canyon
732                (
733                    Head { number: 105235063, timestamp: 1704992401, ..Default::default() },
734                    ForkId { hash: ForkHash([0xbd, 0xd4, 0xfd, 0xb2]), next: 1710374401 },
735                ),
736                // Ecotone
737                (
738                    Head { number: 105235063, timestamp: 1710374401, ..Default::default() },
739                    ForkId { hash: ForkHash([0x19, 0xda, 0x4c, 0x52]), next: 1720627201 },
740                ),
741                // Fjord
742                (
743                    Head { number: 105235063, timestamp: 1720627201, ..Default::default() },
744                    ForkId { hash: ForkHash([0x49, 0xfb, 0xfe, 0x1e]), next: 1726070401 },
745                ),
746                // Granite
747                (
748                    Head { number: 105235063, timestamp: 1726070401, ..Default::default() },
749                    ForkId { hash: ForkHash([0x44, 0x70, 0x4c, 0xde]), next: 1736445601 },
750                ),
751                // Holocene
752                (
753                    Head { number: 105235063, timestamp: 1736445601, ..Default::default() },
754                    ForkId { hash: ForkHash([0x2b, 0xd9, 0x3d, 0xc8]), next: 1746806401 },
755                ),
756                // Isthmus
757                (
758                    Head { number: 105235063, timestamp: 1746806401, ..Default::default() },
759                    ForkId {
760                        hash: ForkHash([0x37, 0xbe, 0x75, 0x8f]),
761                        next: OP_MAINNET_JOVIAN_TIMESTAMP,
762                    },
763                ),
764                // Jovian
765                (
766                    Head {
767                        number: 105235063,
768                        timestamp: OP_MAINNET_JOVIAN_TIMESTAMP,
769                        ..Default::default()
770                    },
771                    OP_MAINNET.hardfork_fork_id(OpHardfork::Jovian).unwrap(),
772                ),
773            ],
774        );
775    }
776
777    #[test]
778    fn base_sepolia_forkids() {
779        test_fork_ids(
780            &BASE_SEPOLIA,
781            &[
782                (
783                    Head { number: 0, ..Default::default() },
784                    ForkId { hash: ForkHash([0xb9, 0x59, 0xb9, 0xf7]), next: 1699981200 },
785                ),
786                (
787                    Head { number: 0, timestamp: 1699981199, ..Default::default() },
788                    ForkId { hash: ForkHash([0xb9, 0x59, 0xb9, 0xf7]), next: 1699981200 },
789                ),
790                (
791                    Head { number: 0, timestamp: 1699981200, ..Default::default() },
792                    ForkId { hash: ForkHash([0x60, 0x7c, 0xd5, 0xa1]), next: 1708534800 },
793                ),
794                (
795                    Head { number: 0, timestamp: 1708534799, ..Default::default() },
796                    ForkId { hash: ForkHash([0x60, 0x7c, 0xd5, 0xa1]), next: 1708534800 },
797                ),
798                (
799                    Head { number: 0, timestamp: 1708534800, ..Default::default() },
800                    ForkId { hash: ForkHash([0xbe, 0x96, 0x9b, 0x17]), next: 1716998400 },
801                ),
802                (
803                    Head { number: 0, timestamp: 1716998399, ..Default::default() },
804                    ForkId { hash: ForkHash([0xbe, 0x96, 0x9b, 0x17]), next: 1716998400 },
805                ),
806                (
807                    Head { number: 0, timestamp: 1716998400, ..Default::default() },
808                    ForkId { hash: ForkHash([0x4e, 0x45, 0x7a, 0x49]), next: 1723478400 },
809                ),
810                (
811                    Head { number: 0, timestamp: 1723478399, ..Default::default() },
812                    ForkId { hash: ForkHash([0x4e, 0x45, 0x7a, 0x49]), next: 1723478400 },
813                ),
814                (
815                    Head { number: 0, timestamp: 1723478400, ..Default::default() },
816                    ForkId { hash: ForkHash([0x5e, 0xdf, 0xa3, 0xb6]), next: 1732633200 },
817                ),
818                (
819                    Head { number: 0, timestamp: 1732633200, ..Default::default() },
820                    ForkId { hash: ForkHash([0x8b, 0x5e, 0x76, 0x29]), next: 1744905600 },
821                ),
822                // Isthmus
823                (
824                    Head { number: 0, timestamp: 1744905600, ..Default::default() },
825                    ForkId {
826                        hash: ForkHash([0x06, 0x0a, 0x4d, 0x1d]),
827                        next: OP_SEPOLIA_JOVIAN_TIMESTAMP,
828                    }, /* TODO: update timestamp when Jovian is planned */
829                ),
830                // Jovian
831                (
832                    Head {
833                        number: 0,
834                        timestamp: OP_SEPOLIA_JOVIAN_TIMESTAMP,
835                        ..Default::default()
836                    },
837                    BASE_SEPOLIA.hardfork_fork_id(OpHardfork::Jovian).unwrap(),
838                ),
839            ],
840        );
841    }
842
843    #[test]
844    fn base_mainnet_genesis() {
845        let genesis = BASE_MAINNET.genesis_header();
846        assert_eq!(
847            genesis.hash_slow(),
848            b256!("0xf712aa9241cc24369b143cf6dce85f0902a9731e70d66818a3a5845b296c73dd")
849        );
850        let base_fee = BASE_MAINNET.next_block_base_fee(genesis, genesis.timestamp).unwrap();
851        // <https://base.blockscout.com/block/1>
852        assert_eq!(base_fee, 980000000);
853    }
854
855    #[test]
856    fn base_sepolia_genesis() {
857        let genesis = BASE_SEPOLIA.genesis_header();
858        assert_eq!(
859            genesis.hash_slow(),
860            b256!("0x0dcc9e089e30b90ddfc55be9a37dd15bc551aeee999d2e2b51414c54eaf934e4")
861        );
862        let base_fee = BASE_SEPOLIA.next_block_base_fee(genesis, genesis.timestamp).unwrap();
863        // <https://base-sepolia.blockscout.com/block/1>
864        assert_eq!(base_fee, 980000000);
865    }
866
867    #[test]
868    fn op_sepolia_genesis() {
869        let genesis = OP_SEPOLIA.genesis_header();
870        assert_eq!(
871            genesis.hash_slow(),
872            b256!("0x102de6ffb001480cc9b8b548fd05c34cd4f46ae4aa91759393db90ea0409887d")
873        );
874        let base_fee = OP_SEPOLIA.next_block_base_fee(genesis, genesis.timestamp).unwrap();
875        // <https://optimism-sepolia.blockscout.com/block/1>
876        assert_eq!(base_fee, 980000000);
877    }
878
879    #[test]
880    fn latest_base_mainnet_fork_id() {
881        assert_eq!(
882            ForkId { hash: ForkHash([0xfa, 0x71, 0x70, 0xef]), next: 0 },
883            BASE_MAINNET.latest_fork_id()
884        )
885    }
886
887    #[test]
888    fn latest_base_mainnet_fork_id_with_builder() {
889        let base_mainnet = OpChainSpecBuilder::base_mainnet().build();
890        assert_eq!(
891            ForkId { hash: ForkHash([0xfa, 0x71, 0x70, 0xef]), next: 0 },
892            base_mainnet.latest_fork_id()
893        )
894    }
895
896    #[test]
897    fn is_bedrock_active() {
898        let op_mainnet = OpChainSpecBuilder::optimism_mainnet().build();
899        assert!(!op_mainnet.is_bedrock_active_at_block(1))
900    }
901
902    #[test]
903    fn parse_optimism_hardforks() {
904        let geth_genesis = r#"
905    {
906      "config": {
907        "bedrockBlock": 10,
908        "regolithTime": 20,
909        "canyonTime": 30,
910        "ecotoneTime": 40,
911        "fjordTime": 50,
912        "graniteTime": 51,
913        "holoceneTime": 52,
914        "isthmusTime": 53,
915        "optimism": {
916          "eip1559Elasticity": 60,
917          "eip1559Denominator": 70
918        }
919      }
920    }
921    "#;
922        let genesis: Genesis = serde_json::from_str(geth_genesis).unwrap();
923
924        let actual_bedrock_block = genesis.config.extra_fields.get("bedrockBlock");
925        assert_eq!(actual_bedrock_block, Some(serde_json::Value::from(10)).as_ref());
926        let actual_regolith_timestamp = genesis.config.extra_fields.get("regolithTime");
927        assert_eq!(actual_regolith_timestamp, Some(serde_json::Value::from(20)).as_ref());
928        let actual_canyon_timestamp = genesis.config.extra_fields.get("canyonTime");
929        assert_eq!(actual_canyon_timestamp, Some(serde_json::Value::from(30)).as_ref());
930        let actual_ecotone_timestamp = genesis.config.extra_fields.get("ecotoneTime");
931        assert_eq!(actual_ecotone_timestamp, Some(serde_json::Value::from(40)).as_ref());
932        let actual_fjord_timestamp = genesis.config.extra_fields.get("fjordTime");
933        assert_eq!(actual_fjord_timestamp, Some(serde_json::Value::from(50)).as_ref());
934        let actual_granite_timestamp = genesis.config.extra_fields.get("graniteTime");
935        assert_eq!(actual_granite_timestamp, Some(serde_json::Value::from(51)).as_ref());
936        let actual_holocene_timestamp = genesis.config.extra_fields.get("holoceneTime");
937        assert_eq!(actual_holocene_timestamp, Some(serde_json::Value::from(52)).as_ref());
938        let actual_isthmus_timestamp = genesis.config.extra_fields.get("isthmusTime");
939        assert_eq!(actual_isthmus_timestamp, Some(serde_json::Value::from(53)).as_ref());
940
941        let optimism_object = genesis.config.extra_fields.get("optimism").unwrap();
942        assert_eq!(
943            optimism_object,
944            &serde_json::json!({
945                "eip1559Elasticity": 60,
946                "eip1559Denominator": 70,
947            })
948        );
949
950        let chain_spec: OpChainSpec = genesis.into();
951
952        assert_eq!(
953            chain_spec.base_fee_params,
954            BaseFeeParamsKind::Constant(BaseFeeParams::new(70, 60))
955        );
956
957        assert!(!chain_spec.is_fork_active_at_block(OpHardfork::Bedrock, 0));
958        assert!(!chain_spec.is_fork_active_at_timestamp(OpHardfork::Regolith, 0));
959        assert!(!chain_spec.is_fork_active_at_timestamp(OpHardfork::Canyon, 0));
960        assert!(!chain_spec.is_fork_active_at_timestamp(OpHardfork::Ecotone, 0));
961        assert!(!chain_spec.is_fork_active_at_timestamp(OpHardfork::Fjord, 0));
962        assert!(!chain_spec.is_fork_active_at_timestamp(OpHardfork::Granite, 0));
963        assert!(!chain_spec.is_fork_active_at_timestamp(OpHardfork::Holocene, 0));
964
965        assert!(chain_spec.is_fork_active_at_block(OpHardfork::Bedrock, 10));
966        assert!(chain_spec.is_fork_active_at_timestamp(OpHardfork::Regolith, 20));
967        assert!(chain_spec.is_fork_active_at_timestamp(OpHardfork::Canyon, 30));
968        assert!(chain_spec.is_fork_active_at_timestamp(OpHardfork::Ecotone, 40));
969        assert!(chain_spec.is_fork_active_at_timestamp(OpHardfork::Fjord, 50));
970        assert!(chain_spec.is_fork_active_at_timestamp(OpHardfork::Granite, 51));
971        assert!(chain_spec.is_fork_active_at_timestamp(OpHardfork::Holocene, 52));
972    }
973
974    #[test]
975    fn parse_optimism_hardforks_variable_base_fee_params() {
976        let geth_genesis = r#"
977    {
978      "config": {
979        "bedrockBlock": 10,
980        "regolithTime": 20,
981        "canyonTime": 30,
982        "ecotoneTime": 40,
983        "fjordTime": 50,
984        "graniteTime": 51,
985        "holoceneTime": 52,
986        "isthmusTime": 53,
987        "optimism": {
988          "eip1559Elasticity": 60,
989          "eip1559Denominator": 70,
990          "eip1559DenominatorCanyon": 80
991        }
992      }
993    }
994    "#;
995        let genesis: Genesis = serde_json::from_str(geth_genesis).unwrap();
996
997        let actual_bedrock_block = genesis.config.extra_fields.get("bedrockBlock");
998        assert_eq!(actual_bedrock_block, Some(serde_json::Value::from(10)).as_ref());
999        let actual_regolith_timestamp = genesis.config.extra_fields.get("regolithTime");
1000        assert_eq!(actual_regolith_timestamp, Some(serde_json::Value::from(20)).as_ref());
1001        let actual_canyon_timestamp = genesis.config.extra_fields.get("canyonTime");
1002        assert_eq!(actual_canyon_timestamp, Some(serde_json::Value::from(30)).as_ref());
1003        let actual_ecotone_timestamp = genesis.config.extra_fields.get("ecotoneTime");
1004        assert_eq!(actual_ecotone_timestamp, Some(serde_json::Value::from(40)).as_ref());
1005        let actual_fjord_timestamp = genesis.config.extra_fields.get("fjordTime");
1006        assert_eq!(actual_fjord_timestamp, Some(serde_json::Value::from(50)).as_ref());
1007        let actual_granite_timestamp = genesis.config.extra_fields.get("graniteTime");
1008        assert_eq!(actual_granite_timestamp, Some(serde_json::Value::from(51)).as_ref());
1009        let actual_holocene_timestamp = genesis.config.extra_fields.get("holoceneTime");
1010        assert_eq!(actual_holocene_timestamp, Some(serde_json::Value::from(52)).as_ref());
1011        let actual_isthmus_timestamp = genesis.config.extra_fields.get("isthmusTime");
1012        assert_eq!(actual_isthmus_timestamp, Some(serde_json::Value::from(53)).as_ref());
1013
1014        let optimism_object = genesis.config.extra_fields.get("optimism").unwrap();
1015        assert_eq!(
1016            optimism_object,
1017            &serde_json::json!({
1018                "eip1559Elasticity": 60,
1019                "eip1559Denominator": 70,
1020                "eip1559DenominatorCanyon": 80
1021            })
1022        );
1023
1024        let chain_spec: OpChainSpec = genesis.into();
1025
1026        assert_eq!(
1027            chain_spec.base_fee_params,
1028            BaseFeeParamsKind::Variable(
1029                vec![
1030                    (EthereumHardfork::London.boxed(), BaseFeeParams::new(70, 60)),
1031                    (OpHardfork::Canyon.boxed(), BaseFeeParams::new(80, 60)),
1032                ]
1033                .into()
1034            )
1035        );
1036
1037        assert!(!chain_spec.is_fork_active_at_block(OpHardfork::Bedrock, 0));
1038        assert!(!chain_spec.is_fork_active_at_timestamp(OpHardfork::Regolith, 0));
1039        assert!(!chain_spec.is_fork_active_at_timestamp(OpHardfork::Canyon, 0));
1040        assert!(!chain_spec.is_fork_active_at_timestamp(OpHardfork::Ecotone, 0));
1041        assert!(!chain_spec.is_fork_active_at_timestamp(OpHardfork::Fjord, 0));
1042        assert!(!chain_spec.is_fork_active_at_timestamp(OpHardfork::Granite, 0));
1043        assert!(!chain_spec.is_fork_active_at_timestamp(OpHardfork::Holocene, 0));
1044
1045        assert!(chain_spec.is_fork_active_at_block(OpHardfork::Bedrock, 10));
1046        assert!(chain_spec.is_fork_active_at_timestamp(OpHardfork::Regolith, 20));
1047        assert!(chain_spec.is_fork_active_at_timestamp(OpHardfork::Canyon, 30));
1048        assert!(chain_spec.is_fork_active_at_timestamp(OpHardfork::Ecotone, 40));
1049        assert!(chain_spec.is_fork_active_at_timestamp(OpHardfork::Fjord, 50));
1050        assert!(chain_spec.is_fork_active_at_timestamp(OpHardfork::Granite, 51));
1051        assert!(chain_spec.is_fork_active_at_timestamp(OpHardfork::Holocene, 52));
1052    }
1053
1054    #[test]
1055    fn parse_genesis_optimism_with_variable_base_fee_params() {
1056        use op_alloy_rpc_types::OpBaseFeeInfo;
1057
1058        let geth_genesis = r#"
1059    {
1060      "config": {
1061        "chainId": 8453,
1062        "homesteadBlock": 0,
1063        "eip150Block": 0,
1064        "eip155Block": 0,
1065        "eip158Block": 0,
1066        "byzantiumBlock": 0,
1067        "constantinopleBlock": 0,
1068        "petersburgBlock": 0,
1069        "istanbulBlock": 0,
1070        "muirGlacierBlock": 0,
1071        "berlinBlock": 0,
1072        "londonBlock": 0,
1073        "arrowGlacierBlock": 0,
1074        "grayGlacierBlock": 0,
1075        "mergeNetsplitBlock": 0,
1076        "bedrockBlock": 0,
1077        "regolithTime": 15,
1078        "terminalTotalDifficulty": 0,
1079        "terminalTotalDifficultyPassed": true,
1080        "optimism": {
1081          "eip1559Elasticity": 6,
1082          "eip1559Denominator": 50
1083        }
1084      }
1085    }
1086    "#;
1087        let genesis: Genesis = serde_json::from_str(geth_genesis).unwrap();
1088        let chainspec = OpChainSpec::from(genesis.clone());
1089
1090        let actual_chain_id = genesis.config.chain_id;
1091        assert_eq!(actual_chain_id, 8453);
1092
1093        assert_eq!(
1094            chainspec.hardforks.get(EthereumHardfork::Istanbul),
1095            Some(ForkCondition::Block(0))
1096        );
1097
1098        let actual_bedrock_block = genesis.config.extra_fields.get("bedrockBlock");
1099        assert_eq!(actual_bedrock_block, Some(serde_json::Value::from(0)).as_ref());
1100        let actual_canyon_timestamp = genesis.config.extra_fields.get("canyonTime");
1101        assert_eq!(actual_canyon_timestamp, None);
1102
1103        assert!(genesis.config.terminal_total_difficulty_passed);
1104
1105        let optimism_object = genesis.config.extra_fields.get("optimism").unwrap();
1106        let optimism_base_fee_info =
1107            serde_json::from_value::<OpBaseFeeInfo>(optimism_object.clone()).unwrap();
1108
1109        assert_eq!(
1110            optimism_base_fee_info,
1111            OpBaseFeeInfo {
1112                eip1559_elasticity: Some(6),
1113                eip1559_denominator: Some(50),
1114                eip1559_denominator_canyon: None,
1115            }
1116        );
1117        assert_eq!(
1118            chainspec.base_fee_params,
1119            BaseFeeParamsKind::Constant(BaseFeeParams {
1120                max_change_denominator: 50,
1121                elasticity_multiplier: 6,
1122            })
1123        );
1124
1125        assert!(chainspec.is_fork_active_at_block(OpHardfork::Bedrock, 0));
1126
1127        assert!(chainspec.is_fork_active_at_timestamp(OpHardfork::Regolith, 20));
1128    }
1129
1130    #[test]
1131    fn test_fork_order_optimism_mainnet() {
1132        use reth_optimism_forks::OpHardfork;
1133
1134        let genesis = Genesis {
1135            config: ChainConfig {
1136                chain_id: 0,
1137                homestead_block: Some(0),
1138                dao_fork_block: Some(0),
1139                dao_fork_support: false,
1140                eip150_block: Some(0),
1141                eip155_block: Some(0),
1142                eip158_block: Some(0),
1143                byzantium_block: Some(0),
1144                constantinople_block: Some(0),
1145                petersburg_block: Some(0),
1146                istanbul_block: Some(0),
1147                muir_glacier_block: Some(0),
1148                berlin_block: Some(0),
1149                london_block: Some(0),
1150                arrow_glacier_block: Some(0),
1151                gray_glacier_block: Some(0),
1152                merge_netsplit_block: Some(0),
1153                shanghai_time: Some(0),
1154                cancun_time: Some(0),
1155                prague_time: Some(0),
1156                terminal_total_difficulty: Some(U256::ZERO),
1157                extra_fields: [
1158                    (String::from("bedrockBlock"), 0.into()),
1159                    (String::from("regolithTime"), 0.into()),
1160                    (String::from("canyonTime"), 0.into()),
1161                    (String::from("ecotoneTime"), 0.into()),
1162                    (String::from("fjordTime"), 0.into()),
1163                    (String::from("graniteTime"), 0.into()),
1164                    (String::from("holoceneTime"), 0.into()),
1165                    (String::from("isthmusTime"), 0.into()),
1166                    (String::from("jovianTime"), 0.into()),
1167                ]
1168                .into_iter()
1169                .collect(),
1170                ..Default::default()
1171            },
1172            ..Default::default()
1173        };
1174
1175        let chain_spec: OpChainSpec = genesis.into();
1176
1177        let hardforks: Vec<_> = chain_spec.hardforks.forks_iter().map(|(h, _)| h).collect();
1178        let expected_hardforks = vec![
1179            EthereumHardfork::Frontier.boxed(),
1180            EthereumHardfork::Homestead.boxed(),
1181            EthereumHardfork::Tangerine.boxed(),
1182            EthereumHardfork::SpuriousDragon.boxed(),
1183            EthereumHardfork::Byzantium.boxed(),
1184            EthereumHardfork::Constantinople.boxed(),
1185            EthereumHardfork::Petersburg.boxed(),
1186            EthereumHardfork::Istanbul.boxed(),
1187            EthereumHardfork::MuirGlacier.boxed(),
1188            EthereumHardfork::Berlin.boxed(),
1189            EthereumHardfork::London.boxed(),
1190            EthereumHardfork::ArrowGlacier.boxed(),
1191            EthereumHardfork::GrayGlacier.boxed(),
1192            EthereumHardfork::Paris.boxed(),
1193            OpHardfork::Bedrock.boxed(),
1194            OpHardfork::Regolith.boxed(),
1195            EthereumHardfork::Shanghai.boxed(),
1196            OpHardfork::Canyon.boxed(),
1197            EthereumHardfork::Cancun.boxed(),
1198            OpHardfork::Ecotone.boxed(),
1199            OpHardfork::Fjord.boxed(),
1200            OpHardfork::Granite.boxed(),
1201            OpHardfork::Holocene.boxed(),
1202            EthereumHardfork::Prague.boxed(),
1203            OpHardfork::Isthmus.boxed(),
1204            OpHardfork::Jovian.boxed(),
1205            // OpHardfork::Interop.boxed(),
1206        ];
1207
1208        for (expected, actual) in expected_hardforks.iter().zip(hardforks.iter()) {
1209            assert_eq!(&**expected, &**actual);
1210        }
1211        assert_eq!(expected_hardforks.len(), hardforks.len());
1212    }
1213
1214    #[test]
1215    fn json_genesis() {
1216        let geth_genesis = r#"
1217{
1218    "config": {
1219        "chainId": 1301,
1220        "homesteadBlock": 0,
1221        "eip150Block": 0,
1222        "eip155Block": 0,
1223        "eip158Block": 0,
1224        "byzantiumBlock": 0,
1225        "constantinopleBlock": 0,
1226        "petersburgBlock": 0,
1227        "istanbulBlock": 0,
1228        "muirGlacierBlock": 0,
1229        "berlinBlock": 0,
1230        "londonBlock": 0,
1231        "arrowGlacierBlock": 0,
1232        "grayGlacierBlock": 0,
1233        "mergeNetsplitBlock": 0,
1234        "shanghaiTime": 0,
1235        "cancunTime": 0,
1236        "bedrockBlock": 0,
1237        "regolithTime": 0,
1238        "canyonTime": 0,
1239        "ecotoneTime": 0,
1240        "fjordTime": 0,
1241        "graniteTime": 0,
1242        "holoceneTime": 1732633200,
1243        "terminalTotalDifficulty": 0,
1244        "terminalTotalDifficultyPassed": true,
1245        "optimism": {
1246            "eip1559Elasticity": 6,
1247            "eip1559Denominator": 50,
1248            "eip1559DenominatorCanyon": 250
1249        }
1250    },
1251    "nonce": "0x0",
1252    "timestamp": "0x66edad4c",
1253    "extraData": "0x424544524f434b",
1254    "gasLimit": "0x1c9c380",
1255    "difficulty": "0x0",
1256    "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
1257    "coinbase": "0x4200000000000000000000000000000000000011",
1258    "alloc": {},
1259    "number": "0x0",
1260    "gasUsed": "0x0",
1261    "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
1262    "baseFeePerGas": "0x3b9aca00",
1263    "excessBlobGas": "0x0",
1264    "blobGasUsed": "0x0"
1265}
1266        "#;
1267
1268        let genesis: Genesis = serde_json::from_str(geth_genesis).unwrap();
1269        let chainspec = OpChainSpec::from_genesis(genesis);
1270        assert!(chainspec.is_holocene_active_at_timestamp(1732633200));
1271    }
1272
1273    #[test]
1274    fn json_genesis_mapped_l1_timestamps() {
1275        let geth_genesis = r#"
1276{
1277    "config": {
1278        "chainId": 1301,
1279        "homesteadBlock": 0,
1280        "eip150Block": 0,
1281        "eip155Block": 0,
1282        "eip158Block": 0,
1283        "byzantiumBlock": 0,
1284        "constantinopleBlock": 0,
1285        "petersburgBlock": 0,
1286        "istanbulBlock": 0,
1287        "muirGlacierBlock": 0,
1288        "berlinBlock": 0,
1289        "londonBlock": 0,
1290        "arrowGlacierBlock": 0,
1291        "grayGlacierBlock": 0,
1292        "mergeNetsplitBlock": 0,
1293        "bedrockBlock": 0,
1294        "regolithTime": 0,
1295        "canyonTime": 0,
1296        "ecotoneTime": 1712633200,
1297        "fjordTime": 0,
1298        "graniteTime": 0,
1299        "holoceneTime": 1732633200,
1300        "isthmusTime": 1742633200,
1301        "terminalTotalDifficulty": 0,
1302        "terminalTotalDifficultyPassed": true,
1303        "optimism": {
1304            "eip1559Elasticity": 6,
1305            "eip1559Denominator": 50,
1306            "eip1559DenominatorCanyon": 250
1307        }
1308    },
1309    "nonce": "0x0",
1310    "timestamp": "0x66edad4c",
1311    "extraData": "0x424544524f434b",
1312    "gasLimit": "0x1c9c380",
1313    "difficulty": "0x0",
1314    "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
1315    "coinbase": "0x4200000000000000000000000000000000000011",
1316    "alloc": {},
1317    "number": "0x0",
1318    "gasUsed": "0x0",
1319    "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
1320    "baseFeePerGas": "0x3b9aca00",
1321    "excessBlobGas": "0x0",
1322    "blobGasUsed": "0x0"
1323}
1324        "#;
1325
1326        let genesis: Genesis = serde_json::from_str(geth_genesis).unwrap();
1327        let chainspec = OpChainSpec::from_genesis(genesis);
1328        assert!(chainspec.is_holocene_active_at_timestamp(1732633200));
1329
1330        assert!(chainspec.is_shanghai_active_at_timestamp(0));
1331        assert!(chainspec.is_canyon_active_at_timestamp(0));
1332
1333        assert!(chainspec.is_ecotone_active_at_timestamp(1712633200));
1334        assert!(chainspec.is_cancun_active_at_timestamp(1712633200));
1335
1336        assert!(chainspec.is_prague_active_at_timestamp(1742633200));
1337        assert!(chainspec.is_isthmus_active_at_timestamp(1742633200));
1338    }
1339
1340    #[test]
1341    fn display_hardorks() {
1342        let content = BASE_MAINNET.display_hardforks().to_string();
1343        for eth_hf in EthereumHardfork::VARIANTS {
1344            assert!(!content.contains(eth_hf.name()));
1345        }
1346    }
1347}