Skip to main content

eot/
lib.rs

1//! # EOT - EVM Opcode Table
2//!
3//! A comprehensive Rust library for EVM opcodes with fork-aware metadata,
4//! gas cost tracking, and bytecode analysis utilities.
5//!
6//! ## Architecture
7//!
8//! The core type is [`OpCode`], a transparent newtype over `u8` backed by a
9//! static `[Option<OpCodeInfo>; 256]` lookup table. This gives O(1) opcode
10//! lookups with zero heap allocation.
11//!
12//! Fork-specific gas costs are handled by [`OpCode::gas_cost`], which applies
13//! known EIP gas changes on top of the base cost stored in [`OpCodeInfo`].
14//!
15//! ## Quick Start
16//!
17//! ```
18//! use eot::{OpCode, Fork};
19//!
20//! let add = OpCode::ADD;
21//! assert_eq!(add.gas_cost(Fork::Frontier), 3);
22//! assert!(add.is_valid_in(Fork::Frontier));
23//!
24//! // Parse from byte
25//! let op = OpCode::new(0x60).unwrap();
26//! assert_eq!(op, OpCode::PUSH1);
27//! ```
28
29#![deny(missing_docs)]
30#![warn(clippy::all)]
31
32use std::fmt;
33use std::str::FromStr;
34
35pub mod gas;
36pub mod validation;
37
38#[cfg(feature = "unified-opcodes")]
39pub mod unified;
40#[cfg(feature = "unified-opcodes")]
41pub use unified::UnifiedOpcode;
42
43// Re-export key gas types
44pub use gas::{DynamicGasCalculator, ExecutionContext, GasAnalysis, GasCostCategory};
45
46// ---------------------------------------------------------------------------
47// Fork
48// ---------------------------------------------------------------------------
49
50/// Ethereum hard forks that affect EVM opcode behavior.
51///
52/// Only execution-layer forks that introduce new opcodes or change gas costs
53/// are included. Consensus-only upgrades (Altair, Bellatrix, Capella, Deneb,
54/// etc.) are omitted because they don't alter the opcode set.
55#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
56pub enum Fork {
57    /// Frontier (July 30, 2015) — genesis block.
58    Frontier,
59    /// Homestead (March 14, 2016) — added DELEGATECALL.
60    Homestead,
61    /// Tangerine Whistle (October 18, 2016) — EIP-150 gas repricing.
62    TangerineWhistle,
63    /// Spurious Dragon (November 22, 2016) — EIP-161, EIP-170.
64    SpuriousDragon,
65    /// Byzantium (October 16, 2017) — added REVERT, RETURNDATASIZE,
66    /// RETURNDATACOPY, STATICCALL.
67    Byzantium,
68    /// Constantinople (February 28, 2019) — added SHL, SHR, SAR, CREATE2,
69    /// EXTCODEHASH.
70    Constantinople,
71    /// Petersburg (February 28, 2019) — reverted EIP-1283 SSTORE changes.
72    Petersburg,
73    /// Istanbul (December 8, 2019) — EIP-1884 gas repricing, added CHAINID
74    /// and SELFBALANCE.
75    Istanbul,
76    /// Berlin (April 15, 2021) — EIP-2929 state access gas changes.
77    Berlin,
78    /// London (August 5, 2021) — EIP-1559, added BASEFEE.
79    London,
80    /// Paris / The Merge (September 15, 2022) — DIFFICULTY becomes PREVRANDAO.
81    Paris,
82    /// Shanghai (April 12, 2023) — added PUSH0 (EIP-3855).
83    Shanghai,
84    /// Cancun (March 13, 2024) — added TLOAD, TSTORE, MCOPY, BLOBHASH,
85    /// BLOBBASEFEE.
86    Cancun,
87    /// Prague (May 7, 2025).
88    Prague,
89    /// Fusaka / Fulu-Osaka (December 3, 2025) — EOF (EIP-7692), CLZ, PeerDAS.
90    Fusaka,
91}
92
93impl Fork {
94    /// All EVM-relevant forks in chronological order.
95    pub const fn ordered() -> &'static [Self] {
96        &[
97            Self::Frontier,
98            Self::Homestead,
99            Self::TangerineWhistle,
100            Self::SpuriousDragon,
101            Self::Byzantium,
102            Self::Constantinople,
103            Self::Petersburg,
104            Self::Istanbul,
105            Self::Berlin,
106            Self::London,
107            Self::Paris,
108            Self::Shanghai,
109            Self::Cancun,
110            Self::Prague,
111            Self::Fusaka,
112        ]
113    }
114
115    /// The latest supported fork.
116    pub const fn latest() -> Self {
117        Self::Fusaka
118    }
119}
120
121impl fmt::Display for Fork {
122    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
123        fmt::Debug::fmt(self, f)
124    }
125}
126
127// ---------------------------------------------------------------------------
128// Group
129// ---------------------------------------------------------------------------
130
131/// Opcode categories following the Yellow Paper grouping.
132#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
133pub enum Group {
134    /// 0x00–0x0b: Stop and arithmetic.
135    StopArithmetic,
136    /// 0x10–0x1e: Comparison and bitwise logic.
137    ComparisonBitwiseLogic,
138    /// 0x20: Keccak-256.
139    Sha3,
140    /// 0x30–0x3f: Environmental information.
141    EnvironmentalInformation,
142    /// 0x40–0x4a: Block information.
143    BlockInformation,
144    /// 0x50–0x5f: Stack, memory, storage and flow.
145    StackMemoryStorageFlow,
146    /// 0x60–0x7f: Push operations.
147    Push,
148    /// 0x80–0x8f: Duplication operations.
149    Duplication,
150    /// 0x90–0x9f: Exchange operations.
151    Exchange,
152    /// 0xa0–0xa4: Logging.
153    Logging,
154    /// 0xf0–0xff: System operations.
155    System,
156    /// 0xd0–0xd3, 0xe0–0xee: EOF (EVM Object Format) operations.
157    Eof,
158}
159
160// ---------------------------------------------------------------------------
161// OpCodeInfo
162// ---------------------------------------------------------------------------
163
164/// Static metadata for a single EVM opcode.
165///
166/// Stored in the global `OPCODE_TABLE` and returned by [`OpCode::info`].
167#[derive(Clone, Copy, Debug, PartialEq, Eq)]
168pub struct OpCodeInfo {
169    /// Mnemonic name (e.g. `"ADD"`, `"PUSH1"`).
170    pub name: &'static str,
171    /// Number of stack items consumed.
172    pub inputs: u8,
173    /// Number of stack items produced.
174    pub outputs: u8,
175    /// Base gas cost at the fork where the opcode was introduced.
176    ///
177    /// Use [`OpCode::gas_cost`] for fork-aware costs.
178    pub base_gas: u16,
179    /// Opcode category.
180    pub group: Group,
181    /// Fork where this opcode was first available.
182    pub introduced_in: Fork,
183    /// EIP that introduced this opcode, if any.
184    pub eip: Option<u16>,
185    /// Bytes of immediate data following the opcode (e.g. 1 for PUSH1).
186    pub immediate_size: u8,
187    /// Whether this opcode halts execution (STOP, RETURN, REVERT, etc.).
188    pub terminates: bool,
189}
190
191impl OpCodeInfo {
192    /// Create a new `OpCodeInfo` with default optional fields.
193    const fn new(
194        name: &'static str,
195        inputs: u8,
196        outputs: u8,
197        gas: u16,
198        group: Group,
199        fork: Fork,
200    ) -> Self {
201        Self {
202            name,
203            inputs,
204            outputs,
205            base_gas: gas,
206            group,
207            introduced_in: fork,
208            eip: None,
209            immediate_size: 0,
210            terminates: false,
211        }
212    }
213
214    /// Set the EIP number.
215    const fn eip(mut self, eip: u16) -> Self {
216        self.eip = Some(eip);
217        self
218    }
219
220    /// Set the immediate data size in bytes.
221    const fn imm(mut self, size: u8) -> Self {
222        self.immediate_size = size;
223        self
224    }
225
226    /// Mark this opcode as terminating execution.
227    const fn term(mut self) -> Self {
228        self.terminates = true;
229        self
230    }
231
232    /// Net stack effect (outputs − inputs).
233    pub const fn stack_diff(&self) -> i16 {
234        self.outputs as i16 - self.inputs as i16
235    }
236}
237
238// ---------------------------------------------------------------------------
239// OpCode — core type
240// ---------------------------------------------------------------------------
241
242/// A single EVM opcode, stored as a transparent `u8` wrapper.
243///
244/// Known opcodes have entries in the global `OPCODE_TABLE` and can be
245/// looked up in O(1) via [`OpCode::info`].
246#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
247#[repr(transparent)]
248pub struct OpCode(u8);
249
250// ---------------------------------------------------------------------------
251// opcodes! macro — generates constants + static lookup table
252// ---------------------------------------------------------------------------
253
254macro_rules! opcodes {
255    ($(
256        $byte:literal => $name:ident
257            ($in:expr, $out:expr, $gas:expr, $group:ident, $fork:ident)
258            $([ $($chain:tt)* ])?
259    ;)*) => {
260        impl OpCode {
261            $(
262                #[doc = concat!("`", stringify!($name), "` (`0x", stringify!($byte), "`)")]
263                pub const $name: Self = Self($byte);
264            )*
265        }
266
267        static OPCODE_TABLE: [Option<OpCodeInfo>; 256] = {
268            const NONE: Option<OpCodeInfo> = None;
269            let mut t = [NONE; 256];
270            $(
271                t[$byte as usize] = Some(
272                    OpCodeInfo::new(
273                        stringify!($name),
274                        $in, $out, $gas,
275                        Group::$group,
276                        Fork::$fork,
277                    )
278                    $( .$($chain)* )?
279                );
280            )*
281            t
282        };
283    };
284}
285
286// ---------------------------------------------------------------------------
287// All 149 EVM opcodes
288// ---------------------------------------------------------------------------
289
290opcodes! {
291    // -- 0x00–0x0b: Stop and Arithmetic ------------------------------------
292    0x00 => STOP       (0, 0,  0, StopArithmetic, Frontier) [term()];
293    0x01 => ADD        (2, 1,  3, StopArithmetic, Frontier);
294    0x02 => MUL        (2, 1,  5, StopArithmetic, Frontier);
295    0x03 => SUB        (2, 1,  3, StopArithmetic, Frontier);
296    0x04 => DIV        (2, 1,  5, StopArithmetic, Frontier);
297    0x05 => SDIV       (2, 1,  5, StopArithmetic, Frontier);
298    0x06 => MOD        (2, 1,  5, StopArithmetic, Frontier);
299    0x07 => SMOD       (2, 1,  5, StopArithmetic, Frontier);
300    0x08 => ADDMOD     (3, 1,  8, StopArithmetic, Frontier);
301    0x09 => MULMOD     (3, 1,  8, StopArithmetic, Frontier);
302    0x0a => EXP        (2, 1, 10, StopArithmetic, Frontier);
303    0x0b => SIGNEXTEND (2, 1,  5, StopArithmetic, Frontier);
304
305    // -- 0x10–0x1d: Comparison & Bitwise Logic -----------------------------
306    0x10 => LT     (2, 1, 3, ComparisonBitwiseLogic, Frontier);
307    0x11 => GT     (2, 1, 3, ComparisonBitwiseLogic, Frontier);
308    0x12 => SLT    (2, 1, 3, ComparisonBitwiseLogic, Frontier);
309    0x13 => SGT    (2, 1, 3, ComparisonBitwiseLogic, Frontier);
310    0x14 => EQ     (2, 1, 3, ComparisonBitwiseLogic, Frontier);
311    0x15 => ISZERO (1, 1, 3, ComparisonBitwiseLogic, Frontier);
312    0x16 => AND    (2, 1, 3, ComparisonBitwiseLogic, Frontier);
313    0x17 => OR     (2, 1, 3, ComparisonBitwiseLogic, Frontier);
314    0x18 => XOR    (2, 1, 3, ComparisonBitwiseLogic, Frontier);
315    0x19 => NOT    (1, 1, 3, ComparisonBitwiseLogic, Frontier);
316    0x1a => BYTE   (2, 1, 3, ComparisonBitwiseLogic, Frontier);
317    0x1b => SHL    (2, 1, 3, ComparisonBitwiseLogic, Constantinople) [eip(145)];
318    0x1c => SHR    (2, 1, 3, ComparisonBitwiseLogic, Constantinople) [eip(145)];
319    0x1d => SAR    (2, 1, 3, ComparisonBitwiseLogic, Constantinople) [eip(145)];
320    0x1e => CLZ    (1, 1, 3, ComparisonBitwiseLogic, Fusaka)        [eip(7939)];
321
322    // -- 0x20: Keccak-256 --------------------------------------------------
323    0x20 => KECCAK256 (2, 1, 30, Sha3, Frontier);
324
325    // -- 0x30–0x3f: Environmental Information ------------------------------
326    0x30 => ADDRESS        (0, 1,  2, EnvironmentalInformation, Frontier);
327    0x31 => BALANCE        (1, 1, 20, EnvironmentalInformation, Frontier);
328    0x32 => ORIGIN         (0, 1,  2, EnvironmentalInformation, Frontier);
329    0x33 => CALLER         (0, 1,  2, EnvironmentalInformation, Frontier);
330    0x34 => CALLVALUE      (0, 1,  2, EnvironmentalInformation, Frontier);
331    0x35 => CALLDATALOAD   (1, 1,  3, EnvironmentalInformation, Frontier);
332    0x36 => CALLDATASIZE   (0, 1,  2, EnvironmentalInformation, Frontier);
333    0x37 => CALLDATACOPY   (3, 0,  3, EnvironmentalInformation, Frontier);
334    0x38 => CODESIZE       (0, 1,  2, EnvironmentalInformation, Frontier);
335    0x39 => CODECOPY       (3, 0,  3, EnvironmentalInformation, Frontier);
336    0x3a => GASPRICE       (0, 1,  2, EnvironmentalInformation, Frontier);
337    0x3b => EXTCODESIZE    (1, 1, 20, EnvironmentalInformation, Frontier);
338    0x3c => EXTCODECOPY    (4, 0, 20, EnvironmentalInformation, Frontier);
339    0x3d => RETURNDATASIZE (0, 1,  2, EnvironmentalInformation, Byzantium) [eip(211)];
340    0x3e => RETURNDATACOPY (3, 0,  3, EnvironmentalInformation, Byzantium) [eip(211)];
341    0x3f => EXTCODEHASH    (1, 1, 400, EnvironmentalInformation, Constantinople) [eip(1052)];
342
343    // -- 0x40–0x4a: Block Information --------------------------------------
344    0x40 => BLOCKHASH   (1, 1, 20, BlockInformation, Frontier);
345    0x41 => COINBASE    (0, 1,  2, BlockInformation, Frontier);
346    0x42 => TIMESTAMP   (0, 1,  2, BlockInformation, Frontier);
347    0x43 => NUMBER      (0, 1,  2, BlockInformation, Frontier);
348    0x44 => DIFFICULTY  (0, 1,  2, BlockInformation, Frontier);
349    0x45 => GASLIMIT    (0, 1,  2, BlockInformation, Frontier);
350    0x46 => CHAINID     (0, 1,  2, BlockInformation, Istanbul)  [eip(1344)];
351    0x47 => SELFBALANCE (0, 1,  5, BlockInformation, Istanbul)  [eip(1884)];
352    0x48 => BASEFEE     (0, 1,  2, BlockInformation, London)    [eip(3198)];
353    0x49 => BLOBHASH    (1, 1,  3, BlockInformation, Cancun)    [eip(4844)];
354    0x4a => BLOBBASEFEE (0, 1,  2, BlockInformation, Cancun)    [eip(7516)];
355
356    // -- 0x50–0x5f: Stack, Memory, Storage and Flow ------------------------
357    0x50 => POP      (1, 0,    2, StackMemoryStorageFlow, Frontier);
358    0x51 => MLOAD    (1, 1,    3, StackMemoryStorageFlow, Frontier);
359    0x52 => MSTORE   (2, 0,    3, StackMemoryStorageFlow, Frontier);
360    0x53 => MSTORE8  (2, 0,    3, StackMemoryStorageFlow, Frontier);
361    0x54 => SLOAD    (1, 1,   50, StackMemoryStorageFlow, Frontier);
362    0x55 => SSTORE   (2, 0, 5000, StackMemoryStorageFlow, Frontier);
363    0x56 => JUMP     (1, 0,    8, StackMemoryStorageFlow, Frontier);
364    0x57 => JUMPI    (2, 0,   10, StackMemoryStorageFlow, Frontier);
365    0x58 => PC       (0, 1,    2, StackMemoryStorageFlow, Frontier);
366    0x59 => MSIZE    (0, 1,    2, StackMemoryStorageFlow, Frontier);
367    0x5a => GAS      (0, 1,    2, StackMemoryStorageFlow, Frontier);
368    0x5b => JUMPDEST (0, 0,    1, StackMemoryStorageFlow, Frontier);
369    0x5c => TLOAD    (1, 1,  100, StackMemoryStorageFlow, Cancun)   [eip(1153)];
370    0x5d => TSTORE   (2, 0,  100, StackMemoryStorageFlow, Cancun)   [eip(1153)];
371    0x5e => MCOPY    (3, 0,    3, StackMemoryStorageFlow, Cancun)   [eip(5656)];
372    0x5f => PUSH0    (0, 1,    2, Push, Shanghai) [eip(3855)];
373
374    // -- 0x60–0x7f: Push Operations ----------------------------------------
375    0x60 => PUSH1  (0, 1, 3, Push, Frontier) [imm(1)];
376    0x61 => PUSH2  (0, 1, 3, Push, Frontier) [imm(2)];
377    0x62 => PUSH3  (0, 1, 3, Push, Frontier) [imm(3)];
378    0x63 => PUSH4  (0, 1, 3, Push, Frontier) [imm(4)];
379    0x64 => PUSH5  (0, 1, 3, Push, Frontier) [imm(5)];
380    0x65 => PUSH6  (0, 1, 3, Push, Frontier) [imm(6)];
381    0x66 => PUSH7  (0, 1, 3, Push, Frontier) [imm(7)];
382    0x67 => PUSH8  (0, 1, 3, Push, Frontier) [imm(8)];
383    0x68 => PUSH9  (0, 1, 3, Push, Frontier) [imm(9)];
384    0x69 => PUSH10 (0, 1, 3, Push, Frontier) [imm(10)];
385    0x6a => PUSH11 (0, 1, 3, Push, Frontier) [imm(11)];
386    0x6b => PUSH12 (0, 1, 3, Push, Frontier) [imm(12)];
387    0x6c => PUSH13 (0, 1, 3, Push, Frontier) [imm(13)];
388    0x6d => PUSH14 (0, 1, 3, Push, Frontier) [imm(14)];
389    0x6e => PUSH15 (0, 1, 3, Push, Frontier) [imm(15)];
390    0x6f => PUSH16 (0, 1, 3, Push, Frontier) [imm(16)];
391    0x70 => PUSH17 (0, 1, 3, Push, Frontier) [imm(17)];
392    0x71 => PUSH18 (0, 1, 3, Push, Frontier) [imm(18)];
393    0x72 => PUSH19 (0, 1, 3, Push, Frontier) [imm(19)];
394    0x73 => PUSH20 (0, 1, 3, Push, Frontier) [imm(20)];
395    0x74 => PUSH21 (0, 1, 3, Push, Frontier) [imm(21)];
396    0x75 => PUSH22 (0, 1, 3, Push, Frontier) [imm(22)];
397    0x76 => PUSH23 (0, 1, 3, Push, Frontier) [imm(23)];
398    0x77 => PUSH24 (0, 1, 3, Push, Frontier) [imm(24)];
399    0x78 => PUSH25 (0, 1, 3, Push, Frontier) [imm(25)];
400    0x79 => PUSH26 (0, 1, 3, Push, Frontier) [imm(26)];
401    0x7a => PUSH27 (0, 1, 3, Push, Frontier) [imm(27)];
402    0x7b => PUSH28 (0, 1, 3, Push, Frontier) [imm(28)];
403    0x7c => PUSH29 (0, 1, 3, Push, Frontier) [imm(29)];
404    0x7d => PUSH30 (0, 1, 3, Push, Frontier) [imm(30)];
405    0x7e => PUSH31 (0, 1, 3, Push, Frontier) [imm(31)];
406    0x7f => PUSH32 (0, 1, 3, Push, Frontier) [imm(32)];
407
408    // -- 0x80–0x8f: Duplication Operations ---------------------------------
409    0x80 => DUP1  ( 1,  2, 3, Duplication, Frontier);
410    0x81 => DUP2  ( 2,  3, 3, Duplication, Frontier);
411    0x82 => DUP3  ( 3,  4, 3, Duplication, Frontier);
412    0x83 => DUP4  ( 4,  5, 3, Duplication, Frontier);
413    0x84 => DUP5  ( 5,  6, 3, Duplication, Frontier);
414    0x85 => DUP6  ( 6,  7, 3, Duplication, Frontier);
415    0x86 => DUP7  ( 7,  8, 3, Duplication, Frontier);
416    0x87 => DUP8  ( 8,  9, 3, Duplication, Frontier);
417    0x88 => DUP9  ( 9, 10, 3, Duplication, Frontier);
418    0x89 => DUP10 (10, 11, 3, Duplication, Frontier);
419    0x8a => DUP11 (11, 12, 3, Duplication, Frontier);
420    0x8b => DUP12 (12, 13, 3, Duplication, Frontier);
421    0x8c => DUP13 (13, 14, 3, Duplication, Frontier);
422    0x8d => DUP14 (14, 15, 3, Duplication, Frontier);
423    0x8e => DUP15 (15, 16, 3, Duplication, Frontier);
424    0x8f => DUP16 (16, 17, 3, Duplication, Frontier);
425
426    // -- 0x90–0x9f: Exchange Operations ------------------------------------
427    0x90 => SWAP1  ( 2,  2, 3, Exchange, Frontier);
428    0x91 => SWAP2  ( 3,  3, 3, Exchange, Frontier);
429    0x92 => SWAP3  ( 4,  4, 3, Exchange, Frontier);
430    0x93 => SWAP4  ( 5,  5, 3, Exchange, Frontier);
431    0x94 => SWAP5  ( 6,  6, 3, Exchange, Frontier);
432    0x95 => SWAP6  ( 7,  7, 3, Exchange, Frontier);
433    0x96 => SWAP7  ( 8,  8, 3, Exchange, Frontier);
434    0x97 => SWAP8  ( 9,  9, 3, Exchange, Frontier);
435    0x98 => SWAP9  (10, 10, 3, Exchange, Frontier);
436    0x99 => SWAP10 (11, 11, 3, Exchange, Frontier);
437    0x9a => SWAP11 (12, 12, 3, Exchange, Frontier);
438    0x9b => SWAP12 (13, 13, 3, Exchange, Frontier);
439    0x9c => SWAP13 (14, 14, 3, Exchange, Frontier);
440    0x9d => SWAP14 (15, 15, 3, Exchange, Frontier);
441    0x9e => SWAP15 (16, 16, 3, Exchange, Frontier);
442    0x9f => SWAP16 (17, 17, 3, Exchange, Frontier);
443
444    // -- 0xa0–0xa4: Logging Operations -------------------------------------
445    0xa0 => LOG0 (2, 0,  375, Logging, Frontier);
446    0xa1 => LOG1 (3, 0,  750, Logging, Frontier);
447    0xa2 => LOG2 (4, 0, 1125, Logging, Frontier);
448    0xa3 => LOG3 (5, 0, 1500, Logging, Frontier);
449    0xa4 => LOG4 (6, 0, 1875, Logging, Frontier);
450
451    // -- 0xf0–0xff: System Operations --------------------------------------
452    0xf0 => CREATE       (3, 1, 32000, System, Frontier);
453    0xf1 => CALL         (7, 1,    40, System, Frontier);
454    0xf2 => CALLCODE     (7, 1,    40, System, Frontier);
455    0xf3 => RETURN       (2, 0,     0, System, Frontier) [term()];
456    0xf4 => DELEGATECALL (6, 1,    40, System, Homestead)      [eip(7)];
457    0xf5 => CREATE2      (4, 1, 32000, System, Constantinople) [eip(1014)];
458    0xfa => STATICCALL   (6, 1,   700, System, Byzantium)      [eip(214)];
459    0xfd => REVERT       (2, 0,     0, System, Byzantium)      [eip(140).term()];
460    0xfe => INVALID      (0, 0,     0, System, Frontier)       [term()];
461    0xff => SELFDESTRUCT (1, 0,     0, System, Frontier);
462
463    // -- 0xd0–0xd3: EOF Data Section Access (EIP-7480) ---------------------
464    0xd0 => DATALOAD       (1, 1, 4, Eof, Fusaka) [eip(7480)];
465    0xd1 => DATALOADN      (0, 1, 3, Eof, Fusaka) [eip(7480).imm(2)];
466    0xd2 => DATASIZE       (0, 1, 2, Eof, Fusaka) [eip(7480)];
467    0xd3 => DATACOPY       (3, 0, 3, Eof, Fusaka) [eip(7480)];
468
469    // -- 0xe0–0xe8: EOF Control Flow & Stack (EIP-4200/4750/6206/663) ------
470    0xe0 => RJUMP          (0, 0, 2, Eof, Fusaka) [eip(4200).imm(2)];
471    0xe1 => RJUMPI         (1, 0, 4, Eof, Fusaka) [eip(4200).imm(2)];
472    0xe2 => RJUMPV         (1, 0, 4, Eof, Fusaka) [eip(4200).imm(1)];
473    0xe3 => CALLF          (0, 0, 5, Eof, Fusaka) [eip(4750).imm(2)];
474    0xe4 => RETF           (0, 0, 3, Eof, Fusaka) [eip(4750).term()];
475    0xe5 => JUMPF          (0, 0, 5, Eof, Fusaka) [eip(6206).imm(2).term()];
476    0xe6 => DUPN           (0, 1, 3, Eof, Fusaka) [eip(663).imm(1)];
477    0xe7 => SWAPN          (0, 0, 3, Eof, Fusaka) [eip(663).imm(1)];
478    0xe8 => EXCHANGE       (0, 0, 3, Eof, Fusaka) [eip(663).imm(1)];
479
480    // -- 0xec, 0xee: EOF Contract Creation (EIP-7620) ----------------------
481    0xec => EOFCREATE      (4, 1, 32000, Eof, Fusaka) [eip(7620).imm(1)];
482    0xee => RETURNCONTRACT (2, 0,     0, Eof, Fusaka) [eip(7620).imm(1).term()];
483
484    // -- 0xf7–0xfb: EOF Calls & Return Data (EIP-7069) --------------------
485    0xf7 => RETURNDATALOAD  (1, 1,   3, System, Fusaka) [eip(7069)];
486    0xf8 => EXTCALL         (4, 1, 100, System, Fusaka) [eip(7069)];
487    0xf9 => EXTDELEGATECALL (3, 1, 100, System, Fusaka) [eip(7069)];
488    0xfb => EXTSTATICCALL   (3, 1, 100, System, Fusaka) [eip(7069)];
489}
490
491// Alias: after The Merge (Paris), DIFFICULTY returns the beacon chain
492// PREVRANDAO value (EIP-4399). The byte 0x44 is unchanged.
493impl OpCode {
494    /// Alias for [`DIFFICULTY`](Self::DIFFICULTY) after The Merge (EIP-4399).
495    pub const PREVRANDAO: Self = Self(0x44);
496}
497
498// ---------------------------------------------------------------------------
499// OpCode — methods
500// ---------------------------------------------------------------------------
501
502impl OpCode {
503    /// Creates an `OpCode` if `byte` maps to a known opcode.
504    pub const fn new(byte: u8) -> Option<Self> {
505        if OPCODE_TABLE[byte as usize].is_some() {
506            Some(Self(byte))
507        } else {
508            None
509        }
510    }
511
512    /// Wraps any `u8` as an `OpCode` without validation.
513    ///
514    /// Unknown bytes will return `None` from [`info`](Self::info).
515    pub const fn from_byte(byte: u8) -> Self {
516        Self(byte)
517    }
518
519    /// Returns the raw byte value.
520    pub const fn byte(&self) -> u8 {
521        self.0
522    }
523
524    /// Returns static metadata, or `None` for unknown opcodes.
525    pub const fn info(&self) -> Option<&'static OpCodeInfo> {
526        match &OPCODE_TABLE[self.0 as usize] {
527            Some(info) => Some(info),
528            None => None,
529        }
530    }
531
532    /// Returns the mnemonic name, or `"UNKNOWN"` for unknown opcodes.
533    pub fn name(&self) -> &'static str {
534        match self.info() {
535            Some(info) => info.name,
536            None => "UNKNOWN",
537        }
538    }
539
540    /// Returns `true` if this opcode existed at `fork`.
541    pub fn is_valid_in(&self, fork: Fork) -> bool {
542        match self.info() {
543            Some(info) => info.introduced_in <= fork,
544            None => false,
545        }
546    }
547
548    /// Returns the static gas cost for this opcode at `fork`.
549    ///
550    /// This accounts for EIP-based gas repricing across forks. For opcodes
551    /// with dynamic pricing (warm/cold access, memory expansion, etc.), use
552    /// [`DynamicGasCalculator`].
553    pub fn gas_cost(&self, fork: Fork) -> u16 {
554        match self.info() {
555            Some(info) => gas_cost_for_fork(self.0, info.base_gas, fork),
556            None => 0,
557        }
558    }
559
560    /// Returns `true` if this is a PUSH opcode (PUSH0–PUSH32).
561    pub const fn is_push(&self) -> bool {
562        matches!(self.0, 0x5f..=0x7f)
563    }
564
565    /// Returns `true` if this is a DUP opcode (DUP1–DUP16).
566    pub const fn is_dup(&self) -> bool {
567        matches!(self.0, 0x80..=0x8f)
568    }
569
570    /// Returns `true` if this is a SWAP opcode (SWAP1–SWAP16).
571    pub const fn is_swap(&self) -> bool {
572        matches!(self.0, 0x90..=0x9f)
573    }
574
575    /// Returns `true` if this is a LOG opcode (LOG0–LOG4).
576    pub const fn is_log(&self) -> bool {
577        matches!(self.0, 0xa0..=0xa4)
578    }
579
580    /// Returns `true` if this opcode terminates execution.
581    pub fn terminates(&self) -> bool {
582        self.info().is_some_and(|i| i.terminates)
583    }
584
585    /// Returns `true` if this opcode affects control flow.
586    pub fn is_control_flow(&self) -> bool {
587        matches!(
588            self.0,
589            0x00 | 0x56 | 0x57 | 0x5b | 0xf3 | 0xfd | 0xfe | 0xff |
590            // EOF: RJUMP, RJUMPI, RJUMPV, CALLF, RETF, JUMPF
591            0xe0..=0xe5
592        )
593    }
594
595    /// Returns `true` if this opcode modifies persistent state.
596    pub fn modifies_state(&self) -> bool {
597        matches!(
598            self.0,
599            0x55 | 0x5d | 0xf0 | 0xf1 | 0xf2 | 0xf4 | 0xf5 | 0xff |
600            // EOF: EOFCREATE, EXTCALL, EXTDELEGATECALL
601            0xec | 0xf8 | 0xf9
602        )
603    }
604
605    /// Returns `true` if this opcode reads or writes memory.
606    pub fn affects_memory(&self) -> bool {
607        matches!(
608            self.0,
609            0x20 | 0x37 | 0x39 | 0x3e | 0x51..=0x53 | 0x5e |
610            0xa0..=0xa4 | 0xf0..=0xf5 | 0xfa | 0xfd |
611            // EOF: DATACOPY, EOFCREATE, RETURNCONTRACT
612            0xd3 | 0xec | 0xee
613        )
614    }
615
616    /// Returns `true` if this opcode reads or writes storage.
617    pub fn affects_storage(&self) -> bool {
618        matches!(self.0, 0x54 | 0x55 | 0x5c | 0x5d)
619    }
620
621    /// Returns `true` if the gas cost varies with execution context.
622    pub fn has_dynamic_gas(&self) -> bool {
623        matches!(
624            self.0,
625            0x0a | 0x20 |
626            0x31 | 0x37 | 0x39 | 0x3b | 0x3c | 0x3e | 0x3f |
627            0x54 | 0x55 | 0x5c | 0x5d | 0x5e |
628            0x51..=0x53 |
629            0xa0..=0xa4 |
630            0xf0..=0xf5 | 0xfa | 0xff |
631            // EOF: DATACOPY, EOFCREATE, EXTCALL, EXTDELEGATECALL, EXTSTATICCALL
632            0xd3 | 0xec | 0xf8 | 0xf9 | 0xfb
633        )
634    }
635
636    /// Returns `true` if this is an EOF-only opcode (EIP-7692).
637    pub fn is_eof(&self) -> bool {
638        matches!(
639            self.0,
640            0xd0..=0xd3 | 0xe0..=0xe8 | 0xec | 0xee
641        )
642    }
643
644    /// Returns an iterator over all known opcodes.
645    pub fn iter_all() -> impl Iterator<Item = OpCode> {
646        (0u16..=255).filter_map(|b| Self::new(b as u8))
647    }
648
649    /// Returns the number of opcodes valid at `fork`.
650    pub fn count_at(fork: Fork) -> usize {
651        Self::iter_all().filter(|op| op.is_valid_in(fork)).count()
652    }
653}
654
655// ---------------------------------------------------------------------------
656// Fork-specific gas cost adjustments
657// ---------------------------------------------------------------------------
658
659/// Applies known EIP gas repricing for a given fork.
660///
661/// This encodes the historical gas changes from Tangerine Whistle (EIP-150),
662/// Istanbul (EIP-1884), and Berlin (EIP-2929) in a single function.
663fn gas_cost_for_fork(byte: u8, base: u16, fork: Fork) -> u16 {
664    use Fork::*;
665    match byte {
666        // BALANCE: 20 → 400 (TW/EIP-150) → 700 (Istanbul/EIP-1884) → 2600 (Berlin/EIP-2929)
667        0x31 => {
668            if fork >= Berlin {
669                2600
670            } else if fork >= Istanbul {
671                700
672            } else if fork >= TangerineWhistle {
673                400
674            } else {
675                base
676            }
677        }
678        // EXTCODESIZE: 20 → 700 (TW) → 2600 (Berlin)
679        0x3b => {
680            if fork >= Berlin {
681                2600
682            } else if fork >= TangerineWhistle {
683                700
684            } else {
685                base
686            }
687        }
688        // EXTCODECOPY: 20 → 700 (TW) → 2600 (Berlin)
689        0x3c => {
690            if fork >= Berlin {
691                2600
692            } else if fork >= TangerineWhistle {
693                700
694            } else {
695                base
696            }
697        }
698        // EXTCODEHASH: 400 (Constantinople) → 700 (Istanbul) → 2600 (Berlin)
699        0x3f => {
700            if fork >= Berlin {
701                2600
702            } else if fork >= Istanbul {
703                700
704            } else {
705                base
706            }
707        }
708        // SLOAD: 50 → 200 (TW) → 800 (Istanbul) → 2100 (Berlin, cold)
709        0x54 => {
710            if fork >= Berlin {
711                2100
712            } else if fork >= Istanbul {
713                800
714            } else if fork >= TangerineWhistle {
715                200
716            } else {
717                base
718            }
719        }
720        // CALL, CALLCODE: 40 → 700 (TW) → 100 (Berlin, warm)
721        0xf1 | 0xf2 => {
722            if fork >= Berlin {
723                100
724            } else if fork >= TangerineWhistle {
725                700
726            } else {
727                base
728            }
729        }
730        // DELEGATECALL: 40 (Homestead) → 700 (TW) → 100 (Berlin, warm)
731        0xf4 => {
732            if fork >= Berlin {
733                100
734            } else if fork >= TangerineWhistle {
735                700
736            } else {
737                base
738            }
739        }
740        // STATICCALL: 700 (Byzantium) → 100 (Berlin, warm)
741        0xfa => {
742            if fork >= Berlin {
743                100
744            } else {
745                base
746            }
747        }
748        // SELFDESTRUCT: 0 → 5000 (TW/EIP-150)
749        0xff => {
750            if fork >= TangerineWhistle {
751                5000
752            } else {
753                base
754            }
755        }
756        _ => base,
757    }
758}
759
760// ---------------------------------------------------------------------------
761// Trait implementations
762// ---------------------------------------------------------------------------
763
764impl fmt::Debug for OpCode {
765    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
766        match self.info() {
767            Some(info) => write!(f, "{}", info.name),
768            None => write!(f, "UNKNOWN(0x{:02x})", self.0),
769        }
770    }
771}
772
773impl fmt::Display for OpCode {
774    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
775        fmt::Debug::fmt(self, f)
776    }
777}
778
779impl From<u8> for OpCode {
780    fn from(byte: u8) -> Self {
781        Self(byte)
782    }
783}
784
785impl From<OpCode> for u8 {
786    fn from(op: OpCode) -> Self {
787        op.0
788    }
789}
790
791impl FromStr for OpCode {
792    type Err = String;
793
794    fn from_str(s: &str) -> Result<Self, Self::Err> {
795        // Linear scan — fine for a parse utility, not a hot path.
796        for (i, entry) in OPCODE_TABLE.iter().enumerate() {
797            if let Some(info) = entry {
798                if info.name.eq_ignore_ascii_case(s) {
799                    return Ok(Self(i as u8));
800                }
801            }
802        }
803        // Aliases
804        match s {
805            "SHA3" => Ok(Self::KECCAK256),
806            "PREVRANDAO" => Ok(Self::DIFFICULTY),
807            _ => Err(format!("unknown opcode: {s}")),
808        }
809    }
810}
811
812// ---------------------------------------------------------------------------
813// Serde support
814// ---------------------------------------------------------------------------
815
816#[cfg(feature = "serde")]
817mod serde_impl {
818    use super::OpCode;
819
820    impl serde::Serialize for OpCode {
821        fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
822            serializer.serialize_str(self.name())
823        }
824    }
825
826    impl<'de> serde::Deserialize<'de> for OpCode {
827        fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
828            struct Visitor;
829            impl serde::de::Visitor<'_> for Visitor {
830                type Value = OpCode;
831                fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
832                    f.write_str("an opcode name or byte value")
833                }
834                fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<OpCode, E> {
835                    v.parse().map_err(E::custom)
836                }
837                fn visit_u64<E: serde::de::Error>(self, v: u64) -> Result<OpCode, E> {
838                    if v > 255 {
839                        return Err(E::custom("opcode byte out of range"));
840                    }
841                    Ok(OpCode::from_byte(v as u8))
842                }
843            }
844            deserializer.deserialize_any(Visitor)
845        }
846    }
847}
848
849// ---------------------------------------------------------------------------
850// Tests
851// ---------------------------------------------------------------------------
852
853#[cfg(test)]
854mod tests {
855    use super::*;
856
857    #[test]
858    fn opcode_constants() {
859        assert_eq!(OpCode::STOP.byte(), 0x00);
860        assert_eq!(OpCode::ADD.byte(), 0x01);
861        assert_eq!(OpCode::PUSH1.byte(), 0x60);
862        assert_eq!(OpCode::DUP1.byte(), 0x80);
863        assert_eq!(OpCode::SWAP1.byte(), 0x90);
864        assert_eq!(OpCode::SELFDESTRUCT.byte(), 0xff);
865        assert_eq!(OpCode::PREVRANDAO, OpCode::DIFFICULTY);
866    }
867
868    #[test]
869    fn opcode_new_valid() {
870        assert!(OpCode::new(0x01).is_some()); // ADD
871        assert!(OpCode::new(0x5f).is_some()); // PUSH0
872        assert!(OpCode::new(0xff).is_some()); // SELFDESTRUCT
873    }
874
875    #[test]
876    fn opcode_new_invalid() {
877        assert!(OpCode::new(0x0c).is_none()); // gap between SIGNEXTEND and LT
878        assert!(OpCode::new(0x21).is_none()); // gap after KECCAK256
879        assert!(OpCode::new(0xef).is_none()); // unassigned
880    }
881
882    #[test]
883    fn opcode_info() {
884        let add = OpCode::ADD;
885        let info = add.info().unwrap();
886        assert_eq!(info.name, "ADD");
887        assert_eq!(info.inputs, 2);
888        assert_eq!(info.outputs, 1);
889        assert_eq!(info.base_gas, 3);
890        assert_eq!(info.group, Group::StopArithmetic);
891        assert_eq!(info.introduced_in, Fork::Frontier);
892    }
893
894    #[test]
895    fn fork_availability() {
896        // ADD available since Frontier
897        assert!(OpCode::ADD.is_valid_in(Fork::Frontier));
898        assert!(OpCode::ADD.is_valid_in(Fork::Prague));
899
900        // DELEGATECALL only from Homestead
901        assert!(!OpCode::DELEGATECALL.is_valid_in(Fork::Frontier));
902        assert!(OpCode::DELEGATECALL.is_valid_in(Fork::Homestead));
903
904        // TLOAD only from Cancun
905        assert!(!OpCode::TLOAD.is_valid_in(Fork::Shanghai));
906        assert!(OpCode::TLOAD.is_valid_in(Fork::Cancun));
907
908        // PUSH0 only from Shanghai
909        assert!(!OpCode::PUSH0.is_valid_in(Fork::London));
910        assert!(OpCode::PUSH0.is_valid_in(Fork::Shanghai));
911    }
912
913    #[test]
914    fn gas_cost_frontier() {
915        assert_eq!(OpCode::ADD.gas_cost(Fork::Frontier), 3);
916        assert_eq!(OpCode::SLOAD.gas_cost(Fork::Frontier), 50);
917        assert_eq!(OpCode::BALANCE.gas_cost(Fork::Frontier), 20);
918        assert_eq!(OpCode::CALL.gas_cost(Fork::Frontier), 40);
919    }
920
921    #[test]
922    fn gas_cost_tangerine_whistle() {
923        assert_eq!(OpCode::SLOAD.gas_cost(Fork::TangerineWhistle), 200);
924        assert_eq!(OpCode::BALANCE.gas_cost(Fork::TangerineWhistle), 400);
925        assert_eq!(OpCode::CALL.gas_cost(Fork::TangerineWhistle), 700);
926        assert_eq!(OpCode::SELFDESTRUCT.gas_cost(Fork::TangerineWhistle), 5000);
927    }
928
929    #[test]
930    fn gas_cost_istanbul() {
931        assert_eq!(OpCode::SLOAD.gas_cost(Fork::Istanbul), 800);
932        assert_eq!(OpCode::BALANCE.gas_cost(Fork::Istanbul), 700);
933    }
934
935    #[test]
936    fn gas_cost_berlin() {
937        assert_eq!(OpCode::SLOAD.gas_cost(Fork::Berlin), 2100);
938        assert_eq!(OpCode::BALANCE.gas_cost(Fork::Berlin), 2600);
939        assert_eq!(OpCode::EXTCODESIZE.gas_cost(Fork::Berlin), 2600);
940        assert_eq!(OpCode::CALL.gas_cost(Fork::Berlin), 100);
941        assert_eq!(OpCode::STATICCALL.gas_cost(Fork::Berlin), 100);
942    }
943
944    #[test]
945    fn classification() {
946        assert!(OpCode::PUSH0.is_push());
947        assert!(OpCode::PUSH1.is_push());
948        assert!(OpCode::PUSH32.is_push());
949        assert!(!OpCode::ADD.is_push());
950
951        assert!(OpCode::DUP1.is_dup());
952        assert!(OpCode::DUP16.is_dup());
953        assert!(!OpCode::PUSH1.is_dup());
954
955        assert!(OpCode::SWAP1.is_swap());
956        assert!(OpCode::SWAP16.is_swap());
957        assert!(!OpCode::DUP1.is_swap());
958
959        assert!(OpCode::STOP.terminates());
960        assert!(OpCode::RETURN.terminates());
961        assert!(OpCode::REVERT.terminates());
962        assert!(!OpCode::ADD.terminates());
963    }
964
965    #[test]
966    fn display() {
967        assert_eq!(OpCode::ADD.to_string(), "ADD");
968        assert_eq!(OpCode::PUSH1.to_string(), "PUSH1");
969        assert_eq!(OpCode::from_byte(0x0c).to_string(), "UNKNOWN(0x0c)");
970    }
971
972    #[test]
973    fn from_str_roundtrip() {
974        for op in OpCode::iter_all() {
975            let name = op.name();
976            let parsed: OpCode = name.parse().unwrap();
977            assert_eq!(parsed, op, "roundtrip failed for {name}");
978        }
979    }
980
981    #[test]
982    fn from_str_aliases() {
983        assert_eq!("SHA3".parse::<OpCode>().unwrap(), OpCode::KECCAK256);
984        assert_eq!("PREVRANDAO".parse::<OpCode>().unwrap(), OpCode::DIFFICULTY);
985    }
986
987    #[test]
988    fn byte_roundtrip() {
989        for b in 0u8..=255 {
990            assert_eq!(OpCode::from_byte(b).byte(), b);
991        }
992    }
993
994    #[test]
995    fn immediate_sizes() {
996        assert_eq!(OpCode::PUSH0.info().unwrap().immediate_size, 0);
997        assert_eq!(OpCode::PUSH1.info().unwrap().immediate_size, 1);
998        assert_eq!(OpCode::PUSH32.info().unwrap().immediate_size, 32);
999        assert_eq!(OpCode::ADD.info().unwrap().immediate_size, 0);
1000    }
1001
1002    #[test]
1003    fn opcode_count_grows_with_forks() {
1004        let frontier = OpCode::count_at(Fork::Frontier);
1005        let homestead = OpCode::count_at(Fork::Homestead);
1006        let cancun = OpCode::count_at(Fork::Cancun);
1007
1008        assert!(homestead > frontier);
1009        assert!(cancun > homestead);
1010    }
1011
1012    #[test]
1013    fn eip_references() {
1014        assert_eq!(OpCode::PUSH0.info().unwrap().eip, Some(3855));
1015        assert_eq!(OpCode::TLOAD.info().unwrap().eip, Some(1153));
1016        assert_eq!(OpCode::CREATE2.info().unwrap().eip, Some(1014));
1017        assert_eq!(OpCode::ADD.info().unwrap().eip, None);
1018    }
1019}