1#![doc = include_str!("../README.md")]
2#![doc(
3 html_logo_url = "https://raw.githubusercontent.com/alloy-rs/core/main/assets/alloy.jpg",
4 html_favicon_url = "https://raw.githubusercontent.com/alloy-rs/core/main/assets/favicon.ico"
5)]
6#![cfg_attr(not(test), warn(unused_crate_dependencies))]
7#![cfg_attr(docsrs, feature(doc_cfg))]
8#![no_std]
9
10extern crate alloc;
11use alloc::vec::Vec;
12use alloy_chains::{Chain, NamedChain};
13use alloy_hardforks::{EthereumHardfork, hardfork};
14pub use alloy_hardforks::{EthereumHardforks, ForkCondition};
15use alloy_primitives::U256;
16use core::ops::Index;
17
18pub mod optimism;
19pub use optimism::{mainnet as op_mainnet, mainnet::*, sepolia as op_sepolia, sepolia::*};
20
21pub mod base;
22pub use base::{mainnet as base_mainnet, mainnet::*, sepolia as base_sepolia, sepolia::*};
23
24hardfork!(
25 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
30 #[derive(Default)]
31 OpHardfork {
32 Bedrock,
34 Regolith,
36 Canyon,
38 Ecotone,
40 Fjord,
42 Granite,
44 Holocene,
46 #[default]
48 Isthmus,
49 Jovian,
51 Interop,
53 }
54);
55
56impl OpHardfork {
57 pub fn from_chain_and_timestamp(chain: Chain, timestamp: u64) -> Option<Self> {
60 let named = chain.named()?;
61
62 match named {
63 NamedChain::Optimism => Some(match timestamp {
64 _i if timestamp < OP_MAINNET_CANYON_TIMESTAMP => Self::Regolith,
65 _i if timestamp < OP_MAINNET_ECOTONE_TIMESTAMP => Self::Canyon,
66 _i if timestamp < OP_MAINNET_FJORD_TIMESTAMP => Self::Ecotone,
67 _i if timestamp < OP_MAINNET_GRANITE_TIMESTAMP => Self::Fjord,
68 _i if timestamp < OP_MAINNET_HOLOCENE_TIMESTAMP => Self::Granite,
69 _i if timestamp < OP_MAINNET_ISTHMUS_TIMESTAMP => Self::Holocene,
70 _i if timestamp < OP_MAINNET_JOVIAN_TIMESTAMP => Self::Isthmus,
71 _ => Self::Jovian,
72 }),
73 NamedChain::OptimismSepolia => Some(match timestamp {
74 _i if timestamp < OP_SEPOLIA_CANYON_TIMESTAMP => Self::Regolith,
75 _i if timestamp < OP_SEPOLIA_ECOTONE_TIMESTAMP => Self::Canyon,
76 _i if timestamp < OP_SEPOLIA_FJORD_TIMESTAMP => Self::Ecotone,
77 _i if timestamp < OP_SEPOLIA_GRANITE_TIMESTAMP => Self::Fjord,
78 _i if timestamp < OP_SEPOLIA_HOLOCENE_TIMESTAMP => Self::Granite,
79 _i if timestamp < OP_SEPOLIA_ISTHMUS_TIMESTAMP => Self::Holocene,
80 _i if timestamp < OP_SEPOLIA_JOVIAN_TIMESTAMP => Self::Isthmus,
81 _ => Self::Jovian,
82 }),
83 NamedChain::Base => Some(match timestamp {
84 _i if timestamp < BASE_MAINNET_CANYON_TIMESTAMP => Self::Regolith,
85 _i if timestamp < BASE_MAINNET_ECOTONE_TIMESTAMP => Self::Canyon,
86 _i if timestamp < BASE_MAINNET_FJORD_TIMESTAMP => Self::Ecotone,
87 _i if timestamp < BASE_MAINNET_GRANITE_TIMESTAMP => Self::Fjord,
88 _i if timestamp < BASE_MAINNET_HOLOCENE_TIMESTAMP => Self::Granite,
89 _i if timestamp < BASE_MAINNET_ISTHMUS_TIMESTAMP => Self::Holocene,
90 _i if timestamp < BASE_MAINNET_JOVIAN_TIMESTAMP => Self::Isthmus,
91 _ => Self::Jovian,
92 }),
93 NamedChain::BaseSepolia => Some(match timestamp {
94 _i if timestamp < BASE_SEPOLIA_CANYON_TIMESTAMP => Self::Regolith,
95 _i if timestamp < BASE_SEPOLIA_ECOTONE_TIMESTAMP => Self::Canyon,
96 _i if timestamp < BASE_SEPOLIA_FJORD_TIMESTAMP => Self::Ecotone,
97 _i if timestamp < BASE_SEPOLIA_GRANITE_TIMESTAMP => Self::Fjord,
98 _i if timestamp < BASE_SEPOLIA_HOLOCENE_TIMESTAMP => Self::Granite,
99 _i if timestamp < BASE_SEPOLIA_ISTHMUS_TIMESTAMP => Self::Holocene,
100 _i if timestamp < BASE_SEPOLIA_JOVIAN_TIMESTAMP => Self::Isthmus,
101 _ => Self::Jovian,
102 }),
103 _ => None,
104 }
105 }
106
107 pub const fn op_mainnet() -> [(Self, ForkCondition); 9] {
109 [
110 (Self::Bedrock, ForkCondition::Block(OP_MAINNET_BEDROCK_BLOCK)),
111 (Self::Regolith, ForkCondition::Timestamp(OP_MAINNET_REGOLITH_TIMESTAMP)),
112 (Self::Canyon, ForkCondition::Timestamp(OP_MAINNET_CANYON_TIMESTAMP)),
113 (Self::Ecotone, ForkCondition::Timestamp(OP_MAINNET_ECOTONE_TIMESTAMP)),
114 (Self::Fjord, ForkCondition::Timestamp(OP_MAINNET_FJORD_TIMESTAMP)),
115 (Self::Granite, ForkCondition::Timestamp(OP_MAINNET_GRANITE_TIMESTAMP)),
116 (Self::Holocene, ForkCondition::Timestamp(OP_MAINNET_HOLOCENE_TIMESTAMP)),
117 (Self::Isthmus, ForkCondition::Timestamp(OP_MAINNET_ISTHMUS_TIMESTAMP)),
118 (Self::Jovian, ForkCondition::Timestamp(OP_MAINNET_JOVIAN_TIMESTAMP)),
119 ]
120 }
121
122 pub const fn op_sepolia() -> [(Self, ForkCondition); 9] {
124 [
125 (Self::Bedrock, ForkCondition::Block(OP_SEPOLIA_BEDROCK_BLOCK)),
126 (Self::Regolith, ForkCondition::Timestamp(OP_SEPOLIA_REGOLITH_TIMESTAMP)),
127 (Self::Canyon, ForkCondition::Timestamp(OP_SEPOLIA_CANYON_TIMESTAMP)),
128 (Self::Ecotone, ForkCondition::Timestamp(OP_SEPOLIA_ECOTONE_TIMESTAMP)),
129 (Self::Fjord, ForkCondition::Timestamp(OP_SEPOLIA_FJORD_TIMESTAMP)),
130 (Self::Granite, ForkCondition::Timestamp(OP_SEPOLIA_GRANITE_TIMESTAMP)),
131 (Self::Holocene, ForkCondition::Timestamp(OP_SEPOLIA_HOLOCENE_TIMESTAMP)),
132 (Self::Isthmus, ForkCondition::Timestamp(OP_SEPOLIA_ISTHMUS_TIMESTAMP)),
133 (Self::Jovian, ForkCondition::Timestamp(OP_SEPOLIA_JOVIAN_TIMESTAMP)),
134 ]
135 }
136
137 pub const fn base_mainnet() -> [(Self, ForkCondition); 9] {
139 [
140 (Self::Bedrock, ForkCondition::Block(BASE_MAINNET_BEDROCK_BLOCK)),
141 (Self::Regolith, ForkCondition::Timestamp(BASE_MAINNET_REGOLITH_TIMESTAMP)),
142 (Self::Canyon, ForkCondition::Timestamp(BASE_MAINNET_CANYON_TIMESTAMP)),
143 (Self::Ecotone, ForkCondition::Timestamp(BASE_MAINNET_ECOTONE_TIMESTAMP)),
144 (Self::Fjord, ForkCondition::Timestamp(BASE_MAINNET_FJORD_TIMESTAMP)),
145 (Self::Granite, ForkCondition::Timestamp(BASE_MAINNET_GRANITE_TIMESTAMP)),
146 (Self::Holocene, ForkCondition::Timestamp(BASE_MAINNET_HOLOCENE_TIMESTAMP)),
147 (Self::Isthmus, ForkCondition::Timestamp(BASE_MAINNET_ISTHMUS_TIMESTAMP)),
148 (Self::Jovian, ForkCondition::Timestamp(BASE_MAINNET_JOVIAN_TIMESTAMP)),
149 ]
150 }
151
152 pub const fn base_sepolia() -> [(Self, ForkCondition); 9] {
154 [
155 (Self::Bedrock, ForkCondition::Block(BASE_SEPOLIA_BEDROCK_BLOCK)),
156 (Self::Regolith, ForkCondition::Timestamp(BASE_SEPOLIA_REGOLITH_TIMESTAMP)),
157 (Self::Canyon, ForkCondition::Timestamp(BASE_SEPOLIA_CANYON_TIMESTAMP)),
158 (Self::Ecotone, ForkCondition::Timestamp(BASE_SEPOLIA_ECOTONE_TIMESTAMP)),
159 (Self::Fjord, ForkCondition::Timestamp(BASE_SEPOLIA_FJORD_TIMESTAMP)),
160 (Self::Granite, ForkCondition::Timestamp(BASE_SEPOLIA_GRANITE_TIMESTAMP)),
161 (Self::Holocene, ForkCondition::Timestamp(BASE_SEPOLIA_HOLOCENE_TIMESTAMP)),
162 (Self::Isthmus, ForkCondition::Timestamp(BASE_SEPOLIA_ISTHMUS_TIMESTAMP)),
163 (Self::Jovian, ForkCondition::Timestamp(BASE_SEPOLIA_JOVIAN_TIMESTAMP)),
164 ]
165 }
166
167 pub const fn devnet() -> [(Self, ForkCondition); 9] {
169 [
170 (Self::Bedrock, ForkCondition::ZERO_BLOCK),
171 (Self::Regolith, ForkCondition::ZERO_TIMESTAMP),
172 (Self::Canyon, ForkCondition::ZERO_TIMESTAMP),
173 (Self::Ecotone, ForkCondition::ZERO_TIMESTAMP),
174 (Self::Fjord, ForkCondition::ZERO_TIMESTAMP),
175 (Self::Granite, ForkCondition::ZERO_TIMESTAMP),
176 (Self::Holocene, ForkCondition::ZERO_TIMESTAMP),
177 (Self::Isthmus, ForkCondition::ZERO_TIMESTAMP),
178 (Self::Jovian, ForkCondition::Timestamp(1762185600)),
179 ]
180 }
181
182 pub const fn idx(&self) -> usize {
184 *self as usize
185 }
186}
187
188#[auto_impl::auto_impl(&, Arc)]
190pub trait OpHardforks: EthereumHardforks {
191 fn op_fork_activation(&self, fork: OpHardfork) -> ForkCondition;
194
195 fn is_bedrock_active_at_block(&self, block_number: u64) -> bool {
198 self.op_fork_activation(OpHardfork::Bedrock).active_at_block(block_number)
199 }
200
201 fn is_regolith_active_at_timestamp(&self, timestamp: u64) -> bool {
204 self.op_fork_activation(OpHardfork::Regolith).active_at_timestamp(timestamp)
205 }
206
207 fn is_canyon_active_at_timestamp(&self, timestamp: u64) -> bool {
209 self.op_fork_activation(OpHardfork::Canyon).active_at_timestamp(timestamp)
210 }
211
212 fn is_ecotone_active_at_timestamp(&self, timestamp: u64) -> bool {
214 self.op_fork_activation(OpHardfork::Ecotone).active_at_timestamp(timestamp)
215 }
216
217 fn is_fjord_active_at_timestamp(&self, timestamp: u64) -> bool {
219 self.op_fork_activation(OpHardfork::Fjord).active_at_timestamp(timestamp)
220 }
221
222 fn is_granite_active_at_timestamp(&self, timestamp: u64) -> bool {
224 self.op_fork_activation(OpHardfork::Granite).active_at_timestamp(timestamp)
225 }
226
227 fn is_holocene_active_at_timestamp(&self, timestamp: u64) -> bool {
230 self.op_fork_activation(OpHardfork::Holocene).active_at_timestamp(timestamp)
231 }
232
233 fn is_isthmus_active_at_timestamp(&self, timestamp: u64) -> bool {
236 self.op_fork_activation(OpHardfork::Isthmus).active_at_timestamp(timestamp)
237 }
238
239 fn is_jovian_active_at_timestamp(&self, timestamp: u64) -> bool {
242 self.op_fork_activation(OpHardfork::Jovian).active_at_timestamp(timestamp)
243 }
244
245 fn is_interop_active_at_timestamp(&self, timestamp: u64) -> bool {
248 self.op_fork_activation(OpHardfork::Interop).active_at_timestamp(timestamp)
249 }
250}
251
252#[derive(Debug, Clone)]
264pub struct OpChainHardforks {
265 forks: Vec<(OpHardfork, ForkCondition)>,
267}
268
269impl OpChainHardforks {
270 pub fn new(forks: impl IntoIterator<Item = (OpHardfork, ForkCondition)>) -> Self {
273 let mut forks = forks.into_iter().collect::<Vec<_>>();
274 forks.sort();
275 Self { forks }
276 }
277
278 pub fn op_mainnet() -> Self {
280 Self::new(OpHardfork::op_mainnet())
281 }
282
283 pub fn op_sepolia() -> Self {
285 Self::new(OpHardfork::op_sepolia())
286 }
287
288 pub fn base_mainnet() -> Self {
290 Self::new(OpHardfork::base_mainnet())
291 }
292
293 pub fn base_sepolia() -> Self {
295 Self::new(OpHardfork::base_sepolia())
296 }
297
298 pub fn devnet() -> Self {
300 Self::new(OpHardfork::devnet())
301 }
302
303 pub fn is_op_mainnet(&self) -> bool {
305 self[OpHardfork::Bedrock] == ForkCondition::Block(OP_MAINNET_BEDROCK_BLOCK)
306 }
307}
308
309impl EthereumHardforks for OpChainHardforks {
310 fn ethereum_fork_activation(&self, fork: EthereumHardfork) -> ForkCondition {
311 use EthereumHardfork::{Cancun, Prague, Shanghai};
312 use OpHardfork::{Canyon, Ecotone, Isthmus};
313
314 if self.forks.is_empty() {
315 return ForkCondition::Never;
316 }
317
318 let forks_len = self.forks.len();
319 match fork {
321 Shanghai if forks_len <= Canyon.idx() => ForkCondition::Never,
322 Cancun if forks_len <= Ecotone.idx() => ForkCondition::Never,
323 Prague if forks_len <= Isthmus.idx() => ForkCondition::Never,
324 _ => self[fork],
325 }
326 }
327}
328
329impl OpHardforks for OpChainHardforks {
330 fn op_fork_activation(&self, fork: OpHardfork) -> ForkCondition {
331 if self.forks.len() <= fork.idx() {
333 return ForkCondition::Never;
334 }
335 self[fork]
336 }
337}
338
339impl Index<OpHardfork> for OpChainHardforks {
340 type Output = ForkCondition;
341
342 fn index(&self, hf: OpHardfork) -> &Self::Output {
343 use OpHardfork::*;
344
345 match hf {
346 Bedrock => &self.forks[Bedrock.idx()].1,
347 Regolith => &self.forks[Regolith.idx()].1,
348 Canyon => &self.forks[Canyon.idx()].1,
349 Ecotone => &self.forks[Ecotone.idx()].1,
350 Fjord => &self.forks[Fjord.idx()].1,
351 Granite => &self.forks[Granite.idx()].1,
352 Holocene => &self.forks[Holocene.idx()].1,
353 Isthmus => &self.forks[Isthmus.idx()].1,
354 Jovian => &self.forks[Jovian.idx()].1,
355 Interop => &self.forks[Interop.idx()].1,
356 }
357 }
358}
359
360impl Index<EthereumHardfork> for OpChainHardforks {
361 type Output = ForkCondition;
362
363 fn index(&self, hf: EthereumHardfork) -> &Self::Output {
364 use EthereumHardfork::*;
365 use OpHardfork::*;
366
367 match hf {
368 Frontier | Homestead | Tangerine | SpuriousDragon | Byzantium | Constantinople
369 | Petersburg | Istanbul | MuirGlacier => &ForkCondition::ZERO_BLOCK,
370 Dao => &ForkCondition::Never,
372 Berlin if self.is_op_mainnet() => &ForkCondition::Block(OP_MAINNET_BERLIN_BLOCK),
373 Berlin => &ForkCondition::ZERO_BLOCK,
374 London | ArrowGlacier | GrayGlacier => &self[Bedrock],
375 Paris if self.is_op_mainnet() => &ForkCondition::TTD {
376 activation_block_number: OP_MAINNET_BEDROCK_BLOCK,
377 fork_block: Some(OP_MAINNET_BEDROCK_BLOCK),
378 total_difficulty: U256::ZERO,
379 },
380 Paris => &ForkCondition::TTD {
381 activation_block_number: 0,
382 fork_block: Some(0),
383 total_difficulty: U256::ZERO,
384 },
385 Shanghai => &self[Canyon],
386 Cancun => &self[Ecotone],
387 Prague => &self[Isthmus],
388 Osaka | Bpo1 | Bpo2 | Bpo3 | Bpo4 | Bpo5 | Amsterdam => &ForkCondition::Never,
390 _ => unreachable!(),
391 }
392 }
393}
394
395#[cfg(test)]
396mod tests {
397 use super::*;
398 use core::str::FromStr;
399
400 extern crate alloc;
401
402 #[test]
403 fn check_op_hardfork_from_str() {
404 let hardfork_str = [
405 "beDrOck", "rEgOlITH", "cAnYoN", "eCoToNe", "FJorD", "GRaNiTe", "hOlOcEnE", "isthMUS",
406 "jOvIaN", "inTerOP",
407 ];
408 let expected_hardforks = [
409 OpHardfork::Bedrock,
410 OpHardfork::Regolith,
411 OpHardfork::Canyon,
412 OpHardfork::Ecotone,
413 OpHardfork::Fjord,
414 OpHardfork::Granite,
415 OpHardfork::Holocene,
416 OpHardfork::Isthmus,
417 OpHardfork::Jovian,
418 OpHardfork::Interop,
419 ];
420
421 let hardforks: alloc::vec::Vec<OpHardfork> =
422 hardfork_str.iter().map(|h| OpHardfork::from_str(h).unwrap()).collect();
423
424 assert_eq!(hardforks, expected_hardforks);
425 }
426
427 #[test]
428 fn check_nonexistent_hardfork_from_str() {
429 assert!(OpHardfork::from_str("not a hardfork").is_err());
430 }
431
432 #[test]
433 fn op_mainnet_fork_conditions() {
434 use OpHardfork::*;
435
436 let op_mainnet_forks = OpChainHardforks::op_mainnet();
437 assert_eq!(op_mainnet_forks[Bedrock], ForkCondition::Block(OP_MAINNET_BEDROCK_BLOCK));
438 assert_eq!(
439 op_mainnet_forks[Regolith],
440 ForkCondition::Timestamp(OP_MAINNET_REGOLITH_TIMESTAMP)
441 );
442 assert_eq!(op_mainnet_forks[Canyon], ForkCondition::Timestamp(OP_MAINNET_CANYON_TIMESTAMP));
443 assert_eq!(
444 op_mainnet_forks[Ecotone],
445 ForkCondition::Timestamp(OP_MAINNET_ECOTONE_TIMESTAMP)
446 );
447 assert_eq!(op_mainnet_forks[Fjord], ForkCondition::Timestamp(OP_MAINNET_FJORD_TIMESTAMP));
448 assert_eq!(
449 op_mainnet_forks[Granite],
450 ForkCondition::Timestamp(OP_MAINNET_GRANITE_TIMESTAMP)
451 );
452 assert_eq!(
453 op_mainnet_forks[Holocene],
454 ForkCondition::Timestamp(OP_MAINNET_HOLOCENE_TIMESTAMP)
455 );
456 assert_eq!(
457 op_mainnet_forks[Isthmus],
458 ForkCondition::Timestamp(OP_MAINNET_ISTHMUS_TIMESTAMP)
459 );
460 assert_eq!(op_mainnet_forks[Jovian], ForkCondition::Timestamp(OP_MAINNET_JOVIAN_TIMESTAMP));
461 assert_eq!(op_mainnet_forks.op_fork_activation(Interop), ForkCondition::Never);
462 }
463
464 #[test]
465 fn op_sepolia_fork_conditions() {
466 use OpHardfork::*;
467
468 let op_sepolia_forks = OpChainHardforks::op_sepolia();
469 assert_eq!(op_sepolia_forks[Bedrock], ForkCondition::Block(OP_SEPOLIA_BEDROCK_BLOCK));
470 assert_eq!(
471 op_sepolia_forks[Regolith],
472 ForkCondition::Timestamp(OP_SEPOLIA_REGOLITH_TIMESTAMP)
473 );
474 assert_eq!(op_sepolia_forks[Canyon], ForkCondition::Timestamp(OP_SEPOLIA_CANYON_TIMESTAMP));
475 assert_eq!(
476 op_sepolia_forks[Ecotone],
477 ForkCondition::Timestamp(OP_SEPOLIA_ECOTONE_TIMESTAMP)
478 );
479 assert_eq!(op_sepolia_forks[Fjord], ForkCondition::Timestamp(OP_SEPOLIA_FJORD_TIMESTAMP));
480 assert_eq!(
481 op_sepolia_forks[Granite],
482 ForkCondition::Timestamp(OP_SEPOLIA_GRANITE_TIMESTAMP)
483 );
484 assert_eq!(
485 op_sepolia_forks[Holocene],
486 ForkCondition::Timestamp(OP_SEPOLIA_HOLOCENE_TIMESTAMP)
487 );
488 assert_eq!(
489 op_sepolia_forks[Isthmus],
490 ForkCondition::Timestamp(OP_SEPOLIA_ISTHMUS_TIMESTAMP)
491 );
492 assert_eq!(op_sepolia_forks[Jovian], ForkCondition::Timestamp(OP_SEPOLIA_JOVIAN_TIMESTAMP));
493 assert_eq!(op_sepolia_forks.op_fork_activation(Interop), ForkCondition::Never);
494 }
495
496 #[test]
497 fn base_mainnet_fork_conditions() {
498 use OpHardfork::*;
499
500 let base_mainnet_forks = OpChainHardforks::base_mainnet();
501 assert_eq!(base_mainnet_forks[Bedrock], ForkCondition::Block(BASE_MAINNET_BEDROCK_BLOCK));
502 assert_eq!(
503 base_mainnet_forks[Regolith],
504 ForkCondition::Timestamp(BASE_MAINNET_REGOLITH_TIMESTAMP)
505 );
506 assert_eq!(
507 base_mainnet_forks[Canyon],
508 ForkCondition::Timestamp(BASE_MAINNET_CANYON_TIMESTAMP)
509 );
510 assert_eq!(
511 base_mainnet_forks[Ecotone],
512 ForkCondition::Timestamp(BASE_MAINNET_ECOTONE_TIMESTAMP)
513 );
514 assert_eq!(
515 base_mainnet_forks[Fjord],
516 ForkCondition::Timestamp(BASE_MAINNET_FJORD_TIMESTAMP)
517 );
518 assert_eq!(
519 base_mainnet_forks[Granite],
520 ForkCondition::Timestamp(BASE_MAINNET_GRANITE_TIMESTAMP)
521 );
522 assert_eq!(
523 base_mainnet_forks[Holocene],
524 ForkCondition::Timestamp(BASE_MAINNET_HOLOCENE_TIMESTAMP)
525 );
526 assert_eq!(
527 base_mainnet_forks[Isthmus],
528 ForkCondition::Timestamp(BASE_MAINNET_ISTHMUS_TIMESTAMP)
529 );
530 assert_eq!(
531 base_mainnet_forks[Jovian],
532 ForkCondition::Timestamp(BASE_MAINNET_JOVIAN_TIMESTAMP)
533 );
534 assert_eq!(
535 base_mainnet_forks[Jovian],
536 ForkCondition::Timestamp(OP_MAINNET_JOVIAN_TIMESTAMP)
537 );
538 assert_eq!(base_mainnet_forks.op_fork_activation(Interop), ForkCondition::Never);
539 }
540
541 #[test]
542 fn base_sepolia_fork_conditions() {
543 use OpHardfork::*;
544
545 let base_sepolia_forks = OpChainHardforks::base_sepolia();
546 assert_eq!(base_sepolia_forks[Bedrock], ForkCondition::Block(BASE_SEPOLIA_BEDROCK_BLOCK));
547 assert_eq!(
548 base_sepolia_forks[Regolith],
549 ForkCondition::Timestamp(BASE_SEPOLIA_REGOLITH_TIMESTAMP)
550 );
551 assert_eq!(
552 base_sepolia_forks[Canyon],
553 ForkCondition::Timestamp(BASE_SEPOLIA_CANYON_TIMESTAMP)
554 );
555 assert_eq!(
556 base_sepolia_forks[Ecotone],
557 ForkCondition::Timestamp(BASE_SEPOLIA_ECOTONE_TIMESTAMP)
558 );
559 assert_eq!(
560 base_sepolia_forks[Fjord],
561 ForkCondition::Timestamp(BASE_SEPOLIA_FJORD_TIMESTAMP)
562 );
563 assert_eq!(
564 base_sepolia_forks[Granite],
565 ForkCondition::Timestamp(BASE_SEPOLIA_GRANITE_TIMESTAMP)
566 );
567 assert_eq!(
568 base_sepolia_forks[Holocene],
569 ForkCondition::Timestamp(BASE_SEPOLIA_HOLOCENE_TIMESTAMP)
570 );
571 assert_eq!(
572 base_sepolia_forks[Isthmus],
573 ForkCondition::Timestamp(BASE_SEPOLIA_ISTHMUS_TIMESTAMP)
574 );
575 assert_eq!(
576 base_sepolia_forks.op_fork_activation(Jovian),
577 ForkCondition::Timestamp(BASE_SEPOLIA_JOVIAN_TIMESTAMP)
578 );
579 assert_eq!(
580 base_sepolia_forks[Jovian],
581 ForkCondition::Timestamp(OP_SEPOLIA_JOVIAN_TIMESTAMP)
582 );
583 assert_eq!(base_sepolia_forks.op_fork_activation(Interop), ForkCondition::Never);
584 }
585
586 #[test]
587 fn is_jovian_active_at_timestamp() {
588 let op_mainnet_forks = OpChainHardforks::op_mainnet();
589 assert!(op_mainnet_forks.is_jovian_active_at_timestamp(OP_MAINNET_JOVIAN_TIMESTAMP));
590 assert!(!op_mainnet_forks.is_jovian_active_at_timestamp(OP_MAINNET_JOVIAN_TIMESTAMP - 1));
591 assert!(op_mainnet_forks.is_jovian_active_at_timestamp(OP_MAINNET_JOVIAN_TIMESTAMP + 1000));
592
593 let op_sepolia_forks = OpChainHardforks::op_sepolia();
594 assert!(op_sepolia_forks.is_jovian_active_at_timestamp(OP_SEPOLIA_JOVIAN_TIMESTAMP));
595 assert!(!op_sepolia_forks.is_jovian_active_at_timestamp(OP_SEPOLIA_JOVIAN_TIMESTAMP - 1));
596 assert!(op_sepolia_forks.is_jovian_active_at_timestamp(OP_SEPOLIA_JOVIAN_TIMESTAMP + 1000));
597
598 let base_mainnet_forks = OpChainHardforks::base_mainnet();
599 assert!(base_mainnet_forks.is_jovian_active_at_timestamp(BASE_MAINNET_JOVIAN_TIMESTAMP));
600 assert!(
601 !base_mainnet_forks.is_jovian_active_at_timestamp(BASE_MAINNET_JOVIAN_TIMESTAMP - 1)
602 );
603 assert!(
604 base_mainnet_forks.is_jovian_active_at_timestamp(BASE_MAINNET_JOVIAN_TIMESTAMP + 1000)
605 );
606
607 let base_sepolia_forks = OpChainHardforks::base_sepolia();
608 assert!(base_sepolia_forks.is_jovian_active_at_timestamp(BASE_SEPOLIA_JOVIAN_TIMESTAMP));
609 assert!(
610 !base_sepolia_forks.is_jovian_active_at_timestamp(BASE_SEPOLIA_JOVIAN_TIMESTAMP - 1)
611 );
612 assert!(
613 base_sepolia_forks.is_jovian_active_at_timestamp(BASE_SEPOLIA_JOVIAN_TIMESTAMP + 1000)
614 );
615 }
616
617 #[test]
618 fn test_reverse_lookup_op_chains() {
619 let test_cases = [
621 (Chain::optimism_mainnet(), OP_MAINNET_CANYON_TIMESTAMP, OpHardfork::Canyon),
624 (Chain::optimism_mainnet(), OP_MAINNET_ECOTONE_TIMESTAMP, OpHardfork::Ecotone),
625 (Chain::optimism_mainnet(), OP_MAINNET_GRANITE_TIMESTAMP, OpHardfork::Granite),
626 (Chain::optimism_mainnet(), OP_MAINNET_CANYON_TIMESTAMP - 1, OpHardfork::Regolith),
627 (Chain::optimism_mainnet(), OP_MAINNET_ISTHMUS_TIMESTAMP + 1000, OpHardfork::Isthmus),
628 (Chain::optimism_mainnet(), OP_MAINNET_JOVIAN_TIMESTAMP, OpHardfork::Jovian),
629 (Chain::optimism_mainnet(), OP_MAINNET_JOVIAN_TIMESTAMP - 1, OpHardfork::Isthmus),
630 (Chain::optimism_mainnet(), OP_MAINNET_JOVIAN_TIMESTAMP + 1000, OpHardfork::Jovian),
631 (Chain::optimism_sepolia(), OP_SEPOLIA_CANYON_TIMESTAMP, OpHardfork::Canyon),
633 (Chain::optimism_sepolia(), OP_SEPOLIA_ECOTONE_TIMESTAMP, OpHardfork::Ecotone),
634 (Chain::optimism_sepolia(), OP_SEPOLIA_CANYON_TIMESTAMP - 1, OpHardfork::Regolith),
635 (Chain::optimism_sepolia(), OP_SEPOLIA_JOVIAN_TIMESTAMP, OpHardfork::Jovian),
636 (Chain::optimism_sepolia(), OP_SEPOLIA_JOVIAN_TIMESTAMP - 1, OpHardfork::Isthmus),
637 (Chain::optimism_sepolia(), OP_SEPOLIA_JOVIAN_TIMESTAMP + 1000, OpHardfork::Jovian),
638 (Chain::base_mainnet(), BASE_MAINNET_CANYON_TIMESTAMP, OpHardfork::Canyon),
640 (Chain::base_mainnet(), BASE_MAINNET_ECOTONE_TIMESTAMP, OpHardfork::Ecotone),
641 (Chain::base_mainnet(), BASE_MAINNET_JOVIAN_TIMESTAMP, OpHardfork::Jovian),
642 (Chain::base_sepolia(), BASE_SEPOLIA_CANYON_TIMESTAMP, OpHardfork::Canyon),
644 (Chain::base_sepolia(), BASE_SEPOLIA_ECOTONE_TIMESTAMP, OpHardfork::Ecotone),
645 (Chain::base_sepolia(), BASE_SEPOLIA_JOVIAN_TIMESTAMP, OpHardfork::Jovian),
646 ];
647
648 for (chain_id, timestamp, expected) in test_cases {
649 assert_eq!(
650 OpHardfork::from_chain_and_timestamp(chain_id, timestamp),
651 Some(expected),
652 "chain {chain_id} at timestamp {timestamp}"
653 );
654 }
655
656 assert_eq!(OpHardfork::from_chain_and_timestamp(Chain::from_id(999999), 1000000), None);
658 }
659
660 #[test]
662 fn test_ethereum_fork_activation_consistency() {
663 let op_mainnet_forks = OpChainHardforks::op_mainnet();
664 for ethereum_hardfork in EthereumHardfork::VARIANTS {
665 let _ = op_mainnet_forks.ethereum_fork_activation(*ethereum_hardfork);
666 }
667 for op_hardfork in OpHardfork::VARIANTS {
668 let _ = op_mainnet_forks.op_fork_activation(*op_hardfork);
669 }
670 }
671}