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