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