TRON 上的 Solidity

TRON 特有的 Solidity 语言特性:trx 单位关键字、用于 TRC-10 的 trcToken 类型、 msg.tokenidmsg.tokenvalue,以及在合约内调用质押和投票的内置函数。

TRON 提供的 Solidity 编译器是基于上游 Solidity 编译器的分支版本,并在其之上增加了少量 TRON 专属的语法扩展。该编译器支持绝大多数以太坊 Solidity 特性,以太坊合约通常无需修改即可编译。差异集中在四类 TRON 特有扩展:trx 货币单位关键字、专门标识 TRC-10 Token 的 trcToken 类型、消息调用扩展属性(msg.tokenidmsg.tokenvalue),以及用于在合约内发起质押与投票的内置函数指针。

本页是 TRON Solidity 语言参考。需要完整的 Stake 2.0 封装 SDK,见 Stake 2.0 Solidity SDK 参考

TRX 作为单位关键字

在上游 Solidity 中,以太坊使用 weigweiether 作为面值单位后缀。与之对应, TRON Solidity 编译器引入了 trx 作为原生 Token 的等价单位,并使用 sun 作为最小 Token 单位:

单位字面量
1 sun1 sun(最小单位)
1 trx1,000,000 sun

在 Solidity 支持数字字面量的任何位置都可以使用这两个单位:

require(msg.value >= 1 trx, "Pay at least 1 TRX");
uint256 fee = 100 sun;

编译器仍支持上游的 weigweiether,但编写 TRON 合约时建议避免:改用 trxsun,Token 单位含义更清晰。

TRC-10 Token 内置函数

TRC-10 是 TRON 协议层直接支持的 Token 类型,不依赖 TRON 虚拟机(TVM);TVM 提供了一组内置函数,使智能合约能够与 TRC-10 Token 交互。关于其完整标准,请参阅 TRC-10 Token 说明

trcToken 类型

trcToken 是独立于 uint256 的专属类型,专门用来标识特定的 TRC-10 Token ID。编写合约时,可以将 trcTokenuint256 相互强制转换:

trcToken id = 1000001;
uint256 idAsUint = uint256(id);
trcToken back = trcToken(idAsUint);
📘

ID 有效范围

按照 TRON 协议规定,合法的 TRC-10 tokenId 必须大于 1,000,000。但由于编译器在编译期并不强制约束这一规则,因此当合约函数接收外部传入的 tokenId 时,建议加 require(id > 1000000) 断言校验。

address.transferToken(value, tokenId)

在合约中,只需通过一次简单的 Solidity 调用,即可将指定数量的 TRC-10 Token 发送到目标地址:

function transferTokenTest(address payable toAddress, uint256 tokenValue, trcToken id) public {
    require(id > 1000000);
    toAddress.transferToken(tokenValue, id);
}

该函数的调用行为与发送 TRX 的 address.transfer(uint256) 完全一致:接收方地址类型必须被声明为 payable,并且在发起调用时,只会向接收方账户的 fallback 函数转发 2,300 能量。

address.tokenBalance(tokenId)

合约通过该函数查询指定地址的特定 TRC-10 Token 余额:

function getTokenBalance(address account, trcToken id) public view returns (uint256) {
    return account.tokenBalance(id);
}

msg.tokenidmsg.tokenvalue

当外部账户通过附带 TRC-10 Token 的方式调用合约函数时(底层通过 CALLTOKEN 指令执行,或在调用 triggerSmartContract 时传入了 callTokenValuetokenId 参数),TVM 会在内置的 msg 上下文对象中自动填充以下两个额外字段:

function receiveTRC10() public payable returns (trcToken, uint256) {
    trcToken id = msg.tokenid;
    uint256 amount = msg.tokenvalue;
    return (id, amount);
}

如果调用时未携带任何 TRC-10 Token,这两个字段的数值默认均为 0。请注意,这些属性字段为 TRON 网络所独有,在以太坊网络中并不存在。

质押、投票与资源代理

TRON 把 Stake 2.0 质押、资源分配、投票和领取收益等系统级功能,以内置函数形式扩展到 Solidity 的 address 类型上。关于所有 26 个内置系统接口的完整定义与用法,请参阅 Stake 2.0 Solidity SDK 参考

下面列出最常用的几个:

// 质押 TRX 以获取带宽(resourceType = 0)或能量(resourceType = 1)
address.freezeBalanceV2(amount, resourceType);

// 开始解除质押——启动 14 天等待期
address.unfreezeBalanceV2(amount, resourceType);

// 提取已过 14 天等待期的 TRX
address.withdrawExpireUnfreeze();

// 为超级代表投票
address.voteWitness(srs, voteCounts);

// 领取累积的投票奖励
address.withdrawReward();

编译器版本支持

TRON 专属的 Solidity 编译器开源维护于 github.com/tronprotocol/solidity。目前 TRON 虚拟机 (TVM) 支持以下几个主要编译器版本分支:

  • 0.4.x — TVM 支持的最早编译器版本分支,适用于兼容旧版历史合约(例如 0.4.240.4.25)。
  • 0.5.x — 历史上使用最广泛的稳定版分支,全面支持自 0.5.00.5.16 之间的各个小版本。
  • 0.6.x0.7.x — TVM 亦对其提供了完整支持。
  • 0.8.x — 当前推荐用于新合约的版本分支。使用时用具体补丁号显式锁定版本,例如 pragma solidity 0.8.20;(不含 ^;具体推荐补丁版本见 TronBox 版本发布说明)。

把以太坊 Solidity 合约移植到 TRON 时,选用对应的 TRON 编译器版本,代码通常能直接通过编译。若在编译过程中遇到任何版本兼容性问题,请参阅从以太坊迁移

与上游 Solidity 的差异

TRON Solidity 与以太坊标准 Solidity 编译器存在以下几处细节差异:

  • gas 关键字 —— 出于向后兼容的考虑,合约代码中仍支持编写此关键字,但 TVM 虚拟机在底层运行时会将其转换为能量进行折算。调用 gasleft() 会返回当前交易剩余的能量额度。
  • tx.gasprice —— 调用此属性将返回当前网络的 energyPrice,而非传统以太坊网络中的 Gas 价格。
  • block.difficulty —— 属性返回值始终为 0。这是因为 TRON 不采用以太坊早期的工作量证明(PoW)共识机制,而是基于委托权益证明(DPoS)机制。
  • block.basefee —— 属性返回值将直接映射为 energyPrice。 TRON 协议中并未实现以太坊的 EIP-1559 提案。

关于虚拟机指令集(Opcode)层面的详细技术差异,请参阅 TVM vs EVM 对照指南


相关资源