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