Opcodes
完整的 TVM opcode 参考,包括用于 TRC-10、质押和投票的 TRON 特有 opcode。
前置阅读
TVM 指令集在兼容 EVM 的基础上,扩展了一组 TRON 特有指令,用于 TRC-10 Token 处理、Stake 2.0 质押与资源代理、超级代表投票、合约内省等场景。本节按功能分组列出。关于 TVM 与 EVM 的高阶架构差异,请参阅 TVM vs EVM 对照指南。
能量栏标注 [A*] 的指令,点击链接查看附录:能量消耗计算中的公式。
算术运算 — 0x00 … 0x0B
| Opcode | 名称 | 能量 | 初始栈 | 结果栈 | 备注 |
|---|---|---|---|---|---|
| 0x00 | STOP | 0 | 停止执行 | ||
| 0x01 | ADD | 3 | a, b | a + b | (u)int256 加法,模 2²⁵⁶ |
| 0x02 | MUL | 5 | a, b | a × b | (u)int256 乘法,模 2²⁵⁶ |
| 0x03 | SUB | 3 | a, b | a − b | (u)int256 减法,模 2²⁵⁶ |
| 0x04 | DIV | 5 | a, b | a // b | uint256 除法 |
| 0x05 | SDIV | 5 | a, b | a // b | int256 除法 |
| 0x06 | MOD | 5 | a, b | a % b | uint256 取模 |
| 0x07 | SMOD | 5 | a, b | a % b | int256 取模 |
| 0x08 | ADDMOD | 8 | a, b, N | (a + b) % N | (u)int256 加法,模 N |
| 0x09 | MULMOD | 8 | a, b, N | (a × b) % N | (u)int256 乘法,模 N |
| 0x0A | EXP | A1 | a, b | a ** b | uint256 幂运算,模 2²⁵⁶ |
| 0x0B | SIGNEXTEND | 5 | b, x | SIGNEXTEND(x, b) | 将 x 从 (b+1) 字节符号扩展为 32 字节 |
比较与位运算 — 0x10 … 0x1D
| Opcode | 名称 | 能量 | 初始栈 | 结果栈 | 备注 |
|---|---|---|---|---|---|
| 0x10 | LT | 3 | a, b | a < b | uint256 小于 |
| 0x11 | GT | 3 | a, b | a > b | uint256 大于 |
| 0x12 | SLT | 3 | a, b | a < b | int256 小于 |
| 0x13 | SGT | 3 | a, b | a > b | int256 大于 |
| 0x14 | EQ | 3 | a, b | a == b | (u)int256 等于 |
| 0x15 | ISZERO | 3 | a | a == 0 | (u)int256 是否为零 |
| 0x16 | AND | 3 | a, b | a & b | 按位与 |
| 0x17 | OR | 3 | a, b | a | b | 按位或 |
| 0x18 | XOR | 3 | a, b | a ^ b | 按位异或 |
| 0x19 | NOT | 3 | a | ~a | 按位非 |
| 0x1A | BYTE | 3 | i, x | x 的第 i 字节 | 从左数第 i 字节(i 从 0 开始) |
| 0x1B | SHL | 3 | shift, val | val << shift | 左移 |
| 0x1C | SHR | 3 | shift, val | val >> shift | 逻辑右移 |
| 0x1D | SAR | 3 | shift, val | val >> shift | 算术右移 |
哈希运算 — 0x20
| Opcode | 名称 | 能量 | 初始栈 | 结果栈 | 备注 |
|---|---|---|---|---|---|
| 0x20 | SHA3 | A2 | ost, len | keccak256(mem[ost:ost+len]) | Keccak-256(尽管历史名称为"SHA3") |
环境信息 — 0x30 … 0x3F
| Opcode | 名称 | 能量 | 初始栈 | 结果栈 | 内存 / 存储 | 备注 |
|---|---|---|---|---|---|---|
| 0x30 | ADDRESS | 2 | address(this) | 当前执行合约的地址 | ||
| 0x31 | BALANCE | 20 | addr | addr.balance | 余额,单位 sun | |
| 0x32 | ORIGIN | 2 | tx.origin | 发起交易的地址 | ||
| 0x33 | CALLER | 2 | msg.sender | msg 发送方地址 | ||
| 0x34 | CALLVALUE | 2 | msg.value | msg 值,单位 sun | ||
| 0x35 | CALLDATALOAD | 3 | idx | msg.data[idx:idx+32] | 从索引 idx 处读取 msg data 的一个字 | |
| 0x36 | CALLDATASIZE | 2 | len(msg.data) | msg data 长度,单位字节 | ||
| 0x37 | CALLDATACOPY | A3 | dstOst, ost, len | mem[dstOst:dstOst+len] := msg.data[ost:ost+len] | 复制 msg data | |
| 0x38 | CODESIZE | 2 | len(this.code) | 当前执行合约代码长度,单位字节 | ||
| 0x39 | CODECOPY | A3 | dstOst, ost, len | mem[dstOst:dstOst+len] := this.code[ost:ost+len] | 复制当前执行合约代码 | |
| 0x3A | GASPRICE | 2 | tx.gasprice | 在 TVM 上返回 energyPrice | ||
| 0x3B | EXTCODESIZE | 20 | addr | len(addr.code) | addr 处代码大小,单位字节 | |
| 0x3C | EXTCODECOPY | A4 | addr, dstOst, ost, len | mem[dstOst:dstOst+len] := addr.code[ost:ost+len] | 从 addr 复制代码 | |
| 0x3D | RETURNDATASIZE | 2 | size | 上次外部调用返回数据的大小 | ||
| 0x3E | RETURNDATACOPY | A3 | dstOst, ost, len | mem[dstOst:dstOst+len] := returndata[ost:ost+len] | 复制上次外部调用的返回数据 | |
| 0x3F | EXTCODEHASH | 400 | addr | hash | addr.exists ? keccak256(addr.code) : 0 |
区块与链信息 — 0x40 … 0x4A
| Opcode | 名称 | 能量 | 初始栈 | 结果栈 | 备注 |
|---|---|---|---|---|---|
| 0x40 | BLOCKHASH | 20 | blockNum | blockHash(blockNum) | |
| 0x41 | COINBASE | 2 | block.coinbase | 当前区块提议者地址 | |
| 0x42 | TIMESTAMP | 2 | block.timestamp | 当前区块时间戳 | |
| 0x43 | NUMBER | 2 | block.number | 当前区块编号 | |
| 0x44 | DIFFICULTY | 2 | 0 | TVM 上始终为 0(无工作量证明) | |
| 0x45 | GASLIMIT | 2 | 0 | TVM 上始终为 0 | |
| 0x46 | CHAINID | 2 | chain_id | 将当前链 ID 压入栈 | |
| 0x47 | SELFBALANCE | 5 | address(this).balance | 当前执行合约余额,单位 sun | |
| 0x48 | BASEFEE | 2 | block.basefee | TVM 上返回 energyPrice(未实现 EIP-1559) | |
| 0x49 | BLOBHASH | 3 | index | versionedHash | Cancun。TVM 上始终返回 0(无 blob 交易) |
| 0x4A | BLOBBASEFEE | 2 | block.blobbasefee | Cancun。TVM 上始终返回 0(无 blob 交易) |
栈、内存、存储、控制流 — 0x50 … 0x5F
| Opcode | 名称 | 能量 | 初始栈 | 结果栈 | 内存 / 存储 | 备注 |
|---|---|---|---|---|---|---|
| 0x50 | POP | 2 | a | 从栈顶移除一项并丢弃 | ||
| 0x51 | MLOAD | A5 | ost | mem[ost:ost+32] | 从偏移量 ost 处读取内存中的一个字 | |
| 0x52 | MSTORE | A5 | ost, val | mem[ost:ost+32] := val | 向内存写入一个字 | |
| 0x53 | MSTORE8 | A6 | ost, val | mem[ost] := val & 0xFF | 向内存写入单个字节 | |
| 0x54 | SLOAD | 50 | key | storage[key] | 从存储读取一个字 | |
| 0x55 | SSTORE | A7 | key, val | storage[key] := val | 向存储写入一个字 | |
| 0x56 | JUMP | 8 | dst | $pc := dst(必须落在 JUMPDEST 上) | ||
| 0x57 | JUMPI | 10 | dst, cond | $pc := cond ? dst : $pc + 1 | ||
| 0x58 | PC | 2 | $pc | 程序计数器 | ||
| 0x59 | MSIZE | 2 | len(mem) | 当前执行上下文的内存大小,单位字节 | ||
| 0x5A | GAS | 2 | gasRemaining | 可用能量数量 | ||
| 0x5B | JUMPDEST | 1 | 有效跳转目标标记;$pc := $pc + 1 | |||
| 0x5C | TLOAD | 100 | key | transient[key] | Cancun。从瞬态存储读取一个字(交易结束时清除) | |
| 0x5D | TSTORE | 100 | key, val | transient[key] := val | Cancun。向瞬态存储写入一个字(交易结束时清除) | |
| 0x5E | MCOPY | A3 | dstOst, ost, len | mem[dstOst:dstOst+len] := mem[ost:ost+len] | Cancun。内存到内存复制 | |
| 0x5F | PUSH0 | 2 | 0 | Shanghai。将常量 0 压入栈 |
栈操作 — PUSH、DUP、SWAP
这些系列的 opcode 和能量结构相同——仅操作数大小或栈位置不同。
| Opcode 范围 | 名称 | 能量 | 栈效果 | 备注 |
|---|---|---|---|---|
| 0x60 – 0x7F | PUSH1 … PUSH32 | 3 | 将 N 字节立即数压入栈 | N = (opcode − 0x5F)。PUSH1 压入 1 字节;PUSH32 压入 32 字节。 |
| 0x80 – 0x8F | DUP1 … DUP16 | 3 | 复制栈顶第 N 项 | N = (opcode − 0x7F)。DUP1 复制栈顶项。 |
| 0x90 – 0x9F | SWAP1 … SWAP16 | 3 | 交换栈顶与第 N 项 | N = (opcode − 0x8F)。SWAP1 交换栈顶与下一项。 |
日志 — 0xA0 … 0xA4
| Opcode | 名称 | 能量 | 初始栈 | 备注 |
|---|---|---|---|---|
| 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 特有 — TRC-10(0xD0 … 0xD3)
| Opcode | 名称 | 能量 | 初始栈 | 结果栈 | 备注 |
|---|---|---|---|---|---|
| 0xD0 | CALLTOKEN | A9 | callEnergy, addr, val, tokenId, argOst, argLen, retOst, retLen | success | 附带 val 和 TRC-10 tokenId 调用 addr |
| 0xD1 | TOKENBALANCE | 20 | tokenId, address | balance | address 在 tokenId TRC-10 Token上的余额 |
| 0xD2 | CALLTOKENVALUE | 2 | value | 当前调用附带的 TRC-10 Token值 | |
| 0xD3 | CALLTOKENID | 2 | tokenId | 当前调用附带的 TRC-10 Token ID |
TRON 特有 — 内省(0xD4)
| Opcode | 名称 | 能量 | 初始栈 | 结果栈 | 备注 |
|---|---|---|---|---|---|
| 0xD4 | ISCONTRACT | 20 | address | isContract | 若 address 是合约则为 true |
TRON 特有 — Stake 1.0(旧版,0xD5 … 0xD7)
旧版。新代码应使用下面的 Stake 2.0 opcode。
| Opcode | 名称 | 能量 | 初始栈 | 结果栈 | 备注 |
|---|---|---|---|---|---|
| 0xD5 | FREEZE | A10 | resourceType, frozenBalance, receiverAddress | success | 向 receiverAddress 质押 frozenBalance 的 resourceType 资源 |
| 0xD6 | UNFREEZE | 20000 | resourceType, targetAddress | success | 解除 targetAddress 上所有 resourceType 的质押 |
| 0xD7 | FREEZEEXPIRETIME | 50 | resourceType, targetAddress | expireTime | targetAddress 上 resourceType 的到期时间 |
TRON 特有 — 投票(0xD8 … 0xD9)
| Opcode | 名称 | 能量 | 初始栈 | 结果栈 | 备注 |
|---|---|---|---|---|---|
| 0xD8 | VOTEWITNESS | A11 | amountArrayLength, amountArrayOffset, witnessArrayLength, witnessArrayOffset | success | 按 witnessArray 中的 SR 按 amountArray 中对应数量投票 |
| 0xD9 | WITHDRAWREWARD | 20000 | withdrawReward | 将累积投票奖励领取到合约余额 |
TRON 特有 — Stake 2.0(0xDA … 0xDF)
当前资源质押与代理 opcode(TIP-467)。
| Opcode | 名称 | 能量 | 初始栈 | 结果栈 | 备注 |
|---|---|---|---|---|---|
| 0xDA | FREEZEBALANCEV2 | 10000 | resourceType, frozenBalance | success | 质押 TRX 以获取资源 |
| 0xDB | UNFREEZEBALANCEV2 | 10000 | resourceType, unfreezeBalance | success | 开始解除质押——启动解质押等待期(主网 14 天) |
| 0xDC | CANCELALLUNFREEZEV2 | 10000 | success | 取消所有待处理的解质押操作 | |
| 0xDD | WITHDRAWEXPIREUNFREEZE | 10000 | success | 提取已过解质押等待期的 TRX | |
| 0xDE | DELEGATERESOURCE | 10000 | resourceType, delegateBalance, receiverAddress | success | 将资源代理给另一地址 |
| 0xDF | UNDELEGATERESOURCE | 10000 | resourceType, unDelegateBalance, receiverAddress | success | 取消资源代理 |
调用与合约创建 — 0xF0 … 0xFF
| Opcode | 名称 | 能量 | 初始栈 | 结果栈 | 内存 / 存储 | 备注 |
|---|---|---|---|---|---|---|
| 0xF0 | CREATE | A12 | val, ost, len | addr | 创建合约 | |
| 0xF1 | CALL | A9 | callEnergy, addr, val, argOst, argLen, retOst, retLen | success | mem[retOst:retOst+retLen] := returndata | 附带 val 和参数调用 addr |
| 0xF2 | CALLCODE | A13 | gas, addr, val, argOst, argLen, retOst, retLen | success | mem[retOst:retOst+retLen] := returndata | 类似 DELEGATECALL,但不传播原始 msg.sender 和 msg.value |
| 0xF3 | RETURN | A14 | ost, len | 停止执行并返回 mem[ost:ost+len] | ||
| 0xF4 | DELEGATECALL | A15 | callEnergy, addr, argOst, argLen, retOst, retLen | success | mem[retOst:retOst+retLen] := returndata | delegatecall——在调用者上下文(存储、余额、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 | 不允许状态变更的调用 |
| 0xFD | REVERT | A14 | ost, len | revert(mem[ost:ost+len]) — 回滚状态,退还剩余能量 | ||
| 0xFF | SUICIDE | A17 | addr | 停止执行;行为取决于 ALLOW_TVM_SELFDESTRUCT_RESTRICTION 是否激活(主网已激活)——对于非在当前交易中创建的合约,仅转移余额和资产而非删除账户。在 Solidity 中也以 SELFDESTRUCT 暴露。参见 TVM vs EVM — SELFDESTRUCT 受限行为。 |
附录:能量消耗计算
A01: memNeed(offset, size)
计算操作所需的内存空间。
offset—— 内存起始偏移量。size—— 自offset起待处理的数据字节大小。- 返回
offset + size的计算结果。
A02: calcMemEnergy(oldMemorySize, memorySize, copySize)
计算内存操作所消耗的能量。
oldMemorySize—— 操作前的内存大小。memorySize—— 操作后的新内存大小。copySize—— 在数据复制操作中,指需要复制的数据长度。memWords = (memorySize + 31) / 32oldMemWords = oldMemorySize / 32energyCost = 3 * memWords + memWords² / 512 − (3 * oldMemWords + oldMemWords² / 512) + 3 * ((copySize + 31) / 32)
A03: penalty(energyCost)
在启用动态能量模型(Dynamic Energy Model)时,计算并返回合约执行的惩罚能量消耗。
DYNAMIC_ENERGY_FACTOR = 10000Factor—— 当前合约的能耗惩罚因子。penalty = energyCost * factor / DYNAMIC_ENERGY_FACTOR − energyCost- 返回值:若计算所得的
penalty > 0,则返回该值;否则返回0。
A04: isDeadAccount(address)
判断目标地址是否为“非合约账户”(未部署合约代码的账户)。如果是,则返回 true,否则返回 false。
A05: sizeInWords(len)
以字(Word,32 字节)为单位计算并返回数据长度。
- 计算公式:
len == 0 ? 0 : (len − 1) / 32 + 1。
A1: EXP 能量成本
byte_len_exponent— 指数的字节数(即栈表示中的b)。energy_cost = 10 + 10 * byte_len_exponent
A2: SHA3 能量成本
energy_cost = 30 + calcMemEnergy(oldMemSize, memNeed(ost, len), 0)
A3: 复制操作
适用于 CALLDATACOPY、CODECOPY、RETURNDATACOPY 和 MCOPY(不适用于 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—storage[key]的旧值。energy_cost = (oldValue == null && val != 0) ? 20000 : 5000
A8: LOG0 到 LOG4
nTopics— 主题数量。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
其能量消耗取决于网络当前已激活的提案门控状态:
| 门控 | 接收方账户存活 | 接收方账户死亡 |
|---|---|---|
| 基础(无门控) | 0 | 0 |
仅 ALLOW_ENERGY_ADJUSTMENT | 0 | 25,000(NEW_ACCT_CALL) |
ALLOW_TVM_SELFDESTRUCT_RESTRICTION(主网已激活) | 5,000 | 30,000(5,000 + 25,000) |
关于能量消耗变更所带来的底层语义变化,请参阅 TVM vs EVM — SELFDESTRUCT 受限行为。
相关资源
- TVM vs EVM 对照指南 —— TVM 与 EVM 底层差异的概览
- TVM 底层原理 —— TRON 虚拟机的核心协议原理
- TRON 上的 Solidity —— 如何在 Solidity 语言层访问 TRON 专属指令集
- Stake 2.0 Solidity SDK 参考 —— 对资源质押和超级代表投票相关指令的高层 SDK 封装说明
- 智能合约简介 —— 智能合约的基础定义与设计机制
- java-tron 源码 (Op.java) —— java-tron 源码中定义的完整虚拟机指令集
Updated 26 days ago