|
1 | 1 | defmodule EEVM.Interpreter.Instructions.Registry do |
2 | 2 | @moduledoc """ |
3 | | - Opcode metadata registry — maps opcode bytes to names and stack I/O signatures. |
| 3 | + Opcode metadata registry — single source of truth for every opcode's name, |
| 4 | + stack I/O signature, implementing module, and static-context flag. |
4 | 5 |
|
5 | | - This module is the single source of truth for opcode metadata. Given an opcode byte |
6 | | - (0x00–0xFF), it returns the human-readable name, the number of stack inputs consumed, |
7 | | - and the number of stack outputs produced. This information is used by the disassembler |
8 | | - and can be used to validate stack depth statically. |
| 6 | + Given an opcode byte (0x00–0xFF) `info/1` returns: |
| 7 | +
|
| 8 | + - `:name` — the human-readable mnemonic (e.g. `"ADD"`) |
| 9 | + - `:inputs` / `:outputs` — stack arity, used by the disassembler and stack-depth checks |
| 10 | + - `:module` — the `EEVM.Interpreter.Instructions.*` module that implements the opcode; |
| 11 | + `EEVM.Interpreter.execute_opcode/2` dispatches on this field |
| 12 | + - `:state_mutating` (when present) — opcode mutates persistent state and must |
| 13 | + halt with `:reverted` if executed inside a STATICCALL frame |
9 | 14 |
|
10 | 15 | PUSH1–PUSH32, DUP1–DUP16, and SWAP1–SWAP16 are generated dynamically from their |
11 | 16 | opcode ranges rather than stored in the static map. |
12 | 17 | """ |
13 | 18 |
|
| 19 | + alias EEVM.Interpreter.Instructions.{ |
| 20 | + Arithmetic, |
| 21 | + Bitwise, |
| 22 | + Comparison, |
| 23 | + ControlFlow, |
| 24 | + Crypto, |
| 25 | + Environment.Data, |
| 26 | + Environment.External, |
| 27 | + Environment.Simple, |
| 28 | + Logging, |
| 29 | + StackMemoryStorage.MemoryOps, |
| 30 | + StackMemoryStorage.StackOps, |
| 31 | + StackMemoryStorage.StorageOps, |
| 32 | + System.Calls, |
| 33 | + System.Creation, |
| 34 | + System.Termination |
| 35 | + } |
| 36 | + |
14 | 37 | @stop 0x00 |
15 | 38 | @add 0x01 |
16 | 39 | @mul 0x02 |
@@ -111,107 +134,113 @@ defmodule EEVM.Interpreter.Instructions.Registry do |
111 | 134 | @invalid 0xFE |
112 | 135 |
|
113 | 136 | @opcodes %{ |
114 | | - @stop => %{name: "STOP", inputs: 0, outputs: 0}, |
115 | | - @add => %{name: "ADD", inputs: 2, outputs: 1}, |
116 | | - @mul => %{name: "MUL", inputs: 2, outputs: 1}, |
117 | | - @sub => %{name: "SUB", inputs: 2, outputs: 1}, |
118 | | - @div_ => %{name: "DIV", inputs: 2, outputs: 1}, |
119 | | - @sdiv => %{name: "SDIV", inputs: 2, outputs: 1}, |
120 | | - @mod => %{name: "MOD", inputs: 2, outputs: 1}, |
121 | | - @smod => %{name: "SMOD", inputs: 2, outputs: 1}, |
122 | | - @addmod => %{name: "ADDMOD", inputs: 3, outputs: 1}, |
123 | | - @mulmod => %{name: "MULMOD", inputs: 3, outputs: 1}, |
124 | | - @exp => %{name: "EXP", inputs: 2, outputs: 1}, |
125 | | - @signextend => %{name: "SIGNEXTEND", inputs: 2, outputs: 1}, |
126 | | - @keccak256 => %{name: "KECCAK256", inputs: 2, outputs: 1}, |
127 | | - @lt => %{name: "LT", inputs: 2, outputs: 1}, |
128 | | - @gt => %{name: "GT", inputs: 2, outputs: 1}, |
129 | | - @slt => %{name: "SLT", inputs: 2, outputs: 1}, |
130 | | - @sgt => %{name: "SGT", inputs: 2, outputs: 1}, |
131 | | - @eq => %{name: "EQ", inputs: 2, outputs: 1}, |
132 | | - @iszero => %{name: "ISZERO", inputs: 1, outputs: 1}, |
133 | | - @and_ => %{name: "AND", inputs: 2, outputs: 1}, |
134 | | - @or_ => %{name: "OR", inputs: 2, outputs: 1}, |
135 | | - @xor_ => %{name: "XOR", inputs: 2, outputs: 1}, |
136 | | - @not_ => %{name: "NOT", inputs: 1, outputs: 1}, |
137 | | - @byte_ => %{name: "BYTE", inputs: 2, outputs: 1}, |
138 | | - @shl => %{name: "SHL", inputs: 2, outputs: 1}, |
139 | | - @shr => %{name: "SHR", inputs: 2, outputs: 1}, |
140 | | - @sar => %{name: "SAR", inputs: 2, outputs: 1}, |
141 | | - @address => %{name: "ADDRESS", inputs: 0, outputs: 1}, |
142 | | - @balance => %{name: "BALANCE", inputs: 1, outputs: 1}, |
143 | | - @origin => %{name: "ORIGIN", inputs: 0, outputs: 1}, |
144 | | - @caller => %{name: "CALLER", inputs: 0, outputs: 1}, |
145 | | - @callvalue => %{name: "CALLVALUE", inputs: 0, outputs: 1}, |
146 | | - @calldataload => %{name: "CALLDATALOAD", inputs: 1, outputs: 1}, |
147 | | - @calldatasize => %{name: "CALLDATASIZE", inputs: 0, outputs: 1}, |
148 | | - @calldatacopy => %{name: "CALLDATACOPY", inputs: 3, outputs: 0}, |
149 | | - @codecopy => %{name: "CODECOPY", inputs: 3, outputs: 0}, |
150 | | - @extcodecopy => %{name: "EXTCODECOPY", inputs: 4, outputs: 0}, |
151 | | - @returndatacopy => %{name: "RETURNDATACOPY", inputs: 3, outputs: 0}, |
152 | | - @codesize => %{name: "CODESIZE", inputs: 0, outputs: 1}, |
153 | | - @extcodesize => %{name: "EXTCODESIZE", inputs: 1, outputs: 1}, |
154 | | - @gasprice => %{name: "GASPRICE", inputs: 0, outputs: 1}, |
155 | | - @returndatasize => %{name: "RETURNDATASIZE", inputs: 0, outputs: 1}, |
156 | | - @extcodehash => %{name: "EXTCODEHASH", inputs: 1, outputs: 1}, |
157 | | - @blockhash => %{name: "BLOCKHASH", inputs: 1, outputs: 1}, |
158 | | - @coinbase => %{name: "COINBASE", inputs: 0, outputs: 1}, |
159 | | - @timestamp => %{name: "TIMESTAMP", inputs: 0, outputs: 1}, |
160 | | - @number => %{name: "NUMBER", inputs: 0, outputs: 1}, |
161 | | - @prevrandao => %{name: "PREVRANDAO", inputs: 0, outputs: 1}, |
162 | | - @gaslimit => %{name: "GASLIMIT", inputs: 0, outputs: 1}, |
163 | | - @chainid => %{name: "CHAINID", inputs: 0, outputs: 1}, |
164 | | - @selfbalance => %{name: "SELFBALANCE", inputs: 0, outputs: 1}, |
165 | | - @basefee => %{name: "BASEFEE", inputs: 0, outputs: 1}, |
166 | | - @blobhash => %{name: "BLOBHASH", inputs: 1, outputs: 1}, |
167 | | - @blobbasefee => %{name: "BLOBBASEFEE", inputs: 0, outputs: 1}, |
168 | | - @gas_ => %{name: "GAS", inputs: 0, outputs: 1}, |
169 | | - @push0 => %{name: "PUSH0", inputs: 0, outputs: 1}, |
170 | | - @pop => %{name: "POP", inputs: 1, outputs: 0}, |
171 | | - @mload => %{name: "MLOAD", inputs: 1, outputs: 1}, |
172 | | - @mstore => %{name: "MSTORE", inputs: 2, outputs: 0}, |
173 | | - @mstore8 => %{name: "MSTORE8", inputs: 2, outputs: 0}, |
174 | | - @sload => %{name: "SLOAD", inputs: 1, outputs: 1}, |
175 | | - @sstore => %{name: "SSTORE", inputs: 2, outputs: 0}, |
176 | | - @tload => %{name: "TLOAD", inputs: 1, outputs: 1}, |
177 | | - @tstore => %{name: "TSTORE", inputs: 2, outputs: 0}, |
178 | | - @msize => %{name: "MSIZE", inputs: 0, outputs: 1}, |
179 | | - @mcopy => %{name: "MCOPY", inputs: 3, outputs: 0}, |
180 | | - @jump => %{name: "JUMP", inputs: 1, outputs: 0}, |
181 | | - @jumpi => %{name: "JUMPI", inputs: 2, outputs: 0}, |
182 | | - @pc => %{name: "PC", inputs: 0, outputs: 1}, |
183 | | - @jumpdest => %{name: "JUMPDEST", inputs: 0, outputs: 0}, |
184 | | - @log0 => %{name: "LOG0", inputs: 2, outputs: 0}, |
185 | | - @log1 => %{name: "LOG1", inputs: 3, outputs: 0}, |
186 | | - @log2 => %{name: "LOG2", inputs: 4, outputs: 0}, |
187 | | - @log3 => %{name: "LOG3", inputs: 5, outputs: 0}, |
188 | | - @log4 => %{name: "LOG4", inputs: 6, outputs: 0}, |
189 | | - @create => %{name: "CREATE", inputs: 3, outputs: 1}, |
190 | | - @call => %{name: "CALL", inputs: 7, outputs: 1}, |
191 | | - @callcode => %{name: "CALLCODE", inputs: 7, outputs: 1}, |
192 | | - @delegatecall => %{name: "DELEGATECALL", inputs: 6, outputs: 1}, |
193 | | - @create2 => %{name: "CREATE2", inputs: 4, outputs: 1}, |
194 | | - @staticcall => %{name: "STATICCALL", inputs: 6, outputs: 1}, |
195 | | - @return_ => %{name: "RETURN", inputs: 2, outputs: 0}, |
196 | | - @revert => %{name: "REVERT", inputs: 2, outputs: 0}, |
197 | | - @invalid => %{name: "INVALID", inputs: 0, outputs: 0}, |
198 | | - @selfdestruct => %{name: "SELFDESTRUCT", inputs: 1, outputs: 0} |
| 137 | + @stop => %{name: "STOP", inputs: 0, outputs: 0, module: Termination}, |
| 138 | + @add => %{name: "ADD", inputs: 2, outputs: 1, module: Arithmetic}, |
| 139 | + @mul => %{name: "MUL", inputs: 2, outputs: 1, module: Arithmetic}, |
| 140 | + @sub => %{name: "SUB", inputs: 2, outputs: 1, module: Arithmetic}, |
| 141 | + @div_ => %{name: "DIV", inputs: 2, outputs: 1, module: Arithmetic}, |
| 142 | + @sdiv => %{name: "SDIV", inputs: 2, outputs: 1, module: Arithmetic}, |
| 143 | + @mod => %{name: "MOD", inputs: 2, outputs: 1, module: Arithmetic}, |
| 144 | + @smod => %{name: "SMOD", inputs: 2, outputs: 1, module: Arithmetic}, |
| 145 | + @addmod => %{name: "ADDMOD", inputs: 3, outputs: 1, module: Arithmetic}, |
| 146 | + @mulmod => %{name: "MULMOD", inputs: 3, outputs: 1, module: Arithmetic}, |
| 147 | + @exp => %{name: "EXP", inputs: 2, outputs: 1, module: Arithmetic}, |
| 148 | + @signextend => %{name: "SIGNEXTEND", inputs: 2, outputs: 1, module: Arithmetic}, |
| 149 | + @keccak256 => %{name: "KECCAK256", inputs: 2, outputs: 1, module: Crypto}, |
| 150 | + @lt => %{name: "LT", inputs: 2, outputs: 1, module: Comparison}, |
| 151 | + @gt => %{name: "GT", inputs: 2, outputs: 1, module: Comparison}, |
| 152 | + @slt => %{name: "SLT", inputs: 2, outputs: 1, module: Comparison}, |
| 153 | + @sgt => %{name: "SGT", inputs: 2, outputs: 1, module: Comparison}, |
| 154 | + @eq => %{name: "EQ", inputs: 2, outputs: 1, module: Comparison}, |
| 155 | + @iszero => %{name: "ISZERO", inputs: 1, outputs: 1, module: Comparison}, |
| 156 | + @and_ => %{name: "AND", inputs: 2, outputs: 1, module: Bitwise}, |
| 157 | + @or_ => %{name: "OR", inputs: 2, outputs: 1, module: Bitwise}, |
| 158 | + @xor_ => %{name: "XOR", inputs: 2, outputs: 1, module: Bitwise}, |
| 159 | + @not_ => %{name: "NOT", inputs: 1, outputs: 1, module: Bitwise}, |
| 160 | + @byte_ => %{name: "BYTE", inputs: 2, outputs: 1, module: Bitwise}, |
| 161 | + @shl => %{name: "SHL", inputs: 2, outputs: 1, module: Bitwise}, |
| 162 | + @shr => %{name: "SHR", inputs: 2, outputs: 1, module: Bitwise}, |
| 163 | + @sar => %{name: "SAR", inputs: 2, outputs: 1, module: Bitwise}, |
| 164 | + @address => %{name: "ADDRESS", inputs: 0, outputs: 1, module: Simple}, |
| 165 | + @balance => %{name: "BALANCE", inputs: 1, outputs: 1, module: External}, |
| 166 | + @origin => %{name: "ORIGIN", inputs: 0, outputs: 1, module: Simple}, |
| 167 | + @caller => %{name: "CALLER", inputs: 0, outputs: 1, module: Simple}, |
| 168 | + @callvalue => %{name: "CALLVALUE", inputs: 0, outputs: 1, module: Simple}, |
| 169 | + @calldataload => %{name: "CALLDATALOAD", inputs: 1, outputs: 1, module: Data}, |
| 170 | + @calldatasize => %{name: "CALLDATASIZE", inputs: 0, outputs: 1, module: Simple}, |
| 171 | + @calldatacopy => %{name: "CALLDATACOPY", inputs: 3, outputs: 0, module: Data}, |
| 172 | + @codecopy => %{name: "CODECOPY", inputs: 3, outputs: 0, module: Data}, |
| 173 | + @extcodecopy => %{name: "EXTCODECOPY", inputs: 4, outputs: 0, module: External}, |
| 174 | + @returndatacopy => %{name: "RETURNDATACOPY", inputs: 3, outputs: 0, module: Data}, |
| 175 | + @codesize => %{name: "CODESIZE", inputs: 0, outputs: 1, module: Simple}, |
| 176 | + @extcodesize => %{name: "EXTCODESIZE", inputs: 1, outputs: 1, module: External}, |
| 177 | + @gasprice => %{name: "GASPRICE", inputs: 0, outputs: 1, module: Simple}, |
| 178 | + @returndatasize => %{name: "RETURNDATASIZE", inputs: 0, outputs: 1, module: Simple}, |
| 179 | + @extcodehash => %{name: "EXTCODEHASH", inputs: 1, outputs: 1, module: External}, |
| 180 | + @blockhash => %{name: "BLOCKHASH", inputs: 1, outputs: 1, module: Simple}, |
| 181 | + @coinbase => %{name: "COINBASE", inputs: 0, outputs: 1, module: Simple}, |
| 182 | + @timestamp => %{name: "TIMESTAMP", inputs: 0, outputs: 1, module: Simple}, |
| 183 | + @number => %{name: "NUMBER", inputs: 0, outputs: 1, module: Simple}, |
| 184 | + @prevrandao => %{name: "PREVRANDAO", inputs: 0, outputs: 1, module: Simple}, |
| 185 | + @gaslimit => %{name: "GASLIMIT", inputs: 0, outputs: 1, module: Simple}, |
| 186 | + @chainid => %{name: "CHAINID", inputs: 0, outputs: 1, module: Simple}, |
| 187 | + @selfbalance => %{name: "SELFBALANCE", inputs: 0, outputs: 1, module: External}, |
| 188 | + @basefee => %{name: "BASEFEE", inputs: 0, outputs: 1, module: Simple}, |
| 189 | + @blobhash => %{name: "BLOBHASH", inputs: 1, outputs: 1, module: Simple}, |
| 190 | + @blobbasefee => %{name: "BLOBBASEFEE", inputs: 0, outputs: 1, module: Simple}, |
| 191 | + @gas_ => %{name: "GAS", inputs: 0, outputs: 1, module: Simple}, |
| 192 | + @push0 => %{name: "PUSH0", inputs: 0, outputs: 1, module: ControlFlow}, |
| 193 | + @pop => %{name: "POP", inputs: 1, outputs: 0, module: StackOps}, |
| 194 | + @mload => %{name: "MLOAD", inputs: 1, outputs: 1, module: MemoryOps}, |
| 195 | + @mstore => %{name: "MSTORE", inputs: 2, outputs: 0, module: MemoryOps}, |
| 196 | + @mstore8 => %{name: "MSTORE8", inputs: 2, outputs: 0, module: MemoryOps}, |
| 197 | + @sload => %{name: "SLOAD", inputs: 1, outputs: 1, module: StorageOps}, |
| 198 | + @sstore => %{name: "SSTORE", inputs: 2, outputs: 0, module: StorageOps, state_mutating: true}, |
| 199 | + @tload => %{name: "TLOAD", inputs: 1, outputs: 1, module: StorageOps}, |
| 200 | + @tstore => %{name: "TSTORE", inputs: 2, outputs: 0, module: StorageOps, state_mutating: true}, |
| 201 | + @msize => %{name: "MSIZE", inputs: 0, outputs: 1, module: MemoryOps}, |
| 202 | + @mcopy => %{name: "MCOPY", inputs: 3, outputs: 0, module: MemoryOps}, |
| 203 | + @jump => %{name: "JUMP", inputs: 1, outputs: 0, module: ControlFlow}, |
| 204 | + @jumpi => %{name: "JUMPI", inputs: 2, outputs: 0, module: ControlFlow}, |
| 205 | + @pc => %{name: "PC", inputs: 0, outputs: 1, module: ControlFlow}, |
| 206 | + @jumpdest => %{name: "JUMPDEST", inputs: 0, outputs: 0, module: ControlFlow}, |
| 207 | + @log0 => %{name: "LOG0", inputs: 2, outputs: 0, module: Logging, state_mutating: true}, |
| 208 | + @log1 => %{name: "LOG1", inputs: 3, outputs: 0, module: Logging, state_mutating: true}, |
| 209 | + @log2 => %{name: "LOG2", inputs: 4, outputs: 0, module: Logging, state_mutating: true}, |
| 210 | + @log3 => %{name: "LOG3", inputs: 5, outputs: 0, module: Logging, state_mutating: true}, |
| 211 | + @log4 => %{name: "LOG4", inputs: 6, outputs: 0, module: Logging, state_mutating: true}, |
| 212 | + @create => %{name: "CREATE", inputs: 3, outputs: 1, module: Creation, state_mutating: true}, |
| 213 | + @call => %{name: "CALL", inputs: 7, outputs: 1, module: Calls}, |
| 214 | + @callcode => %{name: "CALLCODE", inputs: 7, outputs: 1, module: Calls}, |
| 215 | + @delegatecall => %{name: "DELEGATECALL", inputs: 6, outputs: 1, module: Calls}, |
| 216 | + @create2 => %{name: "CREATE2", inputs: 4, outputs: 1, module: Creation, state_mutating: true}, |
| 217 | + @staticcall => %{name: "STATICCALL", inputs: 6, outputs: 1, module: Calls}, |
| 218 | + @return_ => %{name: "RETURN", inputs: 2, outputs: 0, module: Termination}, |
| 219 | + @revert => %{name: "REVERT", inputs: 2, outputs: 0, module: Termination}, |
| 220 | + @invalid => %{name: "INVALID", inputs: 0, outputs: 0, module: Termination}, |
| 221 | + @selfdestruct => %{ |
| 222 | + name: "SELFDESTRUCT", |
| 223 | + inputs: 1, |
| 224 | + outputs: 0, |
| 225 | + module: Termination, |
| 226 | + state_mutating: true |
| 227 | + } |
199 | 228 | } |
200 | 229 |
|
201 | 230 | @spec info(non_neg_integer()) :: {:ok, map()} | {:error, :unknown_opcode} |
202 | 231 | def info(op) when op >= @push1 and op <= @push32 do |
203 | 232 | n = op - @push1 + 1 |
204 | | - {:ok, %{name: "PUSH#{n}", inputs: 0, outputs: 1, push_bytes: n}} |
| 233 | + {:ok, %{name: "PUSH#{n}", inputs: 0, outputs: 1, push_bytes: n, module: ControlFlow}} |
205 | 234 | end |
206 | 235 |
|
207 | 236 | def info(op) when op >= @dup1 and op <= @dup16 do |
208 | 237 | n = op - @dup1 + 1 |
209 | | - {:ok, %{name: "DUP#{n}", inputs: n, outputs: n + 1, dup_depth: n - 1}} |
| 238 | + {:ok, %{name: "DUP#{n}", inputs: n, outputs: n + 1, dup_depth: n - 1, module: ControlFlow}} |
210 | 239 | end |
211 | 240 |
|
212 | 241 | def info(op) when op >= @swap1 and op <= @swap16 do |
213 | 242 | n = op - @swap1 + 1 |
214 | | - {:ok, %{name: "SWAP#{n}", inputs: n + 1, outputs: n + 1, swap_depth: n}} |
| 243 | + {:ok, %{name: "SWAP#{n}", inputs: n + 1, outputs: n + 1, swap_depth: n, module: ControlFlow}} |
215 | 244 | end |
216 | 245 |
|
217 | 246 | def info(op) do |
|
0 commit comments