alloy_hardforks/hardfork/
ethereum.rs

1use crate::{
2    ForkCondition,
3    arbitrum::{mainnet::*, sepolia::*},
4    ethereum::{holesky::*, hoodi::*, mainnet::*, sepolia::*},
5    hardfork,
6};
7use alloc::vec::Vec;
8use alloy_chains::{Chain, NamedChain};
9use alloy_primitives::U256;
10
11hardfork!(
12    /// The name of an Ethereum hardfork.
13    #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
14    #[derive(Default)]
15    EthereumHardfork {
16        /// Frontier: <https://blog.ethereum.org/2015/03/03/ethereum-launch-process>.
17        Frontier,
18        /// Homestead: <https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/homestead.md>.
19        Homestead,
20        /// The DAO fork: <https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/dao-fork.md>.
21        Dao,
22        /// Tangerine: <https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/tangerine-whistle.md>.
23        Tangerine,
24        /// Spurious Dragon: <https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/spurious-dragon.md>.
25        SpuriousDragon,
26        /// Byzantium: <https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/byzantium.md>.
27        Byzantium,
28        /// Constantinople: <https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/constantinople.md>.
29        Constantinople,
30        /// Petersburg: <https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/petersburg.md>.
31        Petersburg,
32        /// Istanbul: <https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/istanbul.md>.
33        Istanbul,
34        /// Muir Glacier: <https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/muir-glacier.md>.
35        MuirGlacier,
36        /// Berlin: <https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/berlin.md>.
37        Berlin,
38        /// London: <https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/london.md>.
39        London,
40        /// Arrow Glacier: <https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/arrow-glacier.md>.
41        ArrowGlacier,
42        /// Gray Glacier: <https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/gray-glacier.md>.
43        GrayGlacier,
44        /// Paris: <https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/paris.md>.
45        Paris,
46        /// Shanghai: <https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/shanghai.md>.
47        Shanghai,
48        /// Cancun: <https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/cancun.md>
49        Cancun,
50        /// Prague.
51        #[default]
52        Prague,
53        /// Osaka: <https://eips.ethereum.org/EIPS/eip-7607>
54        Osaka,
55    }
56);
57
58impl EthereumHardfork {
59    /// Retrieves the activation block for the specified hardfork on the given chain.
60    pub fn activation_block(&self, chain: Chain) -> Option<u64> {
61        if chain == Chain::mainnet() {
62            return self.mainnet_activation_block();
63        }
64        if chain == Chain::sepolia() {
65            return self.sepolia_activation_block();
66        }
67        if chain == Chain::holesky() {
68            return self.holesky_activation_block();
69        }
70        if chain == Chain::hoodi() {
71            return self.hoodi_activation_block();
72        }
73
74        None
75    }
76
77    /// Retrieves the activation block for the specified hardfork on the Ethereum mainnet.
78    pub const fn mainnet_activation_block(&self) -> Option<u64> {
79        match self {
80            Self::Frontier => Some(MAINNET_FRONTIER_BLOCK),
81            Self::Homestead => Some(MAINNET_HOMESTEAD_BLOCK),
82            Self::Dao => Some(MAINNET_DAO_BLOCK),
83            Self::Tangerine => Some(MAINNET_TANGERINE_BLOCK),
84            Self::SpuriousDragon => Some(MAINNET_SPURIOUS_DRAGON_BLOCK),
85            Self::Byzantium => Some(MAINNET_BYZANTIUM_BLOCK),
86            Self::Constantinople => Some(MAINNET_CONSTANTINOPLE_BLOCK),
87            Self::Petersburg => Some(MAINNET_PETERSBURG_BLOCK),
88            Self::Istanbul => Some(MAINNET_ISTANBUL_BLOCK),
89            Self::MuirGlacier => Some(MAINNET_MUIR_GLACIER_BLOCK),
90            Self::Berlin => Some(MAINNET_BERLIN_BLOCK),
91            Self::London => Some(MAINNET_LONDON_BLOCK),
92            Self::ArrowGlacier => Some(MAINNET_ARROW_GLACIER_BLOCK),
93            Self::GrayGlacier => Some(MAINNET_GRAY_GLACIER_BLOCK),
94            Self::Paris => Some(MAINNET_PARIS_BLOCK),
95            Self::Shanghai => Some(MAINNET_SHANGHAI_BLOCK),
96            Self::Cancun => Some(MAINNET_CANCUN_BLOCK),
97            Self::Prague => Some(MAINNET_PRAGUE_BLOCK),
98            _ => None,
99        }
100    }
101
102    /// Retrieves the activation block for the specified hardfork on the Sepolia testnet.
103    pub const fn sepolia_activation_block(&self) -> Option<u64> {
104        match self {
105            Self::Frontier
106            | Self::Homestead
107            | Self::Dao
108            | Self::Tangerine
109            | Self::SpuriousDragon
110            | Self::Byzantium
111            | Self::Constantinople
112            | Self::Petersburg
113            | Self::Istanbul
114            | Self::MuirGlacier
115            | Self::Berlin
116            | Self::London
117            | Self::ArrowGlacier
118            | Self::GrayGlacier => Some(0),
119            Self::Paris => Some(SEPOLIA_PARIS_BLOCK),
120            Self::Shanghai => Some(SEPOLIA_SHANGHAI_BLOCK),
121            Self::Cancun => Some(SEPOLIA_CANCUN_BLOCK),
122            Self::Prague => Some(SEPOLIA_PRAGUE_BLOCK),
123            _ => None,
124        }
125    }
126
127    /// Retrieves the activation block for the specified hardfork on the holesky testnet.
128    const fn holesky_activation_block(&self) -> Option<u64> {
129        match self {
130            Self::Frontier
131            | Self::Dao
132            | Self::Tangerine
133            | Self::SpuriousDragon
134            | Self::Byzantium
135            | Self::Constantinople
136            | Self::Petersburg
137            | Self::Istanbul
138            | Self::MuirGlacier
139            | Self::Berlin
140            | Self::London
141            | Self::ArrowGlacier
142            | Self::GrayGlacier
143            | Self::Paris => Some(0),
144            Self::Shanghai => Some(HOLESKY_SHANGHAI_BLOCK),
145            Self::Cancun => Some(HOLESKY_CANCUN_BLOCK),
146            Self::Prague => Some(HOLESKY_PRAGUE_BLOCK),
147            _ => None,
148        }
149    }
150
151    /// Retrieves the activation block for the specified hardfork on the hoodi testnet.
152    const fn hoodi_activation_block(&self) -> Option<u64> {
153        match self {
154            Self::Frontier
155            | Self::Dao
156            | Self::Tangerine
157            | Self::SpuriousDragon
158            | Self::Byzantium
159            | Self::Constantinople
160            | Self::Petersburg
161            | Self::Istanbul
162            | Self::MuirGlacier
163            | Self::Berlin
164            | Self::London
165            | Self::ArrowGlacier
166            | Self::GrayGlacier
167            | Self::Paris
168            | Self::Shanghai
169            | Self::Cancun => Some(0),
170            Self::Prague => Some(HOODI_PRAGUE_BLOCK),
171            _ => None,
172        }
173    }
174
175    /// Retrieves the activation block for the specified hardfork on the Arbitrum Sepolia testnet.
176    pub const fn arbitrum_sepolia_activation_block(&self) -> Option<u64> {
177        match self {
178            Self::Frontier
179            | Self::Homestead
180            | Self::Dao
181            | Self::Tangerine
182            | Self::SpuriousDragon
183            | Self::Byzantium
184            | Self::Constantinople
185            | Self::Petersburg
186            | Self::Istanbul
187            | Self::MuirGlacier
188            | Self::Berlin
189            | Self::London
190            | Self::ArrowGlacier
191            | Self::GrayGlacier
192            | Self::Paris => Some(0),
193            Self::Shanghai => Some(ARBITRUM_SEPOLIA_SHANGHAI_BLOCK),
194            Self::Cancun => Some(ARBITRUM_SEPOLIA_CANCUN_BLOCK),
195            Self::Prague => Some(ARBITRUM_SEPOLIA_PRAGUE_BLOCK),
196            _ => None,
197        }
198    }
199
200    /// Retrieves the activation block for the specified hardfork on the Arbitrum One mainnet.
201    pub const fn arbitrum_activation_block(&self) -> Option<u64> {
202        match self {
203            Self::Frontier
204            | Self::Homestead
205            | Self::Dao
206            | Self::Tangerine
207            | Self::SpuriousDragon
208            | Self::Byzantium
209            | Self::Constantinople
210            | Self::Petersburg
211            | Self::Istanbul
212            | Self::MuirGlacier
213            | Self::Berlin
214            | Self::London
215            | Self::ArrowGlacier
216            | Self::GrayGlacier
217            | Self::Paris => Some(0),
218            Self::Shanghai => Some(ARBITRUM_ONE_SHANGHAI_BLOCK),
219            Self::Cancun => Some(ARBITRUM_ONE_CANCUN_BLOCK),
220            Self::Prague => Some(ARBITRUM_ONE_PRAGUE_BLOCK),
221            _ => None,
222        }
223    }
224
225    /// Retrieves the activation timestamp for the specified hardfork on the given chain.
226    pub fn activation_timestamp(&self, chain: Chain) -> Option<u64> {
227        if chain == Chain::mainnet() {
228            return self.mainnet_activation_timestamp();
229        }
230        if chain == Chain::sepolia() {
231            return self.sepolia_activation_timestamp();
232        }
233        if chain == Chain::holesky() {
234            return self.holesky_activation_timestamp();
235        }
236        if chain == Chain::hoodi() {
237            return self.hoodi_activation_timestamp();
238        }
239
240        None
241    }
242
243    /// Retrieves the activation timestamp for the specified hardfork on the Ethereum mainnet.
244    pub const fn mainnet_activation_timestamp(&self) -> Option<u64> {
245        match self {
246            Self::Frontier => Some(MAINNET_FRONTIER_TIMESTAMP),
247            Self::Homestead => Some(MAINNET_HOMESTEAD_TIMESTAMP),
248            Self::Dao => Some(MAINNET_DAO_TIMESTAMP),
249            Self::Tangerine => Some(MAINNET_TANGERINE_TIMESTAMP),
250            Self::SpuriousDragon => Some(MAINNET_SPURIOUS_DRAGON_TIMESTAMP),
251            Self::Byzantium => Some(MAINNET_BYZANTIUM_TIMESTAMP),
252            Self::Constantinople => Some(MAINNET_CONSTANTINOPLE_TIMESTAMP),
253            Self::Petersburg => Some(MAINNET_PETERSBURG_TIMESTAMP),
254            Self::Istanbul => Some(MAINNET_ISTANBUL_TIMESTAMP),
255            Self::MuirGlacier => Some(MAINNET_MUIR_GLACIER_TIMESTAMP),
256            Self::Berlin => Some(MAINNET_BERLIN_TIMESTAMP),
257            Self::London => Some(MAINNET_LONDON_TIMESTAMP),
258            Self::ArrowGlacier => Some(MAINNET_ARROW_GLACIER_TIMESTAMP),
259            Self::GrayGlacier => Some(MAINNET_GRAY_GLACIER_TIMESTAMP),
260            Self::Paris => Some(MAINNET_PARIS_TIMESTAMP),
261            Self::Shanghai => Some(MAINNET_SHANGHAI_TIMESTAMP),
262            Self::Cancun => Some(MAINNET_CANCUN_TIMESTAMP),
263            Self::Prague => Some(MAINNET_PRAGUE_TIMESTAMP),
264            // upcoming hardforks
265            _ => None,
266        }
267    }
268
269    /// Retrieves the activation timestamp for the specified hardfork on the Sepolia testnet.
270    pub const fn sepolia_activation_timestamp(&self) -> Option<u64> {
271        match self {
272            Self::Frontier
273            | Self::Homestead
274            | Self::Dao
275            | Self::Tangerine
276            | Self::SpuriousDragon
277            | Self::Byzantium
278            | Self::Constantinople
279            | Self::Petersburg
280            | Self::Istanbul
281            | Self::MuirGlacier
282            | Self::Berlin
283            | Self::London
284            | Self::ArrowGlacier
285            | Self::GrayGlacier
286            | Self::Paris => Some(SEPOLIA_PARIS_TIMESTAMP),
287            Self::Shanghai => Some(SEPOLIA_SHANGHAI_TIMESTAMP),
288            Self::Cancun => Some(SEPOLIA_CANCUN_TIMESTAMP),
289            _ => None,
290        }
291    }
292
293    /// Retrieves the activation timestamp for the specified hardfork on the Holesky testnet.
294    pub const fn holesky_activation_timestamp(&self) -> Option<u64> {
295        match self {
296            Self::Frontier
297            | Self::Homestead
298            | Self::Dao
299            | Self::Tangerine
300            | Self::SpuriousDragon
301            | Self::Byzantium
302            | Self::Constantinople
303            | Self::Petersburg
304            | Self::Istanbul
305            | Self::MuirGlacier
306            | Self::Berlin
307            | Self::London
308            | Self::ArrowGlacier
309            | Self::GrayGlacier
310            | Self::Paris => Some(HOLESKY_PARIS_TIMESTAMP),
311            Self::Shanghai => Some(HOLESKY_SHANGHAI_TIMESTAMP),
312            Self::Cancun => Some(HOLESKY_CANCUN_TIMESTAMP),
313            _ => None,
314        }
315    }
316
317    /// Retrieves the activation timestamp for the specified hardfork on the Hoodi testnet.
318    pub const fn hoodi_activation_timestamp(&self) -> Option<u64> {
319        match self {
320            Self::Prague => Some(HOODI_PRAGUE_TIMESTAMP),
321            Self::Frontier
322            | Self::Homestead
323            | Self::Dao
324            | Self::Tangerine
325            | Self::SpuriousDragon
326            | Self::Byzantium
327            | Self::Constantinople
328            | Self::Petersburg
329            | Self::Istanbul
330            | Self::MuirGlacier
331            | Self::Berlin
332            | Self::London
333            | Self::ArrowGlacier
334            | Self::GrayGlacier
335            | Self::Paris
336            | Self::Shanghai
337            | Self::Cancun => Some(0),
338            _ => None,
339        }
340    }
341
342    /// Retrieves the activation timestamp for the specified hardfork on the Arbitrum Sepolia
343    /// testnet.
344    pub const fn arbitrum_sepolia_activation_timestamp(&self) -> Option<u64> {
345        match self {
346            Self::Frontier
347            | Self::Homestead
348            | Self::Dao
349            | Self::Tangerine
350            | Self::SpuriousDragon
351            | Self::Byzantium
352            | Self::Constantinople
353            | Self::Petersburg
354            | Self::Istanbul
355            | Self::MuirGlacier
356            | Self::Berlin
357            | Self::London
358            | Self::ArrowGlacier
359            | Self::GrayGlacier
360            | Self::Paris => Some(ARBITRUM_SEPOLIA_PARIS_TIMESTAMP),
361            Self::Shanghai => Some(ARBITRUM_SEPOLIA_SHANGHAI_TIMESTAMP),
362            Self::Cancun => Some(ARBITRUM_SEPOLIA_CANCUN_TIMESTAMP),
363            Self::Prague => Some(ARBITRUM_SEPOLIA_PRAGUE_TIMESTAMP),
364            _ => None,
365        }
366    }
367
368    /// Retrieves the activation timestamp for the specified hardfork on the Arbitrum One mainnet.
369    pub const fn arbitrum_activation_timestamp(&self) -> Option<u64> {
370        match self {
371            Self::Frontier
372            | Self::Homestead
373            | Self::Dao
374            | Self::Tangerine
375            | Self::SpuriousDragon
376            | Self::Byzantium
377            | Self::Constantinople
378            | Self::Petersburg
379            | Self::Istanbul
380            | Self::MuirGlacier
381            | Self::Berlin
382            | Self::London
383            | Self::ArrowGlacier
384            | Self::GrayGlacier
385            | Self::Paris => Some(ARBITRUM_ONE_PARIS_TIMESTAMP),
386            Self::Shanghai => Some(ARBITRUM_ONE_SHANGHAI_TIMESTAMP),
387            Self::Cancun => Some(ARBITRUM_ONE_CANCUN_TIMESTAMP),
388            Self::Prague => Some(ARBITRUM_ONE_PRAGUE_TIMESTAMP),
389            _ => None,
390        }
391    }
392
393    /// Ethereum mainnet list of hardforks.
394    pub const fn mainnet() -> [(Self, ForkCondition); 18] {
395        [
396            (Self::Frontier, ForkCondition::Block(MAINNET_FRONTIER_BLOCK)),
397            (Self::Homestead, ForkCondition::Block(MAINNET_HOMESTEAD_BLOCK)),
398            (Self::Dao, ForkCondition::Block(MAINNET_DAO_BLOCK)),
399            (Self::Tangerine, ForkCondition::Block(MAINNET_TANGERINE_BLOCK)),
400            (Self::SpuriousDragon, ForkCondition::Block(MAINNET_SPURIOUS_DRAGON_BLOCK)),
401            (Self::Byzantium, ForkCondition::Block(MAINNET_BYZANTIUM_BLOCK)),
402            (Self::Constantinople, ForkCondition::Block(MAINNET_CONSTANTINOPLE_BLOCK)),
403            (Self::Petersburg, ForkCondition::Block(MAINNET_PETERSBURG_BLOCK)),
404            (Self::Istanbul, ForkCondition::Block(MAINNET_ISTANBUL_BLOCK)),
405            (Self::MuirGlacier, ForkCondition::Block(MAINNET_MUIR_GLACIER_BLOCK)),
406            (Self::Berlin, ForkCondition::Block(MAINNET_BERLIN_BLOCK)),
407            (Self::London, ForkCondition::Block(MAINNET_LONDON_BLOCK)),
408            (Self::ArrowGlacier, ForkCondition::Block(MAINNET_ARROW_GLACIER_BLOCK)),
409            (Self::GrayGlacier, ForkCondition::Block(MAINNET_GRAY_GLACIER_BLOCK)),
410            (
411                Self::Paris,
412                ForkCondition::TTD {
413                    activation_block_number: MAINNET_PARIS_BLOCK,
414                    fork_block: None,
415                    total_difficulty: MAINNET_PARIS_TTD,
416                },
417            ),
418            (Self::Shanghai, ForkCondition::Timestamp(MAINNET_SHANGHAI_TIMESTAMP)),
419            (Self::Cancun, ForkCondition::Timestamp(MAINNET_CANCUN_TIMESTAMP)),
420            (Self::Prague, ForkCondition::Timestamp(MAINNET_PRAGUE_TIMESTAMP)),
421        ]
422    }
423
424    /// Ethereum sepolia list of hardforks.
425    pub const fn sepolia() -> [(Self, ForkCondition); 16] {
426        [
427            (Self::Frontier, ForkCondition::Block(0)),
428            (Self::Homestead, ForkCondition::Block(0)),
429            (Self::Dao, ForkCondition::Block(0)),
430            (Self::Tangerine, ForkCondition::Block(0)),
431            (Self::SpuriousDragon, ForkCondition::Block(0)),
432            (Self::Byzantium, ForkCondition::Block(0)),
433            (Self::Constantinople, ForkCondition::Block(0)),
434            (Self::Petersburg, ForkCondition::Block(0)),
435            (Self::Istanbul, ForkCondition::Block(0)),
436            (Self::MuirGlacier, ForkCondition::Block(0)),
437            (Self::Berlin, ForkCondition::Block(0)),
438            (Self::London, ForkCondition::Block(0)),
439            (
440                Self::Paris,
441                ForkCondition::TTD {
442                    activation_block_number: SEPOLIA_PARIS_BLOCK,
443                    fork_block: Some(SEPOLIA_PARIS_FORK_BLOCK),
444                    total_difficulty: SEPOLIA_PARIS_TTD,
445                },
446            ),
447            (Self::Shanghai, ForkCondition::Timestamp(SEPOLIA_SHANGHAI_TIMESTAMP)),
448            (Self::Cancun, ForkCondition::Timestamp(SEPOLIA_CANCUN_TIMESTAMP)),
449            (Self::Prague, ForkCondition::Timestamp(SEPOLIA_PRAGUE_TIMESTAMP)),
450        ]
451    }
452
453    /// Ethereum holesky list of hardforks.
454    pub const fn holesky() -> [(Self, ForkCondition); 16] {
455        [
456            (Self::Frontier, ForkCondition::Block(0)),
457            (Self::Homestead, ForkCondition::Block(0)),
458            (Self::Dao, ForkCondition::Block(0)),
459            (Self::Tangerine, ForkCondition::Block(0)),
460            (Self::SpuriousDragon, ForkCondition::Block(0)),
461            (Self::Byzantium, ForkCondition::Block(0)),
462            (Self::Constantinople, ForkCondition::Block(0)),
463            (Self::Petersburg, ForkCondition::Block(0)),
464            (Self::Istanbul, ForkCondition::Block(0)),
465            (Self::MuirGlacier, ForkCondition::Block(0)),
466            (Self::Berlin, ForkCondition::Block(0)),
467            (Self::London, ForkCondition::Block(0)),
468            (
469                Self::Paris,
470                ForkCondition::TTD {
471                    activation_block_number: 0,
472                    fork_block: Some(0),
473                    total_difficulty: U256::ZERO,
474                },
475            ),
476            (Self::Shanghai, ForkCondition::Timestamp(HOLESKY_SHANGHAI_TIMESTAMP)),
477            (Self::Cancun, ForkCondition::Timestamp(HOLESKY_CANCUN_TIMESTAMP)),
478            (Self::Prague, ForkCondition::Timestamp(HOLESKY_PRAGUE_TIMESTAMP)),
479        ]
480    }
481
482    /// Ethereum Hoodi list of hardforks.
483    pub const fn hoodi() -> [(Self, ForkCondition); 16] {
484        [
485            (Self::Frontier, ForkCondition::Block(0)),
486            (Self::Homestead, ForkCondition::Block(0)),
487            (Self::Dao, ForkCondition::Block(0)),
488            (Self::Tangerine, ForkCondition::Block(0)),
489            (Self::SpuriousDragon, ForkCondition::Block(0)),
490            (Self::Byzantium, ForkCondition::Block(0)),
491            (Self::Constantinople, ForkCondition::Block(0)),
492            (Self::Petersburg, ForkCondition::Block(0)),
493            (Self::Istanbul, ForkCondition::Block(0)),
494            (Self::MuirGlacier, ForkCondition::Block(0)),
495            (Self::Berlin, ForkCondition::Block(0)),
496            (Self::London, ForkCondition::Block(0)),
497            (
498                Self::Paris,
499                ForkCondition::TTD {
500                    activation_block_number: 0,
501                    fork_block: Some(0),
502                    total_difficulty: U256::ZERO,
503                },
504            ),
505            (Self::Shanghai, ForkCondition::Timestamp(0)),
506            (Self::Cancun, ForkCondition::Timestamp(0)),
507            (Self::Prague, ForkCondition::Timestamp(HOODI_PRAGUE_TIMESTAMP)),
508        ]
509    }
510
511    /// Convert an u64 into an `EthereumHardfork`.
512    pub const fn from_mainnet_block_number(num: u64) -> Self {
513        match num {
514            _i if num < MAINNET_HOMESTEAD_BLOCK => Self::Frontier,
515            _i if num < MAINNET_DAO_BLOCK => Self::Homestead,
516            _i if num < MAINNET_TANGERINE_BLOCK => Self::Dao,
517            _i if num < MAINNET_SPURIOUS_DRAGON_BLOCK => Self::Tangerine,
518            _i if num < MAINNET_BYZANTIUM_BLOCK => Self::SpuriousDragon,
519            _i if num < MAINNET_CONSTANTINOPLE_BLOCK => Self::Byzantium,
520            _i if num < MAINNET_ISTANBUL_BLOCK => Self::Constantinople,
521            _i if num < MAINNET_MUIR_GLACIER_BLOCK => Self::Istanbul,
522            _i if num < MAINNET_BERLIN_BLOCK => Self::MuirGlacier,
523            _i if num < MAINNET_LONDON_BLOCK => Self::Berlin,
524            _i if num < MAINNET_ARROW_GLACIER_BLOCK => Self::London,
525            _i if num < MAINNET_PARIS_BLOCK => Self::ArrowGlacier,
526            _i if num < MAINNET_SHANGHAI_BLOCK => Self::Paris,
527            _i if num < MAINNET_CANCUN_BLOCK => Self::Shanghai,
528            _i if num < MAINNET_PRAGUE_BLOCK => Self::Cancun,
529            _ => Self::Prague,
530        }
531    }
532
533    /// Reverse lookup to find the hardfork given a chain ID and block timestamp.
534    /// Returns the active hardfork at the given timestamp for the specified chain.
535    pub fn from_chain_and_timestamp(chain: Chain, timestamp: u64) -> Option<Self> {
536        let named = chain.named()?;
537
538        match named {
539            NamedChain::Mainnet => Some(match timestamp {
540                _i if timestamp < MAINNET_HOMESTEAD_TIMESTAMP => Self::Frontier,
541                _i if timestamp < MAINNET_DAO_TIMESTAMP => Self::Homestead,
542                _i if timestamp < MAINNET_TANGERINE_TIMESTAMP => Self::Dao,
543                _i if timestamp < MAINNET_SPURIOUS_DRAGON_TIMESTAMP => Self::Tangerine,
544                _i if timestamp < MAINNET_BYZANTIUM_TIMESTAMP => Self::SpuriousDragon,
545                _i if timestamp < MAINNET_PETERSBURG_TIMESTAMP => Self::Byzantium,
546                _i if timestamp < MAINNET_ISTANBUL_TIMESTAMP => Self::Petersburg,
547                _i if timestamp < MAINNET_MUIR_GLACIER_TIMESTAMP => Self::Istanbul,
548                _i if timestamp < MAINNET_BERLIN_TIMESTAMP => Self::MuirGlacier,
549                _i if timestamp < MAINNET_LONDON_TIMESTAMP => Self::Berlin,
550                _i if timestamp < MAINNET_ARROW_GLACIER_TIMESTAMP => Self::London,
551                _i if timestamp < MAINNET_GRAY_GLACIER_TIMESTAMP => Self::ArrowGlacier,
552                _i if timestamp < MAINNET_PARIS_TIMESTAMP => Self::GrayGlacier,
553                _i if timestamp < MAINNET_SHANGHAI_TIMESTAMP => Self::Paris,
554                _i if timestamp < MAINNET_CANCUN_TIMESTAMP => Self::Shanghai,
555                _i if timestamp < MAINNET_PRAGUE_TIMESTAMP => Self::Cancun,
556                _ => Self::Prague,
557            }),
558            NamedChain::Sepolia => Some(match timestamp {
559                _i if timestamp < SEPOLIA_PARIS_TIMESTAMP => Self::London,
560                _i if timestamp < SEPOLIA_SHANGHAI_TIMESTAMP => Self::Paris,
561                _i if timestamp < SEPOLIA_CANCUN_TIMESTAMP => Self::Shanghai,
562                _i if timestamp < SEPOLIA_PRAGUE_TIMESTAMP => Self::Cancun,
563                _ => Self::Prague,
564            }),
565            NamedChain::Holesky => Some(match timestamp {
566                _i if timestamp < HOLESKY_SHANGHAI_TIMESTAMP => Self::Paris,
567                _i if timestamp < HOLESKY_CANCUN_TIMESTAMP => Self::Shanghai,
568                _i if timestamp < HOLESKY_PRAGUE_TIMESTAMP => Self::Cancun,
569                _ => Self::Prague,
570            }),
571            NamedChain::Hoodi => Some(match timestamp {
572                _i if timestamp < HOODI_PRAGUE_TIMESTAMP => Self::Cancun,
573                _ => Self::Prague,
574            }),
575            NamedChain::Arbitrum => Some(match timestamp {
576                _i if timestamp < ARBITRUM_ONE_SHANGHAI_TIMESTAMP => Self::Paris,
577                _i if timestamp < ARBITRUM_ONE_CANCUN_TIMESTAMP => Self::Shanghai,
578                _i if timestamp < ARBITRUM_ONE_PRAGUE_TIMESTAMP => Self::Cancun,
579                _ => Self::Prague,
580            }),
581            NamedChain::ArbitrumSepolia => Some(match timestamp {
582                _i if timestamp < ARBITRUM_SEPOLIA_SHANGHAI_TIMESTAMP => Self::Paris,
583                _i if timestamp < ARBITRUM_SEPOLIA_CANCUN_TIMESTAMP => Self::Shanghai,
584                _i if timestamp < ARBITRUM_SEPOLIA_PRAGUE_TIMESTAMP => Self::Cancun,
585                _ => Self::Prague,
586            }),
587            _ => None,
588        }
589    }
590}
591
592/// Helper methods for Ethereum forks.
593#[auto_impl::auto_impl(&, Arc)]
594pub trait EthereumHardforks {
595    /// Retrieves [`ForkCondition`] by an [`EthereumHardfork`]. If `fork` is not present, returns
596    /// [`ForkCondition::Never`].
597    fn ethereum_fork_activation(&self, fork: EthereumHardfork) -> ForkCondition;
598
599    /// Convenience method to check if an [`EthereumHardfork`] is active at a given timestamp.
600    fn is_ethereum_fork_active_at_timestamp(&self, fork: EthereumHardfork, timestamp: u64) -> bool {
601        self.ethereum_fork_activation(fork).active_at_timestamp(timestamp)
602    }
603
604    /// Convenience method to check if an [`EthereumHardfork`] is active at a given block number.
605    fn is_ethereum_fork_active_at_block(&self, fork: EthereumHardfork, block_number: u64) -> bool {
606        self.ethereum_fork_activation(fork).active_at_block(block_number)
607    }
608
609    /// Convenience method to check if [`EthereumHardfork::Shanghai`] is active at a given
610    /// timestamp.
611    fn is_shanghai_active_at_timestamp(&self, timestamp: u64) -> bool {
612        self.is_ethereum_fork_active_at_timestamp(EthereumHardfork::Shanghai, timestamp)
613    }
614
615    /// Convenience method to check if [`EthereumHardfork::Cancun`] is active at a given timestamp.
616    fn is_cancun_active_at_timestamp(&self, timestamp: u64) -> bool {
617        self.is_ethereum_fork_active_at_timestamp(EthereumHardfork::Cancun, timestamp)
618    }
619
620    /// Convenience method to check if [`EthereumHardfork::Prague`] is active at a given timestamp.
621    fn is_prague_active_at_timestamp(&self, timestamp: u64) -> bool {
622        self.is_ethereum_fork_active_at_timestamp(EthereumHardfork::Prague, timestamp)
623    }
624
625    /// Convenience method to check if [`EthereumHardfork::Osaka`] is active at a given timestamp.
626    fn is_osaka_active_at_timestamp(&self, timestamp: u64) -> bool {
627        self.is_ethereum_fork_active_at_timestamp(EthereumHardfork::Osaka, timestamp)
628    }
629
630    /// Convenience method to check if [`EthereumHardfork::Byzantium`] is active at a given block
631    /// number.
632    fn is_byzantium_active_at_block(&self, block_number: u64) -> bool {
633        self.is_ethereum_fork_active_at_block(EthereumHardfork::Byzantium, block_number)
634    }
635
636    /// Convenience method to check if [`EthereumHardfork::SpuriousDragon`] is active at a given
637    /// block number.
638    fn is_spurious_dragon_active_at_block(&self, block_number: u64) -> bool {
639        self.is_ethereum_fork_active_at_block(EthereumHardfork::SpuriousDragon, block_number)
640    }
641
642    /// Convenience method to check if [`EthereumHardfork::Homestead`] is active at a given block
643    /// number.
644    fn is_homestead_active_at_block(&self, block_number: u64) -> bool {
645        self.is_ethereum_fork_active_at_block(EthereumHardfork::Homestead, block_number)
646    }
647
648    /// Convenience method to check if [`EthereumHardfork::London`] is active at a given block
649    /// number.
650    fn is_london_active_at_block(&self, block_number: u64) -> bool {
651        self.is_ethereum_fork_active_at_block(EthereumHardfork::London, block_number)
652    }
653
654    /// Convenience method to check if [`EthereumHardfork::Constantinople`] is active at a given
655    /// block number.
656    fn is_constantinople_active_at_block(&self, block_number: u64) -> bool {
657        self.is_ethereum_fork_active_at_block(EthereumHardfork::Constantinople, block_number)
658    }
659
660    /// Convenience method to check if [`EthereumHardfork::Paris`] is active at a given block
661    /// number.
662    fn is_paris_active_at_block(&self, block_number: u64) -> bool {
663        self.is_ethereum_fork_active_at_block(EthereumHardfork::Paris, block_number)
664    }
665}
666
667/// A type allowing to configure activation [`ForkCondition`]s for a given list of
668/// [`EthereumHardfork`]s.
669#[derive(Debug, Clone)]
670pub struct EthereumChainHardforks {
671    forks: Vec<(EthereumHardfork, ForkCondition)>,
672}
673
674impl EthereumChainHardforks {
675    /// Creates a new [`EthereumChainHardforks`] with the given list of forks.
676    pub fn new(forks: impl IntoIterator<Item = (EthereumHardfork, ForkCondition)>) -> Self {
677        let mut forks = forks.into_iter().collect::<Vec<_>>();
678        forks.sort();
679        Self { forks }
680    }
681
682    /// Creates a new [`EthereumChainHardforks`] with Mainnet configuration.
683    pub fn mainnet() -> Self {
684        Self::new(EthereumHardfork::mainnet())
685    }
686
687    /// Creates a new [`EthereumChainHardforks`] with Sepolia configuration.
688    pub fn sepolia() -> Self {
689        Self::new(EthereumHardfork::sepolia())
690    }
691
692    /// Creates a new [`EthereumChainHardforks`] with Holesky configuration.
693    pub fn holesky() -> Self {
694        Self::new(EthereumHardfork::holesky())
695    }
696
697    /// Creates a new [`EthereumChainHardforks`] with Hoodi configuration.
698    pub fn hoodi() -> Self {
699        Self::new(EthereumHardfork::hoodi())
700    }
701}
702
703impl EthereumHardforks for EthereumChainHardforks {
704    fn ethereum_fork_activation(&self, fork: EthereumHardfork) -> ForkCondition {
705        let Ok(idx) = self.forks.binary_search_by(|(f, _)| f.cmp(&fork)) else {
706            return ForkCondition::Never;
707        };
708
709        self.forks[idx].1
710    }
711}
712
713#[cfg(test)]
714mod tests {
715    use super::*;
716    use alloc::vec::Vec;
717    use core::str::FromStr;
718
719    #[test]
720    fn check_hardfork_from_str() {
721        let hardfork_str = [
722            "frOntier",
723            "homEstead",
724            "dao",
725            "tAngerIne",
726            "spurIousdrAgon",
727            "byzAntium",
728            "constantinople",
729            "petersburg",
730            "istanbul",
731            "muirglacier",
732            "bErlin",
733            "lonDon",
734            "arrowglacier",
735            "grayglacier",
736            "PARIS",
737            "ShAnGhAI",
738            "CaNcUn",
739            "PrAguE",
740        ];
741        let expected_hardforks = [
742            EthereumHardfork::Frontier,
743            EthereumHardfork::Homestead,
744            EthereumHardfork::Dao,
745            EthereumHardfork::Tangerine,
746            EthereumHardfork::SpuriousDragon,
747            EthereumHardfork::Byzantium,
748            EthereumHardfork::Constantinople,
749            EthereumHardfork::Petersburg,
750            EthereumHardfork::Istanbul,
751            EthereumHardfork::MuirGlacier,
752            EthereumHardfork::Berlin,
753            EthereumHardfork::London,
754            EthereumHardfork::ArrowGlacier,
755            EthereumHardfork::GrayGlacier,
756            EthereumHardfork::Paris,
757            EthereumHardfork::Shanghai,
758            EthereumHardfork::Cancun,
759            EthereumHardfork::Prague,
760        ];
761
762        let hardforks: Vec<EthereumHardfork> =
763            hardfork_str.iter().map(|h| EthereumHardfork::from_str(h).unwrap()).collect();
764
765        assert_eq!(hardforks, expected_hardforks);
766    }
767
768    #[test]
769    fn check_nonexistent_hardfork_from_str() {
770        assert!(EthereumHardfork::from_str("not a hardfork").is_err());
771    }
772
773    #[test]
774    fn test_reverse_lookup_by_chain_id() {
775        // Test major hardforks across all supported Ethereum chains
776        let test_cases = [
777            // (chain_id, timestamp, expected) - Key transitions for each chain
778            // Mainnet
779            // At block 0: Frontier
780            (Chain::mainnet(), MAINNET_FRONTIER_TIMESTAMP - 1, EthereumHardfork::Frontier),
781            (Chain::mainnet(), MAINNET_FRONTIER_TIMESTAMP, EthereumHardfork::Frontier),
782            (Chain::mainnet(), MAINNET_HOMESTEAD_TIMESTAMP, EthereumHardfork::Homestead),
783            (Chain::mainnet(), MAINNET_DAO_TIMESTAMP, EthereumHardfork::Dao),
784            (Chain::mainnet(), MAINNET_TANGERINE_TIMESTAMP, EthereumHardfork::Tangerine),
785            (Chain::mainnet(), MAINNET_SPURIOUS_DRAGON_TIMESTAMP, EthereumHardfork::SpuriousDragon),
786            (Chain::mainnet(), MAINNET_BYZANTIUM_TIMESTAMP, EthereumHardfork::Byzantium),
787            (Chain::mainnet(), MAINNET_PETERSBURG_TIMESTAMP, EthereumHardfork::Petersburg),
788            (Chain::mainnet(), MAINNET_ISTANBUL_TIMESTAMP, EthereumHardfork::Istanbul),
789            (Chain::mainnet(), MAINNET_MUIR_GLACIER_TIMESTAMP, EthereumHardfork::MuirGlacier),
790            (Chain::mainnet(), MAINNET_BERLIN_TIMESTAMP, EthereumHardfork::Berlin),
791            (Chain::mainnet(), MAINNET_LONDON_TIMESTAMP, EthereumHardfork::London),
792            (Chain::mainnet(), MAINNET_ARROW_GLACIER_TIMESTAMP, EthereumHardfork::ArrowGlacier),
793            (Chain::mainnet(), MAINNET_GRAY_GLACIER_TIMESTAMP, EthereumHardfork::GrayGlacier),
794            (Chain::mainnet(), MAINNET_PARIS_TIMESTAMP, EthereumHardfork::Paris),
795            (Chain::mainnet(), MAINNET_SHANGHAI_TIMESTAMP, EthereumHardfork::Shanghai),
796            (Chain::mainnet(), MAINNET_CANCUN_TIMESTAMP, EthereumHardfork::Cancun),
797            // Sepolia
798            // At block 0: London
799            (Chain::sepolia(), SEPOLIA_PARIS_TIMESTAMP - 1, EthereumHardfork::London),
800            (Chain::sepolia(), SEPOLIA_PARIS_TIMESTAMP, EthereumHardfork::Paris),
801            (Chain::sepolia(), SEPOLIA_SHANGHAI_TIMESTAMP - 1, EthereumHardfork::Paris),
802            (Chain::sepolia(), SEPOLIA_SHANGHAI_TIMESTAMP, EthereumHardfork::Shanghai),
803            (Chain::sepolia(), SEPOLIA_CANCUN_TIMESTAMP, EthereumHardfork::Cancun),
804            (Chain::sepolia(), SEPOLIA_PRAGUE_TIMESTAMP - 1, EthereumHardfork::Cancun),
805            (Chain::sepolia(), SEPOLIA_PRAGUE_TIMESTAMP + 1, EthereumHardfork::Prague),
806            // Holesky
807            // At block 0: Paris
808            (Chain::holesky(), HOLESKY_PARIS_TIMESTAMP - 1, EthereumHardfork::Paris),
809            (Chain::holesky(), HOLESKY_PARIS_TIMESTAMP, EthereumHardfork::Paris),
810            (Chain::holesky(), HOLESKY_SHANGHAI_TIMESTAMP - 1, EthereumHardfork::Paris),
811            (Chain::holesky(), HOLESKY_SHANGHAI_TIMESTAMP, EthereumHardfork::Shanghai),
812            (Chain::holesky(), HOLESKY_CANCUN_TIMESTAMP, EthereumHardfork::Cancun),
813            (Chain::holesky(), HOLESKY_PRAGUE_TIMESTAMP - 1, EthereumHardfork::Cancun),
814            (Chain::holesky(), HOLESKY_PRAGUE_TIMESTAMP + 1, EthereumHardfork::Prague),
815            // Arbitrum One
816            // At block 0: Paris
817            (Chain::arbitrum_mainnet(), ARBITRUM_ONE_PARIS_TIMESTAMP - 1, EthereumHardfork::Paris),
818            (Chain::arbitrum_mainnet(), ARBITRUM_ONE_PARIS_TIMESTAMP, EthereumHardfork::Paris),
819            (
820                Chain::arbitrum_mainnet(),
821                ARBITRUM_ONE_SHANGHAI_TIMESTAMP - 1,
822                EthereumHardfork::Paris,
823            ),
824            (
825                Chain::arbitrum_mainnet(),
826                ARBITRUM_ONE_SHANGHAI_TIMESTAMP,
827                EthereumHardfork::Shanghai,
828            ),
829            (Chain::arbitrum_mainnet(), ARBITRUM_ONE_CANCUN_TIMESTAMP, EthereumHardfork::Cancun),
830            (
831                Chain::arbitrum_mainnet(),
832                ARBITRUM_ONE_PRAGUE_TIMESTAMP - 1,
833                EthereumHardfork::Cancun,
834            ),
835            (
836                Chain::arbitrum_mainnet(),
837                ARBITRUM_ONE_PRAGUE_TIMESTAMP + 1,
838                EthereumHardfork::Prague,
839            ),
840            // Arbitrum Sepolia
841            // At block 0: Paris
842            (
843                Chain::arbitrum_sepolia(),
844                ARBITRUM_SEPOLIA_PARIS_TIMESTAMP - 1,
845                EthereumHardfork::Paris,
846            ),
847            (Chain::arbitrum_sepolia(), ARBITRUM_SEPOLIA_PARIS_TIMESTAMP, EthereumHardfork::Paris),
848            (
849                Chain::arbitrum_sepolia(),
850                ARBITRUM_SEPOLIA_SHANGHAI_TIMESTAMP - 1,
851                EthereumHardfork::Paris,
852            ),
853            (
854                Chain::arbitrum_sepolia(),
855                ARBITRUM_SEPOLIA_SHANGHAI_TIMESTAMP,
856                EthereumHardfork::Shanghai,
857            ),
858            (
859                Chain::arbitrum_sepolia(),
860                ARBITRUM_SEPOLIA_CANCUN_TIMESTAMP,
861                EthereumHardfork::Cancun,
862            ),
863            (
864                Chain::arbitrum_sepolia(),
865                ARBITRUM_SEPOLIA_PRAGUE_TIMESTAMP - 1,
866                EthereumHardfork::Cancun,
867            ),
868            (
869                Chain::arbitrum_sepolia(),
870                ARBITRUM_SEPOLIA_PRAGUE_TIMESTAMP + 1,
871                EthereumHardfork::Prague,
872            ),
873        ];
874
875        for (chain_id, timestamp, expected) in test_cases {
876            assert_eq!(
877                EthereumHardfork::from_chain_and_timestamp(chain_id, timestamp),
878                Some(expected),
879                "chain {chain_id} at timestamp {timestamp}"
880            );
881        }
882
883        // Edge cases
884        assert_eq!(
885            EthereumHardfork::from_chain_and_timestamp(Chain::from_id(99999), 1000000),
886            None
887        );
888    }
889
890    #[test]
891    fn test_timestamp_functions_consistency() {
892        let test_cases = [
893            (MAINNET_LONDON_TIMESTAMP, EthereumHardfork::London),
894            (MAINNET_SHANGHAI_TIMESTAMP, EthereumHardfork::Shanghai),
895            (MAINNET_CANCUN_TIMESTAMP, EthereumHardfork::Cancun),
896        ];
897
898        for (timestamp, fork) in test_cases {
899            assert_eq!(
900                EthereumHardfork::from_chain_and_timestamp(Chain::mainnet(), timestamp),
901                Some(fork)
902            );
903            assert_eq!(fork.activation_timestamp(Chain::mainnet()), Some(timestamp));
904        }
905    }
906}