1use std::{
7 str::FromStr,
8 time::{SystemTime, UNIX_EPOCH},
9};
10
11use alloy_chains::Chain;
12use alloy_rpc_types::BlockNumberOrTag;
13use foundry_compilers::artifacts::EvmVersion;
14#[cfg(feature = "optimism")]
15use op_revm::OpSpecId;
16use revm::primitives::hardfork::SpecId;
17use serde::{Deserialize, Serialize};
18
19pub use alloy_hardforks::EthereumHardfork;
20#[cfg(feature = "optimism")]
21pub use alloy_op_hardforks::OpHardfork;
22pub use tempo_hardfork::TempoHardfork;
23
24#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize)]
25#[serde(into = "String")]
26pub enum FoundryHardfork {
27 Ethereum(EthereumHardfork),
28 #[cfg(feature = "optimism")]
29 Optimism(OpHardfork),
30 Tempo(TempoHardfork),
31}
32
33impl From<FoundryHardfork> for String {
34 fn from(fork: FoundryHardfork) -> Self {
35 match fork {
36 FoundryHardfork::Ethereum(h) => format!("{h}"),
37 #[cfg(feature = "optimism")]
38 FoundryHardfork::Optimism(h) => format!("optimism:{h}"),
39 FoundryHardfork::Tempo(h) => format!("tempo:{h}"),
40 }
41 }
42}
43
44impl<'de> Deserialize<'de> for FoundryHardfork {
45 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
46 where
47 D: serde::Deserializer<'de>,
48 {
49 let s = String::deserialize(deserializer)?;
50 Self::from_str(&s).map_err(serde::de::Error::custom)
51 }
52}
53
54impl FromStr for FoundryHardfork {
55 type Err = String;
56
57 fn from_str(s: &str) -> Result<Self, Self::Err> {
58 let raw = s.trim();
59
60 let Some((ns, fork_raw)) = raw.split_once(':') else {
61 return EthereumHardfork::from_str(raw)
62 .map(Self::Ethereum)
63 .map_err(|_| format!("unknown ethereum hardfork '{raw}'"));
64 };
65
66 let ns = ns.trim().to_ascii_lowercase();
67 let fork = fork_raw.trim().to_ascii_lowercase().replace(['-', ' '], "_");
68
69 match ns.as_str() {
70 "eth" | "ethereum" => EthereumHardfork::from_str(&fork)
71 .map(Self::Ethereum)
72 .map_err(|_| format!("unknown ethereum hardfork '{fork_raw}'")),
73
74 #[cfg(feature = "optimism")]
75 "op" | "optimism" => OpHardfork::from_str(&fork)
76 .map(Self::Optimism)
77 .map_err(|_| format!("unknown optimism hardfork '{fork_raw}'")),
78
79 "t" | "tempo" => TempoHardfork::from_str(&fork)
80 .map(Self::Tempo)
81 .map_err(|_| format!("unknown tempo hardfork '{fork_raw}'")),
82 _ => EthereumHardfork::from_str(&fork)
83 .map(Self::Ethereum)
84 .map_err(|_| format!("unknown hardfork '{raw}'")),
85 }
86 }
87}
88
89impl FoundryHardfork {
90 pub const fn ethereum(h: EthereumHardfork) -> Self {
91 Self::Ethereum(h)
92 }
93
94 #[cfg(feature = "optimism")]
95 pub const fn optimism(h: OpHardfork) -> Self {
96 Self::Optimism(h)
97 }
98
99 pub const fn tempo(h: TempoHardfork) -> Self {
100 Self::Tempo(h)
101 }
102
103 pub fn name(&self) -> String {
105 match self {
106 Self::Ethereum(h) => format!("{h}"),
107 #[cfg(feature = "optimism")]
108 Self::Optimism(h) => format!("{h}"),
109 Self::Tempo(h) => format!("{h}"),
110 }
111 }
112
113 pub const fn namespace(&self) -> Option<&'static str> {
117 match self {
118 Self::Ethereum(_) => None,
119 #[cfg(feature = "optimism")]
120 Self::Optimism(_) => Some("optimism"),
121 Self::Tempo(_) => Some("tempo"),
122 }
123 }
124
125 pub fn from_chain_and_timestamp(chain_id: u64, timestamp: u64) -> Option<Self> {
129 let chain = Chain::from_id(chain_id);
130 if let Some(fork) = EthereumHardfork::from_chain_and_timestamp(chain, timestamp) {
131 return Some(Self::Ethereum(fork));
132 }
133 #[cfg(feature = "optimism")]
134 if let Some(fork) = OpHardfork::from_chain_and_timestamp(chain, timestamp) {
135 return Some(Self::Optimism(fork));
136 }
137 TempoHardfork::from_chain_and_timestamp(chain_id, timestamp).map(Self::Tempo)
138 }
139}
140
141impl From<EthereumHardfork> for FoundryHardfork {
142 fn from(value: EthereumHardfork) -> Self {
143 Self::Ethereum(value)
144 }
145}
146
147impl From<FoundryHardfork> for EthereumHardfork {
148 fn from(fork: FoundryHardfork) -> Self {
149 match fork {
150 FoundryHardfork::Ethereum(hardfork) => hardfork,
151 _ => Self::default(),
152 }
153 }
154}
155
156#[cfg(feature = "optimism")]
157impl From<OpHardfork> for FoundryHardfork {
158 fn from(value: OpHardfork) -> Self {
159 Self::Optimism(value)
160 }
161}
162
163#[cfg(feature = "optimism")]
164impl From<FoundryHardfork> for OpHardfork {
165 fn from(fork: FoundryHardfork) -> Self {
166 match fork {
167 FoundryHardfork::Optimism(hardfork) => hardfork,
168 _ => Self::default(),
169 }
170 }
171}
172
173impl From<TempoHardfork> for FoundryHardfork {
174 fn from(value: TempoHardfork) -> Self {
175 Self::Tempo(value)
176 }
177}
178
179impl From<FoundryHardfork> for TempoHardfork {
180 fn from(fork: FoundryHardfork) -> Self {
181 match fork {
182 FoundryHardfork::Tempo(hardfork) => hardfork,
183 _ => Self::default(),
184 }
185 }
186}
187
188impl From<FoundryHardfork> for SpecId {
189 fn from(fork: FoundryHardfork) -> Self {
190 match fork {
191 FoundryHardfork::Ethereum(hardfork) => spec_id_from_ethereum_hardfork(hardfork),
192 #[cfg(feature = "optimism")]
193 FoundryHardfork::Optimism(hardfork) => eth_spec_id_from_optimism_hardfork(hardfork),
194 FoundryHardfork::Tempo(hardfork) => spec_id_from_tempo_hardfork(hardfork),
195 }
196 }
197}
198
199#[cfg(feature = "optimism")]
200impl From<FoundryHardfork> for OpSpecId {
201 fn from(fork: FoundryHardfork) -> Self {
202 match fork {
203 FoundryHardfork::Optimism(hardfork) => spec_id_from_optimism_hardfork(hardfork),
204 _ => Self::default(),
205 }
206 }
207}
208
209pub fn spec_id_from_ethereum_hardfork(hardfork: EthereumHardfork) -> SpecId {
211 match hardfork {
212 EthereumHardfork::Frontier => SpecId::FRONTIER,
213 EthereumHardfork::Homestead => SpecId::HOMESTEAD,
214 EthereumHardfork::Dao => SpecId::HOMESTEAD,
215 EthereumHardfork::Tangerine => SpecId::TANGERINE,
216 EthereumHardfork::SpuriousDragon => SpecId::SPURIOUS_DRAGON,
217 EthereumHardfork::Byzantium => SpecId::BYZANTIUM,
218 EthereumHardfork::Constantinople => SpecId::PETERSBURG,
219 EthereumHardfork::Petersburg => SpecId::PETERSBURG,
220 EthereumHardfork::Istanbul => SpecId::ISTANBUL,
221 EthereumHardfork::MuirGlacier => SpecId::ISTANBUL,
222 EthereumHardfork::Berlin => SpecId::BERLIN,
223 EthereumHardfork::London => SpecId::LONDON,
224 EthereumHardfork::ArrowGlacier => SpecId::LONDON,
225 EthereumHardfork::GrayGlacier => SpecId::LONDON,
226 EthereumHardfork::Paris => SpecId::MERGE,
227 EthereumHardfork::Shanghai => SpecId::SHANGHAI,
228 EthereumHardfork::Cancun => SpecId::CANCUN,
229 EthereumHardfork::Prague => SpecId::PRAGUE,
230 EthereumHardfork::Osaka => SpecId::OSAKA,
231 EthereumHardfork::Bpo1 | EthereumHardfork::Bpo2 => SpecId::OSAKA,
232 EthereumHardfork::Bpo3 | EthereumHardfork::Bpo4 | EthereumHardfork::Bpo5 => {
233 unimplemented!()
234 }
235 EthereumHardfork::Amsterdam => SpecId::AMSTERDAM,
236 f => unreachable!("unimplemented {}", f),
237 }
238}
239
240#[cfg(feature = "optimism")]
242pub fn spec_id_from_optimism_hardfork(hardfork: OpHardfork) -> OpSpecId {
243 match hardfork {
244 OpHardfork::Bedrock => OpSpecId::BEDROCK,
245 OpHardfork::Regolith => OpSpecId::REGOLITH,
246 OpHardfork::Canyon => OpSpecId::CANYON,
247 OpHardfork::Ecotone => OpSpecId::ECOTONE,
248 OpHardfork::Fjord => OpSpecId::FJORD,
249 OpHardfork::Granite => OpSpecId::GRANITE,
250 OpHardfork::Holocene => OpSpecId::HOLOCENE,
251 OpHardfork::Isthmus => OpSpecId::ISTHMUS,
252 OpHardfork::Jovian => OpSpecId::JOVIAN,
253 OpHardfork::Karst => OpSpecId::KARST,
254 OpHardfork::Interop => OpSpecId::INTEROP,
255 f => unreachable!("unimplemented {}", f),
256 }
257}
258
259#[cfg(feature = "optimism")]
261pub fn eth_spec_id_from_optimism_hardfork(hardfork: OpHardfork) -> SpecId {
262 match hardfork {
263 OpHardfork::Bedrock | OpHardfork::Regolith => SpecId::MERGE,
264 OpHardfork::Canyon => SpecId::SHANGHAI,
265 OpHardfork::Ecotone | OpHardfork::Fjord | OpHardfork::Granite | OpHardfork::Holocene => {
266 SpecId::CANCUN
267 }
268 OpHardfork::Isthmus | OpHardfork::Jovian | OpHardfork::Interop => SpecId::PRAGUE,
269 OpHardfork::Karst => SpecId::OSAKA,
270 f => unreachable!("unimplemented {}", f),
271 }
272}
273
274pub const fn spec_id_from_tempo_hardfork(_: TempoHardfork) -> SpecId {
276 SpecId::OSAKA
277}
278
279pub trait FromEvmVersion: From<FoundryHardfork> {
281 fn from_evm_version(version: EvmVersion) -> Self;
282}
283
284pub trait ExecutionSpec: FromEvmVersion {
286 fn evm_version_name(&self) -> String;
288
289 fn from_network_hardfork(_: &str) -> Option<Self> {
291 None
292 }
293
294 fn from_foundry_hardfork(hardfork: FoundryHardfork) -> Option<Self>;
296}
297
298impl FromEvmVersion for SpecId {
299 fn from_evm_version(version: EvmVersion) -> Self {
300 match version {
301 EvmVersion::Homestead => Self::HOMESTEAD,
302 EvmVersion::TangerineWhistle => Self::TANGERINE,
303 EvmVersion::SpuriousDragon => Self::SPURIOUS_DRAGON,
304 EvmVersion::Byzantium => Self::BYZANTIUM,
305 EvmVersion::Constantinople => Self::PETERSBURG,
306 EvmVersion::Petersburg => Self::PETERSBURG,
307 EvmVersion::Istanbul => Self::ISTANBUL,
308 EvmVersion::Berlin => Self::BERLIN,
309 EvmVersion::London => Self::LONDON,
310 EvmVersion::Paris => Self::MERGE,
311 EvmVersion::Shanghai => Self::SHANGHAI,
312 EvmVersion::Cancun => Self::CANCUN,
313 EvmVersion::Prague => Self::PRAGUE,
314 EvmVersion::Osaka => Self::OSAKA,
315 EvmVersion::Amsterdam => Self::AMSTERDAM,
316 }
317 }
318}
319
320impl ExecutionSpec for SpecId {
321 fn evm_version_name(&self) -> String {
323 self.to_string()
324 }
325
326 fn from_network_hardfork(hardfork: &str) -> Option<Self> {
328 EthereumHardfork::from_str(hardfork).ok().map(spec_id_from_ethereum_hardfork)
329 }
330
331 fn from_foundry_hardfork(hardfork: FoundryHardfork) -> Option<Self> {
333 match hardfork {
334 FoundryHardfork::Ethereum(hardfork) => Some(spec_id_from_ethereum_hardfork(hardfork)),
335 _ => None,
336 }
337 }
338}
339
340#[cfg(feature = "optimism")]
341impl FromEvmVersion for OpSpecId {
342 fn from_evm_version(version: EvmVersion) -> Self {
343 match version {
344 EvmVersion::Homestead
345 | EvmVersion::TangerineWhistle
346 | EvmVersion::SpuriousDragon
347 | EvmVersion::Byzantium
348 | EvmVersion::Constantinople
349 | EvmVersion::Petersburg
350 | EvmVersion::Istanbul
351 | EvmVersion::Berlin
352 | EvmVersion::London
353 | EvmVersion::Paris => Self::BEDROCK,
354 EvmVersion::Shanghai => Self::CANYON,
355 EvmVersion::Cancun => Self::ECOTONE,
356 EvmVersion::Prague => Self::ISTHMUS,
357 EvmVersion::Osaka | EvmVersion::Amsterdam => Self::KARST,
358 }
359 }
360}
361
362#[cfg(feature = "optimism")]
363impl ExecutionSpec for OpSpecId {
364 fn evm_version_name(&self) -> String {
366 let name: &'static str = (*self).into();
367 name.to_string()
368 }
369
370 fn from_network_hardfork(hardfork: &str) -> Option<Self> {
372 OpHardfork::from_str(hardfork).ok().map(spec_id_from_optimism_hardfork)
373 }
374
375 fn from_foundry_hardfork(hardfork: FoundryHardfork) -> Option<Self> {
377 match hardfork {
378 FoundryHardfork::Optimism(hardfork) => Some(spec_id_from_optimism_hardfork(hardfork)),
379 _ => None,
380 }
381 }
382}
383
384impl FromEvmVersion for TempoHardfork {
385 fn from_evm_version(_: EvmVersion) -> Self {
386 latest_active_tempo_hardfork()
387 }
388}
389
390impl ExecutionSpec for TempoHardfork {
391 fn evm_version_name(&self) -> String {
393 self.to_string()
394 }
395
396 fn from_network_hardfork(hardfork: &str) -> Option<Self> {
398 Self::from_str(hardfork).ok()
399 }
400
401 fn from_foundry_hardfork(hardfork: FoundryHardfork) -> Option<Self> {
403 match hardfork {
404 FoundryHardfork::Tempo(hardfork) => Some(hardfork),
405 _ => None,
406 }
407 }
408}
409
410pub fn evm_spec_id<SPEC: FromEvmVersion>(evm_version: EvmVersion) -> SPEC {
412 SPEC::from_evm_version(evm_version)
413}
414
415pub fn latest_active_tempo_hardfork() -> TempoHardfork {
417 let now = SystemTime::now()
419 .duration_since(UNIX_EPOCH)
420 .map(|duration| duration.as_secs())
421 .unwrap_or(u64::MAX);
422 TempoHardfork::from_chain_and_timestamp(4217, now)
423 .or_else(|| TempoHardfork::from_chain_and_timestamp(42431, now))
424 .unwrap_or_default()
425}
426
427pub fn evm_spec_id_from_str<SPEC: ExecutionSpec>(evm_version: &str) -> Option<SPEC> {
429 let evm_version = evm_version.trim();
430
431 if let Ok(version) = EvmVersion::from_str(evm_version) {
432 return Some(evm_spec_id(version));
433 }
434
435 if let Some(spec) = SPEC::from_network_hardfork(evm_version) {
436 return Some(spec);
437 }
438
439 FoundryHardfork::from_str(evm_version).ok().and_then(SPEC::from_foundry_hardfork)
440}
441
442pub fn ethereum_hardfork_from_block_tag(block: impl Into<BlockNumberOrTag>) -> EthereumHardfork {
444 let num = match block.into() {
445 BlockNumberOrTag::Earliest => 0,
446 BlockNumberOrTag::Number(num) => num,
447 _ => u64::MAX,
448 };
449
450 EthereumHardfork::from_mainnet_block_number(num)
451}
452
453#[cfg(test)]
454mod tests {
455 use super::*;
456 use alloy_hardforks::ethereum::mainnet::*;
457 use tempo_hardfork::constants::{mainnet::*, moderato::*};
458
459 #[test]
460 fn test_ethereum_spec_id_mapping() {
461 assert_eq!(spec_id_from_ethereum_hardfork(EthereumHardfork::Frontier), SpecId::FRONTIER);
462 assert_eq!(spec_id_from_ethereum_hardfork(EthereumHardfork::Homestead), SpecId::HOMESTEAD);
463
464 assert_eq!(spec_id_from_ethereum_hardfork(EthereumHardfork::Cancun), SpecId::CANCUN);
466 assert_eq!(spec_id_from_ethereum_hardfork(EthereumHardfork::Prague), SpecId::PRAGUE);
467 assert_eq!(spec_id_from_ethereum_hardfork(EthereumHardfork::Osaka), SpecId::OSAKA);
468 assert_eq!(spec_id_from_ethereum_hardfork(EthereumHardfork::Amsterdam), SpecId::AMSTERDAM);
469 }
470
471 #[test]
472 fn test_tempo_spec_id_mapping() {
473 assert_eq!(spec_id_from_tempo_hardfork(TempoHardfork::Genesis), SpecId::OSAKA);
474 assert_eq!(spec_id_from_tempo_hardfork(TempoHardfork::T8), SpecId::OSAKA);
475 }
476
477 #[test]
478 fn test_tempo_evm_version_defaults_to_latest_active_hardfork() {
479 let latest = latest_active_tempo_hardfork();
480 assert_eq!(evm_spec_id::<TempoHardfork>(EvmVersion::Osaka), latest);
481 }
482
483 #[test]
484 fn test_tempo_hardfork_from_chain_and_timestamp() {
485 assert_eq!(
486 FoundryHardfork::from_chain_and_timestamp(4217, u64::MAX),
487 Some(FoundryHardfork::Tempo(TempoHardfork::T8))
488 );
489 assert_eq!(
490 FoundryHardfork::from_chain_and_timestamp(42431, u64::MAX),
491 Some(FoundryHardfork::Tempo(TempoHardfork::T8))
492 );
493
494 assert_eq!(
495 FoundryHardfork::from_chain_and_timestamp(MAINNET_CHAIN_ID, MAINNET_T6_TIMESTAMP - 1),
496 Some(FoundryHardfork::Tempo(TempoHardfork::T5))
497 );
498 assert_eq!(
499 FoundryHardfork::from_chain_and_timestamp(MAINNET_CHAIN_ID, MAINNET_T6_TIMESTAMP),
500 Some(FoundryHardfork::Tempo(TempoHardfork::T6))
501 );
502 assert_eq!(
503 FoundryHardfork::from_chain_and_timestamp(MAINNET_CHAIN_ID, MAINNET_T7_TIMESTAMP - 1),
504 Some(FoundryHardfork::Tempo(TempoHardfork::T6))
505 );
506 assert_eq!(
507 FoundryHardfork::from_chain_and_timestamp(MAINNET_CHAIN_ID, MAINNET_T7_TIMESTAMP),
508 Some(FoundryHardfork::Tempo(TempoHardfork::T7))
509 );
510 assert_eq!(
511 FoundryHardfork::from_chain_and_timestamp(MAINNET_CHAIN_ID, MAINNET_T8_TIMESTAMP - 1),
512 Some(FoundryHardfork::Tempo(TempoHardfork::T7))
513 );
514 assert_eq!(
515 FoundryHardfork::from_chain_and_timestamp(MAINNET_CHAIN_ID, MAINNET_T8_TIMESTAMP),
516 Some(FoundryHardfork::Tempo(TempoHardfork::T8))
517 );
518 assert_eq!(
519 FoundryHardfork::from_chain_and_timestamp(MODERATO_CHAIN_ID, MODERATO_T6_TIMESTAMP - 1),
520 Some(FoundryHardfork::Tempo(TempoHardfork::T5))
521 );
522 assert_eq!(
523 FoundryHardfork::from_chain_and_timestamp(MODERATO_CHAIN_ID, MODERATO_T6_TIMESTAMP),
524 Some(FoundryHardfork::Tempo(TempoHardfork::T6))
525 );
526 assert_eq!(
527 FoundryHardfork::from_chain_and_timestamp(MODERATO_CHAIN_ID, MODERATO_T7_TIMESTAMP - 1),
528 Some(FoundryHardfork::Tempo(TempoHardfork::T6))
529 );
530 assert_eq!(
531 FoundryHardfork::from_chain_and_timestamp(MODERATO_CHAIN_ID, MODERATO_T7_TIMESTAMP),
532 Some(FoundryHardfork::Tempo(TempoHardfork::T7))
533 );
534 assert_eq!(
535 FoundryHardfork::from_chain_and_timestamp(MODERATO_CHAIN_ID, MODERATO_T8_TIMESTAMP - 1),
536 Some(FoundryHardfork::Tempo(TempoHardfork::T7))
537 );
538 assert_eq!(
539 FoundryHardfork::from_chain_and_timestamp(MODERATO_CHAIN_ID, MODERATO_T8_TIMESTAMP),
540 Some(FoundryHardfork::Tempo(TempoHardfork::T8))
541 );
542 }
543
544 #[test]
545 fn test_evm_spec_id_from_str_parses_network_hardforks() {
546 assert_eq!(evm_spec_id_from_str::<TempoHardfork>("T3"), Some(TempoHardfork::T3));
547 assert_eq!(evm_spec_id_from_str::<TempoHardfork>("tempo:T2"), Some(TempoHardfork::T2));
548 assert_eq!(evm_spec_id_from_str::<TempoHardfork>("tempo:T7"), Some(TempoHardfork::T7));
549 assert_eq!(evm_spec_id_from_str::<TempoHardfork>("tempo:T8"), Some(TempoHardfork::T8));
550 assert_eq!(evm_spec_id_from_str::<TempoHardfork>("ethereum:prague"), None);
551 }
552
553 #[test]
554 fn test_hardfork_from_block_tag_numbers() {
555 assert_eq!(
556 ethereum_hardfork_from_block_tag(MAINNET_HOMESTEAD_BLOCK - 1),
557 EthereumHardfork::Frontier
558 );
559 assert_eq!(
560 ethereum_hardfork_from_block_tag(MAINNET_LONDON_BLOCK + 1),
561 EthereumHardfork::London
562 );
563 }
564
565 #[test]
566 fn test_from_chain_and_timestamp_ethereum_mainnet() {
567 assert_eq!(
568 FoundryHardfork::from_chain_and_timestamp(1, 0),
569 Some(FoundryHardfork::Ethereum(EthereumHardfork::Frontier))
570 );
571 assert_eq!(
573 FoundryHardfork::from_chain_and_timestamp(1, 1_681_338_455),
574 Some(FoundryHardfork::Ethereum(EthereumHardfork::Shanghai))
575 );
576 }
577
578 #[test]
579 fn test_from_chain_and_timestamp_sepolia() {
580 let sepolia_chain_id = 11155111;
581 assert!(FoundryHardfork::from_chain_and_timestamp(sepolia_chain_id, u64::MAX).is_some());
582 }
583
584 #[test]
585 fn test_from_chain_and_timestamp_unknown_chain() {
586 assert_eq!(FoundryHardfork::from_chain_and_timestamp(999999, 0), None);
587 }
588
589 #[cfg(feature = "optimism")]
590 mod optimism {
591 use super::*;
592
593 #[test]
594 fn test_optimism_spec_id_mapping() {
595 assert_eq!(spec_id_from_optimism_hardfork(OpHardfork::Bedrock), OpSpecId::BEDROCK);
596 assert_eq!(spec_id_from_optimism_hardfork(OpHardfork::Regolith), OpSpecId::REGOLITH);
597
598 assert_eq!(spec_id_from_optimism_hardfork(OpHardfork::Holocene), OpSpecId::HOLOCENE);
600 assert_eq!(spec_id_from_optimism_hardfork(OpHardfork::Karst), OpSpecId::KARST);
601 assert_eq!(spec_id_from_optimism_hardfork(OpHardfork::Interop), OpSpecId::INTEROP);
602 assert_eq!(evm_spec_id::<OpSpecId>(EvmVersion::Osaka), OpSpecId::KARST);
603 }
604
605 #[test]
606 fn test_from_chain_and_timestamp_op_mainnet() {
607 let op_chain_id = 10;
608 assert!(matches!(
609 FoundryHardfork::from_chain_and_timestamp(op_chain_id, u64::MAX),
610 Some(FoundryHardfork::Optimism(_))
611 ));
612 }
613
614 #[test]
615 fn test_from_chain_and_timestamp_base() {
616 let base_chain_id = 8453;
617 assert!(matches!(
618 FoundryHardfork::from_chain_and_timestamp(base_chain_id, u64::MAX),
619 Some(FoundryHardfork::Optimism(_))
620 ));
621 }
622 }
623}