1use revm_bytecode::opcode as op;
2use revm_interpreter::{
3 host::DummyHost, instructions::instruction_table_gas_changes_spec, interpreter::EthInterpreter,
4};
5use revm_primitives::hardfork::SpecId;
6
7#[derive(Clone, Copy, Debug, PartialEq, Eq)]
9pub struct OpcodeInfo(u32);
10
11impl OpcodeInfo {
12 pub const UNKNOWN: u32 = 1 << 31;
14 pub const DYNAMIC: u32 = 1 << 30;
16 pub const DISABLED: u32 = 1 << 29;
18 pub const MASK: u32 = 0xFFFF;
20
21 #[inline]
23 pub const fn new(gas: u16) -> Self {
24 Self(gas as u32)
25 }
26
27 #[inline]
29 pub const fn is_unknown(self) -> bool {
30 self.0 == Self::UNKNOWN
31 }
32
33 #[inline]
35 pub const fn is_dynamic(self) -> bool {
36 self.0 & Self::DYNAMIC != 0
37 }
38
39 #[inline]
42 pub const fn is_disabled(self) -> bool {
43 self.0 & Self::DISABLED != 0
44 }
45
46 #[inline]
50 pub const fn base_gas(self) -> u16 {
51 (self.0 & Self::MASK) as u16
52 }
53
54 #[inline]
56 pub fn set_unknown(&mut self) {
57 self.0 = Self::UNKNOWN;
58 }
59
60 #[inline]
62 pub fn set_dynamic(&mut self) {
63 self.0 |= Self::DYNAMIC;
64 }
65
66 #[inline]
68 pub fn set_disabled(&mut self) {
69 self.0 |= Self::DISABLED;
70 }
71
72 #[inline]
74 pub fn set_gas(&mut self, gas: u16) {
75 self.0 = (self.0 & !Self::MASK) | (gas as u32);
76 }
77}
78
79pub fn op_info_map(spec_id: SpecId) -> &'static [OpcodeInfo; 256] {
81 use std::sync::OnceLock;
82 static MAPS: OnceLock<[[OpcodeInfo; 256]; 32]> = OnceLock::new();
83 let maps = MAPS.get_or_init(|| {
84 let mut maps = [[OpcodeInfo(OpcodeInfo::UNKNOWN); 256]; 32];
85 for (i, map) in maps.iter_mut().enumerate() {
86 if let Ok(spec) = SpecId::try_from(i as u8) {
87 *map = make_map(spec);
88 }
89 }
90 maps
91 });
92 &maps[spec_id as usize]
93}
94
95const DYNAMIC_WITH_BASE_GAS: &[u8] = &[
98 op::EXP,
99 op::KECCAK256,
100 op::BALANCE,
101 op::CALLDATACOPY,
102 op::CODECOPY,
103 op::EXTCODESIZE,
104 op::EXTCODECOPY,
105 op::RETURNDATACOPY,
106 op::EXTCODEHASH,
107 op::MLOAD,
108 op::MSTORE,
109 op::MSTORE8,
110 op::MCOPY,
111 op::LOG0,
112 op::LOG1,
113 op::LOG2,
114 op::LOG3,
115 op::LOG4,
116 op::SLOAD,
117 op::CALL,
118 op::CALLCODE,
119 op::DELEGATECALL,
120 op::STATICCALL,
121 op::SELFDESTRUCT,
122];
123
124const FULLY_DYNAMIC: &[u8] = &[op::SSTORE, op::CREATE, op::CREATE2];
126
127const SPEC_GATED_OPCODES: &[(u8, SpecId)] = &[
129 (op::SHL, SpecId::CONSTANTINOPLE),
130 (op::SHR, SpecId::CONSTANTINOPLE),
131 (op::SAR, SpecId::CONSTANTINOPLE),
132 (op::CLZ, SpecId::OSAKA),
133 (op::RETURNDATASIZE, SpecId::BYZANTIUM),
134 (op::RETURNDATACOPY, SpecId::BYZANTIUM),
135 (op::EXTCODEHASH, SpecId::CONSTANTINOPLE),
136 (op::CHAINID, SpecId::ISTANBUL),
137 (op::SELFBALANCE, SpecId::ISTANBUL),
138 (op::BASEFEE, SpecId::LONDON),
139 (op::BLOBHASH, SpecId::CANCUN),
140 (op::BLOBBASEFEE, SpecId::CANCUN),
141 (op::TLOAD, SpecId::CANCUN),
142 (op::TSTORE, SpecId::CANCUN),
143 (op::MCOPY, SpecId::CANCUN),
144 (op::PUSH0, SpecId::SHANGHAI),
145 (op::DELEGATECALL, SpecId::HOMESTEAD),
146 (op::CREATE2, SpecId::PETERSBURG),
147 (op::STATICCALL, SpecId::BYZANTIUM),
148 (op::REVERT, SpecId::BYZANTIUM),
149 (op::DUPN, SpecId::AMSTERDAM),
150 (op::SWAPN, SpecId::AMSTERDAM),
151 (op::EXCHANGE, SpecId::AMSTERDAM),
152 (op::SLOTNUM, SpecId::AMSTERDAM),
153];
154
155const UNSUPPORTED_OPCODES: &[u8] = &[];
157
158fn make_map(spec_id: SpecId) -> [OpcodeInfo; 256] {
159 let table = instruction_table_gas_changes_spec::<EthInterpreter, DummyHost>(spec_id);
160
161 let mut map = [OpcodeInfo(OpcodeInfo::UNKNOWN); 256];
162
163 for i in 0..256u16 {
164 let op = i as u8;
165
166 if revm_bytecode::opcode::OpCode::new(op).is_none() {
168 continue;
169 }
170
171 if UNSUPPORTED_OPCODES.contains(&op) {
174 map[op as usize].set_disabled();
175 continue;
176 }
177
178 let is_fully_dynamic = FULLY_DYNAMIC.contains(&op);
179 let is_dynamic_with_base = DYNAMIC_WITH_BASE_GAS.contains(&op);
180
181 let gas = if is_fully_dynamic {
183 0u16
184 } else {
185 let static_gas = table[op as usize].static_gas();
186 assert!(
187 static_gas <= OpcodeInfo::MASK as u64,
188 "static gas for opcode 0x{op:02X} exceeds OpcodeInfo capacity: {static_gas}"
189 );
190 static_gas as u16
191 };
192
193 let mut info = OpcodeInfo::new(gas);
194
195 if is_fully_dynamic || is_dynamic_with_base {
196 info.set_dynamic();
197 }
198
199 map[op as usize] = info;
200 }
201
202 for &(op, required_spec) in SPEC_GATED_OPCODES {
204 if (spec_id as u8) < (required_spec as u8) {
205 map[op as usize].set_disabled();
206 }
207 }
208
209 map
210}
211
212#[cfg(test)]
213mod tests {
214 use super::*;
215
216 #[test]
217 fn test_clz_flags() {
218 let arrow_glacier = op_info_map(SpecId::ARROW_GLACIER);
219 let cancun = op_info_map(SpecId::CANCUN);
220 let osaka = op_info_map(SpecId::OSAKA);
221
222 let clz_ag = arrow_glacier[op::CLZ as usize];
223 let clz_cancun = cancun[op::CLZ as usize];
224 let clz_osaka = osaka[op::CLZ as usize];
225
226 eprintln!(
227 "CLZ on ARROW_GLACIER: is_unknown={}, is_disabled={}, raw={:#06x}",
228 clz_ag.is_unknown(),
229 clz_ag.is_disabled(),
230 clz_ag.0
231 );
232 eprintln!(
233 "CLZ on CANCUN: is_unknown={}, is_disabled={}, raw={:#06x}",
234 clz_cancun.is_unknown(),
235 clz_cancun.is_disabled(),
236 clz_cancun.0
237 );
238 eprintln!(
239 "CLZ on OSAKA: is_unknown={}, is_disabled={}, raw={:#06x}",
240 clz_osaka.is_unknown(),
241 clz_osaka.is_disabled(),
242 clz_osaka.0
243 );
244
245 assert!(!clz_ag.is_unknown(), "CLZ should not be unknown on pre-OSAKA");
246 assert!(clz_ag.is_disabled(), "CLZ should be disabled on ARROW_GLACIER");
247 assert!(!clz_cancun.is_unknown(), "CLZ should not be unknown on pre-OSAKA");
248 assert!(clz_cancun.is_disabled(), "CLZ should be disabled on CANCUN");
249 assert!(!clz_osaka.is_unknown(), "CLZ should not be unknown on OSAKA");
250 assert!(!clz_osaka.is_disabled(), "CLZ should not be disabled on OSAKA");
251 }
252
253 #[test]
254 fn test_gas_values() {
255 let cancun = op_info_map(SpecId::CANCUN);
256
257 assert_eq!(cancun[op::ADD as usize].base_gas(), 3);
259 assert_eq!(cancun[op::MUL as usize].base_gas(), 5);
260 assert_eq!(cancun[op::ADDMOD as usize].base_gas(), 8);
261
262 assert_eq!(cancun[op::EXP as usize].base_gas(), 10);
264 assert!(cancun[op::EXP as usize].is_dynamic());
265
266 assert_eq!(cancun[op::PUSH1 as usize].base_gas(), 3);
268 assert_eq!(cancun[op::DUP1 as usize].base_gas(), 3);
269 assert_eq!(cancun[op::SWAP1 as usize].base_gas(), 3);
270 assert_eq!(cancun[op::PUSH0 as usize].base_gas(), 2);
271
272 assert_eq!(cancun[op::LOG0 as usize].base_gas(), 375);
274 assert_eq!(cancun[op::LOG1 as usize].base_gas(), 375);
275 assert_eq!(cancun[op::LOG2 as usize].base_gas(), 375);
276 assert_eq!(cancun[op::LOG3 as usize].base_gas(), 375);
277 assert_eq!(cancun[op::LOG4 as usize].base_gas(), 375);
278 assert!(cancun[op::LOG0 as usize].is_dynamic());
279
280 assert_eq!(cancun[op::MLOAD as usize].base_gas(), 3);
282 assert!(cancun[op::MLOAD as usize].is_dynamic());
283 assert_eq!(cancun[op::KECCAK256 as usize].base_gas(), 30);
284 assert!(cancun[op::KECCAK256 as usize].is_dynamic());
285
286 assert_eq!(cancun[op::TLOAD as usize].base_gas(), 100);
288 assert!(!cancun[op::TLOAD as usize].is_disabled());
289
290 assert!(cancun[0x0C].is_unknown());
292
293 assert!(cancun[op::DUPN as usize].is_disabled());
295 assert!(!cancun[op::DUPN as usize].is_unknown());
296 assert!(cancun[op::SWAPN as usize].is_disabled());
297 assert!(cancun[op::EXCHANGE as usize].is_disabled());
298 assert!(cancun[op::SLOTNUM as usize].is_disabled());
299
300 let amsterdam = op_info_map(SpecId::AMSTERDAM);
302 assert!(!amsterdam[op::DUPN as usize].is_disabled());
303 assert!(!amsterdam[op::SWAPN as usize].is_disabled());
304 assert!(!amsterdam[op::EXCHANGE as usize].is_disabled());
305 assert!(!amsterdam[op::SLOTNUM as usize].is_disabled());
306
307 let pre_shanghai = op_info_map(SpecId::MERGE);
309 assert!(pre_shanghai[op::PUSH0 as usize].is_disabled());
310 assert!(!cancun[op::PUSH0 as usize].is_disabled());
311
312 let frontier = op_info_map(SpecId::FRONTIER);
314 assert_eq!(frontier[op::SLOAD as usize].base_gas(), 50);
315 assert_eq!(frontier[op::SELFDESTRUCT as usize].base_gas(), 0);
316 assert!(frontier[op::SELFDESTRUCT as usize].is_dynamic());
317 assert_eq!(frontier[op::CALL as usize].base_gas(), 40);
318 assert_eq!(frontier[op::BALANCE as usize].base_gas(), 20);
319 assert_eq!(frontier[op::EXTCODESIZE as usize].base_gas(), 20);
320 let tangerine = op_info_map(SpecId::TANGERINE);
321 assert_eq!(tangerine[op::SLOAD as usize].base_gas(), 200);
322 assert_eq!(tangerine[op::SELFDESTRUCT as usize].base_gas(), 5000);
323 assert!(tangerine[op::SELFDESTRUCT as usize].is_dynamic());
324 assert_eq!(tangerine[op::CALL as usize].base_gas(), 700);
325 assert_eq!(tangerine[op::BALANCE as usize].base_gas(), 400);
326 assert_eq!(tangerine[op::EXTCODESIZE as usize].base_gas(), 700);
327 let berlin = op_info_map(SpecId::BERLIN);
328 assert_eq!(berlin[op::SLOAD as usize].base_gas(), 100);
329 assert_eq!(berlin[op::CALL as usize].base_gas(), 100);
330 assert_eq!(berlin[op::BALANCE as usize].base_gas(), 100);
331 assert_eq!(berlin[op::EXTCODESIZE as usize].base_gas(), 100);
332 }
333}