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 … 0x1D

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算术右移

哈希运算 — 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 受限行为

附录:能量消耗计算

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 受限行为


相关资源