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;
22#[cfg(feature = "monad")]
23pub use monad_revm::MonadHardfork;
24pub use tempo_hardfork::TempoHardfork;
25
26#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
27#[serde(into = "String")]
28pub enum FoundryHardfork {
29 Ethereum(EthereumHardfork),
30 #[cfg(feature = "optimism")]
31 Optimism(OpHardfork),
32 Tempo(TempoHardfork),
33 #[cfg(feature = "monad")]
34 Monad(MonadHardfork),
35}
36
37impl From<FoundryHardfork> for String {
38 fn from(fork: FoundryHardfork) -> Self {
39 match fork {
40 FoundryHardfork::Ethereum(h) => format!("{h}"),
41 #[cfg(feature = "optimism")]
42 FoundryHardfork::Optimism(h) => format!("optimism:{h}"),
43 FoundryHardfork::Tempo(h) => format!("tempo:{h}"),
44 #[cfg(feature = "monad")]
45 FoundryHardfork::Monad(h) => format!("monad:{h}"),
46 }
47 }
48}
49
50impl<'de> Deserialize<'de> for FoundryHardfork {
51 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
52 where
53 D: serde::Deserializer<'de>,
54 {
55 let s = String::deserialize(deserializer)?;
56 Self::from_str(&s).map_err(serde::de::Error::custom)
57 }
58}
59
60impl FromStr for FoundryHardfork {
61 type Err = String;
62
63 fn from_str(s: &str) -> Result<Self, Self::Err> {
64 let raw = s.trim();
65
66 let Some((ns, fork_raw)) = raw.split_once(':') else {
67 return EthereumHardfork::from_str(raw)
68 .map(Self::Ethereum)
69 .map_err(|_| format!("unknown ethereum hardfork '{raw}'"));
70 };
71
72 let ns = ns.trim().to_ascii_lowercase();
73 let fork = fork_raw.trim().to_ascii_lowercase().replace(['-', ' '], "_");
74
75 match ns.as_str() {
76 "eth" | "ethereum" => EthereumHardfork::from_str(&fork)
77 .map(Self::Ethereum)
78 .map_err(|_| format!("unknown ethereum hardfork '{fork_raw}'")),
79
80 #[cfg(feature = "optimism")]
81 "op" | "optimism" => OpHardfork::from_str(&fork)
82 .map(Self::Optimism)
83 .map_err(|_| format!("unknown optimism hardfork '{fork_raw}'")),
84
85 "t" | "tempo" => TempoHardfork::from_str(&fork)
86 .map(Self::Tempo)
87 .map_err(|_| format!("unknown tempo hardfork '{fork_raw}'")),
88
89 #[cfg(feature = "monad")]
90 "m" | "monad" => MonadHardfork::from_str(&fork)
91 .map(Self::Monad)
92 .map_err(|_| format!("unknown monad hardfork '{fork_raw}'")),
93 _ => EthereumHardfork::from_str(&fork)
94 .map(Self::Ethereum)
95 .map_err(|_| format!("unknown hardfork '{raw}'")),
96 }
97 }
98}
99
100impl FoundryHardfork {
101 pub const fn ethereum(h: EthereumHardfork) -> Self {
102 Self::Ethereum(h)
103 }
104
105 #[cfg(feature = "optimism")]
106 pub const fn optimism(h: OpHardfork) -> Self {
107 Self::Optimism(h)
108 }
109
110 pub const fn tempo(h: TempoHardfork) -> Self {
111 Self::Tempo(h)
112 }
113
114 #[cfg(feature = "monad")]
115 pub const fn monad(h: MonadHardfork) -> Self {
116 Self::Monad(h)
117 }
118
119 pub fn name(&self) -> String {
121 match self {
122 Self::Ethereum(h) => format!("{h}"),
123 #[cfg(feature = "optimism")]
124 Self::Optimism(h) => format!("{h}"),
125 Self::Tempo(h) => format!("{h}"),
126 #[cfg(feature = "monad")]
127 Self::Monad(h) => format!("{h}"),
128 }
129 }
130
131 pub const fn namespace(&self) -> Option<&'static str> {
135 match self {
136 Self::Ethereum(_) => None,
137 #[cfg(feature = "optimism")]
138 Self::Optimism(_) => Some("optimism"),
139 Self::Tempo(_) => Some("tempo"),
140 #[cfg(feature = "monad")]
141 Self::Monad(_) => Some("monad"),
142 }
143 }
144
145 pub fn from_chain_and_timestamp(chain_id: u64, timestamp: u64) -> Option<Self> {
149 let chain = Chain::from_id(chain_id);
150 if let Some(fork) = EthereumHardfork::from_chain_and_timestamp(chain, timestamp) {
151 return Some(Self::Ethereum(fork));
152 }
153 #[cfg(feature = "optimism")]
154 if let Some(fork) = OpHardfork::from_chain_and_timestamp(chain, timestamp) {
155 return Some(Self::Optimism(fork));
156 }
157 if let Some(fork) = TempoHardfork::from_chain_and_timestamp(chain_id, timestamp) {
158 return Some(Self::Tempo(fork));
159 }
160 #[cfg(feature = "monad")]
161 if let Some(fork) = MonadHardfork::from_chain_and_timestamp(chain_id, timestamp) {
162 return Some(Self::Monad(fork));
163 }
164 None
165 }
166}
167
168impl From<EthereumHardfork> for FoundryHardfork {
169 fn from(value: EthereumHardfork) -> Self {
170 Self::Ethereum(value)
171 }
172}
173
174impl From<FoundryHardfork> for EthereumHardfork {
175 fn from(fork: FoundryHardfork) -> Self {
176 match fork {
177 FoundryHardfork::Ethereum(hardfork) => hardfork,
178 _ => Self::default(),
179 }
180 }
181}
182
183#[cfg(feature = "optimism")]
184impl From<OpHardfork> for FoundryHardfork {
185 fn from(value: OpHardfork) -> Self {
186 Self::Optimism(value)
187 }
188}
189
190#[cfg(feature = "optimism")]
191impl From<FoundryHardfork> for OpHardfork {
192 fn from(fork: FoundryHardfork) -> Self {
193 match fork {
194 FoundryHardfork::Optimism(hardfork) => hardfork,
195 _ => Self::default(),
196 }
197 }
198}
199
200impl From<TempoHardfork> for FoundryHardfork {
201 fn from(value: TempoHardfork) -> Self {
202 Self::Tempo(value)
203 }
204}
205
206impl From<FoundryHardfork> for TempoHardfork {
207 fn from(fork: FoundryHardfork) -> Self {
208 match fork {
209 FoundryHardfork::Tempo(hardfork) => hardfork,
210 _ => Self::default(),
211 }
212 }
213}
214
215#[cfg(feature = "monad")]
216impl From<MonadHardfork> for FoundryHardfork {
217 fn from(value: MonadHardfork) -> Self {
218 Self::Monad(value)
219 }
220}
221
222#[cfg(feature = "monad")]
223impl From<FoundryHardfork> for MonadHardfork {
224 fn from(fork: FoundryHardfork) -> Self {
225 match fork {
226 FoundryHardfork::Monad(hardfork) => hardfork,
227 _ => Self::default(),
228 }
229 }
230}
231
232impl From<FoundryHardfork> for SpecId {
233 fn from(fork: FoundryHardfork) -> Self {
234 match fork {
235 FoundryHardfork::Ethereum(hardfork) => spec_id_from_ethereum_hardfork(hardfork),
236 #[cfg(feature = "optimism")]
237 FoundryHardfork::Optimism(hardfork) => eth_spec_id_from_optimism_hardfork(hardfork),
238 FoundryHardfork::Tempo(hardfork) => spec_id_from_tempo_hardfork(hardfork),
239 #[cfg(feature = "monad")]
240 FoundryHardfork::Monad(hardfork) => hardfork.into(),
241 }
242 }
243}
244
245#[cfg(feature = "optimism")]
246impl From<FoundryHardfork> for OpSpecId {
247 fn from(fork: FoundryHardfork) -> Self {
248 match fork {
249 FoundryHardfork::Optimism(hardfork) => spec_id_from_optimism_hardfork(hardfork),
250 _ => Self::default(),
251 }
252 }
253}
254
255pub fn spec_id_from_ethereum_hardfork(hardfork: EthereumHardfork) -> SpecId {
257 match hardfork {
258 EthereumHardfork::Frontier => SpecId::FRONTIER,
259 EthereumHardfork::Homestead => SpecId::HOMESTEAD,
260 EthereumHardfork::Dao => SpecId::HOMESTEAD,
261 EthereumHardfork::Tangerine => SpecId::TANGERINE,
262 EthereumHardfork::SpuriousDragon => SpecId::SPURIOUS_DRAGON,
263 EthereumHardfork::Byzantium => SpecId::BYZANTIUM,
264 EthereumHardfork::Constantinople => SpecId::PETERSBURG,
265 EthereumHardfork::Petersburg => SpecId::PETERSBURG,
266 EthereumHardfork::Istanbul => SpecId::ISTANBUL,
267 EthereumHardfork::MuirGlacier => SpecId::ISTANBUL,
268 EthereumHardfork::Berlin => SpecId::BERLIN,
269 EthereumHardfork::London => SpecId::LONDON,
270 EthereumHardfork::ArrowGlacier => SpecId::LONDON,
271 EthereumHardfork::GrayGlacier => SpecId::LONDON,
272 EthereumHardfork::Paris => SpecId::MERGE,
273 EthereumHardfork::Shanghai => SpecId::SHANGHAI,
274 EthereumHardfork::Cancun => SpecId::CANCUN,
275 EthereumHardfork::Prague => SpecId::PRAGUE,
276 EthereumHardfork::Osaka => SpecId::OSAKA,
277 EthereumHardfork::Bpo1 | EthereumHardfork::Bpo2 => SpecId::OSAKA,
278 EthereumHardfork::Bpo3 | EthereumHardfork::Bpo4 | EthereumHardfork::Bpo5 => {
279 unimplemented!()
280 }
281 EthereumHardfork::Amsterdam => SpecId::AMSTERDAM,
282 f => unreachable!("unimplemented {}", f),
283 }
284}
285
286#[cfg(feature = "optimism")]
288pub fn spec_id_from_optimism_hardfork(hardfork: OpHardfork) -> OpSpecId {
289 match hardfork {
290 OpHardfork::Bedrock => OpSpecId::BEDROCK,
291 OpHardfork::Regolith => OpSpecId::REGOLITH,
292 OpHardfork::Canyon => OpSpecId::CANYON,
293 OpHardfork::Ecotone => OpSpecId::ECOTONE,
294 OpHardfork::Fjord => OpSpecId::FJORD,
295 OpHardfork::Granite => OpSpecId::GRANITE,
296 OpHardfork::Holocene => OpSpecId::HOLOCENE,
297 OpHardfork::Isthmus => OpSpecId::ISTHMUS,
298 OpHardfork::Jovian => OpSpecId::JOVIAN,
299 OpHardfork::Karst => OpSpecId::KARST,
300 OpHardfork::Lagoon => OpSpecId::LAGOON,
301 f => unreachable!("unimplemented {}", f),
302 }
303}
304
305#[cfg(feature = "optimism")]
307pub fn eth_spec_id_from_optimism_hardfork(hardfork: OpHardfork) -> SpecId {
308 match hardfork {
309 OpHardfork::Bedrock | OpHardfork::Regolith => SpecId::MERGE,
310 OpHardfork::Canyon => SpecId::SHANGHAI,
311 OpHardfork::Ecotone | OpHardfork::Fjord | OpHardfork::Granite | OpHardfork::Holocene => {
312 SpecId::CANCUN
313 }
314 OpHardfork::Isthmus | OpHardfork::Jovian => SpecId::PRAGUE,
315 OpHardfork::Karst | OpHardfork::Lagoon => SpecId::OSAKA,
316 f => unreachable!("unimplemented {}", f),
317 }
318}
319
320pub const fn spec_id_from_tempo_hardfork(_: TempoHardfork) -> SpecId {
322 SpecId::OSAKA
323}
324
325pub trait FromEvmVersion: From<FoundryHardfork> {
327 fn from_evm_version(version: EvmVersion) -> Self;
328}
329
330pub trait ExecutionSpec: FromEvmVersion {
332 fn evm_version_name(&self) -> String;
334
335 fn from_network_hardfork(_: &str) -> Option<Self> {
337 None
338 }
339
340 fn from_foundry_hardfork(hardfork: FoundryHardfork) -> Option<Self>;
342}
343
344impl FromEvmVersion for SpecId {
345 fn from_evm_version(version: EvmVersion) -> Self {
346 match version {
347 EvmVersion::Homestead => Self::HOMESTEAD,
348 EvmVersion::TangerineWhistle => Self::TANGERINE,
349 EvmVersion::SpuriousDragon => Self::SPURIOUS_DRAGON,
350 EvmVersion::Byzantium => Self::BYZANTIUM,
351 EvmVersion::Constantinople => Self::PETERSBURG,
352 EvmVersion::Petersburg => Self::PETERSBURG,
353 EvmVersion::Istanbul => Self::ISTANBUL,
354 EvmVersion::Berlin => Self::BERLIN,
355 EvmVersion::London => Self::LONDON,
356 EvmVersion::Paris => Self::MERGE,
357 EvmVersion::Shanghai => Self::SHANGHAI,
358 EvmVersion::Cancun => Self::CANCUN,
359 EvmVersion::Prague => Self::PRAGUE,
360 EvmVersion::Osaka => Self::OSAKA,
361 EvmVersion::Amsterdam => Self::AMSTERDAM,
362 }
363 }
364}
365
366impl ExecutionSpec for SpecId {
367 fn evm_version_name(&self) -> String {
369 self.to_string()
370 }
371
372 fn from_network_hardfork(hardfork: &str) -> Option<Self> {
374 EthereumHardfork::from_str(hardfork).ok().map(spec_id_from_ethereum_hardfork)
375 }
376
377 fn from_foundry_hardfork(hardfork: FoundryHardfork) -> Option<Self> {
379 match hardfork {
380 FoundryHardfork::Ethereum(hardfork) => Some(spec_id_from_ethereum_hardfork(hardfork)),
381 _ => None,
382 }
383 }
384}
385
386#[cfg(feature = "optimism")]
387impl FromEvmVersion for OpSpecId {
388 fn from_evm_version(version: EvmVersion) -> Self {
389 match version {
390 EvmVersion::Homestead
391 | EvmVersion::TangerineWhistle
392 | EvmVersion::SpuriousDragon
393 | EvmVersion::Byzantium
394 | EvmVersion::Constantinople
395 | EvmVersion::Petersburg
396 | EvmVersion::Istanbul
397 | EvmVersion::Berlin
398 | EvmVersion::London
399 | EvmVersion::Paris => Self::BEDROCK,
400 EvmVersion::Shanghai => Self::CANYON,
401 EvmVersion::Cancun => Self::ECOTONE,
402 EvmVersion::Prague => Self::ISTHMUS,
403 EvmVersion::Osaka | EvmVersion::Amsterdam => Self::KARST,
404 }
405 }
406}
407
408#[cfg(feature = "optimism")]
409impl ExecutionSpec for OpSpecId {
410 fn evm_version_name(&self) -> String {
412 let name: &'static str = (*self).into();
413 name.to_string()
414 }
415
416 fn from_network_hardfork(hardfork: &str) -> Option<Self> {
418 OpHardfork::from_str(hardfork).ok().map(spec_id_from_optimism_hardfork)
419 }
420
421 fn from_foundry_hardfork(hardfork: FoundryHardfork) -> Option<Self> {
423 match hardfork {
424 FoundryHardfork::Optimism(hardfork) => Some(spec_id_from_optimism_hardfork(hardfork)),
425 _ => None,
426 }
427 }
428}
429
430impl FromEvmVersion for TempoHardfork {
431 fn from_evm_version(_: EvmVersion) -> Self {
432 latest_active_tempo_hardfork()
433 }
434}
435
436impl ExecutionSpec for TempoHardfork {
437 fn evm_version_name(&self) -> String {
439 self.to_string()
440 }
441
442 fn from_network_hardfork(hardfork: &str) -> Option<Self> {
444 Self::from_str(hardfork).ok()
445 }
446
447 fn from_foundry_hardfork(hardfork: FoundryHardfork) -> Option<Self> {
449 match hardfork {
450 FoundryHardfork::Tempo(hardfork) => Some(hardfork),
451 _ => None,
452 }
453 }
454}
455
456#[cfg(feature = "monad")]
457impl FromEvmVersion for MonadHardfork {
458 fn from_evm_version(_: EvmVersion) -> Self {
459 Self::default()
460 }
461}
462
463#[cfg(feature = "monad")]
464impl ExecutionSpec for MonadHardfork {
465 fn evm_version_name(&self) -> String {
467 self.to_string()
468 }
469
470 fn from_network_hardfork(hardfork: &str) -> Option<Self> {
472 Self::from_str(hardfork).ok()
473 }
474
475 fn from_foundry_hardfork(hardfork: FoundryHardfork) -> Option<Self> {
477 match hardfork {
478 #[cfg(feature = "monad")]
479 FoundryHardfork::Monad(hardfork) => Some(hardfork),
480 _ => None,
481 }
482 }
483}
484
485pub fn evm_spec_id<SPEC: FromEvmVersion>(evm_version: EvmVersion) -> SPEC {
487 SPEC::from_evm_version(evm_version)
488}
489
490pub fn latest_active_tempo_hardfork() -> TempoHardfork {
492 let now = SystemTime::now()
494 .duration_since(UNIX_EPOCH)
495 .map(|duration| duration.as_secs())
496 .unwrap_or(u64::MAX);
497 TempoHardfork::from_chain_and_timestamp(4217, now)
498 .or_else(|| TempoHardfork::from_chain_and_timestamp(42431, now))
499 .unwrap_or_default()
500}
501
502pub fn evm_spec_id_from_str<SPEC: ExecutionSpec>(evm_version: &str) -> Option<SPEC> {
504 let evm_version = evm_version.trim();
505
506 if let Ok(version) = EvmVersion::from_str(evm_version) {
507 return Some(evm_spec_id(version));
508 }
509
510 if let Some(spec) = SPEC::from_network_hardfork(evm_version) {
511 return Some(spec);
512 }
513
514 FoundryHardfork::from_str(evm_version).ok().and_then(SPEC::from_foundry_hardfork)
515}
516
517pub fn ethereum_hardfork_from_block_tag(block: impl Into<BlockNumberOrTag>) -> EthereumHardfork {
519 let num = match block.into() {
520 BlockNumberOrTag::Earliest => 0,
521 BlockNumberOrTag::Number(num) => num,
522 _ => u64::MAX,
523 };
524
525 EthereumHardfork::from_mainnet_block_number(num)
526}
527
528#[cfg(test)]
529mod tests {
530 use super::*;
531 use alloy_hardforks::ethereum::mainnet::*;
532 use tempo_hardfork::constants::{mainnet::*, moderato::*};
533
534 #[test]
535 fn test_ethereum_spec_id_mapping() {
536 assert_eq!(spec_id_from_ethereum_hardfork(EthereumHardfork::Frontier), SpecId::FRONTIER);
537 assert_eq!(spec_id_from_ethereum_hardfork(EthereumHardfork::Homestead), SpecId::HOMESTEAD);
538
539 assert_eq!(spec_id_from_ethereum_hardfork(EthereumHardfork::Cancun), SpecId::CANCUN);
541 assert_eq!(spec_id_from_ethereum_hardfork(EthereumHardfork::Prague), SpecId::PRAGUE);
542 assert_eq!(spec_id_from_ethereum_hardfork(EthereumHardfork::Osaka), SpecId::OSAKA);
543 assert_eq!(spec_id_from_ethereum_hardfork(EthereumHardfork::Amsterdam), SpecId::AMSTERDAM);
544 }
545
546 #[test]
547 fn test_tempo_spec_id_mapping() {
548 assert_eq!(spec_id_from_tempo_hardfork(TempoHardfork::Genesis), SpecId::OSAKA);
549 assert_eq!(spec_id_from_tempo_hardfork(TempoHardfork::T8), SpecId::OSAKA);
550 }
551
552 #[test]
553 fn test_tempo_evm_version_defaults_to_latest_active_hardfork() {
554 let latest = latest_active_tempo_hardfork();
555 assert_eq!(evm_spec_id::<TempoHardfork>(EvmVersion::Osaka), latest);
556 }
557
558 #[test]
559 #[cfg(feature = "monad")]
560 fn test_monad_hardfork_parsing() {
561 assert_eq!(
562 "monad:MonadNine".parse::<FoundryHardfork>().unwrap(),
563 FoundryHardfork::Monad(MonadHardfork::MonadNine)
564 );
565 assert_eq!(
566 "monad:MonadTen".parse::<FoundryHardfork>().unwrap(),
567 FoundryHardfork::Monad(MonadHardfork::MonadTen)
568 );
569 assert_eq!(
570 "m:MonadNext".parse::<FoundryHardfork>().unwrap(),
571 FoundryHardfork::Monad(MonadHardfork::MonadNext)
572 );
573 }
574
575 #[test]
576 #[cfg(feature = "monad")]
577 fn test_monad_hardfork_serialization() {
578 assert_eq!(
579 String::from(FoundryHardfork::Monad(MonadHardfork::MonadEight)),
580 "monad:MonadEight"
581 );
582 assert_eq!(FoundryHardfork::Monad(MonadHardfork::MonadEight).namespace(), Some("monad"));
583 assert_eq!(FoundryHardfork::Monad(MonadHardfork::MonadEight).name(), "MonadEight");
584 }
585
586 #[test]
587 #[cfg(feature = "monad")]
588 fn test_monad_hardfork_spec_id_mapping() {
589 assert_eq!(SpecId::from(FoundryHardfork::Monad(MonadHardfork::MonadEight)), SpecId::PRAGUE);
590 assert_eq!(SpecId::from(FoundryHardfork::Monad(MonadHardfork::MonadNine)), SpecId::OSAKA);
591 assert_eq!(SpecId::from(FoundryHardfork::Monad(MonadHardfork::MonadTen)), SpecId::OSAKA);
592 assert_eq!(
593 MonadHardfork::from(FoundryHardfork::Monad(MonadHardfork::MonadNext)),
594 MonadHardfork::MonadNext
595 );
596 }
597
598 #[test]
599 fn test_tempo_hardfork_from_chain_and_timestamp() {
600 assert_eq!(
601 FoundryHardfork::from_chain_and_timestamp(4217, u64::MAX),
602 Some(FoundryHardfork::Tempo(TempoHardfork::T10))
603 );
604 assert_eq!(
605 FoundryHardfork::from_chain_and_timestamp(42431, u64::MAX),
606 Some(FoundryHardfork::Tempo(TempoHardfork::T10))
607 );
608
609 assert_eq!(
610 FoundryHardfork::from_chain_and_timestamp(MAINNET_CHAIN_ID, MAINNET_T6_TIMESTAMP - 1),
611 Some(FoundryHardfork::Tempo(TempoHardfork::T5))
612 );
613 assert_eq!(
614 FoundryHardfork::from_chain_and_timestamp(MAINNET_CHAIN_ID, MAINNET_T6_TIMESTAMP),
615 Some(FoundryHardfork::Tempo(TempoHardfork::T6))
616 );
617 assert_eq!(
618 FoundryHardfork::from_chain_and_timestamp(MAINNET_CHAIN_ID, MAINNET_T7_TIMESTAMP - 1),
619 Some(FoundryHardfork::Tempo(TempoHardfork::T6))
620 );
621 assert_eq!(
622 FoundryHardfork::from_chain_and_timestamp(MAINNET_CHAIN_ID, MAINNET_T7_TIMESTAMP),
623 Some(FoundryHardfork::Tempo(TempoHardfork::T7))
624 );
625 assert_eq!(
626 FoundryHardfork::from_chain_and_timestamp(MAINNET_CHAIN_ID, MAINNET_T8_TIMESTAMP - 1),
627 Some(FoundryHardfork::Tempo(TempoHardfork::T7))
628 );
629 assert_eq!(
630 FoundryHardfork::from_chain_and_timestamp(MAINNET_CHAIN_ID, MAINNET_T8_TIMESTAMP),
631 Some(FoundryHardfork::Tempo(TempoHardfork::T8))
632 );
633 assert_eq!(
634 FoundryHardfork::from_chain_and_timestamp(MAINNET_CHAIN_ID, MAINNET_T9_TIMESTAMP - 1),
635 Some(FoundryHardfork::Tempo(TempoHardfork::T8))
636 );
637 assert_eq!(
638 FoundryHardfork::from_chain_and_timestamp(MAINNET_CHAIN_ID, MAINNET_T9_TIMESTAMP),
639 Some(FoundryHardfork::Tempo(TempoHardfork::T9))
640 );
641 assert_eq!(
642 FoundryHardfork::from_chain_and_timestamp(MODERATO_CHAIN_ID, MODERATO_T6_TIMESTAMP - 1),
643 Some(FoundryHardfork::Tempo(TempoHardfork::T5))
644 );
645 assert_eq!(
646 FoundryHardfork::from_chain_and_timestamp(MODERATO_CHAIN_ID, MODERATO_T6_TIMESTAMP),
647 Some(FoundryHardfork::Tempo(TempoHardfork::T6))
648 );
649 assert_eq!(
650 FoundryHardfork::from_chain_and_timestamp(MODERATO_CHAIN_ID, MODERATO_T7_TIMESTAMP - 1),
651 Some(FoundryHardfork::Tempo(TempoHardfork::T6))
652 );
653 assert_eq!(
654 FoundryHardfork::from_chain_and_timestamp(MODERATO_CHAIN_ID, MODERATO_T7_TIMESTAMP),
655 Some(FoundryHardfork::Tempo(TempoHardfork::T7))
656 );
657 assert_eq!(
658 FoundryHardfork::from_chain_and_timestamp(MODERATO_CHAIN_ID, MODERATO_T8_TIMESTAMP - 1),
659 Some(FoundryHardfork::Tempo(TempoHardfork::T7))
660 );
661 assert_eq!(
662 FoundryHardfork::from_chain_and_timestamp(MODERATO_CHAIN_ID, MODERATO_T8_TIMESTAMP),
663 Some(FoundryHardfork::Tempo(TempoHardfork::T8))
664 );
665 assert_eq!(
666 FoundryHardfork::from_chain_and_timestamp(MODERATO_CHAIN_ID, MODERATO_T9_TIMESTAMP - 1),
667 Some(FoundryHardfork::Tempo(TempoHardfork::T8))
668 );
669 assert_eq!(
670 FoundryHardfork::from_chain_and_timestamp(MODERATO_CHAIN_ID, MODERATO_T9_TIMESTAMP),
671 Some(FoundryHardfork::Tempo(TempoHardfork::T9))
672 );
673 }
674
675 #[test]
676 #[cfg(feature = "monad")]
677 fn test_monad_hardfork_from_chain_and_timestamp() {
678 let mainnet_ten = MonadHardfork::MonadTen.mainnet_activation_timestamp().unwrap();
679 let testnet_ten = MonadHardfork::MonadTen.testnet_activation_timestamp().unwrap();
680
681 assert_eq!(
682 FoundryHardfork::from_chain_and_timestamp(
683 monad_revm::MONAD_MAINNET_CHAIN_ID,
684 1_763_648_999,
685 ),
686 Some(FoundryHardfork::Monad(MonadHardfork::MonadEight))
687 );
688 assert_eq!(
689 FoundryHardfork::from_chain_and_timestamp(
690 monad_revm::MONAD_MAINNET_CHAIN_ID,
691 1_773_930_600,
692 ),
693 Some(FoundryHardfork::Monad(MonadHardfork::MonadNine))
694 );
695 assert_eq!(
696 FoundryHardfork::from_chain_and_timestamp(
697 monad_revm::MONAD_MAINNET_CHAIN_ID,
698 mainnet_ten - 1,
699 ),
700 Some(FoundryHardfork::Monad(MonadHardfork::MonadNine))
701 );
702 assert_eq!(
703 FoundryHardfork::from_chain_and_timestamp(
704 monad_revm::MONAD_MAINNET_CHAIN_ID,
705 mainnet_ten,
706 ),
707 Some(FoundryHardfork::Monad(MonadHardfork::MonadTen))
708 );
709 assert_eq!(
710 FoundryHardfork::from_chain_and_timestamp(
711 monad_revm::MONAD_TESTNET_CHAIN_ID,
712 1_773_152_999,
713 ),
714 Some(FoundryHardfork::Monad(MonadHardfork::MonadEight))
715 );
716 assert_eq!(
717 FoundryHardfork::from_chain_and_timestamp(
718 monad_revm::MONAD_TESTNET_CHAIN_ID,
719 1_773_153_000,
720 ),
721 Some(FoundryHardfork::Monad(MonadHardfork::MonadNine))
722 );
723 assert_eq!(
724 FoundryHardfork::from_chain_and_timestamp(
725 monad_revm::MONAD_TESTNET_CHAIN_ID,
726 testnet_ten - 1,
727 ),
728 Some(FoundryHardfork::Monad(MonadHardfork::MonadNine))
729 );
730 assert_eq!(
731 FoundryHardfork::from_chain_and_timestamp(
732 monad_revm::MONAD_TESTNET_CHAIN_ID,
733 testnet_ten,
734 ),
735 Some(FoundryHardfork::Monad(MonadHardfork::MonadTen))
736 );
737 }
738
739 #[test]
740 fn test_evm_spec_id_from_str_parses_network_hardforks() {
741 assert_eq!(evm_spec_id_from_str::<TempoHardfork>("T3"), Some(TempoHardfork::T3));
742 assert_eq!(evm_spec_id_from_str::<TempoHardfork>("tempo:T2"), Some(TempoHardfork::T2));
743 assert_eq!(evm_spec_id_from_str::<TempoHardfork>("tempo:T7"), Some(TempoHardfork::T7));
744 assert_eq!(evm_spec_id_from_str::<TempoHardfork>("tempo:T8"), Some(TempoHardfork::T8));
745 assert_eq!(evm_spec_id_from_str::<TempoHardfork>("ethereum:prague"), None);
746
747 #[cfg(feature = "monad")]
748 {
749 assert_eq!(
750 evm_spec_id_from_str::<MonadHardfork>("MonadNine"),
751 Some(MonadHardfork::MonadNine)
752 );
753 assert_eq!(
754 evm_spec_id_from_str::<MonadHardfork>("MonadTen"),
755 Some(MonadHardfork::MonadTen)
756 );
757 assert_eq!(
758 evm_spec_id_from_str::<MonadHardfork>("monad:MonadEight"),
759 Some(MonadHardfork::MonadEight)
760 );
761 assert_eq!(evm_spec_id_from_str::<MonadHardfork>("tempo:T3"), None);
762 }
763 }
764
765 #[test]
766 fn test_hardfork_from_block_tag_numbers() {
767 assert_eq!(
768 ethereum_hardfork_from_block_tag(MAINNET_HOMESTEAD_BLOCK - 1),
769 EthereumHardfork::Frontier
770 );
771 assert_eq!(
772 ethereum_hardfork_from_block_tag(MAINNET_LONDON_BLOCK + 1),
773 EthereumHardfork::London
774 );
775 }
776
777 #[test]
778 fn test_from_chain_and_timestamp_ethereum_mainnet() {
779 assert_eq!(
780 FoundryHardfork::from_chain_and_timestamp(1, 0),
781 Some(FoundryHardfork::Ethereum(EthereumHardfork::Frontier))
782 );
783 assert_eq!(
785 FoundryHardfork::from_chain_and_timestamp(1, 1_681_338_455),
786 Some(FoundryHardfork::Ethereum(EthereumHardfork::Shanghai))
787 );
788 }
789
790 #[test]
791 fn test_from_chain_and_timestamp_sepolia() {
792 let sepolia_chain_id = 11155111;
793 assert!(FoundryHardfork::from_chain_and_timestamp(sepolia_chain_id, u64::MAX).is_some());
794 }
795
796 #[test]
797 fn test_from_chain_and_timestamp_unknown_chain() {
798 assert_eq!(FoundryHardfork::from_chain_and_timestamp(999999, 0), None);
799 }
800
801 #[cfg(feature = "optimism")]
802 mod optimism {
803 use super::*;
804
805 #[test]
806 fn test_optimism_spec_id_mapping() {
807 assert_eq!(spec_id_from_optimism_hardfork(OpHardfork::Bedrock), OpSpecId::BEDROCK);
808 assert_eq!(spec_id_from_optimism_hardfork(OpHardfork::Regolith), OpSpecId::REGOLITH);
809
810 assert_eq!(spec_id_from_optimism_hardfork(OpHardfork::Holocene), OpSpecId::HOLOCENE);
812 assert_eq!(spec_id_from_optimism_hardfork(OpHardfork::Karst), OpSpecId::KARST);
813 assert_eq!(spec_id_from_optimism_hardfork(OpHardfork::Lagoon), OpSpecId::LAGOON);
814 assert_eq!(eth_spec_id_from_optimism_hardfork(OpHardfork::Jovian), SpecId::PRAGUE);
815 assert_eq!(eth_spec_id_from_optimism_hardfork(OpHardfork::Karst), SpecId::OSAKA);
816 assert_eq!(eth_spec_id_from_optimism_hardfork(OpHardfork::Lagoon), SpecId::OSAKA);
817 assert_eq!(evm_spec_id::<OpSpecId>(EvmVersion::Osaka), OpSpecId::KARST);
818 }
819
820 #[test]
821 fn test_from_chain_and_timestamp_op_mainnet() {
822 let op_chain_id = 10;
823 assert!(matches!(
824 FoundryHardfork::from_chain_and_timestamp(op_chain_id, u64::MAX),
825 Some(FoundryHardfork::Optimism(_))
826 ));
827 }
828
829 #[test]
830 fn test_from_chain_and_timestamp_base() {
831 let base_chain_id = 8453;
832 assert!(matches!(
833 FoundryHardfork::from_chain_and_timestamp(base_chain_id, u64::MAX),
834 Some(FoundryHardfork::Optimism(_))
835 ));
836 }
837 }
838}