1use alloy_chains::Chain;
7use alloy_rpc_types::BlockNumberOrTag;
8use foundry_compilers::artifacts::EvmVersion;
9use revm::primitives::hardfork::SpecId;
10use serde::{Deserialize, Serialize};
11use std::{
12 str::FromStr,
13 time::{SystemTime, UNIX_EPOCH},
14};
15
16#[cfg(all(feature = "optimism", not(feature = "base")))]
17use alloy_chains::NamedChain;
18#[cfg(feature = "optimism")]
19use op_revm::OpSpecId;
20
21pub use alloy_hardforks::EthereumHardfork;
22pub use tempo_hardfork::TempoHardfork;
23
24#[cfg(feature = "base")]
25pub use base_common_evm::BaseSpecId;
26#[cfg(feature = "base")]
27pub use base_common_genesis::BaseUpgrade;
28
29#[cfg(feature = "monad")]
30pub use monad_revm::MonadHardfork;
31
32#[cfg(feature = "optimism")]
33pub use alloy_op_hardforks::OpHardfork;
34
35#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
36#[serde(into = "String")]
37pub enum FoundryHardfork {
38 Ethereum(EthereumHardfork),
39 #[cfg(feature = "optimism")]
40 Optimism(OpHardfork),
41 #[cfg(feature = "base")]
42 Base(BaseUpgrade),
43 Tempo(TempoHardfork),
44 #[cfg(feature = "monad")]
45 Monad(MonadHardfork),
46}
47
48impl From<FoundryHardfork> for String {
49 fn from(fork: FoundryHardfork) -> Self {
50 match fork {
51 FoundryHardfork::Ethereum(h) => format!("{h}"),
52 #[cfg(feature = "optimism")]
53 FoundryHardfork::Optimism(h) => format!("optimism:{h}"),
54 #[cfg(feature = "base")]
55 FoundryHardfork::Base(h) => format!("base:{h}"),
56 FoundryHardfork::Tempo(h) => format!("tempo:{h}"),
57 #[cfg(feature = "monad")]
58 FoundryHardfork::Monad(h) => format!("monad:{h}"),
59 }
60 }
61}
62
63impl<'de> Deserialize<'de> for FoundryHardfork {
64 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
65 where
66 D: serde::Deserializer<'de>,
67 {
68 let s = String::deserialize(deserializer)?;
69 Self::from_str(&s).map_err(serde::de::Error::custom)
70 }
71}
72
73impl FromStr for FoundryHardfork {
74 type Err = String;
75
76 fn from_str(s: &str) -> Result<Self, Self::Err> {
77 let raw = s.trim();
78
79 let Some((ns, fork_raw)) = raw.split_once(':') else {
80 return EthereumHardfork::from_str(raw)
81 .map(Self::Ethereum)
82 .map_err(|_| format!("unknown ethereum hardfork '{raw}'"));
83 };
84
85 let ns = ns.trim().to_ascii_lowercase();
86 let fork = fork_raw.trim().to_ascii_lowercase().replace(['-', ' '], "_");
87
88 match ns.as_str() {
89 "eth" | "ethereum" => EthereumHardfork::from_str(&fork)
90 .map(Self::Ethereum)
91 .map_err(|_| format!("unknown ethereum hardfork '{fork_raw}'")),
92
93 #[cfg(feature = "optimism")]
94 "op" | "optimism" => OpHardfork::from_str(&fork)
95 .map(Self::Optimism)
96 .map_err(|_| format!("unknown optimism hardfork '{fork_raw}'")),
97 #[cfg(not(feature = "optimism"))]
98 "op" | "optimism" => {
99 Err(format!("hardfork namespace '{ns}' requires the `optimism` feature"))
100 }
101
102 #[cfg(feature = "base")]
103 "base" => BaseUpgrade::from_str(&fork)
104 .map(Self::Base)
105 .map_err(|_| format!("unknown base hardfork '{fork_raw}'")),
106 #[cfg(not(feature = "base"))]
107 "base" => Err(format!("hardfork namespace '{ns}' requires the `base` feature")),
108
109 "t" | "tempo" => TempoHardfork::from_str(&fork)
110 .map(Self::Tempo)
111 .map_err(|_| format!("unknown tempo hardfork '{fork_raw}'")),
112
113 #[cfg(feature = "monad")]
114 "m" | "monad" => MonadHardfork::from_str(&fork)
115 .map(Self::Monad)
116 .map_err(|_| format!("unknown monad hardfork '{fork_raw}'")),
117 #[cfg(not(feature = "monad"))]
118 "m" | "monad" => Err(format!("hardfork namespace '{ns}' requires the `monad` feature")),
119
120 _ => Err(format!("unknown hardfork namespace '{ns}'")),
121 }
122 }
123}
124
125impl FoundryHardfork {
126 pub const fn ethereum(h: EthereumHardfork) -> Self {
127 Self::Ethereum(h)
128 }
129
130 #[cfg(feature = "optimism")]
131 pub const fn optimism(h: OpHardfork) -> Self {
132 Self::Optimism(h)
133 }
134
135 #[cfg(feature = "base")]
136 pub const fn base(h: BaseUpgrade) -> Self {
137 Self::Base(h)
138 }
139
140 pub const fn tempo(h: TempoHardfork) -> Self {
141 Self::Tempo(h)
142 }
143
144 #[cfg(feature = "monad")]
145 pub const fn monad(h: MonadHardfork) -> Self {
146 Self::Monad(h)
147 }
148
149 pub fn name(&self) -> String {
151 match self {
152 Self::Ethereum(h) => format!("{h}"),
153 #[cfg(feature = "optimism")]
154 Self::Optimism(h) => format!("{h}"),
155 #[cfg(feature = "base")]
156 Self::Base(h) => format!("{h}"),
157 Self::Tempo(h) => format!("{h}"),
158 #[cfg(feature = "monad")]
159 Self::Monad(h) => format!("{h}"),
160 }
161 }
162
163 pub const fn namespace(&self) -> Option<&'static str> {
167 match self {
168 Self::Ethereum(_) => None,
169 #[cfg(feature = "optimism")]
170 Self::Optimism(_) => Some("optimism"),
171 #[cfg(feature = "base")]
172 Self::Base(_) => Some("base"),
173 Self::Tempo(_) => Some("tempo"),
174 #[cfg(feature = "monad")]
175 Self::Monad(_) => Some("monad"),
176 }
177 }
178
179 pub fn from_chain_and_timestamp(chain_id: u64, timestamp: u64) -> Option<Self> {
183 let chain = Chain::from_id(chain_id);
184 if let Some(fork) = EthereumHardfork::from_chain_and_timestamp(chain, timestamp) {
185 return Some(Self::Ethereum(fork));
186 }
187 #[cfg(feature = "base")]
188 if let Some(fork) = BaseUpgrade::from_chain_and_timestamp(chain_id, timestamp) {
189 return Some(Self::Base(fork));
190 }
191 #[cfg(feature = "optimism")]
192 if let Some(fork) = OpHardfork::from_chain_and_timestamp(chain, timestamp) {
193 return Some(Self::Optimism(fork));
194 }
195 if let Some(fork) = TempoHardfork::from_chain_and_timestamp(chain_id, timestamp) {
196 return Some(Self::Tempo(fork));
197 }
198 #[cfg(feature = "monad")]
199 if let Some(fork) = MonadHardfork::from_chain_and_timestamp(chain_id, timestamp) {
200 return Some(Self::Monad(fork));
201 }
202 None
203 }
204}
205
206impl From<EthereumHardfork> for FoundryHardfork {
207 fn from(value: EthereumHardfork) -> Self {
208 Self::Ethereum(value)
209 }
210}
211
212impl From<FoundryHardfork> for EthereumHardfork {
213 fn from(fork: FoundryHardfork) -> Self {
214 match fork {
215 FoundryHardfork::Ethereum(hardfork) => hardfork,
216 _ => Self::default(),
217 }
218 }
219}
220
221#[cfg(feature = "optimism")]
222impl From<OpHardfork> for FoundryHardfork {
223 fn from(value: OpHardfork) -> Self {
224 Self::Optimism(value)
225 }
226}
227
228#[cfg(feature = "optimism")]
229impl From<FoundryHardfork> for OpHardfork {
230 fn from(fork: FoundryHardfork) -> Self {
231 match fork {
232 FoundryHardfork::Optimism(hardfork) => hardfork,
233 _ => Self::default(),
234 }
235 }
236}
237
238#[cfg(feature = "base")]
239impl From<BaseUpgrade> for FoundryHardfork {
240 fn from(value: BaseUpgrade) -> Self {
241 Self::Base(value)
242 }
243}
244
245#[cfg(feature = "base")]
246impl From<FoundryHardfork> for BaseUpgrade {
247 fn from(fork: FoundryHardfork) -> Self {
248 match fork {
249 FoundryHardfork::Base(upgrade) => upgrade,
250 _ => Self::default(),
251 }
252 }
253}
254
255#[cfg(feature = "base")]
256impl From<FoundryHardfork> for BaseSpecId {
257 fn from(fork: FoundryHardfork) -> Self {
258 match fork {
259 FoundryHardfork::Base(upgrade) => Self::new(upgrade),
260 _ => Self::default(),
261 }
262 }
263}
264
265impl From<TempoHardfork> for FoundryHardfork {
266 fn from(value: TempoHardfork) -> Self {
267 Self::Tempo(value)
268 }
269}
270
271impl From<FoundryHardfork> for TempoHardfork {
272 fn from(fork: FoundryHardfork) -> Self {
273 match fork {
274 FoundryHardfork::Tempo(hardfork) => hardfork,
275 _ => Self::default(),
276 }
277 }
278}
279
280#[cfg(feature = "monad")]
281impl From<MonadHardfork> for FoundryHardfork {
282 fn from(value: MonadHardfork) -> Self {
283 Self::Monad(value)
284 }
285}
286
287#[cfg(feature = "monad")]
288impl From<FoundryHardfork> for MonadHardfork {
289 fn from(fork: FoundryHardfork) -> Self {
290 match fork {
291 FoundryHardfork::Monad(hardfork) => hardfork,
292 _ => Self::default(),
293 }
294 }
295}
296
297impl From<FoundryHardfork> for SpecId {
298 fn from(fork: FoundryHardfork) -> Self {
299 match fork {
300 FoundryHardfork::Ethereum(hardfork) => spec_id_from_ethereum_hardfork(hardfork),
301 #[cfg(feature = "optimism")]
302 FoundryHardfork::Optimism(hardfork) => eth_spec_id_from_optimism_hardfork(hardfork),
303 #[cfg(feature = "base")]
304 FoundryHardfork::Base(hardfork) => BaseSpecId::new(hardfork).into_eth_spec(),
305 FoundryHardfork::Tempo(hardfork) => spec_id_from_tempo_hardfork(hardfork),
306 #[cfg(feature = "monad")]
307 FoundryHardfork::Monad(hardfork) => hardfork.into(),
308 }
309 }
310}
311
312#[cfg(feature = "optimism")]
313impl From<FoundryHardfork> for OpSpecId {
314 fn from(fork: FoundryHardfork) -> Self {
315 match fork {
316 FoundryHardfork::Optimism(hardfork) => spec_id_from_optimism_hardfork(hardfork),
317 _ => Self::default(),
318 }
319 }
320}
321
322pub fn spec_id_from_ethereum_hardfork(hardfork: EthereumHardfork) -> SpecId {
324 match hardfork {
325 EthereumHardfork::Frontier => SpecId::FRONTIER,
326 EthereumHardfork::Homestead => SpecId::HOMESTEAD,
327 EthereumHardfork::Dao => SpecId::HOMESTEAD,
328 EthereumHardfork::Tangerine => SpecId::TANGERINE,
329 EthereumHardfork::SpuriousDragon => SpecId::SPURIOUS_DRAGON,
330 EthereumHardfork::Byzantium => SpecId::BYZANTIUM,
331 EthereumHardfork::Constantinople => SpecId::PETERSBURG,
332 EthereumHardfork::Petersburg => SpecId::PETERSBURG,
333 EthereumHardfork::Istanbul => SpecId::ISTANBUL,
334 EthereumHardfork::MuirGlacier => SpecId::ISTANBUL,
335 EthereumHardfork::Berlin => SpecId::BERLIN,
336 EthereumHardfork::London => SpecId::LONDON,
337 EthereumHardfork::ArrowGlacier => SpecId::LONDON,
338 EthereumHardfork::GrayGlacier => SpecId::LONDON,
339 EthereumHardfork::Paris => SpecId::MERGE,
340 EthereumHardfork::Shanghai => SpecId::SHANGHAI,
341 EthereumHardfork::Cancun => SpecId::CANCUN,
342 EthereumHardfork::Prague => SpecId::PRAGUE,
343 EthereumHardfork::Osaka => SpecId::OSAKA,
344 EthereumHardfork::Bpo1
345 | EthereumHardfork::Bpo2
346 | EthereumHardfork::Bpo3
347 | EthereumHardfork::Bpo4
348 | EthereumHardfork::Bpo5 => SpecId::OSAKA,
349 EthereumHardfork::Amsterdam => SpecId::AMSTERDAM,
350 f => unreachable!("unimplemented {}", f),
351 }
352}
353
354#[cfg(feature = "optimism")]
356pub fn spec_id_from_optimism_hardfork(hardfork: OpHardfork) -> OpSpecId {
357 match hardfork {
358 OpHardfork::Bedrock => OpSpecId::BEDROCK,
359 OpHardfork::Regolith => OpSpecId::REGOLITH,
360 OpHardfork::Canyon => OpSpecId::CANYON,
361 OpHardfork::Ecotone => OpSpecId::ECOTONE,
362 OpHardfork::Fjord => OpSpecId::FJORD,
363 OpHardfork::Granite => OpSpecId::GRANITE,
364 OpHardfork::Holocene => OpSpecId::HOLOCENE,
365 OpHardfork::Isthmus => OpSpecId::ISTHMUS,
366 OpHardfork::Jovian => OpSpecId::JOVIAN,
367 OpHardfork::Karst => OpSpecId::KARST,
368 OpHardfork::Lagoon => OpSpecId::LAGOON,
369 f => unreachable!("unimplemented {}", f),
370 }
371}
372
373#[cfg(feature = "optimism")]
375pub fn eth_spec_id_from_optimism_hardfork(hardfork: OpHardfork) -> SpecId {
376 match hardfork {
377 OpHardfork::Bedrock | OpHardfork::Regolith => SpecId::MERGE,
378 OpHardfork::Canyon => SpecId::SHANGHAI,
379 OpHardfork::Ecotone | OpHardfork::Fjord | OpHardfork::Granite | OpHardfork::Holocene => {
380 SpecId::CANCUN
381 }
382 OpHardfork::Isthmus | OpHardfork::Jovian => SpecId::PRAGUE,
383 OpHardfork::Karst | OpHardfork::Lagoon => SpecId::OSAKA,
384 f => unreachable!("unimplemented {}", f),
385 }
386}
387
388pub const fn spec_id_from_tempo_hardfork(_: TempoHardfork) -> SpecId {
390 SpecId::OSAKA
391}
392
393pub trait FromEvmVersion: From<FoundryHardfork> {
395 fn from_evm_version(version: EvmVersion) -> Self;
396}
397
398pub trait ExecutionSpec: FromEvmVersion {
400 fn evm_version_name(&self) -> String;
402
403 fn from_network_hardfork(_: &str) -> Option<Self> {
405 None
406 }
407
408 fn from_foundry_hardfork(hardfork: FoundryHardfork) -> Option<Self>;
410
411 fn historical_hardfork(chain_id: u64, timestamp: u64) -> Option<FoundryHardfork> {
414 FoundryHardfork::from_chain_and_timestamp(chain_id, timestamp)
415 .filter(|&hardfork| Self::from_foundry_hardfork(hardfork).is_some())
416 }
417
418 fn fork_hardfork(
421 chain_id: u64,
422 timestamp: u64,
423 endpoint_hardfork: Option<FoundryHardfork>,
424 ) -> Option<FoundryHardfork> {
425 endpoint_hardfork
426 .filter(|&hardfork| Self::from_foundry_hardfork(hardfork).is_some())
427 .or_else(|| Self::historical_hardfork(chain_id, timestamp))
428 }
429
430 fn script_fork_hardfork(
433 chain_id: u64,
434 timestamp: u64,
435 endpoint_hardfork: Option<FoundryHardfork>,
436 _configured: Self,
437 ) -> Option<FoundryHardfork> {
438 Self::fork_hardfork(chain_id, timestamp, endpoint_hardfork)
439 }
440
441 fn reported_hardfork(self) -> Option<FoundryHardfork> {
444 None
445 }
446}
447
448impl FromEvmVersion for SpecId {
449 fn from_evm_version(version: EvmVersion) -> Self {
450 match version {
451 EvmVersion::Homestead => Self::HOMESTEAD,
452 EvmVersion::TangerineWhistle => Self::TANGERINE,
453 EvmVersion::SpuriousDragon => Self::SPURIOUS_DRAGON,
454 EvmVersion::Byzantium => Self::BYZANTIUM,
455 EvmVersion::Constantinople => Self::PETERSBURG,
456 EvmVersion::Petersburg => Self::PETERSBURG,
457 EvmVersion::Istanbul => Self::ISTANBUL,
458 EvmVersion::Berlin => Self::BERLIN,
459 EvmVersion::London => Self::LONDON,
460 EvmVersion::Paris => Self::MERGE,
461 EvmVersion::Shanghai => Self::SHANGHAI,
462 EvmVersion::Cancun => Self::CANCUN,
463 EvmVersion::Prague => Self::PRAGUE,
464 EvmVersion::Osaka => Self::OSAKA,
465 EvmVersion::Amsterdam => Self::AMSTERDAM,
466 }
467 }
468}
469
470impl ExecutionSpec for SpecId {
471 fn evm_version_name(&self) -> String {
473 self.to_string()
474 }
475
476 fn from_network_hardfork(hardfork: &str) -> Option<Self> {
478 EthereumHardfork::from_str(hardfork).ok().map(spec_id_from_ethereum_hardfork)
479 }
480
481 fn from_foundry_hardfork(hardfork: FoundryHardfork) -> Option<Self> {
483 match hardfork {
484 FoundryHardfork::Ethereum(hardfork) => Some(spec_id_from_ethereum_hardfork(hardfork)),
485 _ => None,
486 }
487 }
488
489 fn fork_hardfork(
490 _chain_id: u64,
491 _timestamp: u64,
492 _endpoint_hardfork: Option<FoundryHardfork>,
493 ) -> Option<FoundryHardfork> {
494 None
495 }
496
497 fn script_fork_hardfork(
499 chain_id: u64,
500 timestamp: u64,
501 endpoint_hardfork: Option<FoundryHardfork>,
502 configured: Self,
503 ) -> Option<FoundryHardfork> {
504 endpoint_hardfork
505 .filter(|&hardfork| Self::from_foundry_hardfork(hardfork).is_some())
506 .or_else(|| Self::historical_hardfork(chain_id, timestamp))
507 .filter(|&hardfork| {
508 Self::from_foundry_hardfork(hardfork).is_some_and(|spec| spec > configured)
509 })
510 }
511}
512
513#[cfg(feature = "optimism")]
514impl FromEvmVersion for OpSpecId {
515 fn from_evm_version(version: EvmVersion) -> Self {
516 match version {
517 EvmVersion::Homestead
518 | EvmVersion::TangerineWhistle
519 | EvmVersion::SpuriousDragon
520 | EvmVersion::Byzantium
521 | EvmVersion::Constantinople
522 | EvmVersion::Petersburg
523 | EvmVersion::Istanbul
524 | EvmVersion::Berlin
525 | EvmVersion::London
526 | EvmVersion::Paris => Self::BEDROCK,
527 EvmVersion::Shanghai => Self::CANYON,
528 EvmVersion::Cancun => Self::ECOTONE,
529 EvmVersion::Prague => Self::ISTHMUS,
530 EvmVersion::Osaka | EvmVersion::Amsterdam => Self::KARST,
531 }
532 }
533}
534
535#[cfg(feature = "optimism")]
536impl ExecutionSpec for OpSpecId {
537 fn evm_version_name(&self) -> String {
539 let name: &'static str = (*self).into();
540 name.to_string()
541 }
542
543 fn from_network_hardfork(hardfork: &str) -> Option<Self> {
545 OpHardfork::from_str(hardfork).ok().map(spec_id_from_optimism_hardfork)
546 }
547
548 fn from_foundry_hardfork(hardfork: FoundryHardfork) -> Option<Self> {
550 match hardfork {
551 FoundryHardfork::Optimism(hardfork) => Some(spec_id_from_optimism_hardfork(hardfork)),
552 _ => None,
553 }
554 }
555
556 fn fork_hardfork(
557 chain_id: u64,
558 timestamp: u64,
559 endpoint_hardfork: Option<FoundryHardfork>,
560 ) -> Option<FoundryHardfork> {
561 let endpoint_hardfork =
562 endpoint_hardfork.filter(|&hardfork| Self::from_foundry_hardfork(hardfork).is_some());
563 if endpoint_hardfork.is_some() {
564 return endpoint_hardfork;
565 }
566
567 #[cfg(not(feature = "base"))]
570 if matches!(
571 Chain::from_id(chain_id).named(),
572 Some(NamedChain::Base | NamedChain::BaseSepolia)
573 ) {
574 return None;
575 }
576
577 Self::historical_hardfork(chain_id, timestamp)
578 }
579
580 fn historical_hardfork(chain_id: u64, timestamp: u64) -> Option<FoundryHardfork> {
581 OpHardfork::from_chain_and_timestamp(Chain::from_id(chain_id), timestamp)
582 .map(FoundryHardfork::Optimism)
583 }
584}
585
586impl FromEvmVersion for TempoHardfork {
587 fn from_evm_version(_: EvmVersion) -> Self {
588 latest_active_tempo_hardfork()
589 }
590}
591
592impl ExecutionSpec for TempoHardfork {
593 fn evm_version_name(&self) -> String {
595 self.to_string()
596 }
597
598 fn from_network_hardfork(hardfork: &str) -> Option<Self> {
600 Self::from_str(hardfork).ok()
601 }
602
603 fn from_foundry_hardfork(hardfork: FoundryHardfork) -> Option<Self> {
605 match hardfork {
606 FoundryHardfork::Tempo(hardfork) => Some(hardfork),
607 _ => None,
608 }
609 }
610
611 fn reported_hardfork(self) -> Option<FoundryHardfork> {
612 Some(self.into())
613 }
614}
615
616#[cfg(feature = "monad")]
617impl FromEvmVersion for MonadHardfork {
618 fn from_evm_version(_: EvmVersion) -> Self {
619 Self::default()
620 }
621}
622
623#[cfg(feature = "monad")]
624impl ExecutionSpec for MonadHardfork {
625 fn evm_version_name(&self) -> String {
627 self.to_string()
628 }
629
630 fn from_network_hardfork(hardfork: &str) -> Option<Self> {
632 Self::from_str(hardfork).ok()
633 }
634
635 fn from_foundry_hardfork(hardfork: FoundryHardfork) -> Option<Self> {
637 match hardfork {
638 #[cfg(feature = "monad")]
639 FoundryHardfork::Monad(hardfork) => Some(hardfork),
640 _ => None,
641 }
642 }
643
644 fn reported_hardfork(self) -> Option<FoundryHardfork> {
645 Some(self.into())
646 }
647}
648
649#[cfg(feature = "base")]
650impl FromEvmVersion for BaseSpecId {
651 fn from_evm_version(version: EvmVersion) -> Self {
652 let upgrade = match version {
653 EvmVersion::Homestead
654 | EvmVersion::TangerineWhistle
655 | EvmVersion::SpuriousDragon
656 | EvmVersion::Byzantium
657 | EvmVersion::Constantinople
658 | EvmVersion::Petersburg
659 | EvmVersion::Istanbul
660 | EvmVersion::Berlin
661 | EvmVersion::London
662 | EvmVersion::Paris => BaseUpgrade::Bedrock,
663 EvmVersion::Shanghai => BaseUpgrade::Canyon,
664 EvmVersion::Cancun => BaseUpgrade::Ecotone,
665 EvmVersion::Prague => BaseUpgrade::Isthmus,
666 EvmVersion::Osaka | EvmVersion::Amsterdam => BaseUpgrade::Azul,
667 };
668 Self::new(upgrade)
669 }
670}
671
672#[cfg(feature = "base")]
673impl ExecutionSpec for BaseSpecId {
674 fn evm_version_name(&self) -> String {
676 self.to_string()
677 }
678
679 fn from_network_hardfork(hardfork: &str) -> Option<Self> {
681 Self::from_str(hardfork).ok()
682 }
683
684 fn from_foundry_hardfork(hardfork: FoundryHardfork) -> Option<Self> {
686 match hardfork {
687 FoundryHardfork::Base(hardfork) => Some(Self::new(hardfork)),
688 _ => None,
689 }
690 }
691
692 fn reported_hardfork(self) -> Option<FoundryHardfork> {
693 Some(FoundryHardfork::Base(self.upgrade()))
694 }
695}
696
697pub fn evm_spec_id<SPEC: FromEvmVersion>(evm_version: EvmVersion) -> SPEC {
699 SPEC::from_evm_version(evm_version)
700}
701
702pub fn latest_active_tempo_hardfork() -> TempoHardfork {
704 let now = SystemTime::now()
706 .duration_since(UNIX_EPOCH)
707 .map(|duration| duration.as_secs())
708 .unwrap_or(u64::MAX);
709 TempoHardfork::from_chain_and_timestamp(4217, now)
710 .or_else(|| TempoHardfork::from_chain_and_timestamp(42431, now))
711 .unwrap_or_default()
712}
713
714pub fn evm_spec_id_from_str<SPEC: ExecutionSpec>(evm_version: &str) -> Option<SPEC> {
716 let evm_version = evm_version.trim();
717
718 if let Ok(version) = EvmVersion::from_str(evm_version) {
719 return Some(evm_spec_id(version));
720 }
721
722 if let Some(spec) = SPEC::from_network_hardfork(evm_version) {
723 return Some(spec);
724 }
725
726 FoundryHardfork::from_str(evm_version).ok().and_then(SPEC::from_foundry_hardfork)
727}
728
729pub fn ethereum_hardfork_from_block_tag(block: impl Into<BlockNumberOrTag>) -> EthereumHardfork {
731 let num = match block.into() {
732 BlockNumberOrTag::Earliest => 0,
733 BlockNumberOrTag::Number(num) => num,
734 _ => u64::MAX,
735 };
736
737 EthereumHardfork::from_mainnet_block_number(num)
738}
739
740#[cfg(test)]
741mod tests {
742 use super::*;
743 use alloy_hardforks::ethereum::mainnet::*;
744 use tempo_hardfork::constants::{mainnet::*, moderato::*};
745
746 #[test]
747 fn test_ethereum_spec_id_mapping() {
748 for (hardfork, expected) in [
749 (EthereumHardfork::Frontier, SpecId::FRONTIER),
750 (EthereumHardfork::Homestead, SpecId::HOMESTEAD),
751 (EthereumHardfork::Dao, SpecId::HOMESTEAD),
752 (EthereumHardfork::Tangerine, SpecId::TANGERINE),
753 (EthereumHardfork::SpuriousDragon, SpecId::SPURIOUS_DRAGON),
754 (EthereumHardfork::Byzantium, SpecId::BYZANTIUM),
755 (EthereumHardfork::Constantinople, SpecId::PETERSBURG),
756 (EthereumHardfork::Petersburg, SpecId::PETERSBURG),
757 (EthereumHardfork::Istanbul, SpecId::ISTANBUL),
758 (EthereumHardfork::MuirGlacier, SpecId::ISTANBUL),
759 (EthereumHardfork::Berlin, SpecId::BERLIN),
760 (EthereumHardfork::London, SpecId::LONDON),
761 (EthereumHardfork::ArrowGlacier, SpecId::LONDON),
762 (EthereumHardfork::GrayGlacier, SpecId::LONDON),
763 (EthereumHardfork::Paris, SpecId::MERGE),
764 (EthereumHardfork::Shanghai, SpecId::SHANGHAI),
765 (EthereumHardfork::Cancun, SpecId::CANCUN),
766 (EthereumHardfork::Prague, SpecId::PRAGUE),
767 (EthereumHardfork::Osaka, SpecId::OSAKA),
768 (EthereumHardfork::Bpo1, SpecId::OSAKA),
769 (EthereumHardfork::Bpo2, SpecId::OSAKA),
770 (EthereumHardfork::Bpo3, SpecId::OSAKA),
771 (EthereumHardfork::Bpo4, SpecId::OSAKA),
772 (EthereumHardfork::Bpo5, SpecId::OSAKA),
773 (EthereumHardfork::Amsterdam, SpecId::AMSTERDAM),
774 ] {
775 assert_eq!(spec_id_from_ethereum_hardfork(hardfork), expected, "{hardfork}");
776 }
777 }
778
779 #[test]
780 fn test_tempo_spec_id_mapping() {
781 assert_eq!(spec_id_from_tempo_hardfork(TempoHardfork::Genesis), SpecId::OSAKA);
782 assert_eq!(spec_id_from_tempo_hardfork(TempoHardfork::T8), SpecId::OSAKA);
783 }
784
785 #[test]
786 fn test_tempo_evm_version_defaults_to_latest_active_hardfork() {
787 let latest = latest_active_tempo_hardfork();
788 assert_eq!(evm_spec_id::<TempoHardfork>(EvmVersion::Osaka), latest);
789 }
790
791 #[test]
792 #[cfg(feature = "monad")]
793 fn test_monad_hardfork_parsing() {
794 assert_eq!(
795 "monad:MonadNine".parse::<FoundryHardfork>().unwrap(),
796 FoundryHardfork::Monad(MonadHardfork::MonadNine)
797 );
798 assert_eq!(
799 "monad:MonadTen".parse::<FoundryHardfork>().unwrap(),
800 FoundryHardfork::Monad(MonadHardfork::MonadTen)
801 );
802 assert_eq!(
803 "m:MonadNext".parse::<FoundryHardfork>().unwrap(),
804 FoundryHardfork::Monad(MonadHardfork::MonadNext)
805 );
806 }
807
808 #[test]
809 fn test_hardfork_namespace_parsing() {
810 let cases = [
811 ("prague", Ok(FoundryHardfork::Ethereum(EthereumHardfork::Prague))),
812 ("eth:prague", Ok(FoundryHardfork::Ethereum(EthereumHardfork::Prague))),
813 ("ethereum:Osaka", Ok(FoundryHardfork::Ethereum(EthereumHardfork::Osaka))),
814 (" ETH : Prague ", Ok(FoundryHardfork::Ethereum(EthereumHardfork::Prague))),
815 ("t:T5", Ok(FoundryHardfork::Tempo(TempoHardfork::T5))),
816 ("tempo:t5", Ok(FoundryHardfork::Tempo(TempoHardfork::T5))),
817 #[cfg(feature = "optimism")]
818 ("op:Isthmus", Ok(FoundryHardfork::Optimism(OpHardfork::Isthmus))),
819 #[cfg(feature = "optimism")]
820 ("optimism:jovian", Ok(FoundryHardfork::Optimism(OpHardfork::Jovian))),
821 #[cfg(not(feature = "optimism"))]
822 ("op:prague", Err("hardfork namespace 'op' requires the `optimism` feature")),
823 #[cfg(feature = "base")]
824 ("base:azul", Ok(FoundryHardfork::Base(BaseUpgrade::Azul))),
825 #[cfg(not(feature = "base"))]
826 ("base:prague", Err("hardfork namespace 'base' requires the `base` feature")),
827 #[cfg(feature = "monad")]
828 ("m:MonadTen", Ok(FoundryHardfork::Monad(MonadHardfork::MonadTen))),
829 #[cfg(feature = "monad")]
830 ("monad:monadnine", Ok(FoundryHardfork::Monad(MonadHardfork::MonadNine))),
831 #[cfg(not(feature = "monad"))]
832 ("monad:prague", Err("hardfork namespace 'monad' requires the `monad` feature")),
833 ("foo:prague", Err("unknown hardfork namespace 'foo'")),
834 ("Foo:Prague", Err("unknown hardfork namespace 'foo'")),
835 ];
836
837 for (input, expected) in cases {
838 assert_eq!(
839 input.parse::<FoundryHardfork>(),
840 expected.map_err(str::to_string),
841 "{input}"
842 );
843 }
844 }
845
846 #[test]
847 #[cfg(feature = "monad")]
848 fn test_monad_hardfork_serialization() {
849 assert_eq!(
850 String::from(FoundryHardfork::Monad(MonadHardfork::MonadEight)),
851 "monad:MonadEight"
852 );
853 assert_eq!(FoundryHardfork::Monad(MonadHardfork::MonadEight).namespace(), Some("monad"));
854 assert_eq!(FoundryHardfork::Monad(MonadHardfork::MonadEight).name(), "MonadEight");
855 }
856
857 #[test]
858 #[cfg(feature = "monad")]
859 fn test_monad_hardfork_spec_id_mapping() {
860 assert_eq!(SpecId::from(FoundryHardfork::Monad(MonadHardfork::MonadEight)), SpecId::PRAGUE);
861 assert_eq!(SpecId::from(FoundryHardfork::Monad(MonadHardfork::MonadNine)), SpecId::OSAKA);
862 assert_eq!(SpecId::from(FoundryHardfork::Monad(MonadHardfork::MonadTen)), SpecId::OSAKA);
863 assert_eq!(
864 MonadHardfork::from(FoundryHardfork::Monad(MonadHardfork::MonadNext)),
865 MonadHardfork::MonadNext
866 );
867 }
868
869 #[test]
870 fn test_tempo_hardfork_from_chain_and_timestamp() {
871 assert_eq!(
872 FoundryHardfork::from_chain_and_timestamp(4217, u64::MAX),
873 Some(FoundryHardfork::Tempo(TempoHardfork::T12))
874 );
875 assert_eq!(
876 FoundryHardfork::from_chain_and_timestamp(42431, u64::MAX),
877 Some(FoundryHardfork::Tempo(TempoHardfork::T12))
878 );
879
880 assert_eq!(
881 FoundryHardfork::from_chain_and_timestamp(MAINNET_CHAIN_ID, MAINNET_T6_TIMESTAMP - 1),
882 Some(FoundryHardfork::Tempo(TempoHardfork::T5))
883 );
884 assert_eq!(
885 FoundryHardfork::from_chain_and_timestamp(MAINNET_CHAIN_ID, MAINNET_T6_TIMESTAMP),
886 Some(FoundryHardfork::Tempo(TempoHardfork::T6))
887 );
888 assert_eq!(
889 FoundryHardfork::from_chain_and_timestamp(MAINNET_CHAIN_ID, MAINNET_T7_TIMESTAMP - 1),
890 Some(FoundryHardfork::Tempo(TempoHardfork::T6))
891 );
892 assert_eq!(
893 FoundryHardfork::from_chain_and_timestamp(MAINNET_CHAIN_ID, MAINNET_T7_TIMESTAMP),
894 Some(FoundryHardfork::Tempo(TempoHardfork::T7))
895 );
896 assert_eq!(
897 FoundryHardfork::from_chain_and_timestamp(MAINNET_CHAIN_ID, MAINNET_T8_TIMESTAMP - 1),
898 Some(FoundryHardfork::Tempo(TempoHardfork::T7))
899 );
900 assert_eq!(
901 FoundryHardfork::from_chain_and_timestamp(MAINNET_CHAIN_ID, MAINNET_T8_TIMESTAMP),
902 Some(FoundryHardfork::Tempo(TempoHardfork::T8))
903 );
904 assert_eq!(
905 FoundryHardfork::from_chain_and_timestamp(MAINNET_CHAIN_ID, MAINNET_T9_TIMESTAMP - 1),
906 Some(FoundryHardfork::Tempo(TempoHardfork::T8))
907 );
908 assert_eq!(
909 FoundryHardfork::from_chain_and_timestamp(MAINNET_CHAIN_ID, MAINNET_T9_TIMESTAMP),
910 Some(FoundryHardfork::Tempo(TempoHardfork::T9))
911 );
912 assert_eq!(
913 FoundryHardfork::from_chain_and_timestamp(MODERATO_CHAIN_ID, MODERATO_T6_TIMESTAMP - 1),
914 Some(FoundryHardfork::Tempo(TempoHardfork::T5))
915 );
916 assert_eq!(
917 FoundryHardfork::from_chain_and_timestamp(MODERATO_CHAIN_ID, MODERATO_T6_TIMESTAMP),
918 Some(FoundryHardfork::Tempo(TempoHardfork::T6))
919 );
920 assert_eq!(
921 FoundryHardfork::from_chain_and_timestamp(MODERATO_CHAIN_ID, MODERATO_T7_TIMESTAMP - 1),
922 Some(FoundryHardfork::Tempo(TempoHardfork::T6))
923 );
924 assert_eq!(
925 FoundryHardfork::from_chain_and_timestamp(MODERATO_CHAIN_ID, MODERATO_T7_TIMESTAMP),
926 Some(FoundryHardfork::Tempo(TempoHardfork::T7))
927 );
928 assert_eq!(
929 FoundryHardfork::from_chain_and_timestamp(MODERATO_CHAIN_ID, MODERATO_T8_TIMESTAMP - 1),
930 Some(FoundryHardfork::Tempo(TempoHardfork::T7))
931 );
932 assert_eq!(
933 FoundryHardfork::from_chain_and_timestamp(MODERATO_CHAIN_ID, MODERATO_T8_TIMESTAMP),
934 Some(FoundryHardfork::Tempo(TempoHardfork::T8))
935 );
936 assert_eq!(
937 FoundryHardfork::from_chain_and_timestamp(MODERATO_CHAIN_ID, MODERATO_T9_TIMESTAMP - 1),
938 Some(FoundryHardfork::Tempo(TempoHardfork::T8))
939 );
940 assert_eq!(
941 FoundryHardfork::from_chain_and_timestamp(MODERATO_CHAIN_ID, MODERATO_T9_TIMESTAMP),
942 Some(FoundryHardfork::Tempo(TempoHardfork::T9))
943 );
944 }
945
946 #[test]
947 #[cfg(feature = "monad")]
948 fn test_monad_hardfork_from_chain_and_timestamp() {
949 let mainnet_ten = MonadHardfork::MonadTen.mainnet_activation_timestamp().unwrap();
950 let testnet_ten = MonadHardfork::MonadTen.testnet_activation_timestamp().unwrap();
951
952 assert_eq!(
953 FoundryHardfork::from_chain_and_timestamp(
954 monad_revm::MONAD_MAINNET_CHAIN_ID,
955 1_763_648_999,
956 ),
957 Some(FoundryHardfork::Monad(MonadHardfork::MonadEight))
958 );
959 assert_eq!(
960 FoundryHardfork::from_chain_and_timestamp(
961 monad_revm::MONAD_MAINNET_CHAIN_ID,
962 1_773_930_600,
963 ),
964 Some(FoundryHardfork::Monad(MonadHardfork::MonadNine))
965 );
966 assert_eq!(
967 FoundryHardfork::from_chain_and_timestamp(
968 monad_revm::MONAD_MAINNET_CHAIN_ID,
969 mainnet_ten - 1,
970 ),
971 Some(FoundryHardfork::Monad(MonadHardfork::MonadNine))
972 );
973 assert_eq!(
974 FoundryHardfork::from_chain_and_timestamp(
975 monad_revm::MONAD_MAINNET_CHAIN_ID,
976 mainnet_ten,
977 ),
978 Some(FoundryHardfork::Monad(MonadHardfork::MonadTen))
979 );
980 assert_eq!(
981 FoundryHardfork::from_chain_and_timestamp(
982 monad_revm::MONAD_TESTNET_CHAIN_ID,
983 1_773_152_999,
984 ),
985 Some(FoundryHardfork::Monad(MonadHardfork::MonadEight))
986 );
987 assert_eq!(
988 FoundryHardfork::from_chain_and_timestamp(
989 monad_revm::MONAD_TESTNET_CHAIN_ID,
990 1_773_153_000,
991 ),
992 Some(FoundryHardfork::Monad(MonadHardfork::MonadNine))
993 );
994 assert_eq!(
995 FoundryHardfork::from_chain_and_timestamp(
996 monad_revm::MONAD_TESTNET_CHAIN_ID,
997 testnet_ten - 1,
998 ),
999 Some(FoundryHardfork::Monad(MonadHardfork::MonadNine))
1000 );
1001 assert_eq!(
1002 FoundryHardfork::from_chain_and_timestamp(
1003 monad_revm::MONAD_TESTNET_CHAIN_ID,
1004 testnet_ten,
1005 ),
1006 Some(FoundryHardfork::Monad(MonadHardfork::MonadTen))
1007 );
1008 }
1009
1010 #[test]
1011 fn test_evm_spec_id_from_str_parses_network_hardforks() {
1012 assert_eq!(evm_spec_id_from_str::<TempoHardfork>("T3"), Some(TempoHardfork::T3));
1013 assert_eq!(evm_spec_id_from_str::<TempoHardfork>("tempo:T2"), Some(TempoHardfork::T2));
1014 assert_eq!(evm_spec_id_from_str::<TempoHardfork>("tempo:T7"), Some(TempoHardfork::T7));
1015 assert_eq!(evm_spec_id_from_str::<TempoHardfork>("tempo:T8"), Some(TempoHardfork::T8));
1016 assert_eq!(evm_spec_id_from_str::<TempoHardfork>("tempo:T13"), Some(TempoHardfork::T13));
1017 assert_eq!(evm_spec_id_from_str::<TempoHardfork>("T13"), Some(TempoHardfork::T13));
1018 assert_eq!(evm_spec_id_from_str::<TempoHardfork>("tempo:T14"), Some(TempoHardfork::T14));
1019 assert_eq!(evm_spec_id_from_str::<TempoHardfork>("T14"), Some(TempoHardfork::T14));
1020 assert_eq!(evm_spec_id_from_str::<TempoHardfork>("ethereum:prague"), None);
1021
1022 #[cfg(feature = "monad")]
1023 {
1024 assert_eq!(
1025 evm_spec_id_from_str::<MonadHardfork>("MonadNine"),
1026 Some(MonadHardfork::MonadNine)
1027 );
1028 assert_eq!(
1029 evm_spec_id_from_str::<MonadHardfork>("MonadTen"),
1030 Some(MonadHardfork::MonadTen)
1031 );
1032 assert_eq!(
1033 evm_spec_id_from_str::<MonadHardfork>("monad:MonadEight"),
1034 Some(MonadHardfork::MonadEight)
1035 );
1036 assert_eq!(evm_spec_id_from_str::<MonadHardfork>("tempo:T3"), None);
1037 }
1038 }
1039
1040 #[test]
1041 fn test_hardfork_from_block_tag_numbers() {
1042 assert_eq!(
1043 ethereum_hardfork_from_block_tag(MAINNET_HOMESTEAD_BLOCK - 1),
1044 EthereumHardfork::Frontier
1045 );
1046 assert_eq!(
1047 ethereum_hardfork_from_block_tag(MAINNET_LONDON_BLOCK + 1),
1048 EthereumHardfork::London
1049 );
1050 }
1051
1052 #[test]
1053 fn test_from_chain_and_timestamp_ethereum_mainnet() {
1054 assert_eq!(
1055 FoundryHardfork::from_chain_and_timestamp(1, 0),
1056 Some(FoundryHardfork::Ethereum(EthereumHardfork::Frontier))
1057 );
1058 assert_eq!(
1060 FoundryHardfork::from_chain_and_timestamp(1, 1_681_338_455),
1061 Some(FoundryHardfork::Ethereum(EthereumHardfork::Shanghai))
1062 );
1063 }
1064
1065 #[test]
1066 fn test_from_chain_and_timestamp_sepolia() {
1067 let sepolia_chain_id = 11155111;
1068 assert!(FoundryHardfork::from_chain_and_timestamp(sepolia_chain_id, u64::MAX).is_some());
1069 }
1070
1071 #[test]
1072 fn test_from_chain_and_timestamp_unknown_chain() {
1073 assert_eq!(FoundryHardfork::from_chain_and_timestamp(999999, 0), None);
1074 }
1075
1076 #[cfg(feature = "base")]
1077 mod base {
1078 use super::*;
1079 use base_common_genesis::UpgradeConfig;
1080
1081 #[test]
1082 fn test_base_hardfork_serialization() {
1083 assert_eq!(String::from(FoundryHardfork::Base(BaseUpgrade::Azul)), "base:Azul");
1084 assert_eq!(FoundryHardfork::Base(BaseUpgrade::Azul).namespace(), Some("base"));
1085 assert_eq!(FoundryHardfork::Base(BaseUpgrade::Azul).name(), "Azul");
1086 }
1087
1088 #[test]
1089 fn test_base_hardfork_spec_id_mapping() {
1090 assert_eq!(SpecId::from(FoundryHardfork::Base(BaseUpgrade::Azul)), SpecId::OSAKA);
1091 assert_eq!(SpecId::from(FoundryHardfork::Base(BaseUpgrade::Jovian)), SpecId::PRAGUE);
1092 assert_eq!(
1093 BaseSpecId::from(FoundryHardfork::Base(BaseUpgrade::Azul)),
1094 BaseSpecId::new(BaseUpgrade::Azul)
1095 );
1096 }
1097
1098 #[test]
1099 fn test_base_hardfork_parsing() {
1100 assert_eq!(
1101 "base:Azul".parse::<FoundryHardfork>().unwrap(),
1102 FoundryHardfork::Base(BaseUpgrade::Azul)
1103 );
1104 assert_eq!(
1105 "base:Beryl".parse::<FoundryHardfork>().unwrap(),
1106 FoundryHardfork::Base(BaseUpgrade::Beryl)
1107 );
1108 }
1109
1110 #[test]
1111 fn test_base_hardfork_from_chain_and_timestamp() {
1112 let mainnet_config = UpgradeConfig::BASE_MAINNET;
1113 let sepolia_config = UpgradeConfig::BASE_SEPOLIA;
1114 assert_eq!(
1115 FoundryHardfork::from_chain_and_timestamp(8453, u64::MAX),
1116 Some(FoundryHardfork::Base(BaseUpgrade::Beryl))
1117 );
1118 assert_eq!(
1119 FoundryHardfork::from_chain_and_timestamp(84532, u64::MAX),
1120 Some(FoundryHardfork::Base(BaseUpgrade::Beryl))
1121 );
1122 assert_eq!(
1123 FoundryHardfork::from_chain_and_timestamp(8453, mainnet_config.base.azul.unwrap()),
1124 Some(FoundryHardfork::Base(BaseUpgrade::Azul))
1125 );
1126 assert_eq!(
1127 FoundryHardfork::from_chain_and_timestamp(84532, sepolia_config.base.azul.unwrap()),
1128 Some(FoundryHardfork::Base(BaseUpgrade::Azul))
1129 );
1130 assert_eq!(
1131 FoundryHardfork::from_chain_and_timestamp(
1132 8453,
1133 mainnet_config.base.azul.unwrap() - 1
1134 ),
1135 Some(FoundryHardfork::Base(BaseUpgrade::Jovian))
1136 );
1137 assert_eq!(
1138 FoundryHardfork::from_chain_and_timestamp(
1139 84532,
1140 sepolia_config.base.azul.unwrap() - 1
1141 ),
1142 Some(FoundryHardfork::Base(BaseUpgrade::Jovian))
1143 );
1144 }
1145
1146 #[test]
1147 fn test_evm_spec_id_from_str_parses_base_upgrades() {
1148 assert_eq!(
1149 evm_spec_id_from_str::<BaseSpecId>("Azul"),
1150 Some(BaseSpecId::new(BaseUpgrade::Azul))
1151 );
1152 assert_eq!(
1153 evm_spec_id_from_str::<BaseSpecId>("base:Beryl"),
1154 Some(BaseSpecId::new(BaseUpgrade::Beryl))
1155 );
1156 assert_eq!(evm_spec_id_from_str::<BaseSpecId>("tempo:T3"), None);
1157 }
1158 }
1159
1160 #[cfg(feature = "optimism")]
1161 mod optimism {
1162 use super::*;
1163
1164 #[cfg(feature = "base")]
1165 use base_common_genesis::UpgradeConfig;
1166
1167 #[test]
1168 fn test_optimism_spec_id_mapping() {
1169 assert_eq!(spec_id_from_optimism_hardfork(OpHardfork::Bedrock), OpSpecId::BEDROCK);
1170 assert_eq!(spec_id_from_optimism_hardfork(OpHardfork::Regolith), OpSpecId::REGOLITH);
1171
1172 assert_eq!(spec_id_from_optimism_hardfork(OpHardfork::Holocene), OpSpecId::HOLOCENE);
1174 assert_eq!(spec_id_from_optimism_hardfork(OpHardfork::Karst), OpSpecId::KARST);
1175 assert_eq!(spec_id_from_optimism_hardfork(OpHardfork::Lagoon), OpSpecId::LAGOON);
1176 assert_eq!(eth_spec_id_from_optimism_hardfork(OpHardfork::Jovian), SpecId::PRAGUE);
1177 assert_eq!(eth_spec_id_from_optimism_hardfork(OpHardfork::Karst), SpecId::OSAKA);
1178 assert_eq!(eth_spec_id_from_optimism_hardfork(OpHardfork::Lagoon), SpecId::OSAKA);
1179 assert_eq!(evm_spec_id::<OpSpecId>(EvmVersion::Osaka), OpSpecId::KARST);
1180 }
1181
1182 #[test]
1183 fn test_from_chain_and_timestamp_op_mainnet() {
1184 let op_chain_id = 10;
1185 assert!(matches!(
1186 FoundryHardfork::from_chain_and_timestamp(op_chain_id, u64::MAX),
1187 Some(FoundryHardfork::Optimism(_))
1188 ));
1189 }
1190
1191 #[test]
1192 #[cfg(feature = "base")]
1193 fn test_base_chain_historical_hardfork_is_execution_family_specific() {
1194 let chains = [
1195 (8453, UpgradeConfig::BASE_MAINNET.ecotone_time.unwrap()),
1196 (84532, UpgradeConfig::BASE_SEPOLIA.ecotone_time.unwrap()),
1197 ];
1198
1199 for (chain_id, timestamp) in chains {
1200 assert_eq!(
1201 FoundryHardfork::from_chain_and_timestamp(chain_id, timestamp),
1202 Some(FoundryHardfork::Base(BaseUpgrade::Ecotone))
1203 );
1204 assert_eq!(
1205 BaseSpecId::historical_hardfork(chain_id, timestamp),
1206 Some(FoundryHardfork::Base(BaseUpgrade::Ecotone))
1207 );
1208 assert_eq!(
1209 OpSpecId::historical_hardfork(chain_id, timestamp),
1210 Some(FoundryHardfork::Optimism(OpHardfork::Ecotone))
1211 );
1212 assert_eq!(
1213 OpSpecId::fork_hardfork(chain_id, timestamp, None),
1214 Some(FoundryHardfork::Optimism(OpHardfork::Ecotone))
1215 );
1216 }
1217 }
1218
1219 #[test]
1222 #[cfg(not(feature = "base"))]
1223 fn test_from_chain_and_timestamp_base() {
1224 let base_chain_id = 8453;
1225 assert!(matches!(
1226 FoundryHardfork::from_chain_and_timestamp(base_chain_id, u64::MAX),
1227 Some(FoundryHardfork::Optimism(_))
1228 ));
1229 }
1230 }
1231}