1use crate::{
2 arbitrum::{mainnet::*, sepolia::*},
3 ethereum::{holesky::*, hoodi::*, mainnet::*, sepolia::*},
4 hardfork, ForkCondition,
5};
6use alloc::vec::Vec;
7use alloy_chains::Chain;
8use alloy_primitives::U256;
9
10hardfork!(
11 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
13 EthereumHardfork {
14 Frontier,
16 Homestead,
18 Dao,
20 Tangerine,
22 SpuriousDragon,
24 Byzantium,
26 Constantinople,
28 Petersburg,
30 Istanbul,
32 MuirGlacier,
34 Berlin,
36 London,
38 ArrowGlacier,
40 GrayGlacier,
42 Paris,
44 Shanghai,
46 Cancun,
48 Prague,
50 Osaka,
52 }
53);
54
55impl EthereumHardfork {
56 pub fn activation_block(&self, chain: Chain) -> Option<u64> {
58 if chain == Chain::mainnet() {
59 return self.mainnet_activation_block();
60 }
61 if chain == Chain::sepolia() {
62 return self.sepolia_activation_block();
63 }
64 if chain == Chain::holesky() {
65 return self.holesky_activation_block();
66 }
67 if chain == Chain::hoodi() {
68 return self.hoodi_activation_block();
69 }
70
71 None
72 }
73
74 pub const fn mainnet_activation_block(&self) -> Option<u64> {
76 match self {
77 Self::Frontier => Some(MAINNET_FRONTIER_BLOCK),
78 Self::Homestead => Some(MAINNET_HOMESTEAD_BLOCK),
79 Self::Dao => Some(MAINNET_DAO_BLOCK),
80 Self::Tangerine => Some(MAINNET_TANGERINE_BLOCK),
81 Self::SpuriousDragon => Some(MAINNET_SPURIOUS_DRAGON_BLOCK),
82 Self::Byzantium => Some(MAINNET_BYZANTIUM_BLOCK),
83 Self::Constantinople => Some(MAINNET_CONSTANTINOPLE_BLOCK),
84 Self::Petersburg => Some(MAINNET_PETERSBURG_BLOCK),
85 Self::Istanbul => Some(MAINNET_ISTANBUL_BLOCK),
86 Self::MuirGlacier => Some(MAINNET_MUIR_GLACIER_BLOCK),
87 Self::Berlin => Some(MAINNET_BERLIN_BLOCK),
88 Self::London => Some(MAINNET_LONDON_BLOCK),
89 Self::ArrowGlacier => Some(MAINNET_ARROW_GLACIER_BLOCK),
90 Self::GrayGlacier => Some(MAINNET_GRAY_GLACIER_BLOCK),
91 Self::Paris => Some(MAINNET_PARIS_BLOCK),
92 Self::Shanghai => Some(MAINNET_SHANGHAI_BLOCK),
93 Self::Cancun => Some(MAINNET_CANCUN_BLOCK),
94 _ => None,
95 }
96 }
97
98 pub const fn sepolia_activation_block(&self) -> Option<u64> {
100 match self {
101 Self::Paris => Some(SEPOLIA_PARIS_BLOCK),
102 Self::Shanghai => Some(SEPOLIA_SHANGHAI_BLOCK),
103 Self::Cancun => Some(SEPOLIA_CANCUN_BLOCK),
104 Self::Frontier
105 | Self::Homestead
106 | Self::Dao
107 | Self::Tangerine
108 | Self::SpuriousDragon
109 | Self::Byzantium
110 | Self::Constantinople
111 | Self::Petersburg
112 | Self::Istanbul
113 | Self::MuirGlacier
114 | Self::Berlin
115 | Self::London
116 | Self::ArrowGlacier
117 | Self::GrayGlacier => Some(0),
118 _ => None,
119 }
120 }
121
122 const fn holesky_activation_block(&self) -> Option<u64> {
124 match self {
125 Self::Frontier
126 | Self::Dao
127 | Self::Tangerine
128 | Self::SpuriousDragon
129 | Self::Byzantium
130 | Self::Constantinople
131 | Self::Petersburg
132 | Self::Istanbul
133 | Self::MuirGlacier
134 | Self::Berlin
135 | Self::London
136 | Self::ArrowGlacier
137 | Self::GrayGlacier
138 | Self::Paris => Some(0),
139 Self::Shanghai => Some(HOLESKY_SHANGHAI_BLOCK),
140 Self::Cancun => Some(HOLESKY_CANCUN_BLOCK),
141 _ => None,
142 }
143 }
144
145 const fn hoodi_activation_block(&self) -> Option<u64> {
147 match self {
148 Self::Frontier
149 | Self::Dao
150 | Self::Tangerine
151 | Self::SpuriousDragon
152 | Self::Byzantium
153 | Self::Constantinople
154 | Self::Petersburg
155 | Self::Istanbul
156 | Self::MuirGlacier
157 | Self::Berlin
158 | Self::London
159 | Self::ArrowGlacier
160 | Self::GrayGlacier
161 | Self::Paris
162 | Self::Shanghai
163 | Self::Cancun => Some(0),
164 _ => None,
165 }
166 }
167
168 pub const fn arbitrum_sepolia_activation_block(&self) -> Option<u64> {
170 match self {
171 Self::Frontier
172 | Self::Homestead
173 | Self::Dao
174 | Self::Tangerine
175 | Self::SpuriousDragon
176 | Self::Byzantium
177 | Self::Constantinople
178 | Self::Petersburg
179 | Self::Istanbul
180 | Self::MuirGlacier
181 | Self::Berlin
182 | Self::London
183 | Self::ArrowGlacier
184 | Self::GrayGlacier
185 | Self::Paris => Some(0),
186 Self::Shanghai => Some(ARBITRUM_SEPOLIA_SHANGHAI_BLOCK),
187 Self::Cancun => Some(ARBITRUM_SEPOLIA_CANCUN_BLOCK),
189 _ => None,
191 }
192 }
193
194 pub const fn arbitrum_activation_block(&self) -> Option<u64> {
196 match self {
197 Self::Frontier
198 | Self::Homestead
199 | Self::Dao
200 | Self::Tangerine
201 | Self::SpuriousDragon
202 | Self::Byzantium
203 | Self::Constantinople
204 | Self::Petersburg
205 | Self::Istanbul
206 | Self::MuirGlacier
207 | Self::Berlin
208 | Self::London
209 | Self::ArrowGlacier
210 | Self::GrayGlacier
211 | Self::Paris => Some(0),
212 Self::Shanghai => Some(ARBITRUM_ONE_SHANGHAI_BLOCK),
213 Self::Cancun => Some(ARBITRUM_ONE_CANCUN_BLOCK),
215 _ => None,
217 }
218 }
219
220 pub fn activation_timestamp(&self, chain: Chain) -> Option<u64> {
222 if chain == Chain::mainnet() {
223 return self.mainnet_activation_timestamp();
224 }
225 if chain == Chain::sepolia() {
226 return self.sepolia_activation_timestamp();
227 }
228 if chain == Chain::holesky() {
229 return self.holesky_activation_timestamp();
230 }
231 if chain == Chain::hoodi() {
232 return self.hoodi_activation_timestamp();
233 }
234
235 None
236 }
237
238 pub const fn mainnet_activation_timestamp(&self) -> Option<u64> {
240 match self {
241 Self::Frontier => Some(MAINNET_FRONTIER_TIMESTAMP),
242 Self::Homestead => Some(MAINNET_HOMESTEAD_TIMESTAMP),
243 Self::Dao => Some(MAINNET_DAO_TIMESTAMP),
244 Self::Tangerine => Some(MAINNET_TANGERINE_TIMESTAMP),
245 Self::SpuriousDragon => Some(MAINNET_SPURIOUS_DRAGON_TIMESTAMP),
246 Self::Byzantium => Some(MAINNET_BYZANTIUM_TIMESTAMP),
247 Self::Constantinople => Some(MAINNET_CONSTANTINOPLE_TIMESTAMP),
248 Self::Petersburg => Some(MAINNET_PETERSBURG_TIMESTAMP),
249 Self::Istanbul => Some(MAINNET_ISTANBUL_TIMESTAMP),
250 Self::MuirGlacier => Some(MAINNET_MUIR_GLACIER_TIMESTAMP),
251 Self::Berlin => Some(MAINNET_BERLIN_TIMESTAMP),
252 Self::London => Some(MAINNET_LONDON_TIMESTAMP),
253 Self::ArrowGlacier => Some(MAINNET_ARROW_GLACIER_TIMESTAMP),
254 Self::GrayGlacier => Some(MAINNET_GRAY_GLACIER_TIMESTAMP),
255 Self::Paris => Some(MAINNET_PARIS_TIMESTAMP),
256 Self::Shanghai => Some(MAINNET_SHANGHAI_TIMESTAMP),
257 Self::Cancun => Some(MAINNET_CANCUN_TIMESTAMP),
258 Self::Prague => Some(MAINNET_PRAGUE_TIMESTAMP),
259 _ => None,
261 }
262 }
263
264 pub const fn sepolia_activation_timestamp(&self) -> Option<u64> {
266 match self {
267 Self::Frontier
268 | Self::Homestead
269 | Self::Dao
270 | Self::Tangerine
271 | Self::SpuriousDragon
272 | Self::Byzantium
273 | Self::Constantinople
274 | Self::Petersburg
275 | Self::Istanbul
276 | Self::MuirGlacier
277 | Self::Berlin
278 | Self::London
279 | Self::ArrowGlacier
280 | Self::GrayGlacier
281 | Self::Paris => Some(SEPOLIA_PARIS_TIMESTAMP),
282 Self::Shanghai => Some(SEPOLIA_SHANGHAI_TIMESTAMP),
283 Self::Cancun => Some(SEPOLIA_CANCUN_TIMESTAMP),
284 _ => None,
285 }
286 }
287
288 pub const fn holesky_activation_timestamp(&self) -> Option<u64> {
290 match self {
291 Self::Frontier
292 | Self::Homestead
293 | Self::Dao
294 | Self::Tangerine
295 | Self::SpuriousDragon
296 | Self::Byzantium
297 | Self::Constantinople
298 | Self::Petersburg
299 | Self::Istanbul
300 | Self::MuirGlacier
301 | Self::Berlin
302 | Self::London
303 | Self::ArrowGlacier
304 | Self::GrayGlacier
305 | Self::Paris => Some(HOLESKY_PARIS_TIMESTAMP),
306 Self::Shanghai => Some(HOLESKY_SHANGHAI_TIMESTAMP),
307 Self::Cancun => Some(HOLESKY_CANCUN_TIMESTAMP),
308 _ => None,
309 }
310 }
311
312 pub const fn hoodi_activation_timestamp(&self) -> Option<u64> {
314 match self {
315 Self::Prague => Some(HOODI_PRAGUE_TIMESTAMP),
316 Self::Frontier
317 | Self::Homestead
318 | Self::Dao
319 | Self::Tangerine
320 | Self::SpuriousDragon
321 | Self::Byzantium
322 | Self::Constantinople
323 | Self::Petersburg
324 | Self::Istanbul
325 | Self::MuirGlacier
326 | Self::Berlin
327 | Self::London
328 | Self::ArrowGlacier
329 | Self::GrayGlacier
330 | Self::Paris
331 | Self::Shanghai
332 | Self::Cancun => Some(0),
333 _ => None,
334 }
335 }
336
337 pub const fn arbitrum_sepolia_activation_timestamp(&self) -> Option<u64> {
340 match self {
341 Self::Frontier
342 | Self::Homestead
343 | Self::Dao
344 | Self::Tangerine
345 | Self::SpuriousDragon
346 | Self::Byzantium
347 | Self::Constantinople
348 | Self::Petersburg
349 | Self::Istanbul
350 | Self::MuirGlacier
351 | Self::Berlin
352 | Self::London
353 | Self::ArrowGlacier
354 | Self::GrayGlacier
355 | Self::Paris => Some(ARBITRUM_SEPOLIA_PARIS_TIMESTAMP),
356 Self::Shanghai => Some(ARBITRUM_SEPOLIA_SHANGHAI_TIMESTAMP),
357 Self::Cancun => Some(ARBITRUM_SEPOLIA_CANCUN_TIMESTAMP),
359 _ => None,
361 }
362 }
363
364 pub const fn arbitrum_activation_timestamp(&self) -> Option<u64> {
366 match self {
367 Self::Frontier
368 | Self::Homestead
369 | Self::Dao
370 | Self::Tangerine
371 | Self::SpuriousDragon
372 | Self::Byzantium
373 | Self::Constantinople
374 | Self::Petersburg
375 | Self::Istanbul
376 | Self::MuirGlacier
377 | Self::Berlin
378 | Self::London
379 | Self::ArrowGlacier
380 | Self::GrayGlacier
381 | Self::Paris => Some(ARBITRUM_ONE_PARIS_TIMESTAMP),
382 Self::Shanghai => Some(ARBITRUM_ONE_SHANGHAI_TIMESTAMP),
383 Self::Cancun => Some(ARBITRUM_ONE_CANCUN_TIMESTAMP),
385 _ => None,
387 }
388 }
389
390 pub const fn mainnet() -> [(Self, ForkCondition); 18] {
392 [
393 (Self::Frontier, ForkCondition::Block(MAINNET_FRONTIER_BLOCK)),
394 (Self::Homestead, ForkCondition::Block(MAINNET_HOMESTEAD_BLOCK)),
395 (Self::Dao, ForkCondition::Block(MAINNET_DAO_BLOCK)),
396 (Self::Tangerine, ForkCondition::Block(MAINNET_TANGERINE_BLOCK)),
397 (Self::SpuriousDragon, ForkCondition::Block(MAINNET_SPURIOUS_DRAGON_BLOCK)),
398 (Self::Byzantium, ForkCondition::Block(MAINNET_BYZANTIUM_BLOCK)),
399 (Self::Constantinople, ForkCondition::Block(MAINNET_CONSTANTINOPLE_BLOCK)),
400 (Self::Petersburg, ForkCondition::Block(MAINNET_PETERSBURG_BLOCK)),
401 (Self::Istanbul, ForkCondition::Block(MAINNET_ISTANBUL_BLOCK)),
402 (Self::MuirGlacier, ForkCondition::Block(MAINNET_MUIR_GLACIER_BLOCK)),
403 (Self::Berlin, ForkCondition::Block(MAINNET_BERLIN_BLOCK)),
404 (Self::London, ForkCondition::Block(MAINNET_LONDON_BLOCK)),
405 (Self::ArrowGlacier, ForkCondition::Block(MAINNET_ARROW_GLACIER_BLOCK)),
406 (Self::GrayGlacier, ForkCondition::Block(MAINNET_GRAY_GLACIER_BLOCK)),
407 (
408 Self::Paris,
409 ForkCondition::TTD {
410 activation_block_number: MAINNET_PARIS_BLOCK,
411 fork_block: None,
412 total_difficulty: MAINNET_PARIS_TTD,
413 },
414 ),
415 (Self::Shanghai, ForkCondition::Timestamp(MAINNET_SHANGHAI_TIMESTAMP)),
416 (Self::Cancun, ForkCondition::Timestamp(MAINNET_CANCUN_TIMESTAMP)),
417 (Self::Prague, ForkCondition::Timestamp(MAINNET_PRAGUE_TIMESTAMP)),
418 ]
419 }
420
421 pub const fn sepolia() -> [(Self, ForkCondition); 16] {
423 [
424 (Self::Frontier, ForkCondition::Block(0)),
425 (Self::Homestead, ForkCondition::Block(0)),
426 (Self::Dao, ForkCondition::Block(0)),
427 (Self::Tangerine, ForkCondition::Block(0)),
428 (Self::SpuriousDragon, ForkCondition::Block(0)),
429 (Self::Byzantium, ForkCondition::Block(0)),
430 (Self::Constantinople, ForkCondition::Block(0)),
431 (Self::Petersburg, ForkCondition::Block(0)),
432 (Self::Istanbul, ForkCondition::Block(0)),
433 (Self::MuirGlacier, ForkCondition::Block(0)),
434 (Self::Berlin, ForkCondition::Block(0)),
435 (Self::London, ForkCondition::Block(0)),
436 (
437 Self::Paris,
438 ForkCondition::TTD {
439 activation_block_number: SEPOLIA_PARIS_BLOCK,
440 fork_block: Some(SEPOLIA_PARIS_FORK_BLOCK),
441 total_difficulty: SEPOLIA_PARIS_TTD,
442 },
443 ),
444 (Self::Shanghai, ForkCondition::Timestamp(SEPOLIA_SHANGHAI_TIMESTAMP)),
445 (Self::Cancun, ForkCondition::Timestamp(SEPOLIA_CANCUN_TIMESTAMP)),
446 (Self::Prague, ForkCondition::Timestamp(SEPOLIA_PRAGUE_TIMESTAMP)),
447 ]
448 }
449
450 pub const fn holesky() -> [(Self, ForkCondition); 16] {
452 [
453 (Self::Frontier, ForkCondition::Block(0)),
454 (Self::Homestead, ForkCondition::Block(0)),
455 (Self::Dao, ForkCondition::Block(0)),
456 (Self::Tangerine, ForkCondition::Block(0)),
457 (Self::SpuriousDragon, ForkCondition::Block(0)),
458 (Self::Byzantium, ForkCondition::Block(0)),
459 (Self::Constantinople, ForkCondition::Block(0)),
460 (Self::Petersburg, ForkCondition::Block(0)),
461 (Self::Istanbul, ForkCondition::Block(0)),
462 (Self::MuirGlacier, ForkCondition::Block(0)),
463 (Self::Berlin, ForkCondition::Block(0)),
464 (Self::London, ForkCondition::Block(0)),
465 (
466 Self::Paris,
467 ForkCondition::TTD {
468 activation_block_number: 0,
469 fork_block: Some(0),
470 total_difficulty: U256::ZERO,
471 },
472 ),
473 (Self::Shanghai, ForkCondition::Timestamp(HOLESKY_SHANGHAI_TIMESTAMP)),
474 (Self::Cancun, ForkCondition::Timestamp(HOLESKY_CANCUN_TIMESTAMP)),
475 (Self::Prague, ForkCondition::Timestamp(HOLESKY_PRAGUE_TIMESTAMP)),
476 ]
477 }
478
479 pub const fn hoodi() -> [(Self, ForkCondition); 16] {
481 [
482 (Self::Frontier, ForkCondition::Block(0)),
483 (Self::Homestead, ForkCondition::Block(0)),
484 (Self::Dao, ForkCondition::Block(0)),
485 (Self::Tangerine, ForkCondition::Block(0)),
486 (Self::SpuriousDragon, ForkCondition::Block(0)),
487 (Self::Byzantium, ForkCondition::Block(0)),
488 (Self::Constantinople, ForkCondition::Block(0)),
489 (Self::Petersburg, ForkCondition::Block(0)),
490 (Self::Istanbul, ForkCondition::Block(0)),
491 (Self::MuirGlacier, ForkCondition::Block(0)),
492 (Self::Berlin, ForkCondition::Block(0)),
493 (Self::London, ForkCondition::Block(0)),
494 (
495 Self::Paris,
496 ForkCondition::TTD {
497 activation_block_number: 0,
498 fork_block: Some(0),
499 total_difficulty: U256::ZERO,
500 },
501 ),
502 (Self::Shanghai, ForkCondition::Timestamp(0)),
503 (Self::Cancun, ForkCondition::Timestamp(0)),
504 (Self::Prague, ForkCondition::Timestamp(HOODI_PRAGUE_TIMESTAMP)),
505 ]
506 }
507}
508
509#[auto_impl::auto_impl(&, Arc)]
511pub trait EthereumHardforks {
512 fn ethereum_fork_activation(&self, fork: EthereumHardfork) -> ForkCondition;
515
516 fn is_ethereum_fork_active_at_timestamp(&self, fork: EthereumHardfork, timestamp: u64) -> bool {
518 self.ethereum_fork_activation(fork).active_at_timestamp(timestamp)
519 }
520
521 fn is_ethereum_fork_active_at_block(&self, fork: EthereumHardfork, block_number: u64) -> bool {
523 self.ethereum_fork_activation(fork).active_at_block(block_number)
524 }
525
526 fn is_shanghai_active_at_timestamp(&self, timestamp: u64) -> bool {
529 self.is_ethereum_fork_active_at_timestamp(EthereumHardfork::Shanghai, timestamp)
530 }
531
532 fn is_cancun_active_at_timestamp(&self, timestamp: u64) -> bool {
534 self.is_ethereum_fork_active_at_timestamp(EthereumHardfork::Cancun, timestamp)
535 }
536
537 fn is_prague_active_at_timestamp(&self, timestamp: u64) -> bool {
539 self.is_ethereum_fork_active_at_timestamp(EthereumHardfork::Prague, timestamp)
540 }
541
542 fn is_osaka_active_at_timestamp(&self, timestamp: u64) -> bool {
544 self.is_ethereum_fork_active_at_timestamp(EthereumHardfork::Osaka, timestamp)
545 }
546
547 fn is_byzantium_active_at_block(&self, block_number: u64) -> bool {
550 self.is_ethereum_fork_active_at_block(EthereumHardfork::Byzantium, block_number)
551 }
552
553 fn is_spurious_dragon_active_at_block(&self, block_number: u64) -> bool {
556 self.is_ethereum_fork_active_at_block(EthereumHardfork::SpuriousDragon, block_number)
557 }
558
559 fn is_homestead_active_at_block(&self, block_number: u64) -> bool {
562 self.is_ethereum_fork_active_at_block(EthereumHardfork::Homestead, block_number)
563 }
564
565 fn is_london_active_at_block(&self, block_number: u64) -> bool {
568 self.is_ethereum_fork_active_at_block(EthereumHardfork::London, block_number)
569 }
570
571 fn is_constantinople_active_at_block(&self, block_number: u64) -> bool {
574 self.is_ethereum_fork_active_at_block(EthereumHardfork::Constantinople, block_number)
575 }
576
577 fn is_paris_active_at_block(&self, block_number: u64) -> bool {
580 self.is_ethereum_fork_active_at_block(EthereumHardfork::Paris, block_number)
581 }
582}
583
584#[derive(Debug, Clone)]
587pub struct EthereumChainHardforks {
588 forks: Vec<(EthereumHardfork, ForkCondition)>,
589}
590
591impl EthereumChainHardforks {
592 pub fn new(forks: impl IntoIterator<Item = (EthereumHardfork, ForkCondition)>) -> Self {
594 let mut forks = forks.into_iter().collect::<Vec<_>>();
595 forks.sort();
596 Self { forks }
597 }
598
599 pub fn mainnet() -> Self {
601 Self::new(EthereumHardfork::mainnet())
602 }
603
604 pub fn sepolia() -> Self {
606 Self::new(EthereumHardfork::sepolia())
607 }
608
609 pub fn holesky() -> Self {
611 Self::new(EthereumHardfork::holesky())
612 }
613
614 pub fn hoodi() -> Self {
616 Self::new(EthereumHardfork::hoodi())
617 }
618}
619
620impl EthereumHardforks for EthereumChainHardforks {
621 fn ethereum_fork_activation(&self, fork: EthereumHardfork) -> ForkCondition {
622 let Ok(idx) = self.forks.binary_search_by(|(f, _)| f.cmp(&fork)) else {
623 return ForkCondition::Never;
624 };
625
626 self.forks[idx].1
627 }
628}
629
630#[cfg(test)]
631mod tests {
632 use super::*;
633 use alloc::vec::Vec;
634 use core::str::FromStr;
635
636 #[test]
637 fn check_hardfork_from_str() {
638 let hardfork_str = [
639 "frOntier",
640 "homEstead",
641 "dao",
642 "tAngerIne",
643 "spurIousdrAgon",
644 "byzAntium",
645 "constantinople",
646 "petersburg",
647 "istanbul",
648 "muirglacier",
649 "bErlin",
650 "lonDon",
651 "arrowglacier",
652 "grayglacier",
653 "PARIS",
654 "ShAnGhAI",
655 "CaNcUn",
656 "PrAguE",
657 ];
658 let expected_hardforks = [
659 EthereumHardfork::Frontier,
660 EthereumHardfork::Homestead,
661 EthereumHardfork::Dao,
662 EthereumHardfork::Tangerine,
663 EthereumHardfork::SpuriousDragon,
664 EthereumHardfork::Byzantium,
665 EthereumHardfork::Constantinople,
666 EthereumHardfork::Petersburg,
667 EthereumHardfork::Istanbul,
668 EthereumHardfork::MuirGlacier,
669 EthereumHardfork::Berlin,
670 EthereumHardfork::London,
671 EthereumHardfork::ArrowGlacier,
672 EthereumHardfork::GrayGlacier,
673 EthereumHardfork::Paris,
674 EthereumHardfork::Shanghai,
675 EthereumHardfork::Cancun,
676 EthereumHardfork::Prague,
677 ];
678
679 let hardforks: Vec<EthereumHardfork> =
680 hardfork_str.iter().map(|h| EthereumHardfork::from_str(h).unwrap()).collect();
681
682 assert_eq!(hardforks, expected_hardforks);
683 }
684
685 #[test]
686 fn check_nonexistent_hardfork_from_str() {
687 assert!(EthereumHardfork::from_str("not a hardfork").is_err());
688 }
689}