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