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名称能量初始栈结果栈备注
0x00STOP0停止执行
0x01ADD3a, ba + b(u)int256 加法,模 2²⁵⁶
0x02MUL5a, ba × b(u)int256 乘法,模 2²⁵⁶
0x03SUB3a, ba − b(u)int256 减法,模 2²⁵⁶
0x04DIV5a, ba // buint256 除法
0x05SDIV5a, ba // bint256 除法
0x06MOD5a, ba % buint256 取模
0x07SMOD5a, ba % bint256 取模
0x08ADDMOD8a, b, N(a + b) % N(u)int256 加法,模 N
0x09MULMOD8a, b, N(a × b) % N(u)int256 乘法,模 N
0x0AEXPA1a, ba ** buint256 幂运算,模 2²⁵⁶
0x0BSIGNEXTEND5b, xSIGNEXTEND(x, b)x(b+1) 字节符号扩展为 32 字节

比较与位运算 — 0x10 … 0x1E

Opcode名称能量初始栈结果栈备注
0x10LT3a, ba < buint256 小于
0x11GT3a, ba > buint256 大于
0x12SLT3a, ba < bint256 小于
0x13SGT3a, ba > bint256 大于
0x14EQ3a, ba == b(u)int256 等于
0x15ISZERO3aa == 0(u)int256 是否为零
0x16AND3a, ba & b按位与
0x17OR3a, ba | b按位或
0x18XOR3a, ba ^ b按位异或
0x19NOT3a~a按位非
0x1ABYTE3i, xx 的第 i 字节从左数第 i 字节(i 从 0 开始)
0x1BSHL3shift, valval << shift左移
0x1CSHR3shift, valval >> shift逻辑右移
0x1DSAR3shift, valval >> shift算术右移
0x1ECLZ5xleadingZeros(x)Osaka。返回 256 位无符号整数 x 的前导零位数;x = 0 时返回 256

CLZ 由 Osaka 治理提议控制。节点升级到 GreatVoyage-v4.8.2 只表示具备该指令的实现;在对应链上提议生效前,合约不能依赖该指令。

哈希运算 — 0x20

Opcode名称能量初始栈结果栈备注
0x20SHA3A2ost, lenkeccak256(mem[ost:ost+len])Keccak-256(尽管历史名称为"SHA3")

环境信息 — 0x30 … 0x3F

Opcode名称能量初始栈结果栈内存 / 存储备注
0x30ADDRESS2address(this)当前执行合约的地址
0x31BALANCE20addraddr.balance余额,单位 sun
0x32ORIGIN2tx.origin发起交易的地址
0x33CALLER2msg.sendermsg 发送方地址
0x34CALLVALUE2msg.valuemsg 值,单位 sun
0x35CALLDATALOAD3idxmsg.data[idx:idx+32]从索引 idx 处读取 msg data 的一个字
0x36CALLDATASIZE2len(msg.data)msg data 长度,单位字节
0x37CALLDATACOPYA3dstOst, ost, lenmem[dstOst:dstOst+len] := msg.data[ost:ost+len]复制 msg data
0x38CODESIZE2len(this.code)当前执行合约代码长度,单位字节
0x39CODECOPYA3dstOst, ost, lenmem[dstOst:dstOst+len] := this.code[ost:ost+len]复制当前执行合约代码
0x3AGASPRICE2tx.gasprice在 TVM 上返回 energyPrice
0x3BEXTCODESIZE20addrlen(addr.code)addr 处代码大小,单位字节
0x3CEXTCODECOPYA4addr, dstOst, ost, lenmem[dstOst:dstOst+len] := addr.code[ost:ost+len]addr 复制代码
0x3DRETURNDATASIZE2size上次外部调用返回数据的大小
0x3ERETURNDATACOPYA3dstOst, ost, lenmem[dstOst:dstOst+len] := returndata[ost:ost+len]复制上次外部调用的返回数据
0x3FEXTCODEHASH400addrhashaddr.exists ? keccak256(addr.code) : 0

区块与链信息 — 0x40 … 0x4A

Opcode名称能量初始栈结果栈备注
0x40BLOCKHASH20blockNumblockHash(blockNum)
0x41COINBASE2block.coinbase当前区块提议者地址
0x42TIMESTAMP2block.timestamp当前区块时间戳
0x43NUMBER2block.number当前区块编号
0x44DIFFICULTY20TVM 上始终为 0(无工作量证明)
0x45GASLIMIT20TVM 上始终为 0
0x46CHAINID2chain_id将当前链 ID 压入栈
0x47SELFBALANCE5address(this).balance当前执行合约余额,单位 sun
0x48BASEFEE2block.basefeeTVM 上返回 energyPrice(未实现 EIP-1559)
0x49BLOBHASH3indexversionedHashCancun。TVM 上始终返回 0(无 blob 交易)
0x4ABLOBBASEFEE2block.blobbasefeeCancun。TVM 上始终返回 0(无 blob 交易)

栈、内存、存储、控制流 — 0x50 … 0x5F

Opcode名称能量初始栈结果栈内存 / 存储备注
0x50POP2a从栈顶移除一项并丢弃
0x51MLOADA5ostmem[ost:ost+32]从偏移量 ost 处读取内存中的一个字
0x52MSTOREA5ost, valmem[ost:ost+32] := val向内存写入一个字
0x53MSTORE8A6ost, valmem[ost] := val & 0xFF向内存写入单个字节
0x54SLOAD50keystorage[key]从存储读取一个字
0x55SSTOREA7key, valstorage[key] := val向存储写入一个字
0x56JUMP8dst$pc := dst(必须落在 JUMPDEST 上)
0x57JUMPI10dst, cond$pc := cond ? dst : $pc + 1
0x58PC2$pc程序计数器
0x59MSIZE2len(mem)当前执行上下文的内存大小,单位字节
0x5AGAS2gasRemaining可用能量数量
0x5BJUMPDEST1有效跳转目标标记;$pc := $pc + 1
0x5CTLOAD100keytransient[key]Cancun。从瞬态存储读取一个字(交易结束时清除)
0x5DTSTORE100key, valtransient[key] := valCancun。向瞬态存储写入一个字(交易结束时清除)
0x5EMCOPYA3dstOst, ost, lenmem[dstOst:dstOst+len] := mem[ost:ost+len]Cancun。内存到内存复制
0x5FPUSH020Shanghai。将常量 0 压入栈

栈操作 — PUSH、DUP、SWAP

这些系列的 opcode 和能量结构相同——仅操作数大小或栈位置不同。

Opcode 范围名称能量栈效果备注
0x60 – 0x7FPUSH1PUSH323N 字节立即数压入栈N = (opcode − 0x5F)。PUSH1 压入 1 字节;PUSH32 压入 32 字节。
0x80 – 0x8FDUP1DUP163复制栈顶第 N 项N = (opcode − 0x7F)。DUP1 复制栈顶项。
0x90 – 0x9FSWAP1SWAP163交换栈顶与第 N 项N = (opcode − 0x8F)。SWAP1 交换栈顶与下一项。

日志 — 0xA0 … 0xA4

Opcode名称能量初始栈备注
0xA0LOG0A8ost, lenLOG0(memory[ost:ost+len])
0xA1LOG1A8ost, len, topic0LOG1(memory[ost:ost+len], topic0)
0xA2LOG2A8ost, len, topic0, topic1LOG2(memory[ost:ost+len], topic0, topic1)
0xA3LOG3A8ost, len, topic0, topic1, topic2LOG3(memory[ost:ost+len], topic0, topic1, topic2)
0xA4LOG4A8ost, len, topic0, topic1, topic2, topic3LOG4(memory[ost:ost+len], topic0, topic1, topic2, topic3)

TRON 特有 — TRC-10(0xD0 … 0xD3)

Opcode名称能量初始栈结果栈备注
0xD0CALLTOKENA9callEnergy, addr, val, tokenId, argOst, argLen, retOst, retLensuccess附带 val 和 TRC-10 tokenId 调用 addr
0xD1TOKENBALANCE20tokenId, addressbalanceaddresstokenId TRC-10 Token上的余额
0xD2CALLTOKENVALUE2value当前调用附带的 TRC-10 Token值
0xD3CALLTOKENID2tokenId当前调用附带的 TRC-10 Token ID

TRON 特有 — 内省(0xD4)

Opcode名称能量初始栈结果栈备注
0xD4ISCONTRACT20addressisContractaddress 是合约则为 true

TRON 特有 — Stake 1.0(旧版,0xD5 … 0xD7)

旧版。新代码应使用下面的 Stake 2.0 opcode。

Opcode名称能量初始栈结果栈备注
0xD5FREEZEA10resourceType, frozenBalance, receiverAddresssuccessreceiverAddress 质押 frozenBalanceresourceType 资源
0xD6UNFREEZE20000resourceType, targetAddresssuccess解除 targetAddress 上所有 resourceType 的质押
0xD7FREEZEEXPIRETIME50resourceType, targetAddressexpireTimetargetAddressresourceType 的到期时间

TRON 特有 — 投票(0xD8 … 0xD9)

Opcode名称能量初始栈结果栈备注
0xD8VOTEWITNESSA11amountArrayLength, amountArrayOffset, witnessArrayLength, witnessArrayOffsetsuccesswitnessArray 中的 SR 按 amountArray 中对应数量投票
0xD9WITHDRAWREWARD20000withdrawReward将累积投票奖励领取到合约余额

TRON 特有 — Stake 2.0(0xDA … 0xDF)

当前资源质押与代理 opcode(TIP-467)。

Opcode名称能量初始栈结果栈备注
0xDAFREEZEBALANCEV210000resourceType, frozenBalancesuccess质押 TRX 以获取资源
0xDBUNFREEZEBALANCEV210000resourceType, unfreezeBalancesuccess开始解质押——启动解质押等待期(主网 14 天)
0xDCCANCELALLUNFREEZEV210000success取消所有待处理的解质押操作
0xDDWITHDRAWEXPIREUNFREEZE10000success提取已过解质押等待期的 TRX
0xDEDELEGATERESOURCE10000resourceType, delegateBalance, receiverAddresssuccess将资源代理给另一地址
0xDFUNDELEGATERESOURCE10000resourceType, unDelegateBalance, receiverAddresssuccess取消资源代理

调用与合约创建 — 0xF0 … 0xFF

Opcode名称能量初始栈结果栈内存 / 存储备注
0xF0CREATEA12val, ost, lenaddr创建合约
0xF1CALLA9callEnergy, addr, val, argOst, argLen, retOst, retLensuccessmem[retOst:retOst+retLen] := returndata附带 val 和参数调用 addr
0xF2CALLCODEA13gas, addr, val, argOst, argLen, retOst, retLensuccessmem[retOst:retOst+retLen] := returndata类似 DELEGATECALL,但不传播原始 msg.sendermsg.value
0xF3RETURNA14ost, len停止执行并返回 mem[ost:ost+len]
0xF4DELEGATECALLA15callEnergy, addr, argOst, argLen, retOst, retLensuccessmem[retOst:retOst+retLen] := returndatadelegatecall——在调用者上下文(存储、余额、msg.sender)中执行
0xF5CREATE2A16val, ost, len, saltaddraddr = keccak256(0x41 ++ address(this) ++ salt ++ keccak256(mem[ost:ost+len]))[12:]
0xFASTATICCALLA15gas, addr, argOst, argLen, retOst, retLensuccessmem[retOst:retOst+retLen] := returndata不允许状态变更的调用
0xFDREVERTA14ost, lenrevert(mem[ost:ost+len]) — 回滚状态,退还剩余能量
0xFFSUICIDEA17addr停止执行;行为取决于 ALLOW_TVM_SELFDESTRUCT_RESTRICTION 是否激活(主网已激活)——对于非在当前交易中创建的合约,仅转移余额和资产而非删除账户。在 Solidity 中也以 SELFDESTRUCT 暴露。参见 TVM vs EVM — SELFDESTRUCT 受限行为

Osaka 预编译合约

P256VERIFY

GreatVoyage-v4.8.2 实现了 P256VERIFY(TIP-7951),用于验证 secp256r1/P-256 签名。该预编译合约地址为 0x0000000000000000000000000000000000000100,固定消耗 6900 Energy。

输入必须严格为 160 字节,依次拼接五个 32 字节字段:hash || r || s || qx || qy。签名有效时返回 32 字节的 1;输入长度、签名或公钥无效时返回空字节串,调用本身不回滚。

CLZP256VERIFY 及下面的 MODEXP 和签名验证预编译合约变更,统一由 ALLOW_TVM_OSAKA 治理提议(参数 ID 96)控制。部署依赖这些能力的合约前,应确认目标网络已经激活该提议,而不能只根据节点版本判断。

MODEXP

GreatVoyage-v4.8.2 调整了地址 0x0000000000000000000000000000000000000005 上的 MODEXP 预编译合约:

  • TIP-7823baseLenexpLenmodLen 分别不得超过 1024 字节(8192 位);任一长度超限时调用失败并返回空结果。
  • TIP-7883:重新定价 MODEXP,新增 500 Energy 的最低收费;旧版 TRON 使用 EIP-198 风格公式,没有显式最低收费,并包含 /20 除数,新公式不再使用该除数。同时,将长度超过 32 字节的指数乘数从 8 调整为 16,并重新定义 base/modulus 的乘法复杂度。
  • TIP-871:当 modulus 的值为零时,返回长度恰好为 modLen 的全零字节串,而不是空字节串。

这些变化可能使原有 MODEXP 调用失败、消耗更多 Energy,或观察到不同的返回数据长度。迁移自其他 EVM 链的密码学合约应重新测试相关边界输入和 Energy 预算。

签名验证预编译合约的 calldata 校验

TIP-854BatchValidateSign0x0000000000000000000000000000000000000009)和 ValidateMultiSign0x000000000000000000000000000000000000000a)增加了总长度校验。calldata 必须按 32 字节字对齐,并包含至少一个尾部元素:

  • BatchValidateSign:总长度必须满足 160 + 192 × N 字节,其中 N >= 1
  • ValidateMultiSign:总长度必须满足 160 + 160 × N 字节,其中 N >= 1

不满足上述形式的 calldata 会使预编译合约调用失败且不返回数据;该规则只校验总长度形式,不代表对所有内部动态偏移执行完整的 Solidity ABI 规范化校验。

Prague 历史区块哈希合约

TIP-2935 在状态中部署 BlockHashHistory 系统合约,用于提供最近 8191 个区块的历史哈希。合约使用与 Ethereum EIP-2935 相同的地址 0x0000F90827F1C53a10cb7A02335B175320002935,对应的 TRON Base58Check 地址为 T9yEAebSu94c1ndzJa1rpPtrjXBXp2FCMV

查询时,将区块号编码为 32 字节大端整数并调用该合约;calldata 不是 32 字节,或目标区块不在 [block.number - 8191, block.number - 1] 范围内时,调用会回滚。该环形缓冲区只保存提议激活后逐块写入的父区块哈希,因此激活后最多需要 8191 个区块才能完全填充。

TIP-2935 不改变 BLOCKHASH opcode 的语义、Energy 或原有 256 个区块查询范围。该功能由 ALLOW_TVM_PRAGUE 治理提议(参数 ID 95)控制,并要求目标网络已经激活 ALLOW_TVM_SHANGHAI;节点升级到 GreatVoyage-v4.8.2 本身不等于该功能已经在链上生效。

附录:能量消耗计算

A01: memNeed(offset, size)

计算操作所需的内存空间。

  • offset —— 内存起始偏移量。
  • size —— 自 offset 起待处理的数据字节大小。
  • 返回 offset + size 的计算结果。

A02: calcMemEnergy(oldMemorySize, memorySize, copySize)

计算内存操作所消耗的能量。

  • oldMemorySize —— 操作前的内存大小。
  • memorySize —— 操作后的新内存大小。
  • copySize —— 在数据复制操作中,指需要复制的数据长度。
  • memWords = (memorySize + 31) / 32
  • oldMemWords = oldMemorySize / 32
  • energyCost = 3 * memWords + memWords² / 512 − (3 * oldMemWords + oldMemWords² / 512) + 3 * ((copySize + 31) / 32)

A03: penalty(energyCost)

在启用动态能量模型(Dynamic Energy Model)时,计算并返回合约执行的惩罚能量消耗。

  • DYNAMIC_ENERGY_FACTOR = 10000
  • Factor —— 当前合约的能耗惩罚因子。
  • 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: 复制操作

适用于 CALLDATACOPYCODECOPYRETURNDATACOPYMCOPY(不适用于 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

  • oldValuestorage[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

其能量消耗取决于网络当前已激活的提案门控状态:

门控接收方账户存活接收方账户死亡
基础(无门控)00
ALLOW_ENERGY_ADJUSTMENT025,000(NEW_ACCT_CALL
ALLOW_TVM_SELFDESTRUCT_RESTRICTION(主网已激活)5,00030,000(5,000 + 25,000)

关于能量消耗变更所带来的底层语义变化,请参阅 TVM vs EVM — SELFDESTRUCT 受限行为


相关资源