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