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);
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 = Some(storage_root_unhashed(
487 storage.iter().map(|(k, v)| (*k, Into::<alloy_primitives::U256>::into(*v))),
488 ))
489 }
490 }
491 }
492
493 header
494}
495
496#[cfg(test)]
497mod tests {
498 use alloc::string::String;
499 use alloy_genesis::{ChainConfig, Genesis};
500 use alloy_primitives::b256;
501 use reth_chainspec::{test_fork_ids, BaseFeeParams, BaseFeeParamsKind};
502 use reth_ethereum_forks::{EthereumHardfork, ForkCondition, ForkHash, ForkId, Head};
503 use reth_optimism_forks::{OpHardfork, OpHardforks};
504
505 use crate::*;
506
507 #[test]
508 fn base_mainnet_forkids() {
509 let mut base_mainnet = OpChainSpecBuilder::base_mainnet().build();
510 base_mainnet.inner.genesis_header.set_hash(BASE_MAINNET.genesis_hash());
511 test_fork_ids(
512 &BASE_MAINNET,
513 &[
514 (
515 Head { number: 0, ..Default::default() },
516 ForkId { hash: ForkHash([0x67, 0xda, 0x02, 0x60]), next: 1704992401 },
517 ),
518 (
519 Head { number: 0, timestamp: 1704992400, ..Default::default() },
520 ForkId { hash: ForkHash([0x67, 0xda, 0x02, 0x60]), next: 1704992401 },
521 ),
522 (
523 Head { number: 0, timestamp: 1704992401, ..Default::default() },
524 ForkId { hash: ForkHash([0x3c, 0x28, 0x3c, 0xb3]), next: 1710374401 },
525 ),
526 (
527 Head { number: 0, timestamp: 1710374400, ..Default::default() },
528 ForkId { hash: ForkHash([0x3c, 0x28, 0x3c, 0xb3]), next: 1710374401 },
529 ),
530 (
531 Head { number: 0, timestamp: 1710374401, ..Default::default() },
532 ForkId { hash: ForkHash([0x51, 0xcc, 0x98, 0xb3]), next: 1720627201 },
533 ),
534 (
535 Head { number: 0, timestamp: 1720627200, ..Default::default() },
536 ForkId { hash: ForkHash([0x51, 0xcc, 0x98, 0xb3]), next: 1720627201 },
537 ),
538 (
539 Head { number: 0, timestamp: 1720627201, ..Default::default() },
540 ForkId { hash: ForkHash([0xe4, 0x01, 0x0e, 0xb9]), next: 1726070401 },
541 ),
542 (
543 Head { number: 0, timestamp: 1726070401, ..Default::default() },
544 ForkId { hash: ForkHash([0xbc, 0x38, 0xf9, 0xca]), next: 1736445601 },
545 ),
546 (
547 Head { number: 0, timestamp: 1736445601, ..Default::default() },
548 ForkId { hash: ForkHash([0x3a, 0x2a, 0xf1, 0x83]), next: 1746806401 },
549 ),
550 (
552 Head { number: 0, timestamp: 1746806401, ..Default::default() },
553 ForkId { hash: ForkHash([0x86, 0x72, 0x8b, 0x4e]), next: 0 },
554 ),
555 ],
556 );
557 }
558
559 #[test]
560 fn op_sepolia_forkids() {
561 test_fork_ids(
562 &OP_SEPOLIA,
563 &[
564 (
565 Head { number: 0, ..Default::default() },
566 ForkId { hash: ForkHash([0x67, 0xa4, 0x03, 0x28]), next: 1699981200 },
567 ),
568 (
569 Head { number: 0, timestamp: 1699981199, ..Default::default() },
570 ForkId { hash: ForkHash([0x67, 0xa4, 0x03, 0x28]), next: 1699981200 },
571 ),
572 (
573 Head { number: 0, timestamp: 1699981200, ..Default::default() },
574 ForkId { hash: ForkHash([0xa4, 0x8d, 0x6a, 0x00]), next: 1708534800 },
575 ),
576 (
577 Head { number: 0, timestamp: 1708534799, ..Default::default() },
578 ForkId { hash: ForkHash([0xa4, 0x8d, 0x6a, 0x00]), next: 1708534800 },
579 ),
580 (
581 Head { number: 0, timestamp: 1708534800, ..Default::default() },
582 ForkId { hash: ForkHash([0xcc, 0x17, 0xc7, 0xeb]), next: 1716998400 },
583 ),
584 (
585 Head { number: 0, timestamp: 1716998399, ..Default::default() },
586 ForkId { hash: ForkHash([0xcc, 0x17, 0xc7, 0xeb]), next: 1716998400 },
587 ),
588 (
589 Head { number: 0, timestamp: 1716998400, ..Default::default() },
590 ForkId { hash: ForkHash([0x54, 0x0a, 0x8c, 0x5d]), next: 1723478400 },
591 ),
592 (
593 Head { number: 0, timestamp: 1723478399, ..Default::default() },
594 ForkId { hash: ForkHash([0x54, 0x0a, 0x8c, 0x5d]), next: 1723478400 },
595 ),
596 (
597 Head { number: 0, timestamp: 1723478400, ..Default::default() },
598 ForkId { hash: ForkHash([0x75, 0xde, 0xa4, 0x1e]), next: 1732633200 },
599 ),
600 (
601 Head { number: 0, timestamp: 1732633200, ..Default::default() },
602 ForkId { hash: ForkHash([0x4a, 0x1c, 0x79, 0x2e]), next: 1744905600 },
603 ),
604 (
606 Head { number: 0, timestamp: 1744905600, ..Default::default() },
607 ForkId { hash: ForkHash([0x6c, 0x62, 0x5e, 0xe1]), next: 0 },
608 ),
609 ],
610 );
611 }
612
613 #[test]
614 fn op_mainnet_forkids() {
615 let mut op_mainnet = OpChainSpecBuilder::optimism_mainnet().build();
616 op_mainnet.inner.genesis_header.set_hash(OP_MAINNET.genesis_hash());
619 test_fork_ids(
620 &op_mainnet,
621 &[
622 (
623 Head { number: 0, ..Default::default() },
624 ForkId { hash: ForkHash([0xca, 0xf5, 0x17, 0xed]), next: 3950000 },
625 ),
626 (
628 Head { number: 105235063, ..Default::default() },
629 ForkId { hash: ForkHash([0xe3, 0x39, 0x8d, 0x7c]), next: 1704992401 },
630 ),
631 (
633 Head { number: 105235063, ..Default::default() },
634 ForkId { hash: ForkHash([0xe3, 0x39, 0x8d, 0x7c]), next: 1704992401 },
635 ),
636 (
638 Head { number: 105235063, timestamp: 1704992401, ..Default::default() },
639 ForkId { hash: ForkHash([0xbd, 0xd4, 0xfd, 0xb2]), next: 1710374401 },
640 ),
641 (
645 Head { number: 105235063, timestamp: 1704992401, ..Default::default() },
646 ForkId { hash: ForkHash([0xbd, 0xd4, 0xfd, 0xb2]), next: 1710374401 },
647 ),
648 (
650 Head { number: 105235063, timestamp: 1710374401, ..Default::default() },
651 ForkId { hash: ForkHash([0x19, 0xda, 0x4c, 0x52]), next: 1720627201 },
652 ),
653 (
655 Head { number: 105235063, timestamp: 1720627201, ..Default::default() },
656 ForkId { hash: ForkHash([0x49, 0xfb, 0xfe, 0x1e]), next: 1726070401 },
657 ),
658 (
660 Head { number: 105235063, timestamp: 1726070401, ..Default::default() },
661 ForkId { hash: ForkHash([0x44, 0x70, 0x4c, 0xde]), next: 1736445601 },
662 ),
663 (
665 Head { number: 105235063, timestamp: 1736445601, ..Default::default() },
666 ForkId { hash: ForkHash([0x2b, 0xd9, 0x3d, 0xc8]), next: 1746806401 },
667 ),
668 (
670 Head { number: 105235063, timestamp: 1746806401, ..Default::default() },
671 ForkId { hash: ForkHash([0x37, 0xbe, 0x75, 0x8f]), next: 0 },
672 ),
673 ],
674 );
675 }
676
677 #[test]
678 fn base_sepolia_forkids() {
679 test_fork_ids(
680 &BASE_SEPOLIA,
681 &[
682 (
683 Head { number: 0, ..Default::default() },
684 ForkId { hash: ForkHash([0xb9, 0x59, 0xb9, 0xf7]), next: 1699981200 },
685 ),
686 (
687 Head { number: 0, timestamp: 1699981199, ..Default::default() },
688 ForkId { hash: ForkHash([0xb9, 0x59, 0xb9, 0xf7]), next: 1699981200 },
689 ),
690 (
691 Head { number: 0, timestamp: 1699981200, ..Default::default() },
692 ForkId { hash: ForkHash([0x60, 0x7c, 0xd5, 0xa1]), next: 1708534800 },
693 ),
694 (
695 Head { number: 0, timestamp: 1708534799, ..Default::default() },
696 ForkId { hash: ForkHash([0x60, 0x7c, 0xd5, 0xa1]), next: 1708534800 },
697 ),
698 (
699 Head { number: 0, timestamp: 1708534800, ..Default::default() },
700 ForkId { hash: ForkHash([0xbe, 0x96, 0x9b, 0x17]), next: 1716998400 },
701 ),
702 (
703 Head { number: 0, timestamp: 1716998399, ..Default::default() },
704 ForkId { hash: ForkHash([0xbe, 0x96, 0x9b, 0x17]), next: 1716998400 },
705 ),
706 (
707 Head { number: 0, timestamp: 1716998400, ..Default::default() },
708 ForkId { hash: ForkHash([0x4e, 0x45, 0x7a, 0x49]), next: 1723478400 },
709 ),
710 (
711 Head { number: 0, timestamp: 1723478399, ..Default::default() },
712 ForkId { hash: ForkHash([0x4e, 0x45, 0x7a, 0x49]), next: 1723478400 },
713 ),
714 (
715 Head { number: 0, timestamp: 1723478400, ..Default::default() },
716 ForkId { hash: ForkHash([0x5e, 0xdf, 0xa3, 0xb6]), next: 1732633200 },
717 ),
718 (
719 Head { number: 0, timestamp: 1732633200, ..Default::default() },
720 ForkId { hash: ForkHash([0x8b, 0x5e, 0x76, 0x29]), next: 1744905600 },
721 ),
722 (
724 Head { number: 0, timestamp: 1744905600, ..Default::default() },
725 ForkId { hash: ForkHash([0x06, 0x0a, 0x4d, 0x1d]), next: 0 },
726 ),
727 ],
728 );
729 }
730
731 #[test]
732 fn base_mainnet_genesis() {
733 let genesis = BASE_MAINNET.genesis_header();
734 assert_eq!(
735 genesis.hash_slow(),
736 b256!("0xf712aa9241cc24369b143cf6dce85f0902a9731e70d66818a3a5845b296c73dd")
737 );
738 let base_fee = genesis
739 .next_block_base_fee(BASE_MAINNET.base_fee_params_at_timestamp(genesis.timestamp))
740 .unwrap();
741 assert_eq!(base_fee, 980000000);
743 }
744
745 #[test]
746 fn base_sepolia_genesis() {
747 let genesis = BASE_SEPOLIA.genesis_header();
748 assert_eq!(
749 genesis.hash_slow(),
750 b256!("0x0dcc9e089e30b90ddfc55be9a37dd15bc551aeee999d2e2b51414c54eaf934e4")
751 );
752 let base_fee = genesis
753 .next_block_base_fee(BASE_SEPOLIA.base_fee_params_at_timestamp(genesis.timestamp))
754 .unwrap();
755 assert_eq!(base_fee, 980000000);
757 }
758
759 #[test]
760 fn op_sepolia_genesis() {
761 let genesis = OP_SEPOLIA.genesis_header();
762 assert_eq!(
763 genesis.hash_slow(),
764 b256!("0x102de6ffb001480cc9b8b548fd05c34cd4f46ae4aa91759393db90ea0409887d")
765 );
766 let base_fee = genesis
767 .next_block_base_fee(OP_SEPOLIA.base_fee_params_at_timestamp(genesis.timestamp))
768 .unwrap();
769 assert_eq!(base_fee, 980000000);
771 }
772
773 #[test]
774 fn latest_base_mainnet_fork_id() {
775 assert_eq!(
776 ForkId { hash: ForkHash([0x86, 0x72, 0x8b, 0x4e]), next: 0 },
777 BASE_MAINNET.latest_fork_id()
778 )
779 }
780
781 #[test]
782 fn latest_base_mainnet_fork_id_with_builder() {
783 let base_mainnet = OpChainSpecBuilder::base_mainnet().build();
784 assert_eq!(
785 ForkId { hash: ForkHash([0x86, 0x72, 0x8b, 0x4e]), next: 0 },
786 base_mainnet.latest_fork_id()
787 )
788 }
789
790 #[test]
791 fn is_bedrock_active() {
792 let op_mainnet = OpChainSpecBuilder::optimism_mainnet().build();
793 assert!(!op_mainnet.is_bedrock_active_at_block(1))
794 }
795
796 #[test]
797 fn parse_optimism_hardforks() {
798 let geth_genesis = r#"
799 {
800 "config": {
801 "bedrockBlock": 10,
802 "regolithTime": 20,
803 "canyonTime": 30,
804 "ecotoneTime": 40,
805 "fjordTime": 50,
806 "graniteTime": 51,
807 "holoceneTime": 52,
808 "optimism": {
809 "eip1559Elasticity": 60,
810 "eip1559Denominator": 70
811 }
812 }
813 }
814 "#;
815 let genesis: Genesis = serde_json::from_str(geth_genesis).unwrap();
816
817 let actual_bedrock_block = genesis.config.extra_fields.get("bedrockBlock");
818 assert_eq!(actual_bedrock_block, Some(serde_json::Value::from(10)).as_ref());
819 let actual_regolith_timestamp = genesis.config.extra_fields.get("regolithTime");
820 assert_eq!(actual_regolith_timestamp, Some(serde_json::Value::from(20)).as_ref());
821 let actual_canyon_timestamp = genesis.config.extra_fields.get("canyonTime");
822 assert_eq!(actual_canyon_timestamp, Some(serde_json::Value::from(30)).as_ref());
823 let actual_ecotone_timestamp = genesis.config.extra_fields.get("ecotoneTime");
824 assert_eq!(actual_ecotone_timestamp, Some(serde_json::Value::from(40)).as_ref());
825 let actual_fjord_timestamp = genesis.config.extra_fields.get("fjordTime");
826 assert_eq!(actual_fjord_timestamp, Some(serde_json::Value::from(50)).as_ref());
827 let actual_granite_timestamp = genesis.config.extra_fields.get("graniteTime");
828 assert_eq!(actual_granite_timestamp, Some(serde_json::Value::from(51)).as_ref());
829 let actual_holocene_timestamp = genesis.config.extra_fields.get("holoceneTime");
830 assert_eq!(actual_holocene_timestamp, Some(serde_json::Value::from(52)).as_ref());
831
832 let optimism_object = genesis.config.extra_fields.get("optimism").unwrap();
833 assert_eq!(
834 optimism_object,
835 &serde_json::json!({
836 "eip1559Elasticity": 60,
837 "eip1559Denominator": 70,
838 })
839 );
840
841 let chain_spec: OpChainSpec = genesis.into();
842
843 assert_eq!(
844 chain_spec.base_fee_params,
845 BaseFeeParamsKind::Constant(BaseFeeParams::new(70, 60))
846 );
847
848 assert!(!chain_spec.is_fork_active_at_block(OpHardfork::Bedrock, 0));
849 assert!(!chain_spec.is_fork_active_at_timestamp(OpHardfork::Regolith, 0));
850 assert!(!chain_spec.is_fork_active_at_timestamp(OpHardfork::Canyon, 0));
851 assert!(!chain_spec.is_fork_active_at_timestamp(OpHardfork::Ecotone, 0));
852 assert!(!chain_spec.is_fork_active_at_timestamp(OpHardfork::Fjord, 0));
853 assert!(!chain_spec.is_fork_active_at_timestamp(OpHardfork::Granite, 0));
854 assert!(!chain_spec.is_fork_active_at_timestamp(OpHardfork::Holocene, 0));
855
856 assert!(chain_spec.is_fork_active_at_block(OpHardfork::Bedrock, 10));
857 assert!(chain_spec.is_fork_active_at_timestamp(OpHardfork::Regolith, 20));
858 assert!(chain_spec.is_fork_active_at_timestamp(OpHardfork::Canyon, 30));
859 assert!(chain_spec.is_fork_active_at_timestamp(OpHardfork::Ecotone, 40));
860 assert!(chain_spec.is_fork_active_at_timestamp(OpHardfork::Fjord, 50));
861 assert!(chain_spec.is_fork_active_at_timestamp(OpHardfork::Granite, 51));
862 assert!(chain_spec.is_fork_active_at_timestamp(OpHardfork::Holocene, 52));
863 }
864
865 #[test]
866 fn parse_optimism_hardforks_variable_base_fee_params() {
867 let geth_genesis = r#"
868 {
869 "config": {
870 "bedrockBlock": 10,
871 "regolithTime": 20,
872 "canyonTime": 30,
873 "ecotoneTime": 40,
874 "fjordTime": 50,
875 "graniteTime": 51,
876 "holoceneTime": 52,
877 "optimism": {
878 "eip1559Elasticity": 60,
879 "eip1559Denominator": 70,
880 "eip1559DenominatorCanyon": 80
881 }
882 }
883 }
884 "#;
885 let genesis: Genesis = serde_json::from_str(geth_genesis).unwrap();
886
887 let actual_bedrock_block = genesis.config.extra_fields.get("bedrockBlock");
888 assert_eq!(actual_bedrock_block, Some(serde_json::Value::from(10)).as_ref());
889 let actual_regolith_timestamp = genesis.config.extra_fields.get("regolithTime");
890 assert_eq!(actual_regolith_timestamp, Some(serde_json::Value::from(20)).as_ref());
891 let actual_canyon_timestamp = genesis.config.extra_fields.get("canyonTime");
892 assert_eq!(actual_canyon_timestamp, Some(serde_json::Value::from(30)).as_ref());
893 let actual_ecotone_timestamp = genesis.config.extra_fields.get("ecotoneTime");
894 assert_eq!(actual_ecotone_timestamp, Some(serde_json::Value::from(40)).as_ref());
895 let actual_fjord_timestamp = genesis.config.extra_fields.get("fjordTime");
896 assert_eq!(actual_fjord_timestamp, Some(serde_json::Value::from(50)).as_ref());
897 let actual_granite_timestamp = genesis.config.extra_fields.get("graniteTime");
898 assert_eq!(actual_granite_timestamp, Some(serde_json::Value::from(51)).as_ref());
899 let actual_holocene_timestamp = genesis.config.extra_fields.get("holoceneTime");
900 assert_eq!(actual_holocene_timestamp, Some(serde_json::Value::from(52)).as_ref());
901
902 let optimism_object = genesis.config.extra_fields.get("optimism").unwrap();
903 assert_eq!(
904 optimism_object,
905 &serde_json::json!({
906 "eip1559Elasticity": 60,
907 "eip1559Denominator": 70,
908 "eip1559DenominatorCanyon": 80
909 })
910 );
911
912 let chain_spec: OpChainSpec = genesis.into();
913
914 assert_eq!(
915 chain_spec.base_fee_params,
916 BaseFeeParamsKind::Variable(
917 vec![
918 (EthereumHardfork::London.boxed(), BaseFeeParams::new(70, 60)),
919 (OpHardfork::Canyon.boxed(), BaseFeeParams::new(80, 60)),
920 ]
921 .into()
922 )
923 );
924
925 assert!(!chain_spec.is_fork_active_at_block(OpHardfork::Bedrock, 0));
926 assert!(!chain_spec.is_fork_active_at_timestamp(OpHardfork::Regolith, 0));
927 assert!(!chain_spec.is_fork_active_at_timestamp(OpHardfork::Canyon, 0));
928 assert!(!chain_spec.is_fork_active_at_timestamp(OpHardfork::Ecotone, 0));
929 assert!(!chain_spec.is_fork_active_at_timestamp(OpHardfork::Fjord, 0));
930 assert!(!chain_spec.is_fork_active_at_timestamp(OpHardfork::Granite, 0));
931 assert!(!chain_spec.is_fork_active_at_timestamp(OpHardfork::Holocene, 0));
932
933 assert!(chain_spec.is_fork_active_at_block(OpHardfork::Bedrock, 10));
934 assert!(chain_spec.is_fork_active_at_timestamp(OpHardfork::Regolith, 20));
935 assert!(chain_spec.is_fork_active_at_timestamp(OpHardfork::Canyon, 30));
936 assert!(chain_spec.is_fork_active_at_timestamp(OpHardfork::Ecotone, 40));
937 assert!(chain_spec.is_fork_active_at_timestamp(OpHardfork::Fjord, 50));
938 assert!(chain_spec.is_fork_active_at_timestamp(OpHardfork::Granite, 51));
939 assert!(chain_spec.is_fork_active_at_timestamp(OpHardfork::Holocene, 52));
940 }
941
942 #[test]
943 fn parse_genesis_optimism_with_variable_base_fee_params() {
944 use op_alloy_rpc_types::OpBaseFeeInfo;
945
946 let geth_genesis = r#"
947 {
948 "config": {
949 "chainId": 8453,
950 "homesteadBlock": 0,
951 "eip150Block": 0,
952 "eip155Block": 0,
953 "eip158Block": 0,
954 "byzantiumBlock": 0,
955 "constantinopleBlock": 0,
956 "petersburgBlock": 0,
957 "istanbulBlock": 0,
958 "muirGlacierBlock": 0,
959 "berlinBlock": 0,
960 "londonBlock": 0,
961 "arrowGlacierBlock": 0,
962 "grayGlacierBlock": 0,
963 "mergeNetsplitBlock": 0,
964 "bedrockBlock": 0,
965 "regolithTime": 15,
966 "terminalTotalDifficulty": 0,
967 "terminalTotalDifficultyPassed": true,
968 "optimism": {
969 "eip1559Elasticity": 6,
970 "eip1559Denominator": 50
971 }
972 }
973 }
974 "#;
975 let genesis: Genesis = serde_json::from_str(geth_genesis).unwrap();
976 let chainspec = OpChainSpec::from(genesis.clone());
977
978 let actual_chain_id = genesis.config.chain_id;
979 assert_eq!(actual_chain_id, 8453);
980
981 assert_eq!(
982 chainspec.hardforks.get(EthereumHardfork::Istanbul),
983 Some(ForkCondition::Block(0))
984 );
985
986 let actual_bedrock_block = genesis.config.extra_fields.get("bedrockBlock");
987 assert_eq!(actual_bedrock_block, Some(serde_json::Value::from(0)).as_ref());
988 let actual_canyon_timestamp = genesis.config.extra_fields.get("canyonTime");
989 assert_eq!(actual_canyon_timestamp, None);
990
991 assert!(genesis.config.terminal_total_difficulty_passed);
992
993 let optimism_object = genesis.config.extra_fields.get("optimism").unwrap();
994 let optimism_base_fee_info =
995 serde_json::from_value::<OpBaseFeeInfo>(optimism_object.clone()).unwrap();
996
997 assert_eq!(
998 optimism_base_fee_info,
999 OpBaseFeeInfo {
1000 eip1559_elasticity: Some(6),
1001 eip1559_denominator: Some(50),
1002 eip1559_denominator_canyon: None,
1003 }
1004 );
1005 assert_eq!(
1006 chainspec.base_fee_params,
1007 BaseFeeParamsKind::Constant(BaseFeeParams {
1008 max_change_denominator: 50,
1009 elasticity_multiplier: 6,
1010 })
1011 );
1012
1013 assert!(chainspec.is_fork_active_at_block(OpHardfork::Bedrock, 0));
1014
1015 assert!(chainspec.is_fork_active_at_timestamp(OpHardfork::Regolith, 20));
1016 }
1017
1018 #[test]
1019 fn test_fork_order_optimism_mainnet() {
1020 use reth_optimism_forks::OpHardfork;
1021
1022 let genesis = Genesis {
1023 config: ChainConfig {
1024 chain_id: 0,
1025 homestead_block: Some(0),
1026 dao_fork_block: Some(0),
1027 dao_fork_support: false,
1028 eip150_block: Some(0),
1029 eip155_block: Some(0),
1030 eip158_block: Some(0),
1031 byzantium_block: Some(0),
1032 constantinople_block: Some(0),
1033 petersburg_block: Some(0),
1034 istanbul_block: Some(0),
1035 muir_glacier_block: Some(0),
1036 berlin_block: Some(0),
1037 london_block: Some(0),
1038 arrow_glacier_block: Some(0),
1039 gray_glacier_block: Some(0),
1040 merge_netsplit_block: Some(0),
1041 shanghai_time: Some(0),
1042 cancun_time: Some(0),
1043 prague_time: Some(0),
1044 terminal_total_difficulty: Some(U256::ZERO),
1045 extra_fields: [
1046 (String::from("bedrockBlock"), 0.into()),
1047 (String::from("regolithTime"), 0.into()),
1048 (String::from("canyonTime"), 0.into()),
1049 (String::from("ecotoneTime"), 0.into()),
1050 (String::from("fjordTime"), 0.into()),
1051 (String::from("graniteTime"), 0.into()),
1052 (String::from("holoceneTime"), 0.into()),
1053 (String::from("isthmusTime"), 0.into()),
1054 ]
1055 .into_iter()
1056 .collect(),
1057 ..Default::default()
1058 },
1059 ..Default::default()
1060 };
1061
1062 let chain_spec: OpChainSpec = genesis.into();
1063
1064 let hardforks: Vec<_> = chain_spec.hardforks.forks_iter().map(|(h, _)| h).collect();
1065 let expected_hardforks = vec![
1066 EthereumHardfork::Frontier.boxed(),
1067 EthereumHardfork::Homestead.boxed(),
1068 EthereumHardfork::Tangerine.boxed(),
1069 EthereumHardfork::SpuriousDragon.boxed(),
1070 EthereumHardfork::Byzantium.boxed(),
1071 EthereumHardfork::Constantinople.boxed(),
1072 EthereumHardfork::Petersburg.boxed(),
1073 EthereumHardfork::Istanbul.boxed(),
1074 EthereumHardfork::MuirGlacier.boxed(),
1075 EthereumHardfork::Berlin.boxed(),
1076 EthereumHardfork::London.boxed(),
1077 EthereumHardfork::ArrowGlacier.boxed(),
1078 EthereumHardfork::GrayGlacier.boxed(),
1079 EthereumHardfork::Paris.boxed(),
1080 OpHardfork::Bedrock.boxed(),
1081 OpHardfork::Regolith.boxed(),
1082 EthereumHardfork::Shanghai.boxed(),
1083 OpHardfork::Canyon.boxed(),
1084 EthereumHardfork::Cancun.boxed(),
1085 OpHardfork::Ecotone.boxed(),
1086 OpHardfork::Fjord.boxed(),
1087 OpHardfork::Granite.boxed(),
1088 OpHardfork::Holocene.boxed(),
1089 EthereumHardfork::Prague.boxed(),
1090 OpHardfork::Isthmus.boxed(),
1091 ];
1093
1094 for (expected, actual) in expected_hardforks.iter().zip(hardforks.iter()) {
1095 assert_eq!(&**expected, &**actual);
1096 }
1097 assert_eq!(expected_hardforks.len(), hardforks.len());
1098 }
1099
1100 #[test]
1101 fn json_genesis() {
1102 let geth_genesis = r#"
1103{
1104 "config": {
1105 "chainId": 1301,
1106 "homesteadBlock": 0,
1107 "eip150Block": 0,
1108 "eip155Block": 0,
1109 "eip158Block": 0,
1110 "byzantiumBlock": 0,
1111 "constantinopleBlock": 0,
1112 "petersburgBlock": 0,
1113 "istanbulBlock": 0,
1114 "muirGlacierBlock": 0,
1115 "berlinBlock": 0,
1116 "londonBlock": 0,
1117 "arrowGlacierBlock": 0,
1118 "grayGlacierBlock": 0,
1119 "mergeNetsplitBlock": 0,
1120 "shanghaiTime": 0,
1121 "cancunTime": 0,
1122 "bedrockBlock": 0,
1123 "regolithTime": 0,
1124 "canyonTime": 0,
1125 "ecotoneTime": 0,
1126 "fjordTime": 0,
1127 "graniteTime": 0,
1128 "holoceneTime": 1732633200,
1129 "terminalTotalDifficulty": 0,
1130 "terminalTotalDifficultyPassed": true,
1131 "optimism": {
1132 "eip1559Elasticity": 6,
1133 "eip1559Denominator": 50,
1134 "eip1559DenominatorCanyon": 250
1135 }
1136 },
1137 "nonce": "0x0",
1138 "timestamp": "0x66edad4c",
1139 "extraData": "0x424544524f434b",
1140 "gasLimit": "0x1c9c380",
1141 "difficulty": "0x0",
1142 "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
1143 "coinbase": "0x4200000000000000000000000000000000000011",
1144 "alloc": {},
1145 "number": "0x0",
1146 "gasUsed": "0x0",
1147 "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
1148 "baseFeePerGas": "0x3b9aca00",
1149 "excessBlobGas": "0x0",
1150 "blobGasUsed": "0x0"
1151}
1152 "#;
1153
1154 let genesis: Genesis = serde_json::from_str(geth_genesis).unwrap();
1155 let chainspec = OpChainSpec::from_genesis(genesis);
1156 assert!(chainspec.is_holocene_active_at_timestamp(1732633200));
1157 }
1158
1159 #[test]
1160 fn json_genesis_mapped_l1_timestamps() {
1161 let geth_genesis = r#"
1162{
1163 "config": {
1164 "chainId": 1301,
1165 "homesteadBlock": 0,
1166 "eip150Block": 0,
1167 "eip155Block": 0,
1168 "eip158Block": 0,
1169 "byzantiumBlock": 0,
1170 "constantinopleBlock": 0,
1171 "petersburgBlock": 0,
1172 "istanbulBlock": 0,
1173 "muirGlacierBlock": 0,
1174 "berlinBlock": 0,
1175 "londonBlock": 0,
1176 "arrowGlacierBlock": 0,
1177 "grayGlacierBlock": 0,
1178 "mergeNetsplitBlock": 0,
1179 "bedrockBlock": 0,
1180 "regolithTime": 0,
1181 "canyonTime": 0,
1182 "ecotoneTime": 1712633200,
1183 "fjordTime": 0,
1184 "graniteTime": 0,
1185 "holoceneTime": 1732633200,
1186 "isthmusTime": 1742633200,
1187 "terminalTotalDifficulty": 0,
1188 "terminalTotalDifficultyPassed": true,
1189 "optimism": {
1190 "eip1559Elasticity": 6,
1191 "eip1559Denominator": 50,
1192 "eip1559DenominatorCanyon": 250
1193 }
1194 },
1195 "nonce": "0x0",
1196 "timestamp": "0x66edad4c",
1197 "extraData": "0x424544524f434b",
1198 "gasLimit": "0x1c9c380",
1199 "difficulty": "0x0",
1200 "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
1201 "coinbase": "0x4200000000000000000000000000000000000011",
1202 "alloc": {},
1203 "number": "0x0",
1204 "gasUsed": "0x0",
1205 "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
1206 "baseFeePerGas": "0x3b9aca00",
1207 "excessBlobGas": "0x0",
1208 "blobGasUsed": "0x0"
1209}
1210 "#;
1211
1212 let genesis: Genesis = serde_json::from_str(geth_genesis).unwrap();
1213 let chainspec = OpChainSpec::from_genesis(genesis);
1214 assert!(chainspec.is_holocene_active_at_timestamp(1732633200));
1215
1216 assert!(chainspec.is_shanghai_active_at_timestamp(0));
1217 assert!(chainspec.is_canyon_active_at_timestamp(0));
1218
1219 assert!(chainspec.is_ecotone_active_at_timestamp(1712633200));
1220 assert!(chainspec.is_cancun_active_at_timestamp(1712633200));
1221
1222 assert!(chainspec.is_prague_active_at_timestamp(1742633200));
1223 assert!(chainspec.is_isthmus_active_at_timestamp(1742633200));
1224 }
1225
1226 #[test]
1227 fn display_hardorks() {
1228 let content = BASE_MAINNET.display_hardforks().to_string();
1229 for eth_hf in EthereumHardfork::VARIANTS {
1230 assert!(!content.contains(eth_hf.name()));
1231 }
1232 }
1233}