Skip to main content

revmc_codegen/bytecode/
info.rs

1use revm_bytecode::opcode as op;
2use revm_interpreter::instructions::gas_table_spec;
3use revm_primitives::hardfork::SpecId;
4
5/// Opcode information.
6#[derive(Clone, Copy, Debug, PartialEq, Eq)]
7pub struct OpcodeInfo(u32);
8
9impl OpcodeInfo {
10    /// The unknown flag.
11    pub const UNKNOWN: u32 = 1 << 31;
12    /// The dynamic flag.
13    pub const DYNAMIC: u32 = 1 << 30;
14    /// The disabled flag.
15    pub const DISABLED: u32 = 1 << 29;
16    /// The mask for the gas cost (16 bits, max 65535).
17    pub const MASK: u32 = 0xFFFF;
18
19    /// Creates a new gas info with the given gas cost.
20    #[inline]
21    pub const fn new(gas: u16) -> Self {
22        Self(gas as u32)
23    }
24
25    /// Returns `true` if the opcode is unknown.
26    #[inline]
27    pub const fn is_unknown(self) -> bool {
28        self.0 == Self::UNKNOWN
29    }
30
31    /// Returns `true` if the gas cost is dynamic.
32    #[inline]
33    pub const fn is_dynamic(self) -> bool {
34        self.0 & Self::DYNAMIC != 0
35    }
36
37    /// Returns `true` if the opcode is known, but disabled in the current EVM
38    /// version.
39    #[inline]
40    pub const fn is_disabled(self) -> bool {
41        self.0 & Self::DISABLED != 0
42    }
43
44    /// Returns the base gas cost of the opcode.
45    ///
46    /// This may not be the final/full gas cost of the opcode as it may also have a dynamic cost.
47    #[inline]
48    pub const fn base_gas(self) -> u16 {
49        (self.0 & Self::MASK) as u16
50    }
51
52    /// Sets the unknown flag.
53    #[inline]
54    pub const fn set_unknown(&mut self) {
55        self.0 = Self::UNKNOWN;
56    }
57
58    /// Sets the dynamic flag.
59    #[inline]
60    pub const fn set_dynamic(&mut self) {
61        self.0 |= Self::DYNAMIC;
62    }
63
64    /// Sets the disabled flag.
65    #[inline]
66    pub const fn set_disabled(&mut self) {
67        self.0 |= Self::DISABLED;
68    }
69
70    /// Sets the gas cost.
71    #[inline]
72    pub const fn set_gas(&mut self, gas: u16) {
73        self.0 = (self.0 & !Self::MASK) | (gas as u32);
74    }
75}
76
77/// Returns the static info map for the given `SpecId`.
78pub 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
92/// Opcodes with a dynamic gas component that also have a base (static) cost deducted upfront.
93/// Only the dynamic part is paid in builtins.
94const 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
121/// Opcodes whose gas cost is entirely dynamic — computed fully in builtins at runtime.
122const FULLY_DYNAMIC: &[u8] = &[op::SSTORE, op::CREATE, op::CREATE2];
123
124/// Opcodes that are gated behind a specific `SpecId`, paired with the spec they were introduced in.
125const 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
152/// Opcodes present in the upstream instruction table but not supported by revmc (e.g. EOF-only).
153const 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        // Skip opcodes not defined in revm's opcode table.
175        if revm_bytecode::opcode::OpCode::new(op).is_none() {
176            i += 1;
177            continue;
178        }
179
180        // Mark opcodes not supported by revmc (e.g. EOF-only) as disabled rather than
181        // unknown, so they return `NotActivated` instead of `OpcodeNotFound` at runtime.
182        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        // Fully dynamic opcodes have their entire gas cost handled in builtins.
192        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    // Apply spec-gating: mark opcodes as disabled if the current spec is before their introduction.
214    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        // Basic arithmetic.
272        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        // EXP: base 10, dynamic.
277        assert_eq!(cancun[op::EXP as usize].base_gas(), 10);
278        assert!(cancun[op::EXP as usize].is_dynamic());
279
280        // PUSH/DUP/SWAP gas.
281        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        // LOG: base gas only (topic + data cost charged dynamically in builtin).
287        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        // Memory ops: dynamic with base cost 3.
295        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        // Transient storage (Cancun).
301        assert_eq!(cancun[op::TLOAD as usize].base_gas(), 100);
302        assert!(!cancun[op::TLOAD as usize].is_disabled());
303
304        // Unknown opcode.
305        assert!(cancun[0x0C].is_unknown());
306
307        // AMSTERDAM-gated opcodes should be disabled on CANCUN.
308        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        // AMSTERDAM-gated opcodes should be enabled on AMSTERDAM.
315        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        // Spec-gated: PUSH0 disabled before Shanghai.
322        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        // Dynamic-with-base-gas opcodes: base gas varies by spec.
327        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}