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