TVM 与 EVM 对比

TVM 与以太坊 EVM 的兼容性差异参考,涵盖能量模型、地址编码、nonce、CREATE2、CHAINID、SELFDESTRUCT、opcode 和预编译合约。

📘

前置阅读

TVM 在字节码模型、栈机结构和 Solidity 兼容性上接近以太坊 EVM。大多数 ERC 风格合约可以在 TRON 上编译并运行,但“能运行”并不等于“所有边界行为都相同”。地址编码、费用模型、合约地址推导、链 ID、SELFDESTRUCT、预编译合约和部分 opcode 都有 TRON 特有规则。

如果目标是迁移合约,本文适合作为协议差异清单;实际迁移步骤参见从以太坊迁移到 TRON

核心差异

Energy 替代 Gas

TVM 使用能量(Energy)计量智能合约执行成本,而不是以太坊的 Gas 费用市场。

  • energyPrice 是链参数,可由超级代表(SR)委员会通过治理提案调整。
  • TRON 没有 EIP-1559 式的 baseFee / priorityFee 竞价结构。GASPRICEBASEFEE 在 TVM 上都返回当前 energyPrice
  • 钱包或 SDK 如果沿用以太坊的基础费、小费、最大 Gas 费 UI,在 TRON 上应改为围绕 fee_limit、能量单价和可用能量展示。

多数基础 opcode 的计量思路与 EVM 接近,但部分操作在 TVM 上成本不同。需要精确估算时,应以 TRON 节点返回的能量估算和实际回执为准。

80 毫秒物理执行上限

TVM 除了能量预算,还受单笔交易物理执行时间限制。链参数 #13 getMaxCpuTimeOfOneTx 控制该上限,主网当前值为 80 毫秒。

这与 EVM 的差异很实际:以太坊只通过 Gas 间接限制执行时间; TRON 即使仍有能量余额,也可能因为执行时间过长返回 OUT_OF_TIME。复杂循环、批量验证、大规模内存拷贝和重计算型合约迁移到 TRON 时,需要特别关注这个边界。

私有链调试可通过 --debug 绕过该限制;同步主网的节点不应这样做,否则本地执行结果可能与链上结果不一致。

TVM 内部地址是 20 字节

TRON 钱包、RPC 和 Base58Check 地址通常带有 0x41 网络前缀,对外表现为 21 字节原始地址或以 T 开头的 Base58Check 地址。但在 TVM 内部,地址按 20 字节处理,并左侧补零为 32 字节 DataWord,这一点与 EVM 一致。

实际影响:

  • ADDRESSCALLERORIGINBALANCE 等 opcode 在栈上处理的是 20 字节地址。
  • ERC 库中常见的“从 32 字节字中截取 20 字节地址”的内联汇编模式通常无需修改。
  • 0x41 前缀主要出现在虚拟机边界之外,例如 SDK、RPC 响应、持久化地址编码和 Base58Check 展示。

账户没有 nonce

TRON 账户没有以太坊账户 nonce。交易防重放依赖 TAPOS:交易引用近期区块,并在一定时间后过期。

因此,依赖账户 nonce 的以太坊模式在 TRON 上需要重新评估。CREATE 仍能生成确定性合约地址,但输入不是“创建者地址 + 账户 nonce”,而是 TRON 的合约创建序列逻辑。链下使用以太坊公式预计算 CREATE 地址会得到错误结果;需要确定性部署时,应使用 CREATE2 并按 TRON 规则计算。

CREATE2 使用 0x41 前缀

TVM 与 EVM 在 CREATE2 地址推导中的前缀不同:

keccak256(0x41 ++ creator_address ++ salt ++ keccak256(init_code))[12:]

以太坊在同一位置使用 0xff。迁移工厂合约、账户抽象钱包、状态通道或任何链下预计算地址逻辑时,必须把前缀从 0xff 改为 0x41。这里的 0x41 与 TRON 外部地址前缀数值相同,但语义不同:在 CREATE2 中,它是哈希输入的一部分。

CHAINID 不是静态整数

EVM 上的 CHAINID 通常返回静态整数,例如以太坊主网为 1。TVM 的 CHAINID 返回创世区块 ID 的最后 4 字节,并补齐为 32 字节 DataWord。

实际影响集中在签名域和跨链场景:

  • EIP-712 域分隔符不能硬编码以太坊主网 chainId = 1
  • 跨链订单、桥接消息和自定义防重放逻辑应读取 block.chainid,而不是假设一个固定整数。
  • 客户端、后端和合约如果各自使用不同 chain-id 语义,签名校验会失败。

SELFDESTRUCT 限制行为

启用 getAllowTvmSelfdestructRestriction 后,TVM 对 SELFDESTRUCT 的处理分两类:

  • 当前交易中新创建的合约:可删除账户,余额和资产转移给指定接收方。
  • 既有合约:不删除账户,合约代码仍可被调用;执行主要体现为余额和可转移资产处理。

TRON 还增加了以太坊没有的质押与资源边界。如果合约带有不能干净转移或处理的 Stake 1.0 锁定资产、质押、资源代理、未到期解除质押等状态,SELFDESTRUCT 可能直接 revert。

这与 EIP-6780 在“非新建合约不再删除代码”这一点上相似,但不能描述为等同。 TRON 的原生质押和资源代理状态会引入额外失败路径。开发者不应把 SELFDESTRUCT 作为升级、清空存储或可靠销毁合约的方案。

TRX 到达合约有两条路径

TRX 转入合约时,路径不同,合约是否执行也不同:

  1. TransferContract 系统合约路径:外部账户直接向合约地址转账 TRX,不进入 TVM,合约的 receive() / fallback() 不会被触发。
  2. callValueTriggerSmartContract 路径:调用 payable 函数或 fallback,进入 TVM,合约代码会执行。

以太坊中,向合约转入 ETH 通常会触发 receive()fallback()。迁移资金归集、充值记账、金库或托管合约时,必须处理 TRON 的系统转账路径,否则合约内部账本可能无法感知某些 TRX 流入。

标准指令差异

指令TVMEVM
DIFFICULTY (0x44)返回 0返回当前区块难度或兼容字段
GASLIMIT (0x45)返回 0返回当前区块 gaslimit
GASPRICE (0x3a)返回 energyPrice返回当前 gasPrice
BASEFEE (0x48)返回 energyPrice,无独立基础费市场返回当前区块 baseFee
CREATE2 (0xf5)地址推导前缀为 0x41地址推导前缀为 0xff

预编译合约差异

预编译合约地址TVMEVM
0x03历史兼容行为:执行两次 SHA-256。需要 RIPEMD-160 时应使用 EthRipemd160 预编译合约 0x00020003RIPEMD-160
0x09BatchValidateSign,批量签名校验Blake2F
0x0aValidateMultiSign,账户权限管理多签校验无对应标准预编译

TRON 专有 opcode

TVM 为 TRC-10 Token、质押、投票、资源代理和合约自省增加了一组 EVM 没有的 opcode。完整列表参见 Opcodes

TRC-10 Token

Opcode功能
CALLTOKEN (0xd0)调用合约时附带 TRC-10 Token
TOKENBALANCE (0xd1)查询地址的 TRC-10 Token 余额
CALLTOKENVALUE (0xd2)返回当前调用附带的 TRC-10 Token 数量
CALLTOKENID (0xd3)返回当前调用附带的 TRC-10 Token ID

合约自省

Opcode功能
ISCONTRACT (0xd4)判断给定地址是否为合约账户

Stake 1.0 遗留 opcode

这些 opcode 属于 Stake 1.0 时代的遗留接口。新合约应优先使用 Stake 2.0 相关 opcode。

Opcode功能
FREEZE (0xd5)质押 TRX 获取资源
UNFREEZE (0xd6)解除质押 TRX
FREEZEEXPIRETIME (0xd7)查询质押到期时间

Stake 2.0 与资源代理

Opcode功能
FREEZEBALANCEV2 (0xda)质押 TRX 获取带宽或能量
UNFREEZEBALANCEV2 (0xdb)发起解除质押,进入等待期
CANCELALLUNFREEZEV2 (0xdc)取消所有待处理的解除质押操作
WITHDRAWEXPIREUNFREEZE (0xdd)提取等待期结束的已解除质押 TRX
DELEGATERESOURCE (0xde)将资源代理给另一地址
UNDELEGATERESOURCE (0xdf)撤销资源代理

投票

Opcode功能
VOTEWITNESS (0xd8)为超级代表投票
WITHDRAWREWARD (0xd9)提取累积的投票奖励

TRON 专有预编译合约

预编译合约功能
BatchValidateSign (0x09)批量签名校验
ValidateMultiSign (0x0a)账户权限管理多签校验
RewardBalance (0x1000005)查询投票奖励余额
IsSrCandidate (0x1000006)判断地址是否为超级代表候选人
VoteCount (0x1000007)查询某地址投给特定 SR 的票数
GetChainParameter (0x100000b)查询链参数
AvailableUnfreezeV2Size (0x100000c)查询可新增解除质押队列长度
UnfreezableBalanceV2 (0x100000d)查询指定资源类型可解除质押余额
ExpireUnfreezeBalanceV2 (0x100000e)查询给定时间戳下可提取金额
DelegatableResource (0x100000f)查询指定资源类型可代理数量
ResourceV2 (0x1000010)查询地址之间的资源代理关系
CheckUnDelegateResource (0x1000011)检查合约是否可收回代理资源
ResourceUsage (0x1000012)查询资源使用量和恢复时间
TotalResource (0x1000013)查询地址总可用资源
TotalDelegatedResource (0x1000014)查询地址代理出去的资源总量
TotalAcquiredResource (0x1000015)查询地址通过代理获得的资源总量

以太坊硬分叉跟踪

TRON 通过链参数门控逐步引入 EVM 硬分叉功能。门控未启用时,对应 opcode 会被 TVM 判定为 ILLEGAL_OPERATION。主网、Nile、Shasta 等网络的启用时间可能不同,因此上线前应查询目标网络的实时链参数。

硬分叉TVM 等价阶段链参数门控启用后新增内容
ConstantinopleTVM ConstantinoplegetAllowTvmConstantinopleSHLSHRSAREXTCODEHASHCREATE2
IstanbulTVM IstanbulgetAllowTvmIstanbulCHAINIDSELFBALANCE,以及部分 opcode 能量成本调整
LondonTVM LondongetAllowTvmLondonBASEFEE,在 TVM 上返回 energyPrice
ShanghaiTVM ShanghaigetAllowTvmShangHaiPUSH0
CancunTVM CancungetAllowTvmCancunTLOADTSTOREMCOPY
Cancun blobTVM BlobgetAllowTvmBlobBLOBHASHBLOBBASEFEE,在 TVM 上均返回 0

查询当前门控状态

# 主网
curl -s https://api.trongrid.io/wallet/getchainparameters | \
  jq '.chainParameter[] | select(.key | startswith("getAllowTvm"))'

# Nile 测试网
curl -s https://nile.trongrid.io/wallet/getchainparameters | \
  jq '.chainParameter[] | select(.key | startswith("getAllowTvm"))'

# Shasta 测试网
curl -s https://api.shasta.trongrid.io/wallet/getchainparameters | \
  jq '.chainParameter[] | select(.key | startswith("getAllowTvm"))'

返回值为 1 表示对应门控已启用。不要假设某个 opcode 在所有网络上同时可用。

TVM 专有门控

除以太坊硬分叉外,TVM 还有一些没有 EVM 对应项的链参数门控:

链参数控制内容
getAllowEnergyAdjustment影响部分 opcode 的能量公式分支
getAllowDynamicEnergy启用合约维度的动态能量模型惩罚
getAllowTvmSelfdestructRestriction启用 SELFDESTRUCT 限制行为

相关资源