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, doc_auto_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_holocene_active_at_timestamp(parent.timestamp()) {
297            decode_holocene_base_fee(self, parent, target_timestamp).ok()
298        } else {
299            self.inner.next_block_base_fee(parent, target_timestamp)
300        }
301    }
302}
303
304impl Hardforks for OpChainSpec {
305    fn fork<H: Hardfork>(&self, fork: H) -> ForkCondition {
306        self.inner.fork(fork)
307    }
308
309    fn forks_iter(&self) -> impl Iterator<Item = (&dyn Hardfork, ForkCondition)> {
310        self.inner.forks_iter()
311    }
312
313    fn fork_id(&self, head: &Head) -> ForkId {
314        self.inner.fork_id(head)
315    }
316
317    fn latest_fork_id(&self) -> ForkId {
318        self.inner.latest_fork_id()
319    }
320
321    fn fork_filter(&self, head: Head) -> ForkFilter {
322        self.inner.fork_filter(head)
323    }
324}
325
326impl EthereumHardforks for OpChainSpec {
327    fn ethereum_fork_activation(&self, fork: EthereumHardfork) -> ForkCondition {
328        self.fork(fork)
329    }
330}
331
332impl OpHardforks for OpChainSpec {
333    fn op_fork_activation(&self, fork: OpHardfork) -> ForkCondition {
334        self.fork(fork)
335    }
336}
337
338impl From<Genesis> for OpChainSpec {
339    fn from(genesis: Genesis) -> Self {
340        use reth_optimism_forks::OpHardfork;
341        let optimism_genesis_info = OpGenesisInfo::extract_from(&genesis);
342        let genesis_info =
343            optimism_genesis_info.optimism_chain_info.genesis_info.unwrap_or_default();
344
345        // Block-based hardforks
346        let hardfork_opts = [
347            (EthereumHardfork::Frontier.boxed(), Some(0)),
348            (EthereumHardfork::Homestead.boxed(), genesis.config.homestead_block),
349            (EthereumHardfork::Tangerine.boxed(), genesis.config.eip150_block),
350            (EthereumHardfork::SpuriousDragon.boxed(), genesis.config.eip155_block),
351            (EthereumHardfork::Byzantium.boxed(), genesis.config.byzantium_block),
352            (EthereumHardfork::Constantinople.boxed(), genesis.config.constantinople_block),
353            (EthereumHardfork::Petersburg.boxed(), genesis.config.petersburg_block),
354            (EthereumHardfork::Istanbul.boxed(), genesis.config.istanbul_block),
355            (EthereumHardfork::MuirGlacier.boxed(), genesis.config.muir_glacier_block),
356            (EthereumHardfork::Berlin.boxed(), genesis.config.berlin_block),
357            (EthereumHardfork::London.boxed(), genesis.config.london_block),
358            (EthereumHardfork::ArrowGlacier.boxed(), genesis.config.arrow_glacier_block),
359            (EthereumHardfork::GrayGlacier.boxed(), genesis.config.gray_glacier_block),
360            (OpHardfork::Bedrock.boxed(), genesis_info.bedrock_block),
361        ];
362        let mut block_hardforks = hardfork_opts
363            .into_iter()
364            .filter_map(|(hardfork, opt)| opt.map(|block| (hardfork, ForkCondition::Block(block))))
365            .collect::<Vec<_>>();
366
367        // We set the paris hardfork for OP networks to zero
368        block_hardforks.push((
369            EthereumHardfork::Paris.boxed(),
370            ForkCondition::TTD {
371                activation_block_number: 0,
372                total_difficulty: U256::ZERO,
373                fork_block: genesis.config.merge_netsplit_block,
374            },
375        ));
376
377        // Time-based hardforks
378        let time_hardfork_opts = [
379            // L1
380            // we need to map the L1 hardforks to the activation timestamps of the correspondong op
381            // hardforks
382            (EthereumHardfork::Shanghai.boxed(), genesis_info.canyon_time),
383            (EthereumHardfork::Cancun.boxed(), genesis_info.ecotone_time),
384            (EthereumHardfork::Prague.boxed(), genesis_info.isthmus_time),
385            // OP
386            (OpHardfork::Regolith.boxed(), genesis_info.regolith_time),
387            (OpHardfork::Canyon.boxed(), genesis_info.canyon_time),
388            (OpHardfork::Ecotone.boxed(), genesis_info.ecotone_time),
389            (OpHardfork::Fjord.boxed(), genesis_info.fjord_time),
390            (OpHardfork::Granite.boxed(), genesis_info.granite_time),
391            (OpHardfork::Holocene.boxed(), genesis_info.holocene_time),
392            (OpHardfork::Isthmus.boxed(), genesis_info.isthmus_time),
393            (OpHardfork::Jovian.boxed(), genesis_info.jovian_time),
394            (OpHardfork::Interop.boxed(), genesis_info.interop_time),
395        ];
396
397        let mut time_hardforks = time_hardfork_opts
398            .into_iter()
399            .filter_map(|(hardfork, opt)| {
400                opt.map(|time| (hardfork, ForkCondition::Timestamp(time)))
401            })
402            .collect::<Vec<_>>();
403
404        block_hardforks.append(&mut time_hardforks);
405
406        // Ordered Hardforks
407        let mainnet_hardforks = OP_MAINNET_HARDFORKS.clone();
408        let mainnet_order = mainnet_hardforks.forks_iter();
409
410        let mut ordered_hardforks = Vec::with_capacity(block_hardforks.len());
411        for (hardfork, _) in mainnet_order {
412            if let Some(pos) = block_hardforks.iter().position(|(e, _)| **e == *hardfork) {
413                ordered_hardforks.push(block_hardforks.remove(pos));
414            }
415        }
416
417        // append the remaining unknown hardforks to ensure we don't filter any out
418        ordered_hardforks.append(&mut block_hardforks);
419
420        let hardforks = ChainHardforks::new(ordered_hardforks);
421        let genesis_header = SealedHeader::seal_slow(make_op_genesis_header(&genesis, &hardforks));
422
423        Self {
424            inner: ChainSpec {
425                chain: genesis.config.chain_id.into(),
426                genesis_header,
427                genesis,
428                hardforks,
429                // We assume no OP network merges, and set the paris block and total difficulty to
430                // zero
431                paris_block_and_final_difficulty: Some((0, U256::ZERO)),
432                base_fee_params: optimism_genesis_info.base_fee_params,
433                ..Default::default()
434            },
435        }
436    }
437}
438
439impl From<ChainSpec> for OpChainSpec {
440    fn from(value: ChainSpec) -> Self {
441        Self { inner: value }
442    }
443}
444
445#[derive(Default, Debug)]
446struct OpGenesisInfo {
447    optimism_chain_info: op_alloy_rpc_types::OpChainInfo,
448    base_fee_params: BaseFeeParamsKind,
449}
450
451impl OpGenesisInfo {
452    fn extract_from(genesis: &Genesis) -> Self {
453        let mut info = Self {
454            optimism_chain_info: op_alloy_rpc_types::OpChainInfo::extract_from(
455                &genesis.config.extra_fields,
456            )
457            .unwrap_or_default(),
458            ..Default::default()
459        };
460        if let Some(optimism_base_fee_info) = &info.optimism_chain_info.base_fee_info {
461            if let (Some(elasticity), Some(denominator)) = (
462                optimism_base_fee_info.eip1559_elasticity,
463                optimism_base_fee_info.eip1559_denominator,
464            ) {
465                let base_fee_params = if let Some(canyon_denominator) =
466                    optimism_base_fee_info.eip1559_denominator_canyon
467                {
468                    BaseFeeParamsKind::Variable(
469                        vec![
470                            (
471                                EthereumHardfork::London.boxed(),
472                                BaseFeeParams::new(denominator as u128, elasticity as u128),
473                            ),
474                            (
475                                OpHardfork::Canyon.boxed(),
476                                BaseFeeParams::new(canyon_denominator as u128, elasticity as u128),
477                            ),
478                        ]
479                        .into(),
480                    )
481                } else {
482                    BaseFeeParams::new(denominator as u128, elasticity as u128).into()
483                };
484
485                info.base_fee_params = base_fee_params;
486            }
487        }
488
489        info
490    }
491}
492
493/// Helper method building a [`Header`] given [`Genesis`] and [`ChainHardforks`].
494pub fn make_op_genesis_header(genesis: &Genesis, hardforks: &ChainHardforks) -> Header {
495    let mut header = reth_chainspec::make_genesis_header(genesis, hardforks);
496
497    // If Isthmus is active, overwrite the withdrawals root with the storage root of predeploy
498    // `L2ToL1MessagePasser.sol`
499    if hardforks.fork(OpHardfork::Isthmus).active_at_timestamp(header.timestamp) {
500        if let Some(predeploy) = genesis.alloc.get(&ADDRESS_L2_TO_L1_MESSAGE_PASSER) {
501            if let Some(storage) = &predeploy.storage {
502                header.withdrawals_root =
503                    Some(storage_root_unhashed(storage.iter().map(|(k, v)| (*k, (*v).into()))))
504            }
505        }
506    }
507
508    header
509}
510
511#[cfg(test)]
512mod tests {
513    use alloc::string::String;
514    use alloy_genesis::{ChainConfig, Genesis};
515    use alloy_primitives::b256;
516    use reth_chainspec::{test_fork_ids, BaseFeeParams, BaseFeeParamsKind};
517    use reth_ethereum_forks::{EthereumHardfork, ForkCondition, ForkHash, ForkId, Head};
518    use reth_optimism_forks::{OpHardfork, OpHardforks};
519
520    use crate::*;
521
522    #[test]
523    fn base_mainnet_forkids() {
524        let mut base_mainnet = OpChainSpecBuilder::base_mainnet().build();
525        base_mainnet.inner.genesis_header.set_hash(BASE_MAINNET.genesis_hash());
526        test_fork_ids(
527            &BASE_MAINNET,
528            &[
529                (
530                    Head { number: 0, ..Default::default() },
531                    ForkId { hash: ForkHash([0x67, 0xda, 0x02, 0x60]), next: 1704992401 },
532                ),
533                (
534                    Head { number: 0, timestamp: 1704992400, ..Default::default() },
535                    ForkId { hash: ForkHash([0x67, 0xda, 0x02, 0x60]), next: 1704992401 },
536                ),
537                (
538                    Head { number: 0, timestamp: 1704992401, ..Default::default() },
539                    ForkId { hash: ForkHash([0x3c, 0x28, 0x3c, 0xb3]), next: 1710374401 },
540                ),
541                (
542                    Head { number: 0, timestamp: 1710374400, ..Default::default() },
543                    ForkId { hash: ForkHash([0x3c, 0x28, 0x3c, 0xb3]), next: 1710374401 },
544                ),
545                (
546                    Head { number: 0, timestamp: 1710374401, ..Default::default() },
547                    ForkId { hash: ForkHash([0x51, 0xcc, 0x98, 0xb3]), next: 1720627201 },
548                ),
549                (
550                    Head { number: 0, timestamp: 1720627200, ..Default::default() },
551                    ForkId { hash: ForkHash([0x51, 0xcc, 0x98, 0xb3]), next: 1720627201 },
552                ),
553                (
554                    Head { number: 0, timestamp: 1720627201, ..Default::default() },
555                    ForkId { hash: ForkHash([0xe4, 0x01, 0x0e, 0xb9]), next: 1726070401 },
556                ),
557                (
558                    Head { number: 0, timestamp: 1726070401, ..Default::default() },
559                    ForkId { hash: ForkHash([0xbc, 0x38, 0xf9, 0xca]), next: 1736445601 },
560                ),
561                (
562                    Head { number: 0, timestamp: 1736445601, ..Default::default() },
563                    ForkId { hash: ForkHash([0x3a, 0x2a, 0xf1, 0x83]), next: 1746806401 },
564                ),
565                // Isthmus
566                (
567                    Head { number: 0, timestamp: 1746806401, ..Default::default() },
568                    ForkId { hash: ForkHash([0x86, 0x72, 0x8b, 0x4e]), next: 0 }, /* TODO: update timestamp when Jovian is planned */
569                ),
570                // // Jovian
571                // (
572                //     Head { number: 0, timestamp: u64::MAX, ..Default::default() }, /* TODO:
573                // update timestamp when Jovian is planned */     ForkId { hash:
574                // ForkHash([0xef, 0x0e, 0x58, 0x33]), next: 0 }, ),
575            ],
576        );
577    }
578
579    #[test]
580    fn op_sepolia_forkids() {
581        test_fork_ids(
582            &OP_SEPOLIA,
583            &[
584                (
585                    Head { number: 0, ..Default::default() },
586                    ForkId { hash: ForkHash([0x67, 0xa4, 0x03, 0x28]), next: 1699981200 },
587                ),
588                (
589                    Head { number: 0, timestamp: 1699981199, ..Default::default() },
590                    ForkId { hash: ForkHash([0x67, 0xa4, 0x03, 0x28]), next: 1699981200 },
591                ),
592                (
593                    Head { number: 0, timestamp: 1699981200, ..Default::default() },
594                    ForkId { hash: ForkHash([0xa4, 0x8d, 0x6a, 0x00]), next: 1708534800 },
595                ),
596                (
597                    Head { number: 0, timestamp: 1708534799, ..Default::default() },
598                    ForkId { hash: ForkHash([0xa4, 0x8d, 0x6a, 0x00]), next: 1708534800 },
599                ),
600                (
601                    Head { number: 0, timestamp: 1708534800, ..Default::default() },
602                    ForkId { hash: ForkHash([0xcc, 0x17, 0xc7, 0xeb]), next: 1716998400 },
603                ),
604                (
605                    Head { number: 0, timestamp: 1716998399, ..Default::default() },
606                    ForkId { hash: ForkHash([0xcc, 0x17, 0xc7, 0xeb]), next: 1716998400 },
607                ),
608                (
609                    Head { number: 0, timestamp: 1716998400, ..Default::default() },
610                    ForkId { hash: ForkHash([0x54, 0x0a, 0x8c, 0x5d]), next: 1723478400 },
611                ),
612                (
613                    Head { number: 0, timestamp: 1723478399, ..Default::default() },
614                    ForkId { hash: ForkHash([0x54, 0x0a, 0x8c, 0x5d]), next: 1723478400 },
615                ),
616                (
617                    Head { number: 0, timestamp: 1723478400, ..Default::default() },
618                    ForkId { hash: ForkHash([0x75, 0xde, 0xa4, 0x1e]), next: 1732633200 },
619                ),
620                (
621                    Head { number: 0, timestamp: 1732633200, ..Default::default() },
622                    ForkId { hash: ForkHash([0x4a, 0x1c, 0x79, 0x2e]), next: 1744905600 },
623                ),
624                // Isthmus
625                (
626                    Head { number: 0, timestamp: 1744905600, ..Default::default() },
627                    ForkId { hash: ForkHash([0x6c, 0x62, 0x5e, 0xe1]), next: 0 }, /* TODO: update timestamp when Jovian is planned */
628                ),
629                // // Jovian
630                // (
631                //     Head { number: 0, timestamp: u64::MAX, ..Default::default() }, /* TODO:
632                // update timestamp when Jovian is planned */     ForkId { hash:
633                // ForkHash([0x04, 0x2a, 0x5c, 0x14]), next: 0 }, ),
634            ],
635        );
636    }
637
638    #[test]
639    fn op_mainnet_forkids() {
640        let mut op_mainnet = OpChainSpecBuilder::optimism_mainnet().build();
641        // for OP mainnet we have to do this because the genesis header can't be properly computed
642        // from the genesis.json file
643        op_mainnet.inner.genesis_header.set_hash(OP_MAINNET.genesis_hash());
644        test_fork_ids(
645            &op_mainnet,
646            &[
647                (
648                    Head { number: 0, ..Default::default() },
649                    ForkId { hash: ForkHash([0xca, 0xf5, 0x17, 0xed]), next: 3950000 },
650                ),
651                // London
652                (
653                    Head { number: 105235063, ..Default::default() },
654                    ForkId { hash: ForkHash([0xe3, 0x39, 0x8d, 0x7c]), next: 1704992401 },
655                ),
656                // Bedrock
657                (
658                    Head { number: 105235063, ..Default::default() },
659                    ForkId { hash: ForkHash([0xe3, 0x39, 0x8d, 0x7c]), next: 1704992401 },
660                ),
661                // Shanghai
662                (
663                    Head { number: 105235063, timestamp: 1704992401, ..Default::default() },
664                    ForkId { hash: ForkHash([0xbd, 0xd4, 0xfd, 0xb2]), next: 1710374401 },
665                ),
666                // OP activation timestamps
667                // https://specs.optimism.io/protocol/superchain-upgrades.html#activation-timestamps
668                // Canyon
669                (
670                    Head { number: 105235063, timestamp: 1704992401, ..Default::default() },
671                    ForkId { hash: ForkHash([0xbd, 0xd4, 0xfd, 0xb2]), next: 1710374401 },
672                ),
673                // Ecotone
674                (
675                    Head { number: 105235063, timestamp: 1710374401, ..Default::default() },
676                    ForkId { hash: ForkHash([0x19, 0xda, 0x4c, 0x52]), next: 1720627201 },
677                ),
678                // Fjord
679                (
680                    Head { number: 105235063, timestamp: 1720627201, ..Default::default() },
681                    ForkId { hash: ForkHash([0x49, 0xfb, 0xfe, 0x1e]), next: 1726070401 },
682                ),
683                // Granite
684                (
685                    Head { number: 105235063, timestamp: 1726070401, ..Default::default() },
686                    ForkId { hash: ForkHash([0x44, 0x70, 0x4c, 0xde]), next: 1736445601 },
687                ),
688                // Holocene
689                (
690                    Head { number: 105235063, timestamp: 1736445601, ..Default::default() },
691                    ForkId { hash: ForkHash([0x2b, 0xd9, 0x3d, 0xc8]), next: 1746806401 },
692                ),
693                // Isthmus
694                (
695                    Head { number: 105235063, timestamp: 1746806401, ..Default::default() },
696                    ForkId { hash: ForkHash([0x37, 0xbe, 0x75, 0x8f]), next: 0 }, /* TODO: update timestamp when Jovian is planned */
697                ),
698                // Jovian
699                // (
700                //     Head { number: 105235063, timestamp: u64::MAX, ..Default::default() }, /*
701                // TODO: update timestamp when Jovian is planned */     ForkId {
702                // hash: ForkHash([0x26, 0xce, 0xa1, 0x75]), next: 0 }, ),
703            ],
704        );
705    }
706
707    #[test]
708    fn base_sepolia_forkids() {
709        test_fork_ids(
710            &BASE_SEPOLIA,
711            &[
712                (
713                    Head { number: 0, ..Default::default() },
714                    ForkId { hash: ForkHash([0xb9, 0x59, 0xb9, 0xf7]), next: 1699981200 },
715                ),
716                (
717                    Head { number: 0, timestamp: 1699981199, ..Default::default() },
718                    ForkId { hash: ForkHash([0xb9, 0x59, 0xb9, 0xf7]), next: 1699981200 },
719                ),
720                (
721                    Head { number: 0, timestamp: 1699981200, ..Default::default() },
722                    ForkId { hash: ForkHash([0x60, 0x7c, 0xd5, 0xa1]), next: 1708534800 },
723                ),
724                (
725                    Head { number: 0, timestamp: 1708534799, ..Default::default() },
726                    ForkId { hash: ForkHash([0x60, 0x7c, 0xd5, 0xa1]), next: 1708534800 },
727                ),
728                (
729                    Head { number: 0, timestamp: 1708534800, ..Default::default() },
730                    ForkId { hash: ForkHash([0xbe, 0x96, 0x9b, 0x17]), next: 1716998400 },
731                ),
732                (
733                    Head { number: 0, timestamp: 1716998399, ..Default::default() },
734                    ForkId { hash: ForkHash([0xbe, 0x96, 0x9b, 0x17]), next: 1716998400 },
735                ),
736                (
737                    Head { number: 0, timestamp: 1716998400, ..Default::default() },
738                    ForkId { hash: ForkHash([0x4e, 0x45, 0x7a, 0x49]), next: 1723478400 },
739                ),
740                (
741                    Head { number: 0, timestamp: 1723478399, ..Default::default() },
742                    ForkId { hash: ForkHash([0x4e, 0x45, 0x7a, 0x49]), next: 1723478400 },
743                ),
744                (
745                    Head { number: 0, timestamp: 1723478400, ..Default::default() },
746                    ForkId { hash: ForkHash([0x5e, 0xdf, 0xa3, 0xb6]), next: 1732633200 },
747                ),
748                (
749                    Head { number: 0, timestamp: 1732633200, ..Default::default() },
750                    ForkId { hash: ForkHash([0x8b, 0x5e, 0x76, 0x29]), next: 1744905600 },
751                ),
752                // Isthmus
753                (
754                    Head { number: 0, timestamp: 1744905600, ..Default::default() },
755                    ForkId { hash: ForkHash([0x06, 0x0a, 0x4d, 0x1d]), next: 0 }, /* TODO: update timestamp when Jovian is planned */
756                ),
757                // // Jovian
758                // (
759                //     Head { number: 0, timestamp: u64::MAX, ..Default::default() }, /* TODO:
760                // update timestamp when Jovian is planned */     ForkId { hash:
761                // ForkHash([0xcd, 0xfd, 0x39, 0x99]), next: 0 }, ),
762            ],
763        );
764    }
765
766    #[test]
767    fn base_mainnet_genesis() {
768        let genesis = BASE_MAINNET.genesis_header();
769        assert_eq!(
770            genesis.hash_slow(),
771            b256!("0xf712aa9241cc24369b143cf6dce85f0902a9731e70d66818a3a5845b296c73dd")
772        );
773        let base_fee = BASE_MAINNET.next_block_base_fee(genesis, genesis.timestamp).unwrap();
774        // <https://base.blockscout.com/block/1>
775        assert_eq!(base_fee, 980000000);
776    }
777
778    #[test]
779    fn base_sepolia_genesis() {
780        let genesis = BASE_SEPOLIA.genesis_header();
781        assert_eq!(
782            genesis.hash_slow(),
783            b256!("0x0dcc9e089e30b90ddfc55be9a37dd15bc551aeee999d2e2b51414c54eaf934e4")
784        );
785        let base_fee = BASE_SEPOLIA.next_block_base_fee(genesis, genesis.timestamp).unwrap();
786        // <https://base-sepolia.blockscout.com/block/1>
787        assert_eq!(base_fee, 980000000);
788    }
789
790    #[test]
791    fn op_sepolia_genesis() {
792        let genesis = OP_SEPOLIA.genesis_header();
793        assert_eq!(
794            genesis.hash_slow(),
795            b256!("0x102de6ffb001480cc9b8b548fd05c34cd4f46ae4aa91759393db90ea0409887d")
796        );
797        let base_fee = OP_SEPOLIA.next_block_base_fee(genesis, genesis.timestamp).unwrap();
798        // <https://optimism-sepolia.blockscout.com/block/1>
799        assert_eq!(base_fee, 980000000);
800    }
801
802    #[test]
803    fn latest_base_mainnet_fork_id() {
804        assert_eq!(
805            ForkId { hash: ForkHash([0x86, 0x72, 0x8b, 0x4e]), next: 0 },
806            BASE_MAINNET.latest_fork_id()
807        )
808    }
809
810    #[test]
811    fn latest_base_mainnet_fork_id_with_builder() {
812        let base_mainnet = OpChainSpecBuilder::base_mainnet().build();
813        assert_eq!(
814            ForkId { hash: ForkHash([0x86, 0x72, 0x8b, 0x4e]), next: 0 },
815            base_mainnet.latest_fork_id()
816        )
817    }
818
819    #[test]
820    fn is_bedrock_active() {
821        let op_mainnet = OpChainSpecBuilder::optimism_mainnet().build();
822        assert!(!op_mainnet.is_bedrock_active_at_block(1))
823    }
824
825    #[test]
826    fn parse_optimism_hardforks() {
827        let geth_genesis = r#"
828    {
829      "config": {
830        "bedrockBlock": 10,
831        "regolithTime": 20,
832        "canyonTime": 30,
833        "ecotoneTime": 40,
834        "fjordTime": 50,
835        "graniteTime": 51,
836        "holoceneTime": 52,
837        "isthmusTime": 53,
838        "optimism": {
839          "eip1559Elasticity": 60,
840          "eip1559Denominator": 70
841        }
842      }
843    }
844    "#;
845        let genesis: Genesis = serde_json::from_str(geth_genesis).unwrap();
846
847        let actual_bedrock_block = genesis.config.extra_fields.get("bedrockBlock");
848        assert_eq!(actual_bedrock_block, Some(serde_json::Value::from(10)).as_ref());
849        let actual_regolith_timestamp = genesis.config.extra_fields.get("regolithTime");
850        assert_eq!(actual_regolith_timestamp, Some(serde_json::Value::from(20)).as_ref());
851        let actual_canyon_timestamp = genesis.config.extra_fields.get("canyonTime");
852        assert_eq!(actual_canyon_timestamp, Some(serde_json::Value::from(30)).as_ref());
853        let actual_ecotone_timestamp = genesis.config.extra_fields.get("ecotoneTime");
854        assert_eq!(actual_ecotone_timestamp, Some(serde_json::Value::from(40)).as_ref());
855        let actual_fjord_timestamp = genesis.config.extra_fields.get("fjordTime");
856        assert_eq!(actual_fjord_timestamp, Some(serde_json::Value::from(50)).as_ref());
857        let actual_granite_timestamp = genesis.config.extra_fields.get("graniteTime");
858        assert_eq!(actual_granite_timestamp, Some(serde_json::Value::from(51)).as_ref());
859        let actual_holocene_timestamp = genesis.config.extra_fields.get("holoceneTime");
860        assert_eq!(actual_holocene_timestamp, Some(serde_json::Value::from(52)).as_ref());
861        let actual_isthmus_timestamp = genesis.config.extra_fields.get("isthmusTime");
862        assert_eq!(actual_isthmus_timestamp, Some(serde_json::Value::from(53)).as_ref());
863
864        let optimism_object = genesis.config.extra_fields.get("optimism").unwrap();
865        assert_eq!(
866            optimism_object,
867            &serde_json::json!({
868                "eip1559Elasticity": 60,
869                "eip1559Denominator": 70,
870            })
871        );
872
873        let chain_spec: OpChainSpec = genesis.into();
874
875        assert_eq!(
876            chain_spec.base_fee_params,
877            BaseFeeParamsKind::Constant(BaseFeeParams::new(70, 60))
878        );
879
880        assert!(!chain_spec.is_fork_active_at_block(OpHardfork::Bedrock, 0));
881        assert!(!chain_spec.is_fork_active_at_timestamp(OpHardfork::Regolith, 0));
882        assert!(!chain_spec.is_fork_active_at_timestamp(OpHardfork::Canyon, 0));
883        assert!(!chain_spec.is_fork_active_at_timestamp(OpHardfork::Ecotone, 0));
884        assert!(!chain_spec.is_fork_active_at_timestamp(OpHardfork::Fjord, 0));
885        assert!(!chain_spec.is_fork_active_at_timestamp(OpHardfork::Granite, 0));
886        assert!(!chain_spec.is_fork_active_at_timestamp(OpHardfork::Holocene, 0));
887
888        assert!(chain_spec.is_fork_active_at_block(OpHardfork::Bedrock, 10));
889        assert!(chain_spec.is_fork_active_at_timestamp(OpHardfork::Regolith, 20));
890        assert!(chain_spec.is_fork_active_at_timestamp(OpHardfork::Canyon, 30));
891        assert!(chain_spec.is_fork_active_at_timestamp(OpHardfork::Ecotone, 40));
892        assert!(chain_spec.is_fork_active_at_timestamp(OpHardfork::Fjord, 50));
893        assert!(chain_spec.is_fork_active_at_timestamp(OpHardfork::Granite, 51));
894        assert!(chain_spec.is_fork_active_at_timestamp(OpHardfork::Holocene, 52));
895    }
896
897    #[test]
898    fn parse_optimism_hardforks_variable_base_fee_params() {
899        let geth_genesis = r#"
900    {
901      "config": {
902        "bedrockBlock": 10,
903        "regolithTime": 20,
904        "canyonTime": 30,
905        "ecotoneTime": 40,
906        "fjordTime": 50,
907        "graniteTime": 51,
908        "holoceneTime": 52,
909        "isthmusTime": 53,
910        "optimism": {
911          "eip1559Elasticity": 60,
912          "eip1559Denominator": 70,
913          "eip1559DenominatorCanyon": 80
914        }
915      }
916    }
917    "#;
918        let genesis: Genesis = serde_json::from_str(geth_genesis).unwrap();
919
920        let actual_bedrock_block = genesis.config.extra_fields.get("bedrockBlock");
921        assert_eq!(actual_bedrock_block, Some(serde_json::Value::from(10)).as_ref());
922        let actual_regolith_timestamp = genesis.config.extra_fields.get("regolithTime");
923        assert_eq!(actual_regolith_timestamp, Some(serde_json::Value::from(20)).as_ref());
924        let actual_canyon_timestamp = genesis.config.extra_fields.get("canyonTime");
925        assert_eq!(actual_canyon_timestamp, Some(serde_json::Value::from(30)).as_ref());
926        let actual_ecotone_timestamp = genesis.config.extra_fields.get("ecotoneTime");
927        assert_eq!(actual_ecotone_timestamp, Some(serde_json::Value::from(40)).as_ref());
928        let actual_fjord_timestamp = genesis.config.extra_fields.get("fjordTime");
929        assert_eq!(actual_fjord_timestamp, Some(serde_json::Value::from(50)).as_ref());
930        let actual_granite_timestamp = genesis.config.extra_fields.get("graniteTime");
931        assert_eq!(actual_granite_timestamp, Some(serde_json::Value::from(51)).as_ref());
932        let actual_holocene_timestamp = genesis.config.extra_fields.get("holoceneTime");
933        assert_eq!(actual_holocene_timestamp, Some(serde_json::Value::from(52)).as_ref());
934        let actual_isthmus_timestamp = genesis.config.extra_fields.get("isthmusTime");
935        assert_eq!(actual_isthmus_timestamp, Some(serde_json::Value::from(53)).as_ref());
936
937        let optimism_object = genesis.config.extra_fields.get("optimism").unwrap();
938        assert_eq!(
939            optimism_object,
940            &serde_json::json!({
941                "eip1559Elasticity": 60,
942                "eip1559Denominator": 70,
943                "eip1559DenominatorCanyon": 80
944            })
945        );
946
947        let chain_spec: OpChainSpec = genesis.into();
948
949        assert_eq!(
950            chain_spec.base_fee_params,
951            BaseFeeParamsKind::Variable(
952                vec![
953                    (EthereumHardfork::London.boxed(), BaseFeeParams::new(70, 60)),
954                    (OpHardfork::Canyon.boxed(), BaseFeeParams::new(80, 60)),
955                ]
956                .into()
957            )
958        );
959
960        assert!(!chain_spec.is_fork_active_at_block(OpHardfork::Bedrock, 0));
961        assert!(!chain_spec.is_fork_active_at_timestamp(OpHardfork::Regolith, 0));
962        assert!(!chain_spec.is_fork_active_at_timestamp(OpHardfork::Canyon, 0));
963        assert!(!chain_spec.is_fork_active_at_timestamp(OpHardfork::Ecotone, 0));
964        assert!(!chain_spec.is_fork_active_at_timestamp(OpHardfork::Fjord, 0));
965        assert!(!chain_spec.is_fork_active_at_timestamp(OpHardfork::Granite, 0));
966        assert!(!chain_spec.is_fork_active_at_timestamp(OpHardfork::Holocene, 0));
967
968        assert!(chain_spec.is_fork_active_at_block(OpHardfork::Bedrock, 10));
969        assert!(chain_spec.is_fork_active_at_timestamp(OpHardfork::Regolith, 20));
970        assert!(chain_spec.is_fork_active_at_timestamp(OpHardfork::Canyon, 30));
971        assert!(chain_spec.is_fork_active_at_timestamp(OpHardfork::Ecotone, 40));
972        assert!(chain_spec.is_fork_active_at_timestamp(OpHardfork::Fjord, 50));
973        assert!(chain_spec.is_fork_active_at_timestamp(OpHardfork::Granite, 51));
974        assert!(chain_spec.is_fork_active_at_timestamp(OpHardfork::Holocene, 52));
975    }
976
977    #[test]
978    fn parse_genesis_optimism_with_variable_base_fee_params() {
979        use op_alloy_rpc_types::OpBaseFeeInfo;
980
981        let geth_genesis = r#"
982    {
983      "config": {
984        "chainId": 8453,
985        "homesteadBlock": 0,
986        "eip150Block": 0,
987        "eip155Block": 0,
988        "eip158Block": 0,
989        "byzantiumBlock": 0,
990        "constantinopleBlock": 0,
991        "petersburgBlock": 0,
992        "istanbulBlock": 0,
993        "muirGlacierBlock": 0,
994        "berlinBlock": 0,
995        "londonBlock": 0,
996        "arrowGlacierBlock": 0,
997        "grayGlacierBlock": 0,
998        "mergeNetsplitBlock": 0,
999        "bedrockBlock": 0,
1000        "regolithTime": 15,
1001        "terminalTotalDifficulty": 0,
1002        "terminalTotalDifficultyPassed": true,
1003        "optimism": {
1004          "eip1559Elasticity": 6,
1005          "eip1559Denominator": 50
1006        }
1007      }
1008    }
1009    "#;
1010        let genesis: Genesis = serde_json::from_str(geth_genesis).unwrap();
1011        let chainspec = OpChainSpec::from(genesis.clone());
1012
1013        let actual_chain_id = genesis.config.chain_id;
1014        assert_eq!(actual_chain_id, 8453);
1015
1016        assert_eq!(
1017            chainspec.hardforks.get(EthereumHardfork::Istanbul),
1018            Some(ForkCondition::Block(0))
1019        );
1020
1021        let actual_bedrock_block = genesis.config.extra_fields.get("bedrockBlock");
1022        assert_eq!(actual_bedrock_block, Some(serde_json::Value::from(0)).as_ref());
1023        let actual_canyon_timestamp = genesis.config.extra_fields.get("canyonTime");
1024        assert_eq!(actual_canyon_timestamp, None);
1025
1026        assert!(genesis.config.terminal_total_difficulty_passed);
1027
1028        let optimism_object = genesis.config.extra_fields.get("optimism").unwrap();
1029        let optimism_base_fee_info =
1030            serde_json::from_value::<OpBaseFeeInfo>(optimism_object.clone()).unwrap();
1031
1032        assert_eq!(
1033            optimism_base_fee_info,
1034            OpBaseFeeInfo {
1035                eip1559_elasticity: Some(6),
1036                eip1559_denominator: Some(50),
1037                eip1559_denominator_canyon: None,
1038            }
1039        );
1040        assert_eq!(
1041            chainspec.base_fee_params,
1042            BaseFeeParamsKind::Constant(BaseFeeParams {
1043                max_change_denominator: 50,
1044                elasticity_multiplier: 6,
1045            })
1046        );
1047
1048        assert!(chainspec.is_fork_active_at_block(OpHardfork::Bedrock, 0));
1049
1050        assert!(chainspec.is_fork_active_at_timestamp(OpHardfork::Regolith, 20));
1051    }
1052
1053    #[test]
1054    fn test_fork_order_optimism_mainnet() {
1055        use reth_optimism_forks::OpHardfork;
1056
1057        let genesis = Genesis {
1058            config: ChainConfig {
1059                chain_id: 0,
1060                homestead_block: Some(0),
1061                dao_fork_block: Some(0),
1062                dao_fork_support: false,
1063                eip150_block: Some(0),
1064                eip155_block: Some(0),
1065                eip158_block: Some(0),
1066                byzantium_block: Some(0),
1067                constantinople_block: Some(0),
1068                petersburg_block: Some(0),
1069                istanbul_block: Some(0),
1070                muir_glacier_block: Some(0),
1071                berlin_block: Some(0),
1072                london_block: Some(0),
1073                arrow_glacier_block: Some(0),
1074                gray_glacier_block: Some(0),
1075                merge_netsplit_block: Some(0),
1076                shanghai_time: Some(0),
1077                cancun_time: Some(0),
1078                prague_time: Some(0),
1079                terminal_total_difficulty: Some(U256::ZERO),
1080                extra_fields: [
1081                    (String::from("bedrockBlock"), 0.into()),
1082                    (String::from("regolithTime"), 0.into()),
1083                    (String::from("canyonTime"), 0.into()),
1084                    (String::from("ecotoneTime"), 0.into()),
1085                    (String::from("fjordTime"), 0.into()),
1086                    (String::from("graniteTime"), 0.into()),
1087                    (String::from("holoceneTime"), 0.into()),
1088                    (String::from("isthmusTime"), 0.into()),
1089                    (String::from("jovianTime"), 0.into()),
1090                ]
1091                .into_iter()
1092                .collect(),
1093                ..Default::default()
1094            },
1095            ..Default::default()
1096        };
1097
1098        let chain_spec: OpChainSpec = genesis.into();
1099
1100        let hardforks: Vec<_> = chain_spec.hardforks.forks_iter().map(|(h, _)| h).collect();
1101        let expected_hardforks = vec![
1102            EthereumHardfork::Frontier.boxed(),
1103            EthereumHardfork::Homestead.boxed(),
1104            EthereumHardfork::Tangerine.boxed(),
1105            EthereumHardfork::SpuriousDragon.boxed(),
1106            EthereumHardfork::Byzantium.boxed(),
1107            EthereumHardfork::Constantinople.boxed(),
1108            EthereumHardfork::Petersburg.boxed(),
1109            EthereumHardfork::Istanbul.boxed(),
1110            EthereumHardfork::MuirGlacier.boxed(),
1111            EthereumHardfork::Berlin.boxed(),
1112            EthereumHardfork::London.boxed(),
1113            EthereumHardfork::ArrowGlacier.boxed(),
1114            EthereumHardfork::GrayGlacier.boxed(),
1115            EthereumHardfork::Paris.boxed(),
1116            OpHardfork::Bedrock.boxed(),
1117            OpHardfork::Regolith.boxed(),
1118            EthereumHardfork::Shanghai.boxed(),
1119            OpHardfork::Canyon.boxed(),
1120            EthereumHardfork::Cancun.boxed(),
1121            OpHardfork::Ecotone.boxed(),
1122            OpHardfork::Fjord.boxed(),
1123            OpHardfork::Granite.boxed(),
1124            OpHardfork::Holocene.boxed(),
1125            EthereumHardfork::Prague.boxed(),
1126            OpHardfork::Isthmus.boxed(),
1127            OpHardfork::Jovian.boxed(),
1128            // OpHardfork::Interop.boxed(),
1129        ];
1130
1131        for (expected, actual) in expected_hardforks.iter().zip(hardforks.iter()) {
1132            assert_eq!(&**expected, &**actual);
1133        }
1134        assert_eq!(expected_hardforks.len(), hardforks.len());
1135    }
1136
1137    #[test]
1138    fn json_genesis() {
1139        let geth_genesis = r#"
1140{
1141    "config": {
1142        "chainId": 1301,
1143        "homesteadBlock": 0,
1144        "eip150Block": 0,
1145        "eip155Block": 0,
1146        "eip158Block": 0,
1147        "byzantiumBlock": 0,
1148        "constantinopleBlock": 0,
1149        "petersburgBlock": 0,
1150        "istanbulBlock": 0,
1151        "muirGlacierBlock": 0,
1152        "berlinBlock": 0,
1153        "londonBlock": 0,
1154        "arrowGlacierBlock": 0,
1155        "grayGlacierBlock": 0,
1156        "mergeNetsplitBlock": 0,
1157        "shanghaiTime": 0,
1158        "cancunTime": 0,
1159        "bedrockBlock": 0,
1160        "regolithTime": 0,
1161        "canyonTime": 0,
1162        "ecotoneTime": 0,
1163        "fjordTime": 0,
1164        "graniteTime": 0,
1165        "holoceneTime": 1732633200,
1166        "terminalTotalDifficulty": 0,
1167        "terminalTotalDifficultyPassed": true,
1168        "optimism": {
1169            "eip1559Elasticity": 6,
1170            "eip1559Denominator": 50,
1171            "eip1559DenominatorCanyon": 250
1172        }
1173    },
1174    "nonce": "0x0",
1175    "timestamp": "0x66edad4c",
1176    "extraData": "0x424544524f434b",
1177    "gasLimit": "0x1c9c380",
1178    "difficulty": "0x0",
1179    "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
1180    "coinbase": "0x4200000000000000000000000000000000000011",
1181    "alloc": {},
1182    "number": "0x0",
1183    "gasUsed": "0x0",
1184    "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
1185    "baseFeePerGas": "0x3b9aca00",
1186    "excessBlobGas": "0x0",
1187    "blobGasUsed": "0x0"
1188}
1189        "#;
1190
1191        let genesis: Genesis = serde_json::from_str(geth_genesis).unwrap();
1192        let chainspec = OpChainSpec::from_genesis(genesis);
1193        assert!(chainspec.is_holocene_active_at_timestamp(1732633200));
1194    }
1195
1196    #[test]
1197    fn json_genesis_mapped_l1_timestamps() {
1198        let geth_genesis = r#"
1199{
1200    "config": {
1201        "chainId": 1301,
1202        "homesteadBlock": 0,
1203        "eip150Block": 0,
1204        "eip155Block": 0,
1205        "eip158Block": 0,
1206        "byzantiumBlock": 0,
1207        "constantinopleBlock": 0,
1208        "petersburgBlock": 0,
1209        "istanbulBlock": 0,
1210        "muirGlacierBlock": 0,
1211        "berlinBlock": 0,
1212        "londonBlock": 0,
1213        "arrowGlacierBlock": 0,
1214        "grayGlacierBlock": 0,
1215        "mergeNetsplitBlock": 0,
1216        "bedrockBlock": 0,
1217        "regolithTime": 0,
1218        "canyonTime": 0,
1219        "ecotoneTime": 1712633200,
1220        "fjordTime": 0,
1221        "graniteTime": 0,
1222        "holoceneTime": 1732633200,
1223        "isthmusTime": 1742633200,
1224        "terminalTotalDifficulty": 0,
1225        "terminalTotalDifficultyPassed": true,
1226        "optimism": {
1227            "eip1559Elasticity": 6,
1228            "eip1559Denominator": 50,
1229            "eip1559DenominatorCanyon": 250
1230        }
1231    },
1232    "nonce": "0x0",
1233    "timestamp": "0x66edad4c",
1234    "extraData": "0x424544524f434b",
1235    "gasLimit": "0x1c9c380",
1236    "difficulty": "0x0",
1237    "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
1238    "coinbase": "0x4200000000000000000000000000000000000011",
1239    "alloc": {},
1240    "number": "0x0",
1241    "gasUsed": "0x0",
1242    "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
1243    "baseFeePerGas": "0x3b9aca00",
1244    "excessBlobGas": "0x0",
1245    "blobGasUsed": "0x0"
1246}
1247        "#;
1248
1249        let genesis: Genesis = serde_json::from_str(geth_genesis).unwrap();
1250        let chainspec = OpChainSpec::from_genesis(genesis);
1251        assert!(chainspec.is_holocene_active_at_timestamp(1732633200));
1252
1253        assert!(chainspec.is_shanghai_active_at_timestamp(0));
1254        assert!(chainspec.is_canyon_active_at_timestamp(0));
1255
1256        assert!(chainspec.is_ecotone_active_at_timestamp(1712633200));
1257        assert!(chainspec.is_cancun_active_at_timestamp(1712633200));
1258
1259        assert!(chainspec.is_prague_active_at_timestamp(1742633200));
1260        assert!(chainspec.is_isthmus_active_at_timestamp(1742633200));
1261    }
1262
1263    #[test]
1264    fn display_hardorks() {
1265        let content = BASE_MAINNET.display_hardforks().to_string();
1266        for eth_hf in EthereumHardfork::VARIANTS {
1267            assert!(!content.contains(eth_hf.name()));
1268        }
1269    }
1270}