1use alloy_rpc_types::BlockNumberOrTag;
2use eyre::bail;
3use foundry_evm::revm::primitives::SpecId;
4use std::str::FromStr;
5
6#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
7pub enum ChainHardfork {
8 Ethereum(EthereumHardfork),
9 Optimism(OptimismHardfork),
10}
11
12impl From<EthereumHardfork> for ChainHardfork {
13 fn from(value: EthereumHardfork) -> Self {
14 Self::Ethereum(value)
15 }
16}
17
18impl From<OptimismHardfork> for ChainHardfork {
19 fn from(value: OptimismHardfork) -> Self {
20 Self::Optimism(value)
21 }
22}
23
24impl From<ChainHardfork> for SpecId {
25 fn from(fork: ChainHardfork) -> Self {
26 match fork {
27 ChainHardfork::Ethereum(hardfork) => hardfork.into(),
28 ChainHardfork::Optimism(hardfork) => hardfork.into(),
29 }
30 }
31}
32
33#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
34pub enum EthereumHardfork {
35 Frontier,
36 Homestead,
37 Dao,
38 Tangerine,
39 SpuriousDragon,
40 Byzantium,
41 Constantinople,
42 Petersburg,
43 Istanbul,
44 Muirglacier,
45 Berlin,
46 London,
47 ArrowGlacier,
48 GrayGlacier,
49 Paris,
50 Shanghai,
51 Cancun,
52 Prague,
53 PragueEOF,
54 #[default]
55 Latest,
56}
57
58impl EthereumHardfork {
59 pub fn fork_block(&self) -> u64 {
61 match *self {
62 Self::Frontier => 0,
63 Self::Homestead => 1150000,
64 Self::Dao => 1920000,
65 Self::Tangerine => 2463000,
66 Self::SpuriousDragon => 2675000,
67 Self::Byzantium => 4370000,
68 Self::Constantinople | Self::Petersburg => 7280000,
69 Self::Istanbul => 9069000,
70 Self::Muirglacier => 9200000,
71 Self::Berlin => 12244000,
72 Self::London => 12965000,
73 Self::ArrowGlacier => 13773000,
74 Self::GrayGlacier => 15050000,
75 Self::Paris => 15537394,
76 Self::Shanghai => 17034870,
77 Self::Cancun | Self::Latest => 19426587,
78 Self::Prague | Self::PragueEOF => unreachable!(),
80 }
81 }
82}
83
84impl FromStr for EthereumHardfork {
85 type Err = eyre::Report;
86
87 fn from_str(s: &str) -> Result<Self, Self::Err> {
88 let s = s.to_lowercase();
89 let hardfork = match s.as_str() {
90 "frontier" | "1" => Self::Frontier,
91 "homestead" | "2" => Self::Homestead,
92 "dao" | "3" => Self::Dao,
93 "tangerine" | "4" => Self::Tangerine,
94 "spuriousdragon" | "5" => Self::SpuriousDragon,
95 "byzantium" | "6" => Self::Byzantium,
96 "constantinople" | "7" => Self::Constantinople,
97 "petersburg" | "8" => Self::Petersburg,
98 "istanbul" | "9" => Self::Istanbul,
99 "muirglacier" | "10" => Self::Muirglacier,
100 "berlin" | "11" => Self::Berlin,
101 "london" | "12" => Self::London,
102 "arrowglacier" | "13" => Self::ArrowGlacier,
103 "grayglacier" | "14" => Self::GrayGlacier,
104 "paris" | "merge" | "15" => Self::Paris,
105 "shanghai" | "16" => Self::Shanghai,
106 "cancun" | "17" => Self::Cancun,
107 "prague" | "18" => Self::Prague,
108 "pragueeof" | "19" | "prague-eof" => Self::PragueEOF,
109 "latest" => Self::Latest,
110 _ => bail!("Unknown hardfork {s}"),
111 };
112 Ok(hardfork)
113 }
114}
115
116impl From<EthereumHardfork> for SpecId {
117 fn from(fork: EthereumHardfork) -> Self {
118 match fork {
119 EthereumHardfork::Frontier => Self::FRONTIER,
120 EthereumHardfork::Homestead => Self::HOMESTEAD,
121 EthereumHardfork::Dao => Self::HOMESTEAD,
122 EthereumHardfork::Tangerine => Self::TANGERINE,
123 EthereumHardfork::SpuriousDragon => Self::SPURIOUS_DRAGON,
124 EthereumHardfork::Byzantium => Self::BYZANTIUM,
125 EthereumHardfork::Constantinople => Self::CONSTANTINOPLE,
126 EthereumHardfork::Petersburg => Self::PETERSBURG,
127 EthereumHardfork::Istanbul => Self::ISTANBUL,
128 EthereumHardfork::Muirglacier => Self::MUIR_GLACIER,
129 EthereumHardfork::Berlin => Self::BERLIN,
130 EthereumHardfork::London => Self::LONDON,
131 EthereumHardfork::ArrowGlacier => Self::LONDON,
132 EthereumHardfork::GrayGlacier => Self::GRAY_GLACIER,
133 EthereumHardfork::Paris => Self::MERGE,
134 EthereumHardfork::Shanghai => Self::SHANGHAI,
135 EthereumHardfork::Cancun | EthereumHardfork::Latest => Self::CANCUN,
136 EthereumHardfork::Prague => Self::PRAGUE,
137 EthereumHardfork::PragueEOF => Self::OSAKA,
140 }
141 }
142}
143
144impl<T: Into<BlockNumberOrTag>> From<T> for EthereumHardfork {
145 fn from(block: T) -> Self {
146 let num = match block.into() {
147 BlockNumberOrTag::Earliest => 0,
148 BlockNumberOrTag::Number(num) => num,
149 _ => u64::MAX,
150 };
151
152 match num {
153 _i if num < 1_150_000 => Self::Frontier,
154 _i if num < 1_920_000 => Self::Dao,
155 _i if num < 2_463_000 => Self::Homestead,
156 _i if num < 2_675_000 => Self::Tangerine,
157 _i if num < 4_370_000 => Self::SpuriousDragon,
158 _i if num < 7_280_000 => Self::Byzantium,
159 _i if num < 9_069_000 => Self::Constantinople,
160 _i if num < 9_200_000 => Self::Istanbul,
161 _i if num < 12_244_000 => Self::Muirglacier,
162 _i if num < 12_965_000 => Self::Berlin,
163 _i if num < 13_773_000 => Self::London,
164 _i if num < 15_050_000 => Self::ArrowGlacier,
165 _i if num < 17_034_870 => Self::Paris,
166 _i if num < 19_426_587 => Self::Shanghai,
167 _ => Self::Latest,
168 }
169 }
170}
171
172#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
173pub enum OptimismHardfork {
174 Bedrock,
175 Regolith,
176 Canyon,
177 Ecotone,
178 Fjord,
179 Granite,
180 Holocene,
181 Isthmus,
182 #[default]
183 Latest,
184}
185
186impl FromStr for OptimismHardfork {
187 type Err = eyre::Report;
188
189 fn from_str(s: &str) -> Result<Self, Self::Err> {
190 let s = s.to_lowercase();
191 let hardfork = match s.as_str() {
192 "bedrock" => Self::Bedrock,
193 "regolith" => Self::Regolith,
194 "canyon" => Self::Canyon,
195 "ecotone" => Self::Ecotone,
196 "fjord" => Self::Fjord,
197 "granite" => Self::Granite,
198 "holocene" => Self::Holocene,
199 "isthmus" => Self::Isthmus,
200 "latest" => Self::Latest,
201 _ => bail!("Unknown hardfork {s}"),
202 };
203 Ok(hardfork)
204 }
205}
206
207impl From<OptimismHardfork> for SpecId {
208 fn from(fork: OptimismHardfork) -> Self {
209 match fork {
210 OptimismHardfork::Bedrock => Self::BEDROCK,
211 OptimismHardfork::Regolith => Self::REGOLITH,
212 OptimismHardfork::Canyon => Self::CANYON,
213 OptimismHardfork::Ecotone => Self::ECOTONE,
214 OptimismHardfork::Fjord => Self::FJORD,
215 OptimismHardfork::Granite => Self::GRANITE,
216 OptimismHardfork::Holocene => Self::HOLOCENE,
217 OptimismHardfork::Isthmus => Self::ISTHMUS,
218 OptimismHardfork::Latest => Self::LATEST,
219 }
220 }
221}
222
223#[cfg(test)]
224mod tests {
225 use crate::EthereumHardfork;
226
227 #[test]
228 fn test_hardfork_blocks() {
229 let hf: EthereumHardfork = 12_965_000u64.into();
230 assert_eq!(hf, EthereumHardfork::London);
231
232 let hf: EthereumHardfork = 4370000u64.into();
233 assert_eq!(hf, EthereumHardfork::Byzantium);
234
235 let hf: EthereumHardfork = 12244000u64.into();
236 assert_eq!(hf, EthereumHardfork::Berlin);
237 }
238}