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竞价结构。GASPRICE和BASEFEE在 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 一致。
实际影响:
ADDRESS、CALLER、ORIGIN、BALANCE等 opcode 在栈上处理的是 20 字节地址。- ERC 库中常见的“从 32 字节字中截取 20 字节地址”的内联汇编模式通常无需修改。
0x41前缀主要出现在虚拟机边界之外,例如 SDK、RPC 响应、持久化地址编码和 Base58Check 展示。
账户没有 nonce
TRON 账户没有以太坊账户 nonce。交易防重放依赖 TAPOS:交易引用近期区块,并在一定时间后过期。
因此,依赖账户 nonce 的以太坊模式在 TRON 上需要重新评估。CREATE 仍能生成确定性合约地址,但输入不是“创建者地址 + 账户 nonce”,而是 TRON 的合约创建序列逻辑。链下使用以太坊公式预计算 CREATE 地址会得到错误结果;需要确定性部署时,应使用 CREATE2 并按 TRON 规则计算。
CREATE2 使用 0x41 前缀
CREATE2 使用 0x41 前缀TVM 与 EVM 在 CREATE2 地址推导中的前缀不同:
keccak256(0x41 ++ creator_address ++ salt ++ keccak256(init_code))[12:]以太坊在同一位置使用 0xff。迁移工厂合约、账户抽象钱包、状态通道或任何链下预计算地址逻辑时,必须把前缀从 0xff 改为 0x41。这里的 0x41 与 TRON 外部地址前缀数值相同,但语义不同:在 CREATE2 中,它是哈希输入的一部分。
CHAINID 不是静态整数
CHAINID 不是静态整数EVM 上的 CHAINID 通常返回静态整数,例如以太坊主网为 1。TVM 的 CHAINID 返回创世区块 ID 的最后 4 字节,并补齐为 32 字节 DataWord。
实际影响集中在签名域和跨链场景:
- EIP-712 域分隔符不能硬编码以太坊主网
chainId = 1。 - 跨链订单、桥接消息和自定义防重放逻辑应读取
block.chainid,而不是假设一个固定整数。 - 客户端、后端和合约如果各自使用不同 chain-id 语义,签名校验会失败。
SELFDESTRUCT 限制行为
SELFDESTRUCT 限制行为启用 getAllowTvmSelfdestructRestriction 后,TVM 对 SELFDESTRUCT 的处理分两类:
- 当前交易中新创建的合约:可删除账户,余额和资产转移给指定接收方。
- 既有合约:不删除账户,合约代码仍可被调用;执行主要体现为余额和可转移资产处理。
TRON 还增加了以太坊没有的质押与资源边界。如果合约带有不能干净转移或处理的 Stake 1.0 锁定资产、质押、资源代理、未到期解除质押等状态,SELFDESTRUCT 可能直接 revert。
这与 EIP-6780 在“非新建合约不再删除代码”这一点上相似,但不能描述为等同。 TRON 的原生质押和资源代理状态会引入额外失败路径。开发者不应把 SELFDESTRUCT 作为升级、清空存储或可靠销毁合约的方案。
TRX 到达合约有两条路径
TRX 转入合约时,路径不同,合约是否执行也不同:
TransferContract系统合约路径:外部账户直接向合约地址转账 TRX,不进入 TVM,合约的receive()/fallback()不会被触发。- 带
callValue的TriggerSmartContract路径:调用 payable 函数或 fallback,进入 TVM,合约代码会执行。
以太坊中,向合约转入 ETH 通常会触发 receive() 或 fallback()。迁移资金归集、充值记账、金库或托管合约时,必须处理 TRON 的系统转账路径,否则合约内部账本可能无法感知某些 TRX 流入。
标准指令差异
| 指令 | TVM | EVM |
|---|---|---|
DIFFICULTY (0x44) | 返回 0 | 返回当前区块难度或兼容字段 |
GASLIMIT (0x45) | 返回 0 | 返回当前区块 gaslimit |
GASPRICE (0x3a) | 返回 energyPrice | 返回当前 gasPrice |
BASEFEE (0x48) | 返回 energyPrice,无独立基础费市场 | 返回当前区块 baseFee |
CREATE2 (0xf5) | 地址推导前缀为 0x41 | 地址推导前缀为 0xff |
预编译合约差异
| 预编译合约地址 | TVM | EVM |
|---|---|---|
0x03 | 历史兼容行为:执行两次 SHA-256。需要 RIPEMD-160 时应使用 EthRipemd160 预编译合约 0x00020003 | RIPEMD-160 |
0x09 | BatchValidateSign,批量签名校验 | Blake2F |
0x0a | ValidateMultiSign,账户权限管理多签校验 | 无对应标准预编译 |
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 等价阶段 | 链参数门控 | 启用后新增内容 |
|---|---|---|---|
| Constantinople | TVM Constantinople | getAllowTvmConstantinople | SHL、SHR、SAR、EXTCODEHASH、CREATE2 |
| Istanbul | TVM Istanbul | getAllowTvmIstanbul | CHAINID、SELFBALANCE,以及部分 opcode 能量成本调整 |
| London | TVM London | getAllowTvmLondon | BASEFEE,在 TVM 上返回 energyPrice |
| Shanghai | TVM Shanghai | getAllowTvmShangHai | PUSH0 |
| Cancun | TVM Cancun | getAllowTvmCancun | TLOAD、TSTORE、MCOPY |
| Cancun blob | TVM Blob | getAllowTvmBlob | BLOBHASH、BLOBBASEFEE,在 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 限制行为 |
相关资源
- Opcodes — 完整 TVM opcode 参考
- TVM — TRON 虚拟机
- 从以太坊迁移 — 合约迁移实用步骤
- Stake 2.0 Solidity SDK 参考 — 合约中的质押、资源代理和投票
- TRON 上的 Solidity — TRON 专有 Solidity 语言扩展
- TIP-272 — EVM 兼容性改进讨论
Updated 26 days ago