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