Opcodes
Full TVM opcode reference, including TRON-specific opcodes for TRC-10, staking, and voting.
Prerequisites
The TVM opcodes are the same as the EVM opcodes, with a small set of TRON-specific additions for TRC-10 tokens, staking (Stake 2.0), voting, and contract introspection. Each section below groups related opcodes; for the high-level differences from the EVM, see TVM vs EVM.
Energy entries marked [A*] link to the formula in the Appendix: Energy cost calculations.
Arithmetic — 0x00 … 0x0B
| Opcode | Name | Energy | Initial Stack | Resulting Stack | Notes |
|---|---|---|---|---|---|
| 0x00 | STOP | 0 | halt execution | ||
| 0x01 | ADD | 3 | a, b | a + b | (u)int256 addition modulo 2²⁵⁶ |
| 0x02 | MUL | 5 | a, b | a × b | (u)int256 multiplication modulo 2²⁵⁶ |
| 0x03 | SUB | 3 | a, b | a − b | (u)int256 subtraction modulo 2²⁵⁶ |
| 0x04 | DIV | 5 | a, b | a // b | uint256 division |
| 0x05 | SDIV | 5 | a, b | a // b | int256 division |
| 0x06 | MOD | 5 | a, b | a % b | uint256 modulus |
| 0x07 | SMOD | 5 | a, b | a % b | int256 modulus |
| 0x08 | ADDMOD | 8 | a, b, N | (a + b) % N | (u)int256 addition modulo N |
| 0x09 | MULMOD | 8 | a, b, N | (a × b) % N | (u)int256 multiplication modulo N |
| 0x0A | EXP | A1 | a, b | a ** b | uint256 exponentiation modulo 2²⁵⁶ |
| 0x0B | SIGNEXTEND | 5 | b, x | SIGNEXTEND(x, b) | sign-extend x from (b+1) bytes to 32 bytes |
Comparison and bitwise — 0x10 … 0x1E
| Opcode | Name | Energy | Initial Stack | Resulting Stack | Notes |
|---|---|---|---|---|---|
| 0x10 | LT | 3 | a, b | a < b | uint256 less-than |
| 0x11 | GT | 3 | a, b | a > b | uint256 greater-than |
| 0x12 | SLT | 3 | a, b | a < b | int256 less-than |
| 0x13 | SGT | 3 | a, b | a > b | int256 greater-than |
| 0x14 | EQ | 3 | a, b | a == b | (u)int256 equality |
| 0x15 | ISZERO | 3 | a | a == 0 | (u)int256 iszero |
| 0x16 | AND | 3 | a, b | a & b | bitwise AND |
| 0x17 | OR | 3 | a, b | a | b | bitwise OR |
| 0x18 | XOR | 3 | a, b | a ^ b | bitwise XOR |
| 0x19 | NOT | 3 | a | ~a | bitwise NOT |
| 0x1A | BYTE | 3 | i, x | i-th byte of x | i-th byte from the left (i starts at 0) |
| 0x1B | SHL | 3 | shift, val | val << shift | shift left |
| 0x1C | SHR | 3 | shift, val | val >> shift | logical shift right |
| 0x1D | SAR | 3 | shift, val | val >> shift | arithmetic shift right |
| 0x1E | CLZ | 5 | x | leadingZeros(x) | Osaka. Returns the number of leading zero bits in the 256-bit unsigned integer x; returns 256 when x = 0 |
CLZ is controlled by the Osaka governance proposal. Upgrading a node to GreatVoyage-v4.8.2 only provides the implementation; contracts cannot rely on this opcode until the corresponding proposal has been activated on the target network.
Hashing — 0x20
| Opcode | Name | Energy | Initial Stack | Resulting Stack | Notes |
|---|---|---|---|---|---|
| 0x20 | SHA3 | A2 | ost, len | keccak256(mem[ost:ost+len]) | Keccak-256 (despite the historical "SHA3" name) |
Environment — 0x30 … 0x3F
| Opcode | Name | Energy | Initial Stack | Resulting Stack | Mem / Storage | Notes |
|---|---|---|---|---|---|---|
| 0x30 | ADDRESS | 2 | address(this) | address of executing contract | ||
| 0x31 | BALANCE | 20 | addr | addr.balance | balance, in sun | |
| 0x32 | ORIGIN | 2 | tx.origin | address that originated the tx | ||
| 0x33 | CALLER | 2 | msg.sender | address of msg sender | ||
| 0x34 | CALLVALUE | 2 | msg.value | msg value, in sun | ||
| 0x35 | CALLDATALOAD | 3 | idx | msg.data[idx:idx+32] | read word from msg data at index idx | |
| 0x36 | CALLDATASIZE | 2 | len(msg.data) | length of msg data, in bytes | ||
| 0x37 | CALLDATACOPY | A3 | dstOst, ost, len | mem[dstOst:dstOst+len] := msg.data[ost:ost+len] | copy msg data | |
| 0x38 | CODESIZE | 2 | len(this.code) | length of executing contract's code, in bytes | ||
| 0x39 | CODECOPY | A3 | dstOst, ost, len | mem[dstOst:dstOst+len] := this.code[ost:ost+len] | copy executing contract's code | |
| 0x3A | GASPRICE | 2 | tx.gasprice | returns energyPrice on TVM | ||
| 0x3B | EXTCODESIZE | 20 | addr | len(addr.code) | size of code at addr, in bytes | |
| 0x3C | EXTCODECOPY | A4 | addr, dstOst, ost, len | mem[dstOst:dstOst+len] := addr.code[ost:ost+len] | copy code from addr | |
| 0x3D | RETURNDATASIZE | 2 | size | size of returned data from last external call | ||
| 0x3E | RETURNDATACOPY | A3 | dstOst, ost, len | mem[dstOst:dstOst+len] := returndata[ost:ost+len] | copy returned data from last external call | |
| 0x3F | EXTCODEHASH | 400 | addr | hash | addr.exists ? keccak256(addr.code) : 0 |
Block and chain — 0x40 … 0x4A
| Opcode | Name | Energy | Initial Stack | Resulting Stack | Notes |
|---|---|---|---|---|---|
| 0x40 | BLOCKHASH | 20 | blockNum | blockHash(blockNum) | |
| 0x41 | COINBASE | 2 | block.coinbase | address of proposer of current block | |
| 0x42 | TIMESTAMP | 2 | block.timestamp | timestamp of current block | |
| 0x43 | NUMBER | 2 | block.number | number of current block | |
| 0x44 | DIFFICULTY | 2 | 0 | always 0 on TVM (no Proof-of-Work) | |
| 0x45 | GASLIMIT | 2 | 0 | always 0 on TVM | |
| 0x46 | CHAINID | 2 | chain_id | push current chain id onto stack | |
| 0x47 | SELFBALANCE | 5 | address(this).balance | balance of executing contract, in sun | |
| 0x48 | BASEFEE | 2 | block.basefee | returns energyPrice on TVM (no EIP-1559) | |
| 0x49 | BLOBHASH | 3 | index | versionedHash | Cancun. Always returns 0 on TVM (no blob transactions) |
| 0x4A | BLOBBASEFEE | 2 | block.blobbasefee | Cancun. Always returns 0 on TVM (no blob transactions) |
Stack, memory, storage, control flow — 0x50 … 0x5F
| Opcode | Name | Energy | Initial Stack | Resulting Stack | Mem / Storage | Notes |
|---|---|---|---|---|---|---|
| 0x50 | POP | 2 | a | remove item from top of stack and discard | ||
| 0x51 | MLOAD | A5 | ost | mem[ost:ost+32] | read word from memory at offset ost | |
| 0x52 | MSTORE | A5 | ost, val | mem[ost:ost+32] := val | write a word to memory | |
| 0x53 | MSTORE8 | A6 | ost, val | mem[ost] := val & 0xFF | write a single byte to memory | |
| 0x54 | SLOAD | 50 | key | storage[key] | read word from storage | |
| 0x55 | SSTORE | A7 | key, val | storage[key] := val | write word to storage | |
| 0x56 | JUMP | 8 | dst | $pc := dst (must land on a JUMPDEST) | ||
| 0x57 | JUMPI | 10 | dst, cond | $pc := cond ? dst : $pc + 1 | ||
| 0x58 | PC | 2 | $pc | program counter | ||
| 0x59 | MSIZE | 2 | len(mem) | size of memory in current execution context, in bytes | ||
| 0x5A | GAS | 2 | gasRemaining | the amount of available Energy | ||
| 0x5B | JUMPDEST | 1 | valid jump destination marker; $pc := $pc + 1 | |||
| 0x5C | TLOAD | 100 | key | transient[key] | Cancun. read word from transient storage (cleared at end of tx) | |
| 0x5D | TSTORE | 100 | key, val | transient[key] := val | Cancun. write word to transient storage (cleared at end of tx) | |
| 0x5E | MCOPY | A3 | dstOst, ost, len | mem[dstOst:dstOst+len] := mem[ost:ost+len] | Cancun. memory-to-memory copy | |
| 0x5F | PUSH0 | 2 | 0 | Shanghai. push constant 0 onto stack |
Stack manipulation — PUSH, DUP, SWAP
These families share opcode-and-Energy structure — only the size of the operand or the stack position differs.
| Opcode range | Name | Energy | Stack effect | Notes |
|---|---|---|---|---|
| 0x60 – 0x7F | PUSH1 … PUSH32 | 3 | push an N-byte immediate value onto stack | N = (opcode − 0x5F). PUSH1 pushes 1 byte; PUSH32 32. |
| 0x80 – 0x8F | DUP1 … DUP16 | 3 | clone the N-th item from the top of the stack | N = (opcode − 0x7F). DUP1 clones the top of stack. |
| 0x90 – 0x9F | SWAP1 … SWAP16 | 3 | swap the top of stack with the N-th item below | N = (opcode − 0x8F). SWAP1 swaps top with the next item. |
Logging — 0xA0 … 0xA4
| Opcode | Name | Energy | Initial Stack | Notes |
|---|---|---|---|---|
| 0xA0 | LOG0 | A8 | ost, len | LOG0(memory[ost:ost+len]) |
| 0xA1 | LOG1 | A8 | ost, len, topic0 | LOG1(memory[ost:ost+len], topic0) |
| 0xA2 | LOG2 | A8 | ost, len, topic0, topic1 | LOG2(memory[ost:ost+len], topic0, topic1) |
| 0xA3 | LOG3 | A8 | ost, len, topic0, topic1, topic2 | LOG3(memory[ost:ost+len], topic0, topic1, topic2) |
| 0xA4 | LOG4 | A8 | ost, len, topic0, topic1, topic2, topic3 | LOG4(memory[ost:ost+len], topic0, topic1, topic2, topic3) |
TRON-specific — TRC-10 (0xD0 … 0xD3)
| Opcode | Name | Energy | Initial Stack | Resulting Stack | Notes |
|---|---|---|---|---|---|
| 0xD0 | CALLTOKEN | A9 | callEnergy, addr, val, tokenId, argOst, argLen, retOst, retLen | success | call addr with val and TRC-10 tokenId attached |
| 0xD1 | TOKENBALANCE | 20 | tokenId, address | balance | balance of address on tokenId TRC-10 token |
| 0xD2 | CALLTOKENVALUE | 2 | value | TRC-10 token value attached to current call | |
| 0xD3 | CALLTOKENID | 2 | tokenId | TRC-10 token ID attached to current call |
TRON-specific — Introspection (0xD4)
| Opcode | Name | Energy | Initial Stack | Resulting Stack | Notes |
|---|---|---|---|---|---|
| 0xD4 | ISCONTRACT | 20 | address | isContract | true if address is a contract |
TRON-specific — Stake 1.0 (legacy, 0xD5 … 0xD7)
Legacy. New code should use the Stake 2.0 opcodes below.
| Opcode | Name | Energy | Initial Stack | Resulting Stack | Notes |
|---|---|---|---|---|---|
| 0xD5 | FREEZE | A10 | resourceType, frozenBalance, receiverAddress | success | freeze frozenBalance of resourceType to receiverAddress |
| 0xD6 | UNFREEZE | 20000 | resourceType, targetAddress | success | unfreeze all resourceType on targetAddress |
| 0xD7 | FREEZEEXPIRETIME | 50 | resourceType, targetAddress | expireTime | expiration of resourceType on targetAddress |
TRON-specific — Voting (0xD8 … 0xD9)
| Opcode | Name | Energy | Initial Stack | Resulting Stack | Notes |
|---|---|---|---|---|---|
| 0xD8 | VOTEWITNESS | A11 | amountArrayLength, amountArrayOffset, witnessArrayLength, witnessArrayOffset | success | vote amounts in amountArray for SRs in witnessArray |
| 0xD9 | WITHDRAWREWARD | 20000 | withdrawReward | claim accumulated voting rewards to the contract balance |
TRON-specific — Stake 2.0 (0xDA … 0xDF)
Current resource-staking and delegation opcodes (TIP-467).
| Opcode | Name | Energy | Initial Stack | Resulting Stack | Notes |
|---|---|---|---|---|---|
| 0xDA | FREEZEBALANCEV2 | 10000 | resourceType, frozenBalance | success | stake TRX to acquire resources |
| 0xDB | UNFREEZEBALANCEV2 | 10000 | resourceType, unfreezeBalance | success | begin unstaking — starts the unstake delay (14 days on Mainnet) |
| 0xDC | CANCELALLUNFREEZEV2 | 10000 | success | cancel all pending unstake operations | |
| 0xDD | WITHDRAWEXPIREUNFREEZE | 10000 | success | withdraw TRX whose unstake waiting period has elapsed | |
| 0xDE | DELEGATERESOURCE | 10000 | resourceType, delegateBalance, receiverAddress | success | delegate resource to another address |
| 0xDF | UNDELEGATERESOURCE | 10000 | resourceType, unDelegateBalance, receiverAddress | success | cancel resource delegation |
Calls and contract creation — 0xF0 … 0xFF
| Opcode | Name | Energy | Initial Stack | Resulting Stack | Mem / Storage | Notes |
|---|---|---|---|---|---|---|
| 0xF0 | CREATE | A12 | val, ost, len | addr | create contract | |
| 0xF1 | CALL | A9 | callEnergy, addr, val, argOst, argLen, retOst, retLen | success | mem[retOst:retOst+retLen] := returndata | call addr with val and arguments |
| 0xF2 | CALLCODE | A13 | gas, addr, val, argOst, argLen, retOst, retLen | success | mem[retOst:retOst+retLen] := returndata | like DELEGATECALL but does not propagate original msg.sender and msg.value |
| 0xF3 | RETURN | A14 | ost, len | halt execution and return mem[ost:ost+len] | ||
| 0xF4 | DELEGATECALL | A15 | callEnergy, addr, argOst, argLen, retOst, retLen | success | mem[retOst:retOst+retLen] := returndata | delegate call — executes in caller's context (storage, balance, msg.sender) |
| 0xF5 | CREATE2 | A16 | val, ost, len, salt | addr | addr = keccak256(0x41 ++ address(this) ++ salt ++ keccak256(mem[ost:ost+len]))[12:] | |
| 0xFA | STATICCALL | A15 | gas, addr, argOst, argLen, retOst, retLen | success | mem[retOst:retOst+retLen] := returndata | call without permitting state changes |
| 0xFD | REVERT | A14 | ost, len | revert(mem[ost:ost+len]) — rolls back state, refunds remaining Energy | ||
| 0xFF | SUICIDE | A17 | addr | halt execution; behavior depends on ALLOW_TVM_SELFDESTRUCT_RESTRICTION (active on Mainnet) — for contracts not created in the current transaction, only transfers balance and assets instead of deleting the account. Also exposed as SELFDESTRUCT in Solidity. See TVM vs EVM — SELFDESTRUCT. |
Osaka precompiled contracts
P256VERIFY
GreatVoyage-v4.8.2 implements P256VERIFY (TIP-7951) for secp256r1/P-256 signature verification. The precompiled contract is located at 0x0000000000000000000000000000000000000100 and has a fixed cost of 6900 Energy.
The input must be exactly 160 bytes, formed by concatenating five 32-byte fields: hash || r || s || qx || qy. A valid signature returns a 32-byte value of 1. An invalid input length, signature, or public key returns empty bytes without reverting the call.
CLZ, P256VERIFY, and the MODEXP and signature-verification changes below are controlled together by the ALLOW_TVM_OSAKA governance proposal (parameter ID 96). Before deploying contracts that depend on them, confirm that this proposal has been activated on the target network instead of relying on the node version alone.
MODEXP
GreatVoyage-v4.8.2 changes the MODEXP precompile at 0x0000000000000000000000000000000000000005:
- TIP-7823:
baseLen,expLen, andmodLenmust each be no greater than1024bytes (8192 bits). If any declared length exceeds this limit, the call fails with empty output. - TIP-7883: reprices MODEXP by introducing a minimum charge of
500Energy. Legacy TRON used EIP-198-style pricing with no explicit minimum and a/20divisor; the new formula no longer uses that divisor. It also increases the multiplier for exponents longer than 32 bytes from8to16and redefines multiplication complexity for the base and modulus. - TIP-871: when the modulus value is zero, the precompile returns exactly
modLenzero bytes instead of an empty byte array.
These changes can cause an existing MODEXP call to fail, consume more Energy, or expose a different return-data length. Retest boundary inputs and Energy budgets in cryptographic contracts migrated from other EVM chains.
Calldata validation for signature-verification precompiles
TIP-854 adds a total-length check to BatchValidateSign (0x0000000000000000000000000000000000000009) and ValidateMultiSign (0x000000000000000000000000000000000000000a). Calldata must be aligned to 32-byte words and contain at least one tail item:
BatchValidateSign: the total length must equal160 + 192 × Nbytes, whereN >= 1.ValidateMultiSign: the total length must equal160 + 160 × Nbytes, whereN >= 1.
Calldata that does not have one of these shapes makes the precompile call fail with no return data. This rule validates only the total-length shape; it does not perform full Solidity ABI canonicalization of all inner dynamic offsets.
Prague historical block-hash contract
TIP-2935 deploys the BlockHashHistory system contract in state to serve the most recent 8191 historical block hashes. It uses the same address as Ethereum EIP-2935, 0x0000F90827F1C53a10cb7A02335B175320002935; its TRON Base58Check address is T9yEAebSu94c1ndzJa1rpPtrjXBXp2FCMV.
To query it, encode the block number as a 32-byte big-endian integer and call the contract. The call reverts if calldata is not exactly 32 bytes or if the requested block is outside [block.number - 8191, block.number - 1]. The ring buffer contains only parent hashes written after activation, so it can take up to 8191 blocks after activation to become fully populated.
TIP-2935 does not change the semantics, Energy cost, or existing 256-block range of the BLOCKHASH opcode. It is controlled by the ALLOW_TVM_PRAGUE governance proposal (parameter ID 95) and requires ALLOW_TVM_SHANGHAI to have been activated on the target network. Upgrading a node to GreatVoyage-v4.8.2 alone does not activate this feature on-chain.
Appendix: Energy cost calculations
A01: memNeed(offset, size)
Calculate the memory needed.
offset— the start position in memory.size— data length to handle, beginning atoffset.- Returns
offset + size.
A02: calcMemEnergy(oldMemorySize, memorySize, copySize)
Calculate the memory Energy cost.
oldMemorySize— old memory size.memorySize— new memory size after the operation.copySize— for copy operations, the data size to copy.memWords = (memorySize + 31) / 32oldMemWords = oldMemorySize / 32energyCost = 3 * memWords + memWords² / 512 − (3 * oldMemWords + oldMemWords² / 512) + 3 * ((copySize + 31) / 32)
A03: penalty(energyCost)
If the Dynamic Energy Model is allowed, return the penalty.
DYNAMIC_ENERGY_FACTOR = 10000Factor— contract context factor.penalty = energyCost * factor / DYNAMIC_ENERGY_FACTOR − energyCost- return
penalty > 0 ? penalty : 0
A04: isDeadAccount(address)
Returns true if the address is not a contract, otherwise false.
A05: sizeInWords(len)
Returns len == 0 ? 0 : (len − 1) / 32 + 1.
A1: EXP Energy cost
byte_len_exponent— number of bytes in the exponent (bin the stack representation).energy_cost = 10 + 10 * byte_len_exponent
A2: SHA3 Energy cost
energy_cost = 30 + calcMemEnergy(oldMemSize, memNeed(ost, len), 0)
A3: COPY operations
Applies to CALLDATACOPY, CODECOPY, RETURNDATACOPY, and MCOPY (not EXTCODECOPY).
energy_cost = calcMemEnergy(oldMemSize, memNeed(dstOst, len), len)
A4: EXTCODECOPY
energy_cost = 20 + calcMemEnergy(oldMemSize, memNeed(dstOst, len), len)
A5: MLOAD, MSTORE
energy_cost = calcMemEnergy(oldMemSize, memNeed(ost, 32), 0)
A6: MSTORE8
energy_cost = calcMemEnergy(oldMemSize, memNeed(ost, 1), 0)
A7: SSTORE
oldValue— old value ofstorage[key].energy_cost = (oldValue == null && val != 0) ? 20000 : 5000
A8: LOG0 to LOG4
nTopics— number of topics.energyCost = 375 + 375 * nTopics + 8 * len + calcMemEnergy(oldMemSize, memNeed(ost, len), 0)
A9: CALLTOKEN, CALL
energyCost_1 = 40
+ (val != 0 ? 9000 : 0)
+ (val != 0 && isDeadAccount(addr) ? 25000 : 0)
+ calcMemEnergy(oldMemSize,
max(memNeed(orgOst, orgLen), memNeed(retOst, retLen)),
0)
energyCost_2 = energyCost_1 + penalty(energyCost_1)
energyLimitLeft = energyLimit - energyCost_2
energyCost = energyCost_2 + min(callEnergy, energyLimitLeft)A10: FREEZE
energyCost = 20000 + (isDeadAccount(receiverAddress) ? 25000 : 0)
A11: VOTEWITNESS
energyCost = 30000 + (ALLOW_ENERGY_ADJUSTMENT
? calcMemEnergy(oldMemSize,
max(memNeed(amountArrayOffset, amountArrayLength),
memNeed(witnessArrayOffset, witnessArrayLength)),
0)
: calcMemEnergy(oldMemSize,
max(memNeed(amountArrayOffset, amountArrayLength * 32 + 32),
memNeed(witnessArrayOffset, witnessArrayLength * 32 + 32)),
0))A12: CREATE
energyCost = 32000 + calcMemEnergy(oldMemSize, memNeed(ost, len), 0)
A13: CALLCODE
energyCost_1 = 40
+ (val != 0 ? 9000 : 0)
+ calcMemEnergy(oldMemSize,
max(memNeed(orgOst, orgLen), memNeed(retOst, retLen)),
0)
energyCost_2 = energyCost_1 + penalty(energyCost_1)
energyLimitLeft = energyLimit - energyCost_2
energyCost = energyCost_2 + min(callEnergy, energyLimitLeft)A14: RETURN, REVERT
energy_cost = calcMemEnergy(oldMemSize, memNeed(ost, len), 0)
A15: DELEGATECALL, STATICCALL
energyCost_1 = 40 + calcMemEnergy(oldMemSize,
max(memNeed(orgOst, orgLen), memNeed(retOst, retLen)),
0)
energyCost_2 = energyCost_1 + penalty(energyCost_1)
energyLimitLeft = energyLimit - energyCost_2
energyCost = energyCost_2 + min(callEnergy, energyLimitLeft)A16: CREATE2
energyCost = 32000 + calcMemEnergy(oldMemSize, memNeed(ost, len), 0) + 6 * sizeInWords(len)
A17: SUICIDE
Cost depends on which proposal gates are active:
| Gate | Live obtainer account | Dead obtainer account |
|---|---|---|
| Base (no gates) | 0 | 0 |
ALLOW_ENERGY_ADJUSTMENT only | 0 | 25,000 (NEW_ACCT_CALL) |
ALLOW_TVM_SELFDESTRUCT_RESTRICTION (active on Mainnet) | 5,000 | 30,000 (5,000 + 25,000) |
See TVM vs EVM — SELFDESTRUCT restricted behavior for the semantic shift that accompanies the cost change.
Related resources
- TVM vs EVM — high-level summary of differences from the EVM
- TVM — the TRON Virtual Machine
- Solidity on TRON — Solidity-level access to TRON-specific opcodes
- Stake 2.0 Solidity SDK reference — high-level wrappers for the staking and voting opcodes
- Smart contracts introduction — what smart contracts are
- java-tron
Op.java— opcode definitions in source
Updated 9 days ago