alloy_evm/eth/
spec_id.rs

1use alloy_consensus::BlockHeader;
2use alloy_hardforks::EthereumHardforks;
3use alloy_primitives::{BlockNumber, BlockTimestamp};
4use revm::primitives::hardfork::SpecId;
5
6/// Map the latest active hardfork at the given header to a [`SpecId`].
7pub fn spec<C, H>(chain_spec: &C, header: &H) -> SpecId
8where
9    C: EthereumHardforks,
10    H: BlockHeader,
11{
12    spec_by_timestamp_and_block_number(chain_spec, header.timestamp(), header.number())
13}
14
15/// Map the latest active hardfork at the given timestamp or block number to a [`SpecId`].
16pub fn spec_by_timestamp_and_block_number<C>(
17    chain_spec: &C,
18    timestamp: BlockTimestamp,
19    block_number: BlockNumber,
20) -> SpecId
21where
22    C: EthereumHardforks,
23{
24    if chain_spec.is_osaka_active_at_timestamp(timestamp) {
25        SpecId::OSAKA
26    } else if chain_spec.is_prague_active_at_timestamp(timestamp) {
27        SpecId::PRAGUE
28    } else if chain_spec.is_cancun_active_at_timestamp(timestamp) {
29        SpecId::CANCUN
30    } else if chain_spec.is_shanghai_active_at_timestamp(timestamp) {
31        SpecId::SHANGHAI
32    } else if chain_spec.is_paris_active_at_block(block_number) {
33        SpecId::MERGE
34    } else if chain_spec.is_london_active_at_block(block_number) {
35        SpecId::LONDON
36    } else if chain_spec.is_berlin_active_at_block(block_number) {
37        SpecId::BERLIN
38    } else if chain_spec.is_istanbul_active_at_block(block_number) {
39        SpecId::ISTANBUL
40    } else if chain_spec.is_petersburg_active_at_block(block_number) {
41        SpecId::PETERSBURG
42    } else if chain_spec.is_byzantium_active_at_block(block_number) {
43        SpecId::BYZANTIUM
44    } else if chain_spec.is_spurious_dragon_active_at_block(block_number) {
45        SpecId::SPURIOUS_DRAGON
46    } else if chain_spec.is_tangerine_whistle_active_at_block(block_number) {
47        SpecId::TANGERINE
48    } else if chain_spec.is_homestead_active_at_block(block_number) {
49        SpecId::HOMESTEAD
50    } else {
51        SpecId::FRONTIER
52    }
53}
54
55#[cfg(test)]
56mod tests {
57    use super::*;
58    use crate::eth::spec::EthSpec;
59    use alloy_consensus::Header;
60    use alloy_hardforks::{
61        mainnet::{
62            MAINNET_BERLIN_BLOCK, MAINNET_BYZANTIUM_BLOCK, MAINNET_CANCUN_TIMESTAMP,
63            MAINNET_FRONTIER_BLOCK, MAINNET_HOMESTEAD_BLOCK, MAINNET_ISTANBUL_BLOCK,
64            MAINNET_LONDON_BLOCK, MAINNET_PARIS_BLOCK, MAINNET_PETERSBURG_BLOCK,
65            MAINNET_PRAGUE_TIMESTAMP, MAINNET_SHANGHAI_TIMESTAMP, MAINNET_SPURIOUS_DRAGON_BLOCK,
66            MAINNET_TANGERINE_BLOCK,
67        },
68        EthereumHardfork, ForkCondition,
69    };
70    use alloy_primitives::{BlockNumber, BlockTimestamp};
71
72    struct FakeHardfork {
73        fork: EthereumHardfork,
74        cond: ForkCondition,
75    }
76
77    impl FakeHardfork {
78        fn osaka() -> Self {
79            Self::from_timestamp_zero(EthereumHardfork::Osaka)
80        }
81
82        fn prague() -> Self {
83            Self::from_timestamp_zero(EthereumHardfork::Prague)
84        }
85
86        fn cancun() -> Self {
87            Self::from_timestamp_zero(EthereumHardfork::Cancun)
88        }
89
90        fn shanghai() -> Self {
91            Self::from_timestamp_zero(EthereumHardfork::Shanghai)
92        }
93
94        fn paris() -> Self {
95            Self::from_block_zero(EthereumHardfork::Paris)
96        }
97
98        fn london() -> Self {
99            Self::from_block_zero(EthereumHardfork::London)
100        }
101
102        fn berlin() -> Self {
103            Self::from_block_zero(EthereumHardfork::Berlin)
104        }
105
106        fn istanbul() -> Self {
107            Self::from_block_zero(EthereumHardfork::Istanbul)
108        }
109
110        fn petersburg() -> Self {
111            Self::from_block_zero(EthereumHardfork::Petersburg)
112        }
113
114        fn spurious_dragon() -> Self {
115            Self::from_block_zero(EthereumHardfork::SpuriousDragon)
116        }
117
118        fn homestead() -> Self {
119            Self::from_block_zero(EthereumHardfork::Homestead)
120        }
121
122        fn frontier() -> Self {
123            Self::from_block_zero(EthereumHardfork::Frontier)
124        }
125
126        fn from_block_zero(fork: EthereumHardfork) -> Self {
127            Self { fork, cond: ForkCondition::Block(0) }
128        }
129
130        fn from_timestamp_zero(fork: EthereumHardfork) -> Self {
131            Self { fork, cond: ForkCondition::Timestamp(0) }
132        }
133    }
134
135    impl EthereumHardforks for FakeHardfork {
136        fn ethereum_fork_activation(&self, fork: EthereumHardfork) -> ForkCondition {
137            if fork == self.fork {
138                self.cond
139            } else {
140                ForkCondition::Never
141            }
142        }
143    }
144
145    #[test_case::test_case(FakeHardfork::osaka(), SpecId::OSAKA; "Osaka")]
146    #[test_case::test_case(FakeHardfork::prague(), SpecId::PRAGUE; "Prague")]
147    #[test_case::test_case(FakeHardfork::cancun(), SpecId::CANCUN; "Cancun")]
148    #[test_case::test_case(FakeHardfork::shanghai(), SpecId::SHANGHAI; "Shanghai")]
149    #[test_case::test_case(FakeHardfork::paris(), SpecId::MERGE; "Merge")]
150    #[test_case::test_case(FakeHardfork::london(), SpecId::LONDON; "London")]
151    #[test_case::test_case(FakeHardfork::berlin(), SpecId::BERLIN; "Berlin")]
152    #[test_case::test_case(FakeHardfork::istanbul(), SpecId::ISTANBUL; "Istanbul")]
153    #[test_case::test_case(FakeHardfork::petersburg(), SpecId::PETERSBURG; "Petersburg")]
154    #[test_case::test_case(FakeHardfork::spurious_dragon(), SpecId::SPURIOUS_DRAGON; "Spurious dragon")]
155    #[test_case::test_case(FakeHardfork::homestead(), SpecId::HOMESTEAD; "Homestead")]
156    #[test_case::test_case(FakeHardfork::frontier(), SpecId::FRONTIER; "Frontier")]
157    fn test_spec_maps_hardfork_successfully(fork: impl EthereumHardforks, expected_spec: SpecId) {
158        let header = Header::default();
159        let actual_spec = spec(&fork, &header);
160
161        assert_eq!(actual_spec, expected_spec);
162    }
163
164    #[test_case::test_case(MAINNET_PRAGUE_TIMESTAMP, 0, SpecId::PRAGUE; "Prague")]
165    #[test_case::test_case(MAINNET_CANCUN_TIMESTAMP, 0, SpecId::CANCUN; "Cancun")]
166    #[test_case::test_case(MAINNET_SHANGHAI_TIMESTAMP, 0, SpecId::SHANGHAI; "Shanghai")]
167    #[test_case::test_case(0, MAINNET_PARIS_BLOCK, SpecId::MERGE; "Merge")]
168    #[test_case::test_case(0, MAINNET_LONDON_BLOCK, SpecId::LONDON; "London")]
169    #[test_case::test_case(0, MAINNET_BERLIN_BLOCK, SpecId::BERLIN; "Berlin")]
170    #[test_case::test_case(0, MAINNET_ISTANBUL_BLOCK, SpecId::ISTANBUL; "Istanbul")]
171    #[test_case::test_case(0, MAINNET_PETERSBURG_BLOCK, SpecId::PETERSBURG; "Petersburg")]
172    #[test_case::test_case(0, MAINNET_BYZANTIUM_BLOCK, SpecId::BYZANTIUM; "Byzantium")]
173    #[test_case::test_case(0, MAINNET_SPURIOUS_DRAGON_BLOCK, SpecId::SPURIOUS_DRAGON; "Spurious dragon")]
174    #[test_case::test_case(0, MAINNET_TANGERINE_BLOCK, SpecId::TANGERINE; "Tangerine")]
175    #[test_case::test_case(0, MAINNET_HOMESTEAD_BLOCK, SpecId::HOMESTEAD; "Homestead")]
176    #[test_case::test_case(0, MAINNET_FRONTIER_BLOCK, SpecId::FRONTIER; "Frontier")]
177    fn test_eth_spec_maps_hardfork_successfully(
178        timestamp: BlockTimestamp,
179        number: BlockNumber,
180        expected_spec: SpecId,
181    ) {
182        let fork = EthSpec::mainnet();
183        let header = Header { timestamp, number, ..Default::default() };
184        let actual_spec = spec(&fork, &header);
185
186        assert_eq!(actual_spec, expected_spec);
187    }
188}