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(feature = "std"), no_std)]
10
11extern crate alloc;
12
13mod base;
14mod base_sepolia;
15pub mod constants;
16mod dev;
17mod op;
18mod op_sepolia;
19
20use alloc::{boxed::Box, vec, vec::Vec};
21use alloy_chains::Chain;
22use alloy_consensus::{proofs::storage_root_unhashed, Header};
23use alloy_eips::eip7840::BlobParams;
24use alloy_genesis::Genesis;
25use alloy_primitives::{B256, U256};
26pub use base::BASE_MAINNET;
27pub use base_sepolia::BASE_SEPOLIA;
28use derive_more::{Constructor, Deref, From, Into};
29pub use dev::OP_DEV;
30pub use op::OP_MAINNET;
31pub use op_sepolia::OP_SEPOLIA;
32use reth_chainspec::{
33 BaseFeeParams, BaseFeeParamsKind, ChainSpec, ChainSpecBuilder, DepositContract, EthChainSpec,
34 EthereumHardforks, ForkFilter, ForkId, Hardforks, Head,
35};
36use reth_ethereum_forks::{ChainHardforks, EthereumHardfork, ForkCondition, Hardfork};
37use reth_network_peers::NodeRecord;
38use reth_optimism_forks::{OpHardfork, OpHardforks, OP_MAINNET_HARDFORKS};
39use reth_optimism_primitives::ADDRESS_L2_TO_L1_MESSAGE_PASSER;
40use reth_primitives_traits::{sync::LazyLock, SealedHeader};
41
42#[derive(Debug, Default, From)]
44pub struct OpChainSpecBuilder {
45 inner: ChainSpecBuilder,
47}
48
49impl OpChainSpecBuilder {
50 pub fn base_mainnet() -> Self {
52 let mut inner = ChainSpecBuilder::default()
53 .chain(BASE_MAINNET.chain)
54 .genesis(BASE_MAINNET.genesis.clone());
55 let forks = BASE_MAINNET.hardforks.clone();
56 inner = inner.with_forks(forks);
57
58 Self { inner }
59 }
60
61 pub fn optimism_mainnet() -> Self {
63 let mut inner =
64 ChainSpecBuilder::default().chain(OP_MAINNET.chain).genesis(OP_MAINNET.genesis.clone());
65 let forks = OP_MAINNET.hardforks.clone();
66 inner = inner.with_forks(forks);
67
68 Self { inner }
69 }
70}
71
72impl OpChainSpecBuilder {
73 pub fn chain(mut self, chain: Chain) -> Self {
75 self.inner = self.inner.chain(chain);
76 self
77 }
78
79 pub fn genesis(mut self, genesis: Genesis) -> Self {
81 self.inner = self.inner.genesis(genesis);
82 self
83 }
84
85 pub fn with_fork<H: Hardfork>(mut self, fork: H, condition: ForkCondition) -> Self {
87 self.inner = self.inner.with_fork(fork, condition);
88 self
89 }
90
91 pub fn with_forks(mut self, forks: ChainHardforks) -> Self {
93 self.inner = self.inner.with_forks(forks);
94 self
95 }
96
97 pub fn without_fork(mut self, fork: OpHardfork) -> Self {
99 self.inner = self.inner.without_fork(fork);
100 self
101 }
102
103 pub fn bedrock_activated(mut self) -> Self {
105 self.inner = self.inner.paris_activated();
106 self.inner = self.inner.with_fork(OpHardfork::Bedrock, ForkCondition::Block(0));
107 self
108 }
109
110 pub fn regolith_activated(mut self) -> Self {
112 self = self.bedrock_activated();
113 self.inner = self.inner.with_fork(OpHardfork::Regolith, ForkCondition::Timestamp(0));
114 self
115 }
116
117 pub fn canyon_activated(mut self) -> Self {
119 self = self.regolith_activated();
120 self.inner = self.inner.with_fork(EthereumHardfork::Shanghai, ForkCondition::Timestamp(0));
122 self.inner = self.inner.with_fork(OpHardfork::Canyon, ForkCondition::Timestamp(0));
123 self
124 }
125
126 pub fn ecotone_activated(mut self) -> Self {
128 self = self.canyon_activated();
129 self.inner = self.inner.with_fork(EthereumHardfork::Cancun, ForkCondition::Timestamp(0));
130 self.inner = self.inner.with_fork(OpHardfork::Ecotone, ForkCondition::Timestamp(0));
131 self
132 }
133
134 pub fn fjord_activated(mut self) -> Self {
136 self = self.ecotone_activated();
137 self.inner = self.inner.with_fork(OpHardfork::Fjord, ForkCondition::Timestamp(0));
138 self
139 }
140
141 pub fn granite_activated(mut self) -> Self {
143 self = self.fjord_activated();
144 self.inner = self.inner.with_fork(OpHardfork::Granite, ForkCondition::Timestamp(0));
145 self
146 }
147
148 pub fn holocene_activated(mut self) -> Self {
150 self = self.granite_activated();
151 self.inner = self.inner.with_fork(OpHardfork::Holocene, ForkCondition::Timestamp(0));
152 self
153 }
154
155 pub fn isthmus_activated(mut self) -> Self {
157 self = self.holocene_activated();
158 self.inner = self.inner.with_fork(OpHardfork::Isthmus, ForkCondition::Timestamp(0));
159 self
160 }
161
162 pub fn interop_activated(mut self) -> Self {
164 self = self.isthmus_activated();
165 self.inner = self.inner.with_fork(OpHardfork::Interop, ForkCondition::Timestamp(0));
166 self
167 }
168
169 pub fn build(self) -> OpChainSpec {
176 let mut inner = self.inner.build();
177 inner.genesis_header =
178 SealedHeader::seal_slow(make_op_genesis_header(&inner.genesis, &inner.hardforks));
179
180 OpChainSpec { inner }
181 }
182}
183
184#[derive(Debug, Clone, Deref, Into, Constructor, PartialEq, Eq)]
186pub struct OpChainSpec {
187 pub inner: ChainSpec,
189}
190
191impl OpChainSpec {
192 pub fn from_genesis(genesis: Genesis) -> Self {
194 genesis.into()
195 }
196}
197
198impl EthChainSpec for OpChainSpec {
199 type Header = Header;
200
201 fn chain(&self) -> Chain {
202 self.inner.chain()
203 }
204
205 fn base_fee_params_at_block(&self, block_number: u64) -> BaseFeeParams {
206 self.inner.base_fee_params_at_block(block_number)
207 }
208
209 fn base_fee_params_at_timestamp(&self, timestamp: u64) -> BaseFeeParams {
210 self.inner.base_fee_params_at_timestamp(timestamp)
211 }
212
213 fn blob_params_at_timestamp(&self, timestamp: u64) -> Option<BlobParams> {
214 self.inner.blob_params_at_timestamp(timestamp)
215 }
216
217 fn deposit_contract(&self) -> Option<&DepositContract> {
218 self.inner.deposit_contract()
219 }
220
221 fn genesis_hash(&self) -> B256 {
222 self.inner.genesis_hash()
223 }
224
225 fn prune_delete_limit(&self) -> usize {
226 self.inner.prune_delete_limit()
227 }
228
229 fn display_hardforks(&self) -> Box<dyn core::fmt::Display> {
230 Box::new(ChainSpec::display_hardforks(self))
231 }
232
233 fn genesis_header(&self) -> &Self::Header {
234 self.inner.genesis_header()
235 }
236
237 fn genesis(&self) -> &Genesis {
238 self.inner.genesis()
239 }
240
241 fn bootnodes(&self) -> Option<Vec<NodeRecord>> {
242 self.inner.bootnodes()
243 }
244
245 fn is_optimism(&self) -> bool {
246 true
247 }
248
249 fn final_paris_total_difficulty(&self) -> Option<U256> {
250 self.inner.final_paris_total_difficulty()
251 }
252}
253
254impl Hardforks for OpChainSpec {
255 fn fork<H: Hardfork>(&self, fork: H) -> ForkCondition {
256 self.inner.fork(fork)
257 }
258
259 fn forks_iter(&self) -> impl Iterator<Item = (&dyn Hardfork, ForkCondition)> {
260 self.inner.forks_iter()
261 }
262
263 fn fork_id(&self, head: &Head) -> ForkId {
264 self.inner.fork_id(head)
265 }
266
267 fn latest_fork_id(&self) -> ForkId {
268 self.inner.latest_fork_id()
269 }
270
271 fn fork_filter(&self, head: Head) -> ForkFilter {
272 self.inner.fork_filter(head)
273 }
274}
275
276impl EthereumHardforks for OpChainSpec {
277 fn ethereum_fork_activation(&self, fork: EthereumHardfork) -> ForkCondition {
278 self.fork(fork)
279 }
280}
281
282impl OpHardforks for OpChainSpec {
283 fn op_fork_activation(&self, fork: OpHardfork) -> ForkCondition {
284 self.fork(fork)
285 }
286}
287
288impl From<Genesis> for OpChainSpec {
289 fn from(genesis: Genesis) -> Self {
290 use reth_optimism_forks::OpHardfork;
291 let optimism_genesis_info = OpGenesisInfo::extract_from(&genesis);
292 let genesis_info =
293 optimism_genesis_info.optimism_chain_info.genesis_info.unwrap_or_default();
294
295 let hardfork_opts = [
297 (EthereumHardfork::Frontier.boxed(), Some(0)),
298 (EthereumHardfork::Homestead.boxed(), genesis.config.homestead_block),
299 (EthereumHardfork::Tangerine.boxed(), genesis.config.eip150_block),
300 (EthereumHardfork::SpuriousDragon.boxed(), genesis.config.eip155_block),
301 (EthereumHardfork::Byzantium.boxed(), genesis.config.byzantium_block),
302 (EthereumHardfork::Constantinople.boxed(), genesis.config.constantinople_block),
303 (EthereumHardfork::Petersburg.boxed(), genesis.config.petersburg_block),
304 (EthereumHardfork::Istanbul.boxed(), genesis.config.istanbul_block),
305 (EthereumHardfork::MuirGlacier.boxed(), genesis.config.muir_glacier_block),
306 (EthereumHardfork::Berlin.boxed(), genesis.config.berlin_block),
307 (EthereumHardfork::London.boxed(), genesis.config.london_block),
308 (EthereumHardfork::ArrowGlacier.boxed(), genesis.config.arrow_glacier_block),
309 (EthereumHardfork::GrayGlacier.boxed(), genesis.config.gray_glacier_block),
310 (OpHardfork::Bedrock.boxed(), genesis_info.bedrock_block),
311 ];
312 let mut block_hardforks = hardfork_opts
313 .into_iter()
314 .filter_map(|(hardfork, opt)| opt.map(|block| (hardfork, ForkCondition::Block(block))))
315 .collect::<Vec<_>>();
316
317 block_hardforks.push((
319 EthereumHardfork::Paris.boxed(),
320 ForkCondition::TTD {
321 activation_block_number: 0,
322 total_difficulty: U256::ZERO,
323 fork_block: genesis.config.merge_netsplit_block,
324 },
325 ));
326
327 let time_hardfork_opts = [
329 (EthereumHardfork::Shanghai.boxed(), genesis.config.shanghai_time),
330 (EthereumHardfork::Cancun.boxed(), genesis.config.cancun_time),
331 (EthereumHardfork::Prague.boxed(), genesis.config.prague_time),
332 (OpHardfork::Regolith.boxed(), genesis_info.regolith_time),
333 (OpHardfork::Canyon.boxed(), genesis_info.canyon_time),
334 (OpHardfork::Ecotone.boxed(), genesis_info.ecotone_time),
335 (OpHardfork::Fjord.boxed(), genesis_info.fjord_time),
336 (OpHardfork::Granite.boxed(), genesis_info.granite_time),
337 (OpHardfork::Holocene.boxed(), genesis_info.holocene_time),
338 (OpHardfork::Isthmus.boxed(), genesis_info.isthmus_time),
339 (OpHardfork::Interop.boxed(), genesis_info.interop_time),
340 ];
341
342 let mut time_hardforks = time_hardfork_opts
343 .into_iter()
344 .filter_map(|(hardfork, opt)| {
345 opt.map(|time| (hardfork, ForkCondition::Timestamp(time)))
346 })
347 .collect::<Vec<_>>();
348
349 block_hardforks.append(&mut time_hardforks);
350
351 let mainnet_hardforks = OP_MAINNET_HARDFORKS.clone();
353 let mainnet_order = mainnet_hardforks.forks_iter();
354
355 let mut ordered_hardforks = Vec::with_capacity(block_hardforks.len());
356 for (hardfork, _) in mainnet_order {
357 if let Some(pos) = block_hardforks.iter().position(|(e, _)| **e == *hardfork) {
358 ordered_hardforks.push(block_hardforks.remove(pos));
359 }
360 }
361
362 ordered_hardforks.append(&mut block_hardforks);
364
365 let hardforks = ChainHardforks::new(ordered_hardforks);
366 let genesis_header = SealedHeader::seal_slow(make_op_genesis_header(&genesis, &hardforks));
367
368 Self {
369 inner: ChainSpec {
370 chain: genesis.config.chain_id.into(),
371 genesis_header,
372 genesis,
373 hardforks,
374 paris_block_and_final_difficulty: Some((0, U256::ZERO)),
377 base_fee_params: optimism_genesis_info.base_fee_params,
378 ..Default::default()
379 },
380 }
381 }
382}
383
384impl From<ChainSpec> for OpChainSpec {
385 fn from(value: ChainSpec) -> Self {
386 Self { inner: value }
387 }
388}
389
390#[derive(Default, Debug)]
391struct OpGenesisInfo {
392 optimism_chain_info: op_alloy_rpc_types::OpChainInfo,
393 base_fee_params: BaseFeeParamsKind,
394}
395
396impl OpGenesisInfo {
397 fn extract_from(genesis: &Genesis) -> Self {
398 let mut info = Self {
399 optimism_chain_info: op_alloy_rpc_types::OpChainInfo::extract_from(
400 &genesis.config.extra_fields,
401 )
402 .unwrap_or_default(),
403 ..Default::default()
404 };
405 if let Some(optimism_base_fee_info) = &info.optimism_chain_info.base_fee_info {
406 if let (Some(elasticity), Some(denominator)) = (
407 optimism_base_fee_info.eip1559_elasticity,
408 optimism_base_fee_info.eip1559_denominator,
409 ) {
410 let base_fee_params = if let Some(canyon_denominator) =
411 optimism_base_fee_info.eip1559_denominator_canyon
412 {
413 BaseFeeParamsKind::Variable(
414 vec![
415 (
416 EthereumHardfork::London.boxed(),
417 BaseFeeParams::new(denominator as u128, elasticity as u128),
418 ),
419 (
420 OpHardfork::Canyon.boxed(),
421 BaseFeeParams::new(canyon_denominator as u128, elasticity as u128),
422 ),
423 ]
424 .into(),
425 )
426 } else {
427 BaseFeeParams::new(denominator as u128, elasticity as u128).into()
428 };
429
430 info.base_fee_params = base_fee_params;
431 }
432 }
433
434 info
435 }
436}
437
438pub fn make_op_genesis_header(genesis: &Genesis, hardforks: &ChainHardforks) -> Header {
440 let mut header = reth_chainspec::make_genesis_header(genesis, hardforks);
441
442 if hardforks.fork(OpHardfork::Isthmus).active_at_timestamp(header.timestamp) {
445 if let Some(predeploy) = genesis.alloc.get(&ADDRESS_L2_TO_L1_MESSAGE_PASSER) {
446 if let Some(storage) = &predeploy.storage {
447 header.withdrawals_root =
448 Some(storage_root_unhashed(storage.iter().map(|(k, v)| (*k, (*v).into()))))
449 }
450 }
451 }
452
453 header
454}
455
456#[cfg(test)]
457mod tests {
458 use alloy_genesis::{ChainConfig, Genesis};
459 use alloy_primitives::b256;
460 use reth_chainspec::{test_fork_ids, BaseFeeParams, BaseFeeParamsKind};
461 use reth_ethereum_forks::{EthereumHardfork, ForkCondition, ForkHash, ForkId, Head};
462 use reth_optimism_forks::{OpHardfork, OpHardforks};
463
464 use crate::*;
465
466 #[test]
467 fn base_mainnet_forkids() {
468 let mut base_mainnet = OpChainSpecBuilder::base_mainnet().build();
469 base_mainnet.inner.genesis_header.set_hash(BASE_MAINNET.genesis_hash());
470 test_fork_ids(
471 &BASE_MAINNET,
472 &[
473 (
474 Head { number: 0, ..Default::default() },
475 ForkId { hash: ForkHash([0x67, 0xda, 0x02, 0x60]), next: 1704992401 },
476 ),
477 (
478 Head { number: 0, timestamp: 1704992400, ..Default::default() },
479 ForkId { hash: ForkHash([0x67, 0xda, 0x02, 0x60]), next: 1704992401 },
480 ),
481 (
482 Head { number: 0, timestamp: 1704992401, ..Default::default() },
483 ForkId { hash: ForkHash([0x3c, 0x28, 0x3c, 0xb3]), next: 1710374401 },
484 ),
485 (
486 Head { number: 0, timestamp: 1710374400, ..Default::default() },
487 ForkId { hash: ForkHash([0x3c, 0x28, 0x3c, 0xb3]), next: 1710374401 },
488 ),
489 (
490 Head { number: 0, timestamp: 1710374401, ..Default::default() },
491 ForkId { hash: ForkHash([0x51, 0xcc, 0x98, 0xb3]), next: 1720627201 },
492 ),
493 (
494 Head { number: 0, timestamp: 1720627200, ..Default::default() },
495 ForkId { hash: ForkHash([0x51, 0xcc, 0x98, 0xb3]), next: 1720627201 },
496 ),
497 (
498 Head { number: 0, timestamp: 1720627201, ..Default::default() },
499 ForkId { hash: ForkHash([0xe4, 0x01, 0x0e, 0xb9]), next: 1726070401 },
500 ),
501 (
502 Head { number: 0, timestamp: 1726070401, ..Default::default() },
503 ForkId { hash: ForkHash([0xbc, 0x38, 0xf9, 0xca]), next: 1736445601 },
504 ),
505 (
506 Head { number: 0, timestamp: 1736445601, ..Default::default() },
507 ForkId { hash: ForkHash([0x3a, 0x2a, 0xf1, 0x83]), next: 0 },
508 ),
509 ],
510 );
511 }
512
513 #[test]
514 fn op_sepolia_forkids() {
515 test_fork_ids(
516 &OP_SEPOLIA,
517 &[
518 (
519 Head { number: 0, ..Default::default() },
520 ForkId { hash: ForkHash([0x67, 0xa4, 0x03, 0x28]), next: 1699981200 },
521 ),
522 (
523 Head { number: 0, timestamp: 1699981199, ..Default::default() },
524 ForkId { hash: ForkHash([0x67, 0xa4, 0x03, 0x28]), next: 1699981200 },
525 ),
526 (
527 Head { number: 0, timestamp: 1699981200, ..Default::default() },
528 ForkId { hash: ForkHash([0xa4, 0x8d, 0x6a, 0x00]), next: 1708534800 },
529 ),
530 (
531 Head { number: 0, timestamp: 1708534799, ..Default::default() },
532 ForkId { hash: ForkHash([0xa4, 0x8d, 0x6a, 0x00]), next: 1708534800 },
533 ),
534 (
535 Head { number: 0, timestamp: 1708534800, ..Default::default() },
536 ForkId { hash: ForkHash([0xcc, 0x17, 0xc7, 0xeb]), next: 1716998400 },
537 ),
538 (
539 Head { number: 0, timestamp: 1716998399, ..Default::default() },
540 ForkId { hash: ForkHash([0xcc, 0x17, 0xc7, 0xeb]), next: 1716998400 },
541 ),
542 (
543 Head { number: 0, timestamp: 1716998400, ..Default::default() },
544 ForkId { hash: ForkHash([0x54, 0x0a, 0x8c, 0x5d]), next: 1723478400 },
545 ),
546 (
547 Head { number: 0, timestamp: 1723478399, ..Default::default() },
548 ForkId { hash: ForkHash([0x54, 0x0a, 0x8c, 0x5d]), next: 1723478400 },
549 ),
550 (
551 Head { number: 0, timestamp: 1723478400, ..Default::default() },
552 ForkId { hash: ForkHash([0x75, 0xde, 0xa4, 0x1e]), next: 1732633200 },
553 ),
554 (
555 Head { number: 0, timestamp: 1732633200, ..Default::default() },
556 ForkId { hash: ForkHash([0x4a, 0x1c, 0x79, 0x2e]), next: 0 },
557 ),
558 ],
559 );
560 }
561
562 #[test]
563 fn op_mainnet_forkids() {
564 let mut op_mainnet = OpChainSpecBuilder::optimism_mainnet().build();
565 op_mainnet.inner.genesis_header.set_hash(OP_MAINNET.genesis_hash());
568 test_fork_ids(
569 &op_mainnet,
570 &[
571 (
572 Head { number: 0, ..Default::default() },
573 ForkId { hash: ForkHash([0xca, 0xf5, 0x17, 0xed]), next: 3950000 },
574 ),
575 (
577 Head { number: 105235063, timestamp: 1710374401, ..Default::default() },
578 ForkId { hash: ForkHash([0x19, 0xda, 0x4c, 0x52]), next: 1720627201 },
579 ),
580 ],
581 );
582 }
583
584 #[test]
585 fn base_sepolia_forkids() {
586 test_fork_ids(
587 &BASE_SEPOLIA,
588 &[
589 (
590 Head { number: 0, ..Default::default() },
591 ForkId { hash: ForkHash([0xb9, 0x59, 0xb9, 0xf7]), next: 1699981200 },
592 ),
593 (
594 Head { number: 0, timestamp: 1699981199, ..Default::default() },
595 ForkId { hash: ForkHash([0xb9, 0x59, 0xb9, 0xf7]), next: 1699981200 },
596 ),
597 (
598 Head { number: 0, timestamp: 1699981200, ..Default::default() },
599 ForkId { hash: ForkHash([0x60, 0x7c, 0xd5, 0xa1]), next: 1708534800 },
600 ),
601 (
602 Head { number: 0, timestamp: 1708534799, ..Default::default() },
603 ForkId { hash: ForkHash([0x60, 0x7c, 0xd5, 0xa1]), next: 1708534800 },
604 ),
605 (
606 Head { number: 0, timestamp: 1708534800, ..Default::default() },
607 ForkId { hash: ForkHash([0xbe, 0x96, 0x9b, 0x17]), next: 1716998400 },
608 ),
609 (
610 Head { number: 0, timestamp: 1716998399, ..Default::default() },
611 ForkId { hash: ForkHash([0xbe, 0x96, 0x9b, 0x17]), next: 1716998400 },
612 ),
613 (
614 Head { number: 0, timestamp: 1716998400, ..Default::default() },
615 ForkId { hash: ForkHash([0x4e, 0x45, 0x7a, 0x49]), next: 1723478400 },
616 ),
617 (
618 Head { number: 0, timestamp: 1723478399, ..Default::default() },
619 ForkId { hash: ForkHash([0x4e, 0x45, 0x7a, 0x49]), next: 1723478400 },
620 ),
621 (
622 Head { number: 0, timestamp: 1723478400, ..Default::default() },
623 ForkId { hash: ForkHash([0x5e, 0xdf, 0xa3, 0xb6]), next: 1732633200 },
624 ),
625 (
626 Head { number: 0, timestamp: 1732633200, ..Default::default() },
627 ForkId { hash: ForkHash([0x8b, 0x5e, 0x76, 0x29]), next: 0 },
628 ),
629 ],
630 );
631 }
632
633 #[test]
634 fn base_mainnet_genesis() {
635 let genesis = BASE_MAINNET.genesis_header();
636 assert_eq!(
637 genesis.hash_slow(),
638 b256!("0xf712aa9241cc24369b143cf6dce85f0902a9731e70d66818a3a5845b296c73dd")
639 );
640 let base_fee = genesis
641 .next_block_base_fee(BASE_MAINNET.base_fee_params_at_timestamp(genesis.timestamp))
642 .unwrap();
643 assert_eq!(base_fee, 980000000);
645 }
646
647 #[test]
648 fn base_sepolia_genesis() {
649 let genesis = BASE_SEPOLIA.genesis_header();
650 assert_eq!(
651 genesis.hash_slow(),
652 b256!("0x0dcc9e089e30b90ddfc55be9a37dd15bc551aeee999d2e2b51414c54eaf934e4")
653 );
654 let base_fee = genesis
655 .next_block_base_fee(BASE_SEPOLIA.base_fee_params_at_timestamp(genesis.timestamp))
656 .unwrap();
657 assert_eq!(base_fee, 980000000);
659 }
660
661 #[test]
662 fn op_sepolia_genesis() {
663 let genesis = OP_SEPOLIA.genesis_header();
664 assert_eq!(
665 genesis.hash_slow(),
666 b256!("0x102de6ffb001480cc9b8b548fd05c34cd4f46ae4aa91759393db90ea0409887d")
667 );
668 let base_fee = genesis
669 .next_block_base_fee(OP_SEPOLIA.base_fee_params_at_timestamp(genesis.timestamp))
670 .unwrap();
671 assert_eq!(base_fee, 980000000);
673 }
674
675 #[test]
676 fn latest_base_mainnet_fork_id() {
677 assert_eq!(
678 ForkId { hash: ForkHash([0x3a, 0x2a, 0xf1, 0x83]), next: 0 },
679 BASE_MAINNET.latest_fork_id()
680 )
681 }
682
683 #[test]
684 fn latest_base_mainnet_fork_id_with_builder() {
685 let base_mainnet = OpChainSpecBuilder::base_mainnet().build();
686 assert_eq!(
687 ForkId { hash: ForkHash([0x3a, 0x2a, 0xf1, 0x83]), next: 0 },
688 base_mainnet.latest_fork_id()
689 )
690 }
691
692 #[test]
693 fn is_bedrock_active() {
694 let op_mainnet = OpChainSpecBuilder::optimism_mainnet().build();
695 assert!(!op_mainnet.is_bedrock_active_at_block(1))
696 }
697
698 #[test]
699 fn parse_optimism_hardforks() {
700 let geth_genesis = r#"
701 {
702 "config": {
703 "bedrockBlock": 10,
704 "regolithTime": 20,
705 "canyonTime": 30,
706 "ecotoneTime": 40,
707 "fjordTime": 50,
708 "graniteTime": 51,
709 "holoceneTime": 52,
710 "optimism": {
711 "eip1559Elasticity": 60,
712 "eip1559Denominator": 70
713 }
714 }
715 }
716 "#;
717 let genesis: Genesis = serde_json::from_str(geth_genesis).unwrap();
718
719 let actual_bedrock_block = genesis.config.extra_fields.get("bedrockBlock");
720 assert_eq!(actual_bedrock_block, Some(serde_json::Value::from(10)).as_ref());
721 let actual_regolith_timestamp = genesis.config.extra_fields.get("regolithTime");
722 assert_eq!(actual_regolith_timestamp, Some(serde_json::Value::from(20)).as_ref());
723 let actual_canyon_timestamp = genesis.config.extra_fields.get("canyonTime");
724 assert_eq!(actual_canyon_timestamp, Some(serde_json::Value::from(30)).as_ref());
725 let actual_ecotone_timestamp = genesis.config.extra_fields.get("ecotoneTime");
726 assert_eq!(actual_ecotone_timestamp, Some(serde_json::Value::from(40)).as_ref());
727 let actual_fjord_timestamp = genesis.config.extra_fields.get("fjordTime");
728 assert_eq!(actual_fjord_timestamp, Some(serde_json::Value::from(50)).as_ref());
729 let actual_granite_timestamp = genesis.config.extra_fields.get("graniteTime");
730 assert_eq!(actual_granite_timestamp, Some(serde_json::Value::from(51)).as_ref());
731 let actual_holocene_timestamp = genesis.config.extra_fields.get("holoceneTime");
732 assert_eq!(actual_holocene_timestamp, Some(serde_json::Value::from(52)).as_ref());
733
734 let optimism_object = genesis.config.extra_fields.get("optimism").unwrap();
735 assert_eq!(
736 optimism_object,
737 &serde_json::json!({
738 "eip1559Elasticity": 60,
739 "eip1559Denominator": 70,
740 })
741 );
742
743 let chain_spec: OpChainSpec = genesis.into();
744
745 assert_eq!(
746 chain_spec.base_fee_params,
747 BaseFeeParamsKind::Constant(BaseFeeParams::new(70, 60))
748 );
749
750 assert!(!chain_spec.is_fork_active_at_block(OpHardfork::Bedrock, 0));
751 assert!(!chain_spec.is_fork_active_at_timestamp(OpHardfork::Regolith, 0));
752 assert!(!chain_spec.is_fork_active_at_timestamp(OpHardfork::Canyon, 0));
753 assert!(!chain_spec.is_fork_active_at_timestamp(OpHardfork::Ecotone, 0));
754 assert!(!chain_spec.is_fork_active_at_timestamp(OpHardfork::Fjord, 0));
755 assert!(!chain_spec.is_fork_active_at_timestamp(OpHardfork::Granite, 0));
756 assert!(!chain_spec.is_fork_active_at_timestamp(OpHardfork::Holocene, 0));
757
758 assert!(chain_spec.is_fork_active_at_block(OpHardfork::Bedrock, 10));
759 assert!(chain_spec.is_fork_active_at_timestamp(OpHardfork::Regolith, 20));
760 assert!(chain_spec.is_fork_active_at_timestamp(OpHardfork::Canyon, 30));
761 assert!(chain_spec.is_fork_active_at_timestamp(OpHardfork::Ecotone, 40));
762 assert!(chain_spec.is_fork_active_at_timestamp(OpHardfork::Fjord, 50));
763 assert!(chain_spec.is_fork_active_at_timestamp(OpHardfork::Granite, 51));
764 assert!(chain_spec.is_fork_active_at_timestamp(OpHardfork::Holocene, 52));
765 }
766
767 #[test]
768 fn parse_optimism_hardforks_variable_base_fee_params() {
769 let geth_genesis = r#"
770 {
771 "config": {
772 "bedrockBlock": 10,
773 "regolithTime": 20,
774 "canyonTime": 30,
775 "ecotoneTime": 40,
776 "fjordTime": 50,
777 "graniteTime": 51,
778 "holoceneTime": 52,
779 "optimism": {
780 "eip1559Elasticity": 60,
781 "eip1559Denominator": 70,
782 "eip1559DenominatorCanyon": 80
783 }
784 }
785 }
786 "#;
787 let genesis: Genesis = serde_json::from_str(geth_genesis).unwrap();
788
789 let actual_bedrock_block = genesis.config.extra_fields.get("bedrockBlock");
790 assert_eq!(actual_bedrock_block, Some(serde_json::Value::from(10)).as_ref());
791 let actual_regolith_timestamp = genesis.config.extra_fields.get("regolithTime");
792 assert_eq!(actual_regolith_timestamp, Some(serde_json::Value::from(20)).as_ref());
793 let actual_canyon_timestamp = genesis.config.extra_fields.get("canyonTime");
794 assert_eq!(actual_canyon_timestamp, Some(serde_json::Value::from(30)).as_ref());
795 let actual_ecotone_timestamp = genesis.config.extra_fields.get("ecotoneTime");
796 assert_eq!(actual_ecotone_timestamp, Some(serde_json::Value::from(40)).as_ref());
797 let actual_fjord_timestamp = genesis.config.extra_fields.get("fjordTime");
798 assert_eq!(actual_fjord_timestamp, Some(serde_json::Value::from(50)).as_ref());
799 let actual_granite_timestamp = genesis.config.extra_fields.get("graniteTime");
800 assert_eq!(actual_granite_timestamp, Some(serde_json::Value::from(51)).as_ref());
801 let actual_holocene_timestamp = genesis.config.extra_fields.get("holoceneTime");
802 assert_eq!(actual_holocene_timestamp, Some(serde_json::Value::from(52)).as_ref());
803
804 let optimism_object = genesis.config.extra_fields.get("optimism").unwrap();
805 assert_eq!(
806 optimism_object,
807 &serde_json::json!({
808 "eip1559Elasticity": 60,
809 "eip1559Denominator": 70,
810 "eip1559DenominatorCanyon": 80
811 })
812 );
813
814 let chain_spec: OpChainSpec = genesis.into();
815
816 assert_eq!(
817 chain_spec.base_fee_params,
818 BaseFeeParamsKind::Variable(
819 vec![
820 (EthereumHardfork::London.boxed(), BaseFeeParams::new(70, 60)),
821 (OpHardfork::Canyon.boxed(), BaseFeeParams::new(80, 60)),
822 ]
823 .into()
824 )
825 );
826
827 assert!(!chain_spec.is_fork_active_at_block(OpHardfork::Bedrock, 0));
828 assert!(!chain_spec.is_fork_active_at_timestamp(OpHardfork::Regolith, 0));
829 assert!(!chain_spec.is_fork_active_at_timestamp(OpHardfork::Canyon, 0));
830 assert!(!chain_spec.is_fork_active_at_timestamp(OpHardfork::Ecotone, 0));
831 assert!(!chain_spec.is_fork_active_at_timestamp(OpHardfork::Fjord, 0));
832 assert!(!chain_spec.is_fork_active_at_timestamp(OpHardfork::Granite, 0));
833 assert!(!chain_spec.is_fork_active_at_timestamp(OpHardfork::Holocene, 0));
834
835 assert!(chain_spec.is_fork_active_at_block(OpHardfork::Bedrock, 10));
836 assert!(chain_spec.is_fork_active_at_timestamp(OpHardfork::Regolith, 20));
837 assert!(chain_spec.is_fork_active_at_timestamp(OpHardfork::Canyon, 30));
838 assert!(chain_spec.is_fork_active_at_timestamp(OpHardfork::Ecotone, 40));
839 assert!(chain_spec.is_fork_active_at_timestamp(OpHardfork::Fjord, 50));
840 assert!(chain_spec.is_fork_active_at_timestamp(OpHardfork::Granite, 51));
841 assert!(chain_spec.is_fork_active_at_timestamp(OpHardfork::Holocene, 52));
842 }
843
844 #[test]
845 fn parse_genesis_optimism_with_variable_base_fee_params() {
846 use op_alloy_rpc_types::OpBaseFeeInfo;
847
848 let geth_genesis = r#"
849 {
850 "config": {
851 "chainId": 8453,
852 "homesteadBlock": 0,
853 "eip150Block": 0,
854 "eip155Block": 0,
855 "eip158Block": 0,
856 "byzantiumBlock": 0,
857 "constantinopleBlock": 0,
858 "petersburgBlock": 0,
859 "istanbulBlock": 0,
860 "muirGlacierBlock": 0,
861 "berlinBlock": 0,
862 "londonBlock": 0,
863 "arrowGlacierBlock": 0,
864 "grayGlacierBlock": 0,
865 "mergeNetsplitBlock": 0,
866 "bedrockBlock": 0,
867 "regolithTime": 15,
868 "terminalTotalDifficulty": 0,
869 "terminalTotalDifficultyPassed": true,
870 "optimism": {
871 "eip1559Elasticity": 6,
872 "eip1559Denominator": 50
873 }
874 }
875 }
876 "#;
877 let genesis: Genesis = serde_json::from_str(geth_genesis).unwrap();
878 let chainspec = OpChainSpec::from(genesis.clone());
879
880 let actual_chain_id = genesis.config.chain_id;
881 assert_eq!(actual_chain_id, 8453);
882
883 assert_eq!(
884 chainspec.hardforks.get(EthereumHardfork::Istanbul),
885 Some(ForkCondition::Block(0))
886 );
887
888 let actual_bedrock_block = genesis.config.extra_fields.get("bedrockBlock");
889 assert_eq!(actual_bedrock_block, Some(serde_json::Value::from(0)).as_ref());
890 let actual_canyon_timestamp = genesis.config.extra_fields.get("canyonTime");
891 assert_eq!(actual_canyon_timestamp, None);
892
893 assert!(genesis.config.terminal_total_difficulty_passed);
894
895 let optimism_object = genesis.config.extra_fields.get("optimism").unwrap();
896 let optimism_base_fee_info =
897 serde_json::from_value::<OpBaseFeeInfo>(optimism_object.clone()).unwrap();
898
899 assert_eq!(
900 optimism_base_fee_info,
901 OpBaseFeeInfo {
902 eip1559_elasticity: Some(6),
903 eip1559_denominator: Some(50),
904 eip1559_denominator_canyon: None,
905 }
906 );
907 assert_eq!(
908 chainspec.base_fee_params,
909 BaseFeeParamsKind::Constant(BaseFeeParams {
910 max_change_denominator: 50,
911 elasticity_multiplier: 6,
912 })
913 );
914
915 assert!(chainspec.is_fork_active_at_block(OpHardfork::Bedrock, 0));
916
917 assert!(chainspec.is_fork_active_at_timestamp(OpHardfork::Regolith, 20));
918 }
919
920 #[test]
921 fn test_fork_order_optimism_mainnet() {
922 use reth_optimism_forks::OpHardfork;
923
924 let genesis = Genesis {
925 config: ChainConfig {
926 chain_id: 0,
927 homestead_block: Some(0),
928 dao_fork_block: Some(0),
929 dao_fork_support: false,
930 eip150_block: Some(0),
931 eip155_block: Some(0),
932 eip158_block: Some(0),
933 byzantium_block: Some(0),
934 constantinople_block: Some(0),
935 petersburg_block: Some(0),
936 istanbul_block: Some(0),
937 muir_glacier_block: Some(0),
938 berlin_block: Some(0),
939 london_block: Some(0),
940 arrow_glacier_block: Some(0),
941 gray_glacier_block: Some(0),
942 merge_netsplit_block: Some(0),
943 shanghai_time: Some(0),
944 cancun_time: Some(0),
945 terminal_total_difficulty: Some(U256::ZERO),
946 extra_fields: [
947 (String::from("bedrockBlock"), 0.into()),
948 (String::from("regolithTime"), 0.into()),
949 (String::from("canyonTime"), 0.into()),
950 (String::from("ecotoneTime"), 0.into()),
951 (String::from("fjordTime"), 0.into()),
952 (String::from("graniteTime"), 0.into()),
953 (String::from("holoceneTime"), 0.into()),
954 ]
955 .into_iter()
956 .collect(),
957 ..Default::default()
958 },
959 ..Default::default()
960 };
961
962 let chain_spec: OpChainSpec = genesis.into();
963
964 let hardforks: Vec<_> = chain_spec.hardforks.forks_iter().map(|(h, _)| h).collect();
965 let expected_hardforks = vec![
966 EthereumHardfork::Frontier.boxed(),
967 EthereumHardfork::Homestead.boxed(),
968 EthereumHardfork::Tangerine.boxed(),
969 EthereumHardfork::SpuriousDragon.boxed(),
970 EthereumHardfork::Byzantium.boxed(),
971 EthereumHardfork::Constantinople.boxed(),
972 EthereumHardfork::Petersburg.boxed(),
973 EthereumHardfork::Istanbul.boxed(),
974 EthereumHardfork::MuirGlacier.boxed(),
975 EthereumHardfork::Berlin.boxed(),
976 EthereumHardfork::London.boxed(),
977 EthereumHardfork::ArrowGlacier.boxed(),
978 EthereumHardfork::GrayGlacier.boxed(),
979 EthereumHardfork::Paris.boxed(),
980 OpHardfork::Bedrock.boxed(),
981 OpHardfork::Regolith.boxed(),
982 EthereumHardfork::Shanghai.boxed(),
983 OpHardfork::Canyon.boxed(),
984 EthereumHardfork::Cancun.boxed(),
985 OpHardfork::Ecotone.boxed(),
986 OpHardfork::Fjord.boxed(),
987 OpHardfork::Granite.boxed(),
988 OpHardfork::Holocene.boxed(),
989 ];
992
993 for (expected, actual) in expected_hardforks.iter().zip(hardforks.iter()) {
994 println!("got {expected:?}, {actual:?}");
995 assert_eq!(&**expected, &**actual);
996 }
997 assert_eq!(expected_hardforks.len(), hardforks.len());
998 }
999
1000 #[test]
1001 fn json_genesis() {
1002 let geth_genesis = r#"
1003{
1004 "config": {
1005 "chainId": 1301,
1006 "homesteadBlock": 0,
1007 "eip150Block": 0,
1008 "eip155Block": 0,
1009 "eip158Block": 0,
1010 "byzantiumBlock": 0,
1011 "constantinopleBlock": 0,
1012 "petersburgBlock": 0,
1013 "istanbulBlock": 0,
1014 "muirGlacierBlock": 0,
1015 "berlinBlock": 0,
1016 "londonBlock": 0,
1017 "arrowGlacierBlock": 0,
1018 "grayGlacierBlock": 0,
1019 "mergeNetsplitBlock": 0,
1020 "shanghaiTime": 0,
1021 "cancunTime": 0,
1022 "bedrockBlock": 0,
1023 "regolithTime": 0,
1024 "canyonTime": 0,
1025 "ecotoneTime": 0,
1026 "fjordTime": 0,
1027 "graniteTime": 0,
1028 "holoceneTime": 1732633200,
1029 "terminalTotalDifficulty": 0,
1030 "terminalTotalDifficultyPassed": true,
1031 "optimism": {
1032 "eip1559Elasticity": 6,
1033 "eip1559Denominator": 50,
1034 "eip1559DenominatorCanyon": 250
1035 }
1036 },
1037 "nonce": "0x0",
1038 "timestamp": "0x66edad4c",
1039 "extraData": "0x424544524f434b",
1040 "gasLimit": "0x1c9c380",
1041 "difficulty": "0x0",
1042 "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
1043 "coinbase": "0x4200000000000000000000000000000000000011",
1044 "alloc": {},
1045 "number": "0x0",
1046 "gasUsed": "0x0",
1047 "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
1048 "baseFeePerGas": "0x3b9aca00",
1049 "excessBlobGas": "0x0",
1050 "blobGasUsed": "0x0"
1051}
1052 "#;
1053
1054 let genesis: Genesis = serde_json::from_str(geth_genesis).unwrap();
1055 let chainspec = OpChainSpec::from_genesis(genesis);
1056 assert!(chainspec.is_holocene_active_at_timestamp(1732633200));
1057 }
1058}