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        // BPOs: <https://eips.ethereum.org/EIPS/eip-7892>
56        /// BPO 1
57        Bpo1,
58        /// BPO 2
59        Bpo2,
60        /// BPO 3
61        Bpo3,
62        /// BPO 4
63        Bpo4,
64        /// BPO 5
65        Bpo5,
66        /// Amsterdam: <https://eips.ethereum.org/EIPS/eip-7773>
67        Amsterdam,
68    }
69);
70
71impl EthereumHardfork {
72    /// Retrieves the activation block for the specified hardfork on the given chain.
73    pub fn activation_block(&self, chain: Chain) -> Option<u64> {
74        if chain == Chain::mainnet() {
75            return self.mainnet_activation_block();
76        }
77        if chain == Chain::sepolia() {
78            return self.sepolia_activation_block();
79        }
80        if chain == Chain::holesky() {
81            return self.holesky_activation_block();
82        }
83        if chain == Chain::hoodi() {
84            return self.hoodi_activation_block();
85        }
86
87        None
88    }
89
90    /// Retrieves the activation block for the specified hardfork on the Ethereum mainnet.
91    pub const fn mainnet_activation_block(&self) -> Option<u64> {
92        match self {
93            Self::Frontier => Some(MAINNET_FRONTIER_BLOCK),
94            Self::Homestead => Some(MAINNET_HOMESTEAD_BLOCK),
95            Self::Dao => Some(MAINNET_DAO_BLOCK),
96            Self::Tangerine => Some(MAINNET_TANGERINE_BLOCK),
97            Self::SpuriousDragon => Some(MAINNET_SPURIOUS_DRAGON_BLOCK),
98            Self::Byzantium => Some(MAINNET_BYZANTIUM_BLOCK),
99            Self::Constantinople => Some(MAINNET_CONSTANTINOPLE_BLOCK),
100            Self::Petersburg => Some(MAINNET_PETERSBURG_BLOCK),
101            Self::Istanbul => Some(MAINNET_ISTANBUL_BLOCK),
102            Self::MuirGlacier => Some(MAINNET_MUIR_GLACIER_BLOCK),
103            Self::Berlin => Some(MAINNET_BERLIN_BLOCK),
104            Self::London => Some(MAINNET_LONDON_BLOCK),
105            Self::ArrowGlacier => Some(MAINNET_ARROW_GLACIER_BLOCK),
106            Self::GrayGlacier => Some(MAINNET_GRAY_GLACIER_BLOCK),
107            Self::Paris => Some(MAINNET_PARIS_BLOCK),
108            Self::Shanghai => Some(MAINNET_SHANGHAI_BLOCK),
109            Self::Cancun => Some(MAINNET_CANCUN_BLOCK),
110            Self::Prague => Some(MAINNET_PRAGUE_BLOCK),
111            _ => None,
112        }
113    }
114
115    /// Retrieves the activation block for the specified hardfork on the Sepolia testnet.
116    pub const fn sepolia_activation_block(&self) -> Option<u64> {
117        match self {
118            Self::Frontier
119            | Self::Homestead
120            | Self::Dao
121            | Self::Tangerine
122            | Self::SpuriousDragon
123            | Self::Byzantium
124            | Self::Constantinople
125            | Self::Petersburg
126            | Self::Istanbul
127            | Self::MuirGlacier
128            | Self::Berlin
129            | Self::London
130            | Self::ArrowGlacier
131            | Self::GrayGlacier => Some(0),
132            Self::Paris => Some(SEPOLIA_PARIS_BLOCK),
133            Self::Shanghai => Some(SEPOLIA_SHANGHAI_BLOCK),
134            Self::Cancun => Some(SEPOLIA_CANCUN_BLOCK),
135            Self::Prague => Some(SEPOLIA_PRAGUE_BLOCK),
136            _ => None,
137        }
138    }
139
140    /// Retrieves the activation block for the specified hardfork on the holesky testnet.
141    const fn holesky_activation_block(&self) -> Option<u64> {
142        match self {
143            Self::Frontier
144            | Self::Homestead
145            | Self::Dao
146            | Self::Tangerine
147            | Self::SpuriousDragon
148            | Self::Byzantium
149            | Self::Constantinople
150            | Self::Petersburg
151            | Self::Istanbul
152            | Self::MuirGlacier
153            | Self::Berlin
154            | Self::London
155            | Self::ArrowGlacier
156            | Self::GrayGlacier
157            | Self::Paris => Some(0),
158            Self::Shanghai => Some(HOLESKY_SHANGHAI_BLOCK),
159            Self::Cancun => Some(HOLESKY_CANCUN_BLOCK),
160            Self::Prague => Some(HOLESKY_PRAGUE_BLOCK),
161            _ => None,
162        }
163    }
164
165    /// Retrieves the activation block for the specified hardfork on the hoodi testnet.
166    const fn hoodi_activation_block(&self) -> Option<u64> {
167        match self {
168            Self::Frontier
169            | Self::Homestead
170            | Self::Dao
171            | Self::Tangerine
172            | Self::SpuriousDragon
173            | Self::Byzantium
174            | Self::Constantinople
175            | Self::Petersburg
176            | Self::Istanbul
177            | Self::MuirGlacier
178            | Self::Berlin
179            | Self::London
180            | Self::ArrowGlacier
181            | Self::GrayGlacier
182            | Self::Paris
183            | Self::Shanghai
184            | Self::Cancun => Some(0),
185            Self::Prague => Some(HOODI_PRAGUE_BLOCK),
186            _ => None,
187        }
188    }
189
190    /// Retrieves the activation block for the specified hardfork on the Arbitrum Sepolia testnet.
191    pub const fn arbitrum_sepolia_activation_block(&self) -> Option<u64> {
192        match self {
193            Self::Frontier
194            | Self::Homestead
195            | Self::Dao
196            | Self::Tangerine
197            | Self::SpuriousDragon
198            | Self::Byzantium
199            | Self::Constantinople
200            | Self::Petersburg
201            | Self::Istanbul
202            | Self::MuirGlacier
203            | Self::Berlin
204            | Self::London
205            | Self::ArrowGlacier
206            | Self::GrayGlacier
207            | Self::Paris => Some(0),
208            Self::Shanghai => Some(ARBITRUM_SEPOLIA_SHANGHAI_BLOCK),
209            Self::Cancun => Some(ARBITRUM_SEPOLIA_CANCUN_BLOCK),
210            Self::Prague => Some(ARBITRUM_SEPOLIA_PRAGUE_BLOCK),
211            _ => None,
212        }
213    }
214
215    /// Retrieves the activation block for the specified hardfork on the Arbitrum One mainnet.
216    pub const fn arbitrum_activation_block(&self) -> Option<u64> {
217        match self {
218            Self::Frontier
219            | Self::Homestead
220            | Self::Dao
221            | Self::Tangerine
222            | Self::SpuriousDragon
223            | Self::Byzantium
224            | Self::Constantinople
225            | Self::Petersburg
226            | Self::Istanbul
227            | Self::MuirGlacier
228            | Self::Berlin
229            | Self::London
230            | Self::ArrowGlacier
231            | Self::GrayGlacier
232            | Self::Paris => Some(0),
233            Self::Shanghai => Some(ARBITRUM_ONE_SHANGHAI_BLOCK),
234            Self::Cancun => Some(ARBITRUM_ONE_CANCUN_BLOCK),
235            Self::Prague => Some(ARBITRUM_ONE_PRAGUE_BLOCK),
236            _ => None,
237        }
238    }
239
240    /// Retrieves the activation timestamp for the specified hardfork on the given chain.
241    pub fn activation_timestamp(&self, chain: Chain) -> Option<u64> {
242        if chain == Chain::mainnet() {
243            return self.mainnet_activation_timestamp();
244        }
245        if chain == Chain::sepolia() {
246            return self.sepolia_activation_timestamp();
247        }
248        if chain == Chain::holesky() {
249            return self.holesky_activation_timestamp();
250        }
251        if chain == Chain::hoodi() {
252            return self.hoodi_activation_timestamp();
253        }
254
255        None
256    }
257
258    /// Retrieves the activation timestamp for the specified hardfork on the Ethereum mainnet.
259    pub const fn mainnet_activation_timestamp(&self) -> Option<u64> {
260        match self {
261            Self::Frontier => Some(MAINNET_FRONTIER_TIMESTAMP),
262            Self::Homestead => Some(MAINNET_HOMESTEAD_TIMESTAMP),
263            Self::Dao => Some(MAINNET_DAO_TIMESTAMP),
264            Self::Tangerine => Some(MAINNET_TANGERINE_TIMESTAMP),
265            Self::SpuriousDragon => Some(MAINNET_SPURIOUS_DRAGON_TIMESTAMP),
266            Self::Byzantium => Some(MAINNET_BYZANTIUM_TIMESTAMP),
267            Self::Constantinople => Some(MAINNET_CONSTANTINOPLE_TIMESTAMP),
268            Self::Petersburg => Some(MAINNET_PETERSBURG_TIMESTAMP),
269            Self::Istanbul => Some(MAINNET_ISTANBUL_TIMESTAMP),
270            Self::MuirGlacier => Some(MAINNET_MUIR_GLACIER_TIMESTAMP),
271            Self::Berlin => Some(MAINNET_BERLIN_TIMESTAMP),
272            Self::London => Some(MAINNET_LONDON_TIMESTAMP),
273            Self::ArrowGlacier => Some(MAINNET_ARROW_GLACIER_TIMESTAMP),
274            Self::GrayGlacier => Some(MAINNET_GRAY_GLACIER_TIMESTAMP),
275            Self::Paris => Some(MAINNET_PARIS_TIMESTAMP),
276            Self::Shanghai => Some(MAINNET_SHANGHAI_TIMESTAMP),
277            Self::Cancun => Some(MAINNET_CANCUN_TIMESTAMP),
278            Self::Prague => Some(MAINNET_PRAGUE_TIMESTAMP),
279            Self::Osaka => Some(MAINNET_OSAKA_TIMESTAMP),
280            Self::Bpo1 => Some(MAINNET_BPO1_TIMESTAMP),
281            Self::Bpo2 => Some(MAINNET_BPO2_TIMESTAMP),
282            _ => None,
283        }
284    }
285
286    /// Retrieves the activation timestamp for the specified hardfork on the Sepolia testnet.
287    pub const fn sepolia_activation_timestamp(&self) -> Option<u64> {
288        match self {
289            Self::Frontier
290            | Self::Homestead
291            | Self::Dao
292            | Self::Tangerine
293            | Self::SpuriousDragon
294            | Self::Byzantium
295            | Self::Constantinople
296            | Self::Petersburg
297            | Self::Istanbul
298            | Self::MuirGlacier
299            | Self::Berlin
300            | Self::London
301            | Self::ArrowGlacier
302            | Self::GrayGlacier
303            | Self::Paris => Some(SEPOLIA_PARIS_TIMESTAMP),
304            Self::Shanghai => Some(SEPOLIA_SHANGHAI_TIMESTAMP),
305            Self::Cancun => Some(SEPOLIA_CANCUN_TIMESTAMP),
306            Self::Prague => Some(SEPOLIA_PRAGUE_TIMESTAMP),
307            Self::Osaka => Some(SEPOLIA_OSAKA_TIMESTAMP),
308            Self::Bpo1 => Some(SEPOLIA_BPO1_TIMESTAMP),
309            Self::Bpo2 => Some(SEPOLIA_BPO2_TIMESTAMP),
310            _ => None,
311        }
312    }
313
314    /// Retrieves the activation timestamp for the specified hardfork on the Holesky testnet.
315    pub const fn holesky_activation_timestamp(&self) -> Option<u64> {
316        match self {
317            Self::Frontier
318            | Self::Homestead
319            | Self::Dao
320            | Self::Tangerine
321            | Self::SpuriousDragon
322            | Self::Byzantium
323            | Self::Constantinople
324            | Self::Petersburg
325            | Self::Istanbul
326            | Self::MuirGlacier
327            | Self::Berlin
328            | Self::London
329            | Self::ArrowGlacier
330            | Self::GrayGlacier
331            | Self::Paris => Some(HOLESKY_PARIS_TIMESTAMP),
332            Self::Shanghai => Some(HOLESKY_SHANGHAI_TIMESTAMP),
333            Self::Cancun => Some(HOLESKY_CANCUN_TIMESTAMP),
334            Self::Prague => Some(HOLESKY_PRAGUE_TIMESTAMP),
335            Self::Osaka => Some(HOLESKY_OSAKA_TIMESTAMP),
336            Self::Bpo1 => Some(HOLESKY_BPO1_TIMESTAMP),
337            Self::Bpo2 => Some(HOLESKY_BPO2_TIMESTAMP),
338            _ => None,
339        }
340    }
341
342    /// Retrieves the activation timestamp for the specified hardfork on the Hoodi testnet.
343    pub const fn hoodi_activation_timestamp(&self) -> Option<u64> {
344        match self {
345            Self::Frontier
346            | Self::Homestead
347            | Self::Dao
348            | Self::Tangerine
349            | Self::SpuriousDragon
350            | Self::Byzantium
351            | Self::Constantinople
352            | Self::Petersburg
353            | Self::Istanbul
354            | Self::MuirGlacier
355            | Self::Berlin
356            | Self::London
357            | Self::ArrowGlacier
358            | Self::GrayGlacier
359            | Self::Paris
360            | Self::Shanghai
361            | Self::Cancun => Some(0),
362            Self::Prague => Some(HOODI_PRAGUE_TIMESTAMP),
363            Self::Osaka => Some(HOODI_OSAKA_TIMESTAMP),
364            Self::Bpo1 => Some(HOODI_BPO1_TIMESTAMP),
365            Self::Bpo2 => Some(HOODI_BPO2_TIMESTAMP),
366            _ => None,
367        }
368    }
369
370    /// Retrieves the activation timestamp for the specified hardfork on the Arbitrum Sepolia
371    /// testnet.
372    pub const fn arbitrum_sepolia_activation_timestamp(&self) -> Option<u64> {
373        match self {
374            Self::Frontier
375            | Self::Homestead
376            | Self::Dao
377            | Self::Tangerine
378            | Self::SpuriousDragon
379            | Self::Byzantium
380            | Self::Constantinople
381            | Self::Petersburg
382            | Self::Istanbul
383            | Self::MuirGlacier
384            | Self::Berlin
385            | Self::London
386            | Self::ArrowGlacier
387            | Self::GrayGlacier
388            | Self::Paris => Some(ARBITRUM_SEPOLIA_PARIS_TIMESTAMP),
389            Self::Shanghai => Some(ARBITRUM_SEPOLIA_SHANGHAI_TIMESTAMP),
390            Self::Cancun => Some(ARBITRUM_SEPOLIA_CANCUN_TIMESTAMP),
391            Self::Prague => Some(ARBITRUM_SEPOLIA_PRAGUE_TIMESTAMP),
392            _ => None,
393        }
394    }
395
396    /// Retrieves the activation timestamp for the specified hardfork on the Arbitrum One mainnet.
397    pub const fn arbitrum_activation_timestamp(&self) -> Option<u64> {
398        match self {
399            Self::Frontier
400            | Self::Homestead
401            | Self::Dao
402            | Self::Tangerine
403            | Self::SpuriousDragon
404            | Self::Byzantium
405            | Self::Constantinople
406            | Self::Petersburg
407            | Self::Istanbul
408            | Self::MuirGlacier
409            | Self::Berlin
410            | Self::London
411            | Self::ArrowGlacier
412            | Self::GrayGlacier
413            | Self::Paris => Some(ARBITRUM_ONE_PARIS_TIMESTAMP),
414            Self::Shanghai => Some(ARBITRUM_ONE_SHANGHAI_TIMESTAMP),
415            Self::Cancun => Some(ARBITRUM_ONE_CANCUN_TIMESTAMP),
416            Self::Prague => Some(ARBITRUM_ONE_PRAGUE_TIMESTAMP),
417            _ => None,
418        }
419    }
420
421    /// Ethereum mainnet list of hardforks.
422    pub const fn mainnet() -> [(Self, ForkCondition); 21] {
423        [
424            (Self::Frontier, ForkCondition::Block(MAINNET_FRONTIER_BLOCK)),
425            (Self::Homestead, ForkCondition::Block(MAINNET_HOMESTEAD_BLOCK)),
426            (Self::Dao, ForkCondition::Block(MAINNET_DAO_BLOCK)),
427            (Self::Tangerine, ForkCondition::Block(MAINNET_TANGERINE_BLOCK)),
428            (Self::SpuriousDragon, ForkCondition::Block(MAINNET_SPURIOUS_DRAGON_BLOCK)),
429            (Self::Byzantium, ForkCondition::Block(MAINNET_BYZANTIUM_BLOCK)),
430            (Self::Constantinople, ForkCondition::Block(MAINNET_CONSTANTINOPLE_BLOCK)),
431            (Self::Petersburg, ForkCondition::Block(MAINNET_PETERSBURG_BLOCK)),
432            (Self::Istanbul, ForkCondition::Block(MAINNET_ISTANBUL_BLOCK)),
433            (Self::MuirGlacier, ForkCondition::Block(MAINNET_MUIR_GLACIER_BLOCK)),
434            (Self::Berlin, ForkCondition::Block(MAINNET_BERLIN_BLOCK)),
435            (Self::London, ForkCondition::Block(MAINNET_LONDON_BLOCK)),
436            (Self::ArrowGlacier, ForkCondition::Block(MAINNET_ARROW_GLACIER_BLOCK)),
437            (Self::GrayGlacier, ForkCondition::Block(MAINNET_GRAY_GLACIER_BLOCK)),
438            (
439                Self::Paris,
440                ForkCondition::TTD {
441                    activation_block_number: MAINNET_PARIS_BLOCK,
442                    fork_block: None,
443                    total_difficulty: MAINNET_PARIS_TTD,
444                },
445            ),
446            (Self::Shanghai, ForkCondition::Timestamp(MAINNET_SHANGHAI_TIMESTAMP)),
447            (Self::Cancun, ForkCondition::Timestamp(MAINNET_CANCUN_TIMESTAMP)),
448            (Self::Prague, ForkCondition::Timestamp(MAINNET_PRAGUE_TIMESTAMP)),
449            (Self::Osaka, ForkCondition::Timestamp(MAINNET_OSAKA_TIMESTAMP)),
450            (Self::Bpo1, ForkCondition::Timestamp(MAINNET_BPO1_TIMESTAMP)),
451            (Self::Bpo2, ForkCondition::Timestamp(MAINNET_BPO2_TIMESTAMP)),
452        ]
453    }
454
455    /// Ethereum sepolia list of hardforks.
456    pub const fn sepolia() -> [(Self, ForkCondition); 19] {
457        [
458            (Self::Frontier, ForkCondition::Block(0)),
459            (Self::Homestead, ForkCondition::Block(0)),
460            (Self::Dao, ForkCondition::Block(0)),
461            (Self::Tangerine, ForkCondition::Block(0)),
462            (Self::SpuriousDragon, ForkCondition::Block(0)),
463            (Self::Byzantium, ForkCondition::Block(0)),
464            (Self::Constantinople, ForkCondition::Block(0)),
465            (Self::Petersburg, ForkCondition::Block(0)),
466            (Self::Istanbul, ForkCondition::Block(0)),
467            (Self::MuirGlacier, ForkCondition::Block(0)),
468            (Self::Berlin, ForkCondition::Block(0)),
469            (Self::London, ForkCondition::Block(0)),
470            (
471                Self::Paris,
472                ForkCondition::TTD {
473                    activation_block_number: SEPOLIA_PARIS_BLOCK,
474                    fork_block: Some(SEPOLIA_PARIS_FORK_BLOCK),
475                    total_difficulty: SEPOLIA_PARIS_TTD,
476                },
477            ),
478            (Self::Shanghai, ForkCondition::Timestamp(SEPOLIA_SHANGHAI_TIMESTAMP)),
479            (Self::Cancun, ForkCondition::Timestamp(SEPOLIA_CANCUN_TIMESTAMP)),
480            (Self::Prague, ForkCondition::Timestamp(SEPOLIA_PRAGUE_TIMESTAMP)),
481            (Self::Osaka, ForkCondition::Timestamp(SEPOLIA_OSAKA_TIMESTAMP)),
482            (Self::Bpo1, ForkCondition::Timestamp(SEPOLIA_BPO1_TIMESTAMP)),
483            (Self::Bpo2, ForkCondition::Timestamp(SEPOLIA_BPO2_TIMESTAMP)),
484        ]
485    }
486
487    /// Ethereum holesky list of hardforks.
488    pub const fn holesky() -> [(Self, ForkCondition); 19] {
489        [
490            (Self::Frontier, ForkCondition::Block(0)),
491            (Self::Homestead, ForkCondition::Block(0)),
492            (Self::Dao, ForkCondition::Block(0)),
493            (Self::Tangerine, ForkCondition::Block(0)),
494            (Self::SpuriousDragon, ForkCondition::Block(0)),
495            (Self::Byzantium, ForkCondition::Block(0)),
496            (Self::Constantinople, ForkCondition::Block(0)),
497            (Self::Petersburg, ForkCondition::Block(0)),
498            (Self::Istanbul, ForkCondition::Block(0)),
499            (Self::MuirGlacier, ForkCondition::Block(0)),
500            (Self::Berlin, ForkCondition::Block(0)),
501            (Self::London, ForkCondition::Block(0)),
502            (
503                Self::Paris,
504                ForkCondition::TTD {
505                    activation_block_number: 0,
506                    fork_block: Some(0),
507                    total_difficulty: U256::ZERO,
508                },
509            ),
510            (Self::Shanghai, ForkCondition::Timestamp(HOLESKY_SHANGHAI_TIMESTAMP)),
511            (Self::Cancun, ForkCondition::Timestamp(HOLESKY_CANCUN_TIMESTAMP)),
512            (Self::Prague, ForkCondition::Timestamp(HOLESKY_PRAGUE_TIMESTAMP)),
513            (Self::Osaka, ForkCondition::Timestamp(HOLESKY_OSAKA_TIMESTAMP)),
514            (Self::Bpo1, ForkCondition::Timestamp(HOLESKY_BPO1_TIMESTAMP)),
515            (Self::Bpo2, ForkCondition::Timestamp(HOLESKY_BPO2_TIMESTAMP)),
516        ]
517    }
518
519    /// Ethereum Hoodi list of hardforks.
520    pub const fn hoodi() -> [(Self, ForkCondition); 19] {
521        [
522            (Self::Frontier, ForkCondition::Block(0)),
523            (Self::Homestead, ForkCondition::Block(0)),
524            (Self::Dao, ForkCondition::Block(0)),
525            (Self::Tangerine, ForkCondition::Block(0)),
526            (Self::SpuriousDragon, ForkCondition::Block(0)),
527            (Self::Byzantium, ForkCondition::Block(0)),
528            (Self::Constantinople, ForkCondition::Block(0)),
529            (Self::Petersburg, ForkCondition::Block(0)),
530            (Self::Istanbul, ForkCondition::Block(0)),
531            (Self::MuirGlacier, ForkCondition::Block(0)),
532            (Self::Berlin, ForkCondition::Block(0)),
533            (Self::London, ForkCondition::Block(0)),
534            (
535                Self::Paris,
536                ForkCondition::TTD {
537                    activation_block_number: 0,
538                    fork_block: Some(0),
539                    total_difficulty: U256::ZERO,
540                },
541            ),
542            (Self::Shanghai, ForkCondition::Timestamp(0)),
543            (Self::Cancun, ForkCondition::Timestamp(0)),
544            (Self::Prague, ForkCondition::Timestamp(HOODI_PRAGUE_TIMESTAMP)),
545            (Self::Osaka, ForkCondition::Timestamp(HOODI_OSAKA_TIMESTAMP)),
546            (Self::Bpo1, ForkCondition::Timestamp(HOODI_BPO1_TIMESTAMP)),
547            (Self::Bpo2, ForkCondition::Timestamp(HOODI_BPO2_TIMESTAMP)),
548        ]
549    }
550
551    /// Ethereum Devnet list of hardforks.
552    pub const fn devnet() -> [(Self, ForkCondition); 19] {
553        [
554            (Self::Frontier, ForkCondition::ZERO_BLOCK),
555            (Self::Homestead, ForkCondition::ZERO_BLOCK),
556            (Self::Dao, ForkCondition::ZERO_BLOCK),
557            (Self::Tangerine, ForkCondition::ZERO_BLOCK),
558            (Self::SpuriousDragon, ForkCondition::ZERO_BLOCK),
559            (Self::Byzantium, ForkCondition::ZERO_BLOCK),
560            (Self::Constantinople, ForkCondition::ZERO_BLOCK),
561            (Self::Petersburg, ForkCondition::ZERO_BLOCK),
562            (Self::Istanbul, ForkCondition::ZERO_BLOCK),
563            (Self::MuirGlacier, ForkCondition::ZERO_BLOCK),
564            (Self::Berlin, ForkCondition::ZERO_BLOCK),
565            (Self::London, ForkCondition::ZERO_BLOCK),
566            (
567                Self::Paris,
568                ForkCondition::TTD {
569                    activation_block_number: 0,
570                    fork_block: None,
571                    total_difficulty: U256::ZERO,
572                },
573            ),
574            (Self::Shanghai, ForkCondition::ZERO_TIMESTAMP),
575            (Self::Cancun, ForkCondition::ZERO_TIMESTAMP),
576            (Self::Prague, ForkCondition::ZERO_TIMESTAMP),
577            (Self::Osaka, ForkCondition::ZERO_TIMESTAMP),
578            (Self::Bpo1, ForkCondition::ZERO_TIMESTAMP),
579            (Self::Bpo2, ForkCondition::ZERO_TIMESTAMP),
580        ]
581    }
582
583    /// Convert an u64 into an `EthereumHardfork`.
584    pub const fn from_mainnet_block_number(num: u64) -> Self {
585        match num {
586            _i if num < MAINNET_HOMESTEAD_BLOCK => Self::Frontier,
587            _i if num < MAINNET_DAO_BLOCK => Self::Homestead,
588            _i if num < MAINNET_TANGERINE_BLOCK => Self::Dao,
589            _i if num < MAINNET_SPURIOUS_DRAGON_BLOCK => Self::Tangerine,
590            _i if num < MAINNET_BYZANTIUM_BLOCK => Self::SpuriousDragon,
591            _i if num < MAINNET_CONSTANTINOPLE_BLOCK => Self::Byzantium,
592            _i if num < MAINNET_ISTANBUL_BLOCK => Self::Constantinople,
593            _i if num < MAINNET_MUIR_GLACIER_BLOCK => Self::Istanbul,
594            _i if num < MAINNET_BERLIN_BLOCK => Self::MuirGlacier,
595            _i if num < MAINNET_LONDON_BLOCK => Self::Berlin,
596            _i if num < MAINNET_ARROW_GLACIER_BLOCK => Self::London,
597            _i if num < MAINNET_PARIS_BLOCK => Self::ArrowGlacier,
598            _i if num < MAINNET_SHANGHAI_BLOCK => Self::Paris,
599            _i if num < MAINNET_CANCUN_BLOCK => Self::Shanghai,
600            _i if num < MAINNET_PRAGUE_BLOCK => Self::Cancun,
601            _ => Self::Prague,
602        }
603    }
604
605    /// Reverse lookup to find the hardfork given a chain ID and block timestamp.
606    /// Returns the active hardfork at the given timestamp for the specified chain.
607    pub fn from_chain_and_timestamp(chain: Chain, timestamp: u64) -> Option<Self> {
608        let named = chain.named()?;
609
610        match named {
611            NamedChain::Mainnet => Some(match timestamp {
612                _i if timestamp < MAINNET_HOMESTEAD_TIMESTAMP => Self::Frontier,
613                _i if timestamp < MAINNET_DAO_TIMESTAMP => Self::Homestead,
614                _i if timestamp < MAINNET_TANGERINE_TIMESTAMP => Self::Dao,
615                _i if timestamp < MAINNET_SPURIOUS_DRAGON_TIMESTAMP => Self::Tangerine,
616                _i if timestamp < MAINNET_BYZANTIUM_TIMESTAMP => Self::SpuriousDragon,
617                _i if timestamp < MAINNET_PETERSBURG_TIMESTAMP => Self::Byzantium,
618                _i if timestamp < MAINNET_ISTANBUL_TIMESTAMP => Self::Petersburg,
619                _i if timestamp < MAINNET_MUIR_GLACIER_TIMESTAMP => Self::Istanbul,
620                _i if timestamp < MAINNET_BERLIN_TIMESTAMP => Self::MuirGlacier,
621                _i if timestamp < MAINNET_LONDON_TIMESTAMP => Self::Berlin,
622                _i if timestamp < MAINNET_ARROW_GLACIER_TIMESTAMP => Self::London,
623                _i if timestamp < MAINNET_GRAY_GLACIER_TIMESTAMP => Self::ArrowGlacier,
624                _i if timestamp < MAINNET_PARIS_TIMESTAMP => Self::GrayGlacier,
625                _i if timestamp < MAINNET_SHANGHAI_TIMESTAMP => Self::Paris,
626                _i if timestamp < MAINNET_CANCUN_TIMESTAMP => Self::Shanghai,
627                _i if timestamp < MAINNET_PRAGUE_TIMESTAMP => Self::Cancun,
628                _i if timestamp < MAINNET_OSAKA_TIMESTAMP => Self::Prague,
629                _ => Self::Osaka,
630            }),
631            NamedChain::Sepolia => Some(match timestamp {
632                _i if timestamp < SEPOLIA_PARIS_TIMESTAMP => Self::London,
633                _i if timestamp < SEPOLIA_SHANGHAI_TIMESTAMP => Self::Paris,
634                _i if timestamp < SEPOLIA_CANCUN_TIMESTAMP => Self::Shanghai,
635                _i if timestamp < SEPOLIA_PRAGUE_TIMESTAMP => Self::Cancun,
636                _i if timestamp < SEPOLIA_OSAKA_TIMESTAMP => Self::Prague,
637                _ => Self::Osaka,
638            }),
639            NamedChain::Holesky => Some(match timestamp {
640                _i if timestamp < HOLESKY_SHANGHAI_TIMESTAMP => Self::Paris,
641                _i if timestamp < HOLESKY_CANCUN_TIMESTAMP => Self::Shanghai,
642                _i if timestamp < HOLESKY_PRAGUE_TIMESTAMP => Self::Cancun,
643                _i if timestamp < HOLESKY_OSAKA_TIMESTAMP => Self::Prague,
644                _ => Self::Osaka,
645            }),
646            NamedChain::Hoodi => Some(match timestamp {
647                _i if timestamp < HOODI_PRAGUE_TIMESTAMP => Self::Cancun,
648                _i if timestamp < HOODI_OSAKA_TIMESTAMP => Self::Prague,
649                _ => Self::Osaka,
650            }),
651            NamedChain::Arbitrum => Some(match timestamp {
652                _i if timestamp < ARBITRUM_ONE_SHANGHAI_TIMESTAMP => Self::Paris,
653                _i if timestamp < ARBITRUM_ONE_CANCUN_TIMESTAMP => Self::Shanghai,
654                _i if timestamp < ARBITRUM_ONE_PRAGUE_TIMESTAMP => Self::Cancun,
655                _ => Self::Prague,
656            }),
657            NamedChain::ArbitrumSepolia => Some(match timestamp {
658                _i if timestamp < ARBITRUM_SEPOLIA_SHANGHAI_TIMESTAMP => Self::Paris,
659                _i if timestamp < ARBITRUM_SEPOLIA_CANCUN_TIMESTAMP => Self::Shanghai,
660                _i if timestamp < ARBITRUM_SEPOLIA_PRAGUE_TIMESTAMP => Self::Cancun,
661                _ => Self::Prague,
662            }),
663            _ => None,
664        }
665    }
666}
667
668/// Helper methods for Ethereum forks.
669#[auto_impl::auto_impl(&, Arc)]
670pub trait EthereumHardforks {
671    /// Retrieves [`ForkCondition`] by an [`EthereumHardfork`]. If `fork` is not present, returns
672    /// [`ForkCondition::Never`].
673    fn ethereum_fork_activation(&self, fork: EthereumHardfork) -> ForkCondition;
674
675    /// Convenience method to check if an [`EthereumHardfork`] is active at a given timestamp.
676    fn is_ethereum_fork_active_at_timestamp(&self, fork: EthereumHardfork, timestamp: u64) -> bool {
677        self.ethereum_fork_activation(fork).active_at_timestamp(timestamp)
678    }
679
680    /// Convenience method to check if an [`EthereumHardfork`] is active at a given block number.
681    fn is_ethereum_fork_active_at_block(&self, fork: EthereumHardfork, block_number: u64) -> bool {
682        self.ethereum_fork_activation(fork).active_at_block(block_number)
683    }
684
685    /// Convenience method to check if [`EthereumHardfork::Homestead`] is active at a given block
686    /// number.
687    fn is_homestead_active_at_block(&self, block_number: u64) -> bool {
688        self.is_ethereum_fork_active_at_block(EthereumHardfork::Homestead, block_number)
689    }
690
691    /// Convenience method to check if [`EthereumHardfork::Tangerine`] is active at a given
692    /// block number.
693    fn is_tangerine_whistle_active_at_block(&self, block_number: u64) -> bool {
694        self.is_ethereum_fork_active_at_block(EthereumHardfork::Tangerine, block_number)
695    }
696
697    /// Convenience method to check if [`EthereumHardfork::SpuriousDragon`] is active at a given
698    /// block number.
699    fn is_spurious_dragon_active_at_block(&self, block_number: u64) -> bool {
700        self.is_ethereum_fork_active_at_block(EthereumHardfork::SpuriousDragon, block_number)
701    }
702
703    /// Convenience method to check if [`EthereumHardfork::Byzantium`] is active at a given block
704    /// number.
705    fn is_byzantium_active_at_block(&self, block_number: u64) -> bool {
706        self.is_ethereum_fork_active_at_block(EthereumHardfork::Byzantium, block_number)
707    }
708
709    /// Convenience method to check if [`EthereumHardfork::Constantinople`] is active at a given
710    /// block number.
711    fn is_constantinople_active_at_block(&self, block_number: u64) -> bool {
712        self.is_ethereum_fork_active_at_block(EthereumHardfork::Constantinople, block_number)
713    }
714
715    /// Convenience method to check if [`EthereumHardfork::Petersburg`] is active at a given block
716    /// number.
717    fn is_petersburg_active_at_block(&self, block_number: u64) -> bool {
718        self.is_ethereum_fork_active_at_block(EthereumHardfork::Petersburg, block_number)
719    }
720
721    /// Convenience method to check if [`EthereumHardfork::Istanbul`] is active at a given block
722    /// number.
723    fn is_istanbul_active_at_block(&self, block_number: u64) -> bool {
724        self.is_ethereum_fork_active_at_block(EthereumHardfork::Istanbul, block_number)
725    }
726
727    /// Convenience method to check if [`EthereumHardfork::Berlin`] is active at a given block
728    /// number.
729    fn is_berlin_active_at_block(&self, block_number: u64) -> bool {
730        self.is_ethereum_fork_active_at_block(EthereumHardfork::Berlin, block_number)
731    }
732
733    /// Convenience method to check if [`EthereumHardfork::London`] is active at a given block
734    /// number.
735    fn is_london_active_at_block(&self, block_number: u64) -> bool {
736        self.is_ethereum_fork_active_at_block(EthereumHardfork::London, block_number)
737    }
738
739    /// Convenience method to check if [`EthereumHardfork::Paris`] is active at a given block
740    /// number.
741    fn is_paris_active_at_block(&self, block_number: u64) -> bool {
742        self.is_ethereum_fork_active_at_block(EthereumHardfork::Paris, block_number)
743    }
744
745    /// Convenience method to check if [`EthereumHardfork::Shanghai`] is active at a given
746    /// timestamp.
747    fn is_shanghai_active_at_timestamp(&self, timestamp: u64) -> bool {
748        self.is_ethereum_fork_active_at_timestamp(EthereumHardfork::Shanghai, timestamp)
749    }
750
751    /// Convenience method to check if [`EthereumHardfork::Cancun`] is active at a given timestamp.
752    fn is_cancun_active_at_timestamp(&self, timestamp: u64) -> bool {
753        self.is_ethereum_fork_active_at_timestamp(EthereumHardfork::Cancun, timestamp)
754    }
755
756    /// Convenience method to check if [`EthereumHardfork::Prague`] is active at a given timestamp.
757    fn is_prague_active_at_timestamp(&self, timestamp: u64) -> bool {
758        self.is_ethereum_fork_active_at_timestamp(EthereumHardfork::Prague, timestamp)
759    }
760
761    /// Convenience method to check if [`EthereumHardfork::Osaka`] is active at a given timestamp.
762    fn is_osaka_active_at_timestamp(&self, timestamp: u64) -> bool {
763        self.is_ethereum_fork_active_at_timestamp(EthereumHardfork::Osaka, timestamp)
764    }
765
766    /// Convenience method to check if [`EthereumHardfork::Amsterdam`] is active at a given
767    /// timestamp.
768    fn is_amsterdam_active_at_timestamp(&self, timestamp: u64) -> bool {
769        self.is_ethereum_fork_active_at_timestamp(EthereumHardfork::Amsterdam, timestamp)
770    }
771
772    /// Convenience method to check if [`EthereumHardfork::Bpo1`] is active at a given timestamp.
773    fn is_bpo1_active_at_timestamp(&self, timestamp: u64) -> bool {
774        self.is_ethereum_fork_active_at_timestamp(EthereumHardfork::Bpo1, timestamp)
775    }
776
777    /// Convenience method to check if [`EthereumHardfork::Bpo2`] is active at a given timestamp.
778    fn is_bpo2_active_at_timestamp(&self, timestamp: u64) -> bool {
779        self.is_ethereum_fork_active_at_timestamp(EthereumHardfork::Bpo2, timestamp)
780    }
781
782    /// Convenience method to check if [`EthereumHardfork::Bpo3`] is active at a given timestamp.
783    fn is_bpo3_active_at_timestamp(&self, timestamp: u64) -> bool {
784        self.is_ethereum_fork_active_at_timestamp(EthereumHardfork::Bpo3, timestamp)
785    }
786
787    /// Convenience method to check if [`EthereumHardfork::Bpo4`] is active at a given timestamp.
788    fn is_bpo4_active_at_timestamp(&self, timestamp: u64) -> bool {
789        self.is_ethereum_fork_active_at_timestamp(EthereumHardfork::Bpo4, timestamp)
790    }
791
792    /// Convenience method to check if [`EthereumHardfork::Bpo5`] is active at a given timestamp.
793    fn is_bpo5_active_at_timestamp(&self, timestamp: u64) -> bool {
794        self.is_ethereum_fork_active_at_timestamp(EthereumHardfork::Bpo5, timestamp)
795    }
796}
797
798/// A type allowing to configure activation [`ForkCondition`]s for a given list of
799/// [`EthereumHardfork`]s.
800#[derive(Debug, Clone)]
801pub struct EthereumChainHardforks {
802    forks: Vec<(EthereumHardfork, ForkCondition)>,
803}
804
805impl EthereumChainHardforks {
806    /// Creates a new [`EthereumChainHardforks`] with the given list of forks.
807    pub fn new(forks: impl IntoIterator<Item = (EthereumHardfork, ForkCondition)>) -> Self {
808        let mut forks = forks.into_iter().collect::<Vec<_>>();
809        forks.sort();
810        Self { forks }
811    }
812
813    /// Creates a new [`EthereumChainHardforks`] with Mainnet configuration.
814    pub fn mainnet() -> Self {
815        Self::new(EthereumHardfork::mainnet())
816    }
817
818    /// Creates a new [`EthereumChainHardforks`] with Sepolia configuration.
819    pub fn sepolia() -> Self {
820        Self::new(EthereumHardfork::sepolia())
821    }
822
823    /// Creates a new [`EthereumChainHardforks`] with Holesky configuration.
824    pub fn holesky() -> Self {
825        Self::new(EthereumHardfork::holesky())
826    }
827
828    /// Creates a new [`EthereumChainHardforks`] with Hoodi configuration.
829    pub fn hoodi() -> Self {
830        Self::new(EthereumHardfork::hoodi())
831    }
832
833    /// Creates a new [`EthereumChainHardforks`] with Devnet configuration.
834    pub fn devnet() -> Self {
835        Self::new(EthereumHardfork::devnet())
836    }
837}
838
839impl EthereumHardforks for EthereumChainHardforks {
840    fn ethereum_fork_activation(&self, fork: EthereumHardfork) -> ForkCondition {
841        let Ok(idx) = self.forks.binary_search_by(|(f, _)| f.cmp(&fork)) else {
842            return ForkCondition::Never;
843        };
844
845        self.forks[idx].1
846    }
847}
848
849#[cfg(test)]
850mod tests {
851    use super::*;
852    use alloc::vec::Vec;
853    use core::str::FromStr;
854
855    #[test]
856    fn check_hardfork_from_str() {
857        let hardfork_str = [
858            "frOntier",
859            "homEstead",
860            "dao",
861            "tAngerIne",
862            "spurIousdrAgon",
863            "byzAntium",
864            "constantinople",
865            "petersburg",
866            "istanbul",
867            "muirglacier",
868            "bErlin",
869            "lonDon",
870            "arrowglacier",
871            "grayglacier",
872            "PARIS",
873            "ShAnGhAI",
874            "CaNcUn",
875            "PrAguE",
876            "OsAkA",
877            "Bpo1",
878            "BPO2",
879            "bpo3",
880            "bPo4",
881            "bpO5",
882        ];
883        let expected_hardforks = [
884            EthereumHardfork::Frontier,
885            EthereumHardfork::Homestead,
886            EthereumHardfork::Dao,
887            EthereumHardfork::Tangerine,
888            EthereumHardfork::SpuriousDragon,
889            EthereumHardfork::Byzantium,
890            EthereumHardfork::Constantinople,
891            EthereumHardfork::Petersburg,
892            EthereumHardfork::Istanbul,
893            EthereumHardfork::MuirGlacier,
894            EthereumHardfork::Berlin,
895            EthereumHardfork::London,
896            EthereumHardfork::ArrowGlacier,
897            EthereumHardfork::GrayGlacier,
898            EthereumHardfork::Paris,
899            EthereumHardfork::Shanghai,
900            EthereumHardfork::Cancun,
901            EthereumHardfork::Prague,
902            EthereumHardfork::Osaka,
903            EthereumHardfork::Bpo1,
904            EthereumHardfork::Bpo2,
905            EthereumHardfork::Bpo3,
906            EthereumHardfork::Bpo4,
907            EthereumHardfork::Bpo5,
908        ];
909
910        let hardforks: Vec<EthereumHardfork> =
911            hardfork_str.iter().map(|h| EthereumHardfork::from_str(h).unwrap()).collect();
912
913        assert_eq!(hardforks, expected_hardforks);
914    }
915
916    #[test]
917    fn check_nonexistent_hardfork_from_str() {
918        assert!(EthereumHardfork::from_str("not a hardfork").is_err());
919    }
920
921    #[test]
922    fn test_reverse_lookup_by_chain_id() {
923        // Test major hardforks across all supported Ethereum chains
924        let test_cases = [
925            // (chain_id, timestamp, expected) - Key transitions for each chain
926            // Mainnet
927            // At block 0: Frontier
928            (Chain::mainnet(), MAINNET_FRONTIER_TIMESTAMP - 1, EthereumHardfork::Frontier),
929            (Chain::mainnet(), MAINNET_FRONTIER_TIMESTAMP, EthereumHardfork::Frontier),
930            (Chain::mainnet(), MAINNET_HOMESTEAD_TIMESTAMP, EthereumHardfork::Homestead),
931            (Chain::mainnet(), MAINNET_DAO_TIMESTAMP, EthereumHardfork::Dao),
932            (Chain::mainnet(), MAINNET_TANGERINE_TIMESTAMP, EthereumHardfork::Tangerine),
933            (Chain::mainnet(), MAINNET_SPURIOUS_DRAGON_TIMESTAMP, EthereumHardfork::SpuriousDragon),
934            (Chain::mainnet(), MAINNET_BYZANTIUM_TIMESTAMP, EthereumHardfork::Byzantium),
935            (Chain::mainnet(), MAINNET_PETERSBURG_TIMESTAMP, EthereumHardfork::Petersburg),
936            (Chain::mainnet(), MAINNET_ISTANBUL_TIMESTAMP, EthereumHardfork::Istanbul),
937            (Chain::mainnet(), MAINNET_MUIR_GLACIER_TIMESTAMP, EthereumHardfork::MuirGlacier),
938            (Chain::mainnet(), MAINNET_BERLIN_TIMESTAMP, EthereumHardfork::Berlin),
939            (Chain::mainnet(), MAINNET_LONDON_TIMESTAMP, EthereumHardfork::London),
940            (Chain::mainnet(), MAINNET_ARROW_GLACIER_TIMESTAMP, EthereumHardfork::ArrowGlacier),
941            (Chain::mainnet(), MAINNET_GRAY_GLACIER_TIMESTAMP, EthereumHardfork::GrayGlacier),
942            (Chain::mainnet(), MAINNET_PARIS_TIMESTAMP, EthereumHardfork::Paris),
943            (Chain::mainnet(), MAINNET_SHANGHAI_TIMESTAMP, EthereumHardfork::Shanghai),
944            (Chain::mainnet(), MAINNET_CANCUN_TIMESTAMP, EthereumHardfork::Cancun),
945            (Chain::mainnet(), MAINNET_PRAGUE_TIMESTAMP, EthereumHardfork::Prague),
946            // Sepolia
947            // At block 0: London
948            (Chain::sepolia(), SEPOLIA_PARIS_TIMESTAMP - 1, EthereumHardfork::London),
949            (Chain::sepolia(), SEPOLIA_PARIS_TIMESTAMP, EthereumHardfork::Paris),
950            (Chain::sepolia(), SEPOLIA_SHANGHAI_TIMESTAMP - 1, EthereumHardfork::Paris),
951            (Chain::sepolia(), SEPOLIA_SHANGHAI_TIMESTAMP, EthereumHardfork::Shanghai),
952            (Chain::sepolia(), SEPOLIA_CANCUN_TIMESTAMP, EthereumHardfork::Cancun),
953            (Chain::sepolia(), SEPOLIA_PRAGUE_TIMESTAMP - 1, EthereumHardfork::Cancun),
954            (Chain::sepolia(), SEPOLIA_PRAGUE_TIMESTAMP + 1, EthereumHardfork::Prague),
955            (Chain::sepolia(), SEPOLIA_OSAKA_TIMESTAMP, EthereumHardfork::Osaka),
956            // Holesky
957            // At block 0: Paris
958            (Chain::holesky(), HOLESKY_PARIS_TIMESTAMP - 1, EthereumHardfork::Paris),
959            (Chain::holesky(), HOLESKY_PARIS_TIMESTAMP, EthereumHardfork::Paris),
960            (Chain::holesky(), HOLESKY_SHANGHAI_TIMESTAMP - 1, EthereumHardfork::Paris),
961            (Chain::holesky(), HOLESKY_SHANGHAI_TIMESTAMP, EthereumHardfork::Shanghai),
962            (Chain::holesky(), HOLESKY_CANCUN_TIMESTAMP, EthereumHardfork::Cancun),
963            (Chain::holesky(), HOLESKY_PRAGUE_TIMESTAMP - 1, EthereumHardfork::Cancun),
964            (Chain::holesky(), HOLESKY_PRAGUE_TIMESTAMP + 1, EthereumHardfork::Prague),
965            (Chain::holesky(), HOLESKY_OSAKA_TIMESTAMP, EthereumHardfork::Osaka),
966            // Hoodi
967            // At block 0: Cancun
968            (Chain::hoodi(), HOODI_PRAGUE_TIMESTAMP - 1, EthereumHardfork::Cancun),
969            (Chain::hoodi(), HOODI_PRAGUE_TIMESTAMP, EthereumHardfork::Prague),
970            (Chain::hoodi(), HOODI_OSAKA_TIMESTAMP, EthereumHardfork::Osaka),
971            // Arbitrum One
972            // At block 0: Paris
973            (Chain::arbitrum_mainnet(), ARBITRUM_ONE_PARIS_TIMESTAMP - 1, EthereumHardfork::Paris),
974            (Chain::arbitrum_mainnet(), ARBITRUM_ONE_PARIS_TIMESTAMP, EthereumHardfork::Paris),
975            (
976                Chain::arbitrum_mainnet(),
977                ARBITRUM_ONE_SHANGHAI_TIMESTAMP - 1,
978                EthereumHardfork::Paris,
979            ),
980            (
981                Chain::arbitrum_mainnet(),
982                ARBITRUM_ONE_SHANGHAI_TIMESTAMP,
983                EthereumHardfork::Shanghai,
984            ),
985            (Chain::arbitrum_mainnet(), ARBITRUM_ONE_CANCUN_TIMESTAMP, EthereumHardfork::Cancun),
986            (
987                Chain::arbitrum_mainnet(),
988                ARBITRUM_ONE_PRAGUE_TIMESTAMP - 1,
989                EthereumHardfork::Cancun,
990            ),
991            (
992                Chain::arbitrum_mainnet(),
993                ARBITRUM_ONE_PRAGUE_TIMESTAMP + 1,
994                EthereumHardfork::Prague,
995            ),
996            // Arbitrum Sepolia
997            // At block 0: Paris
998            (
999                Chain::arbitrum_sepolia(),
1000                ARBITRUM_SEPOLIA_PARIS_TIMESTAMP - 1,
1001                EthereumHardfork::Paris,
1002            ),
1003            (Chain::arbitrum_sepolia(), ARBITRUM_SEPOLIA_PARIS_TIMESTAMP, EthereumHardfork::Paris),
1004            (
1005                Chain::arbitrum_sepolia(),
1006                ARBITRUM_SEPOLIA_SHANGHAI_TIMESTAMP - 1,
1007                EthereumHardfork::Paris,
1008            ),
1009            (
1010                Chain::arbitrum_sepolia(),
1011                ARBITRUM_SEPOLIA_SHANGHAI_TIMESTAMP,
1012                EthereumHardfork::Shanghai,
1013            ),
1014            (
1015                Chain::arbitrum_sepolia(),
1016                ARBITRUM_SEPOLIA_CANCUN_TIMESTAMP,
1017                EthereumHardfork::Cancun,
1018            ),
1019            (
1020                Chain::arbitrum_sepolia(),
1021                ARBITRUM_SEPOLIA_PRAGUE_TIMESTAMP - 1,
1022                EthereumHardfork::Cancun,
1023            ),
1024            (
1025                Chain::arbitrum_sepolia(),
1026                ARBITRUM_SEPOLIA_PRAGUE_TIMESTAMP + 1,
1027                EthereumHardfork::Prague,
1028            ),
1029        ];
1030
1031        for (chain_id, timestamp, expected) in test_cases {
1032            assert_eq!(
1033                EthereumHardfork::from_chain_and_timestamp(chain_id, timestamp),
1034                Some(expected),
1035                "chain {chain_id} at timestamp {timestamp}"
1036            );
1037        }
1038
1039        // Edge cases
1040        assert_eq!(
1041            EthereumHardfork::from_chain_and_timestamp(Chain::from_id(99999), 1000000),
1042            None
1043        );
1044    }
1045
1046    #[test]
1047    fn test_timestamp_functions_consistency() {
1048        let test_cases = [
1049            (MAINNET_LONDON_TIMESTAMP, EthereumHardfork::London),
1050            (MAINNET_SHANGHAI_TIMESTAMP, EthereumHardfork::Shanghai),
1051            (MAINNET_CANCUN_TIMESTAMP, EthereumHardfork::Cancun),
1052        ];
1053
1054        for (timestamp, fork) in test_cases {
1055            assert_eq!(
1056                EthereumHardfork::from_chain_and_timestamp(Chain::mainnet(), timestamp),
1057                Some(fork)
1058            );
1059            assert_eq!(fork.activation_timestamp(Chain::mainnet()), Some(timestamp));
1060        }
1061    }
1062
1063    macro_rules! test_chain_config {
1064        ($modname:ident, $ts_fn:ident, $bn_fn:ident) => {
1065            mod $modname {
1066                use super::*;
1067                #[test]
1068                fn test_chain_config() {
1069                    for (fork, condition) in EthereumHardfork::$modname() {
1070                        match condition {
1071                            ForkCondition::Timestamp(ts) => {
1072                                assert_eq!(fork.$ts_fn(), Some(ts));
1073                            }
1074                            ForkCondition::Block(bn) => {
1075                                assert_eq!(fork.$bn_fn(), Some(bn));
1076                            }
1077                            ForkCondition::TTD { activation_block_number, .. } => {
1078                                assert_eq!(fork.$bn_fn(), Some(activation_block_number));
1079                            }
1080                            _ => {}
1081                        }
1082                    }
1083                }
1084            }
1085        };
1086    }
1087
1088    test_chain_config!(mainnet, mainnet_activation_timestamp, mainnet_activation_block);
1089    test_chain_config!(sepolia, sepolia_activation_timestamp, sepolia_activation_block);
1090    test_chain_config!(holesky, holesky_activation_timestamp, holesky_activation_block);
1091    test_chain_config!(hoodi, hoodi_activation_timestamp, hoodi_activation_block);
1092}