1use alloy_consensus::BlockHeader;
2use alloy_hardforks::EthereumHardforks;
3use alloy_primitives::{BlockNumber, BlockTimestamp};
4use revm::primitives::hardfork::SpecId;
5
6pub 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
15pub 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}