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