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