Opcode Format
Opcodes are encoded as:Register Encoding
Registers are packed into bytes using 4-bit fields:- Single register:
[opcode, RRRR____](1 register in upper nibble) - Two registers:
[opcode, RRRR_SSSS](2 registers, 4 bits each) - Three registers:
[opcode, DDDD_SSS1, SSS2____](3 registers across 2 bytes)
Register fields use 4 bits (0-15) to encode R0-R15. The underscore
_ represents unused bits (typically zero).Opcode Categories
Control Flow
7 opcodes for program control
Arithmetic
6 opcodes for math operations
Bitwise
6 opcodes for bit manipulation
Comparison
7 opcodes for comparisons
Memory
6 opcodes for RAM access
Storage
2 opcodes for persistent storage
Immediate
2 opcodes for constants
Context
6 opcodes for execution context
Debug
1 opcode for debugging
Complete Opcode Table
Control Flow (0x00-0x0F)
| Opcode | Byte | Format | Size | Gas | Description |
|---|---|---|---|---|---|
| HALT | 0x00 | HALT | 1 | 0 | Stop execution successfully |
| NOP | 0x01 | NOP | 1 | 0 | No operation |
| JUMP | 0x02 | JUMP Rt | 2 | 8 | Jump to address in Rt |
| JUMPI | 0x03 | JUMPI Rc, Rt | 3 | 8 | Jump to Rt if Rc ≠ 0 |
| CALL | 0x04 | CALL | 1 | 700 | Call external contract |
| RET | 0x05 | RET | 1 | 0 | Return from execution |
| REVERT | 0x0F | REVERT | 1 | 0 | Revert execution (error) |
Control Flow Examples
Control Flow Examples
Arithmetic (0x10-0x1F)
| Opcode | Byte | Format | Size | Gas | Description |
|---|---|---|---|---|---|
| ADD | 0x10 | ADD Rd, Rs1, Rs2 | 3 | 2 | Rd = Rs1 + Rs2 |
| SUB | 0x11 | SUB Rd, Rs1, Rs2 | 3 | 2 | Rd = Rs1 - Rs2 |
| MUL | 0x12 | MUL Rd, Rs1, Rs2 | 3 | 3 | Rd = Rs1 × Rs2 |
| DIV | 0x13 | DIV Rd, Rs1, Rs2 | 3 | 5 | Rd = Rs1 ÷ Rs2 |
| MOD | 0x14 | MOD Rd, Rs1, Rs2 | 3 | 5 | Rd = Rs1 % Rs2 |
| ADDI | 0x15 | ADDI Rd, Rs, imm32 | 6 | 2 | Rd = Rs + imm32 |
Arithmetic Examples
Arithmetic Examples
Bitwise (0x20-0x2F)
| Opcode | Byte | Format | Size | Gas | Description |
|---|---|---|---|---|---|
| AND | 0x20 | AND Rd, Rs1, Rs2 | 3 | 2 | Rd = Rs1 & Rs2 |
| OR | 0x21 | OR Rd, Rs1, Rs2 | 3 | 2 | Rd = Rs1 | Rs2 |
| XOR | 0x22 | XOR Rd, Rs1, Rs2 | 3 | 2 | Rd = Rs1 ^ Rs2 |
| NOT | 0x23 | NOT Rd | 2 | 2 | Rd = ~Rd (in-place) |
| SHL | 0x24 | SHL Rd, Rs1, Rs2 | 3 | 5 | Rd = Rs1 left shift Rs2 |
| SHR | 0x25 | SHR Rd, Rs1, Rs2 | 3 | 5 | Rd = Rs1 right shift Rs2 |
Bitwise Examples
Bitwise Examples
Shift amounts are masked to 6 bits (0-63) to prevent undefined behavior.
Comparison (0x30-0x3F)
| Opcode | Byte | Format | Size | Gas | Description |
|---|---|---|---|---|---|
| EQ | 0x30 | EQ Rd, Rs1, Rs2 | 3 | 2 | Rd = (Rs1 == Rs2) ? 1 : 0 |
| NE | 0x31 | NE Rd, Rs1, Rs2 | 3 | 2 | Rd = (Rs1 != Rs2) ? 1 : 0 |
| LT | 0x32 | LT Rd, Rs1, Rs2 | 3 | 2 | Rd = (Rs1 < Rs2) ? 1 : 0 |
| GT | 0x33 | GT Rd, Rs1, Rs2 | 3 | 2 | Rd = (Rs1 > Rs2) ? 1 : 0 |
| LE | 0x34 | LE Rd, Rs1, Rs2 | 3 | 2 | Rd = (Rs1 ≤ Rs2) ? 1 : 0 |
| GE | 0x35 | GE Rd, Rs1, Rs2 | 3 | 2 | Rd = (Rs1 ≥ Rs2) ? 1 : 0 |
| ISZERO | 0x36 | ISZERO Rd | 2 | 2 | Rd = (Rd == 0) ? 1 : 0 |
Comparison Examples
Comparison Examples
Memory (0x40-0x4F)
| Opcode | Byte | Format | Size | Gas | Description |
|---|---|---|---|---|---|
| LOAD8 | 0x40 | LOAD8 Rd, [Ra] | 2 | 3 | Rd = memory[Ra] (1 byte) |
| LOAD64 | 0x41 | LOAD64 Rd, [Ra] | 2 | 3 | Rd = memory[Ra] (8 bytes, LE) |
| STORE8 | 0x42 | STORE8 [Ra], Rv | 2 | 3 | memory[Ra] = Rv & 0xFF |
| STORE64 | 0x43 | STORE64 [Ra], Rv | 2 | 3 | memory[Ra] = Rv (8 bytes, LE) |
| MSIZE | 0x44 | MSIZE Rd | 2 | 2 | Rd = memory.size() |
| MCOPY | 0x45 | MCOPY Rd, Rs, Rl | 3 | 3 | Copy Rl bytes from Rs to Rd |
Memory Examples
Memory Examples
Storage (0x50-0x5F)
| Opcode | Byte | Format | Size | Gas | Description |
|---|---|---|---|---|---|
| SLOAD | 0x50 | SLOAD Rd, Rk | 2 | 100 | Rd = storage[Rk] |
| SSTORE | 0x51 | SSTORE Rk, Rv | 2 | 5,000-20,000 | storage[Rk] = Rv |
Storage Examples
Storage Examples
Immediate (0x70-0x7F)
| Opcode | Byte | Format | Size | Gas | Description |
|---|---|---|---|---|---|
| LOADI | 0x70 | LOADI Rd, imm64 | 10 | 2 | Rd = imm64 (64-bit immediate) |
| MOV | 0x71 | MOV Rd, Rs | 2 | 2 | Rd = Rs |
Immediate Examples
Immediate Examples
Context (0x80-0x8F)
| Opcode | Byte | Format | Size | Gas | Description |
|---|---|---|---|---|---|
| CALLER | 0x80 | CALLER Rd | 2 | 2 | Rd = caller address (first 8 bytes) |
| CALLVALUE | 0x81 | CALLVALUE Rd | 2 | 2 | Rd = call value |
| ADDRESS | 0x82 | ADDRESS Rd | 2 | 2 | Rd = contract address (first 8 bytes) |
| BLOCKNUMBER | 0x83 | BLOCKNUMBER Rd | 2 | 2 | Rd = current block number |
| TIMESTAMP | 0x84 | TIMESTAMP Rd | 2 | 2 | Rd = block timestamp |
| GAS | 0x85 | GAS Rd | 2 | 2 | Rd = remaining gas |
Context Examples
Context Examples
CALLER and ADDRESS return only the first 8 bytes of the 32-byte address in little-endian format.
Debug (0xF0-0xFF)
| Opcode | Byte | Format | Size | Gas | Description |
|---|---|---|---|---|---|
| LOG | 0xF0 | LOG Rs | 2 | 2 | Emit Rs to logs |
Debug Examples
Debug Examples
Opcode Encoding Details
Three-Register Format
Most ALU operations use 3 registers:crates/vm/src/executor.rs:533:
Two-Register Format
Immediate Format (LOADI)
Instruction Sizes
Fromcrates/vm/src/opcodes.rs:130:
| Size | Opcodes |
|---|---|
| 1 byte | HALT, NOP, RET |
| 2 bytes | JUMP, NOT, LOG, MSIZE, CALLER, CALLVALUE, ADDRESS, BLOCKNUMBER, TIMESTAMP, GAS, MOV, LOAD8, STORE8, LOAD64, STORE64, SLOAD, SSTORE, ISZERO |
| 3 bytes | ADD, SUB, MUL, DIV, MOD, AND, OR, XOR, SHL, SHR, EQ, NE, LT, GT, LE, GE, MCOPY, JUMPI |
| 6 bytes | ADDI |
| 10 bytes | LOADI |
Example Programs
Fibonacci Sequence
Factorial
Error Conditions
Opcodes can fail with these errors:- InvalidOpcode: Unknown byte encountered
- OutOfGas: Insufficient gas for operation
- DivisionByZero: DIV or MOD with zero divisor
- MemoryOverflow: Memory access beyond 1MB limit
- InvalidJump: Jump to invalid bytecode address
- Reverted: REVERT instruction executed
crates/vm/src/executor.rs:12:
Next Steps
Gas Metering
Learn about gas costs for each opcode
Execution Model
Understand how opcodes are executed
Registers & Memory
Review memory model and register usage