FeeLimit 与能量成本

详述在 TRON 智能合约交易中如何通过 fee_limit 限制您的 TRX 风险敞口,探讨其与部署端参数 origin_energy_limit 的本质差异,并提供在发送交易前合理设置此参数的三种策略。

📘

前置阅读

在 TRON 网络中部署或调用智能合约时,都必须设置 fee_limit 参数。如果配置不当,极易导致交易执行失败或造成意外的 TRX 资金损失。本文将为您详解三个核心问题:fee_limit 的实际作用机制、它与部署端参数 origin_energy_limit 的本质区别,以及如何在发送交易前合理地设置该参数值。

fee_limit 的本质作用

fee_limit 是您在发起智能合约交互交易时指定的一个限额参数。您可以这样直观地理解它:

fee_limit 是该笔交易允许消耗的能量上限,只是以 TRX Token进行计价。

虽然在设置时使用的是 TRX(技术上是以其最小单位 sun 计价,请参见下方单位说明),但 TRON 底层网络在执行交易时,会将其折算为可消耗的能量上限,折算公式如下:

maxEnergyForThisTx = fee_limit / EnergyPrice

如果在合约执行过程中,所需的能量超过了这一折算后的上限,TVM 虚拟机将立即停止运行并抛出 OUT_OF_ENERGY 错误,但绝不会超出您所授权的额度去扣除您账户中更多的 TRX 余额。因此,fee_limit 同时承担着双重职责:

  • 安全熔断阀:防止因合约代码 Bug 或恶意攻击导致能量无限消耗,保护账户余额不被掏空。
  • 每交易能量预算:为单笔交易提供一个以 TRX 计价的能量额度,让开发者在无需繁琐换算网络底层单位的情况下便捷地控制成本。

从底层机制来看,网络在 TVM 虚拟机开始执行合约指令前,就已经将 fee_limit 换算成了具体的能量限额。随后,虚拟机开始运行合约代码,直至执行完毕或限额耗尽。由于整个执行过程受到这一能量上限的强约束,最终结算的 TRX 扣款金额将严格保证不大于您设定的 fee_limit。这种“能量先行”的设计确保了每一条被执行的操作码(Opcode)都能够得到精准的资源计量,即便交易由于资源不足而中途夭折,已消耗的部分也依然能够准确扣减。

使用 fee_limit 必须牢记的三点

  • 单位为 sun:其值是以小写 sun(TRX 的最小单位)来指定的,而非 TRX。1 TRX 等于 1,000,000 sun。在代码配置中务必注意单位换算。
  • 受当前主网参数限制:目前主网单笔交易允许设置的 fee_limit 上限为 15,000 TRX(即 1.5 × 10¹⁰ sun)。该上限由链参数(getMaxFeeLimit,参数 ID 为 47)决定。如果您设置的 fee_limit 高于当前主网的 getMaxFeeLimit,交易将在验证阶段直接报错返回。请注意,15,000 TRX 为当前主网设置的值,在实际部署和运行时,应通过 API 动态查询该参数的最新值,而非将其视作永恒不变的静态事实。
  • 质押充足时依然生效:即使账户中已质押大量能量,fee_limit 仍在结算时生效。TVM 通过以下公式确定调用方可用的最大能量:
    callerEnergyLimit = min(callerStakedEnergy + (balance − callValue) / sunPerEnergy, fee_limit / sunPerEnergy)
    fee_limit 换算后与"质押能量 + 可燃烧能量"取最小值,因此即使质押能量充足,若 fee_limit 设置过低,交易仍可能因上限受限而抛出 OUT_OF_ENERGY。引入该限制的目的是在系统层面熔断失控的能耗风险,而非仅仅保护 TRX 余额。完整结算链参见下方"TVM 底层对 fee_limit 的执行细节"。
📘

EnergyPrice 也是链级参数

EnergyPrice(即每 Energy 对应的价格)由 TRON 的链参数 getEnergyFee 统一管理。主网当前值为每 Energy 0.0001 TRX(100 sun)。本文中的所有计算和示例均基于此换算比例。在生产环境部署时,建议通过 API 实时查询 getEnergyFee 的最新值,以防链参数发生调整。

fee_limit(调用方)与 origin_energy_limit(部署方)的对比

TRON 网络的智能合约能量管理机制引入了两个独立的额度上限,共同控制一次合约调用能耗的上限。虽然它们在功能上有些相似,但二者属于完全不同的系统作用域——一个是合约自身的持久化属性,另一个则是每笔交互交易的即时属性。厘清这种作用域的差异是避免混淆的最快方式。

维度fee_limitorigin_energy_limit
所属作用域单笔交易部署的智能合约
设置与所有权合约调用方(针对单交易定义)合约拥有者/部署方(部署时定义或后续更新)
数值单位小写 sun(10⁻⁶ TRX)能量(无单位数值)
最大配置边界当前主网允许的单笔 getMaxFeeLimit 上限(当前值为 15,000 TRX)协议层并无硬性上限,仅强制要求大于 0。默认初始值为 10,000,000 能量(该值为初始值而非限额)
未显式指定时的默认值0(调用方可承担的 Energy 上限为 0;若部署方不能完整覆盖,交易会因预算不足失败)10,000,000 能量
参数如何更新直接在下一笔要发起的交易数据中修改并指定新值由部署方提交 UpdateEnergyLimitContract 系统交易;上链生效后,该合约后续的所有调用自动遵循新限额
实践中的更新频率高频 —— 每笔交互交易均需重新计算和微调低频 —— 仅在合约管理者需要变更运营补贴策略时手动修改
防御的风险模型保护调用方免受单交易失控能耗与高昂扣费的风险保护部署方免受外部恶意用户针对该合约发起能耗榨取攻击的风险
心智模型“我这笔交易最多能消耗多少钱的能量”“项目方给该合约单次交互提供的最大能耗报销额度”

这两个上限参数相互独立,但在每次合约交互发生时会同时参与计算origin_energy_limit 不足只会降低部署方可提供的补贴额度,剩余部分由调用方承担;只有调用方与部署方合计可承担的 Energy 上限仍不足以完成执行时,交易才会以 OUT_OF_ENERGY 失败。

作用域差异的重要意义

许多开发者极易把它们当成同一概念的两种称呼,这是一种误解。

origin_energy_limit 属于合约的静态元数据。它在开发者部署合约时一次性设定,并持久化记录在链上的合约信息(SmartContract)中。每当有账户调用该合约,执行节点都会读取该合约在链上保存的这个限制。如果合约拥有者想要调整该值,只需广播一笔 UpdateEnergyLimitContract 系统合约交易即可;一旦该修改交易上链,后续的所有合约调用都将自动遵循这一新限制。对于普通调用者而言,这个过程是无感知的——调用限额在他们不知情的情况下发生了改变。

fee_limit 则是交易级指令参数。它被编码在每笔交易的 raw_data 结构中,仅在当前交易的执行生命周期内有效。在 TVM 虚拟机开始执行时,会读取当前交易携带的该参数,换算为本次调用的可用能量上限。当这笔交易执行完毕,该参数的作用即告结束,并不在合约或链上进行任何状态留存。下一个调用者发起交易时,需在他们自己的交易包中重新指定其专属的 fee_limit

两者的本质差异决定了开发者在不同场景下的思考维度:

  • 作为合约开发者/拥有者:如果您希望通过补贴用户来降低其交互门槛,需要在合约层面一次性调整并优化 origin_energy_limit 以及 consume_user_resource_percent。这些参数会影响所有与您合约产生交互的用户。
  • 作为合约调用者/用户:如果您要调用别人的合约,且希望限制自己的支出风险,只需根据能耗预期针对该笔交易指定并微调 fee_limit 即可。这只影响您当前发起的这一笔交易。

consume_user_resource_percent:资源分摊的纽带

在合约层面上,还有一个名为 consume_user_resource_percent 的比例参数,它决定了执行交易所消耗的能量如何在合约调用者与部署者之间进行分摊。它的取值范围是 0100 的整数:

  • 100:表示调用者承担 100% 的能耗,部署者不提供任何资源。目前绝大多数主流的公共智能合约均采用此项配置,以防高频的调用耗尽部署者的账户资源。
  • 60:表示调用者分摊 60% 的能耗,部署者分摊 40%。
  • 0:表示部署者全额补贴该笔交易所消耗的能量。这适用于面向新手用户且提供全额能耗免减的 DApp。但请注意,如果未同时合理设置 origin_energy_limit,可能会导致部署者账户被恶意用户的交易快速耗尽。

origin_energy_limit 类似,consume_user_resource_percent 在合约部署完成后,依然可以由合约拥有者调用 /wallet/updatesetting 接口进行动态修改。

合约调用时的双上限结算流程

假设一笔合约调用交易在执行时总共产生了 X 的能量损耗。 TRON 协议将按照以下算法在合约部署者和调用者之间划分账单:

部署方实际支付 = min(
    X × (100 - consume_user_resource_percent) / 100   # 理论代付份额
    origin_energy_limit                               # 部署方的单次调用代付上限
    deployer_available_energy                         # 部署方账户中实际可用的能量余额
)

调用方实际支付 = X − 部署方实际支付

对于调用者应承担的份额,系统会优先从调用者账户已质押获取的能量额度中扣除;如果额度不足,则会通过燃烧其账户中的 TRX 余额来进行抵扣(燃烧也受调用者设定的 fee_limit 上限约束)。

调用方可承担的最大 Energy 取以下两者的较小值:fee_limit / EnergyPrice,以及调用方可用质押 Energy 与可燃烧 TRX 可换算 Energy 之和。如果实际需要调用方承担的 Energy 超过该上限,交易将以 OUT_OF_ENERGY 失败。

📘

核心区别总结

fee_limit 保护的是调用方(用户),防止其单笔交易因异常的大量能耗而遭遇非预期的 TRX 扣款。origin_energy_limit 保护的则是部署方(项目方),防止其补贴账户被针对合约的恶意耗尽攻击掏空。这两个上限机制相互独立、同时生效。

实战计算案例演示

假设我们有以下调用场景:

  • 合约调用最终执行总能耗:X = 80 能量
  • 资源分摊比例 consume_user_resource_percent 设为 60(即调用者理论分摊 60%,部署者分摊 40%)
  • 部署方设定的限制 origin_energy_limit40 能量
  • 部署方当前账户中实际仅剩:10 能量可用
  • 调用方质押能量为 0,但在发起交易时设置了 fee_limit1,000,000 sun(即 1 TRX,按默认 100 sun/能量折算,相当于最多允许消耗 10,000 能量)

根据扣费模型执行计算:

部署方实际支付 = min(
    80 × 40% = 32 能量        # 理论应代付份额
    40 能量                   # 合约配置的 origin_energy_limit 每次限制
    10 能量                   # 部署者当前账户实际可用的能量
) = 10 能量

在计算中,由于部署者账户中实际可用的能量已经耗尽,部署者最终只能分摊并代付 10 能量。调用者必须支付剩下的 70 能量:

调用方实际支付 = 80 − 10 = 70 能量

因为调用者的账户中质押能量为 0,这 70 能量将全部通过燃烧 TRX 的方式进行结算:

调用者需燃烧的 TRX = 70 × 100 sun = 7,000 sun (即 0.007 TRX)

由于调用者为这笔交易设置的 fee_limit1,000,000 sun(远远高于实际燃烧所需的 7,000 sun),交易得以顺利执行。最终系统扣除调用者账户 7,000 sun 的 TRX 余额,而 fee_limit 中剩余的 993,000 sun 不会被扣减。这再次印证了 fee_limit 是单笔交易的计费上限,而非固定扣除金额。

🚧

TRX 燃烧扣除后无法退回

交易所消耗的能量(无论最终源于用户质押扣除还是燃烧 TRX 抵扣)一经耗用便无法退还。即使该笔交易在虚拟机执行的中后期被 revert 或者是由于其他业务逻辑检查失败,已扣除的资源也不会返还。因此,强烈建议在向主网发送大额能耗交易前,先在本地或测试网进行精确估算。

TVM 底层对 fee_limit 的执行细节

对大多数开发者而言,仅需掌握“fee_limit 限制调用方愿意为当前交易承担的最大 Energy 成本”这一原则便足够。然而,深入理解虚拟机底层的执行链有助于您规避交易超时、多方资源分摊等非直觉性行为。

协议底层的完整执行校验链如下:

  1. 边界校验:在交易真正开始前,执行节点将首先验证 0 <= fee_limit <= MaxFeeLimit。这里的 MaxFeeLimit 即为主网当前通过提案设置的最大交易费限额(可通过链上查询)。如果您设定的值超出了此边界,节点会立即抛出 ContractValidateException 异常。由于该交易在验证阶段就被拒绝,并没有真正进入 TVM 虚拟机,因此不会产生任何能量消耗。
  2. 折算能量限制:系统使用公式 energyFromFeeLimit = fee_limit / sunPerEnergy 计算调用方愿意为本次交易承担的最大 Energy。其中的 sunPerEnergy 获取自链上动态配置 DynamicPropertiesStore.getEnergyFee()。该上限同时参与调用方质押 Energy 与燃烧 TRX 可支付 Energy 的预算计算。
  3. 核算调用方预算:节点计算调用方在这笔交易中能支付的最大能量:callerEnergyLimit = min(callerStakedEnergy + (balance - callValue) / sunPerEnergy, energyFromFeeLimit)。该值即为调用者所能分摊的最大能量上限。
  4. 合并部署方代付部分:如果合约设置了补贴(consume_user_resource_percent < 100),系统会将部署方愿意并有能力代付的能量加入计算:deployerEnergyLimit = min(理论代付份额, origin_energy_limit, 部署方可用能量)。特别需要注意的是,部署方代付的这部分能量完全不受调用方交易中 fee_limit 参数的控制
  5. 执行虚拟机指令:TVM 虚拟机加载计算出的总能量上限 totalEnergyLimit = callerEnergyLimit + deployerEnergyLimit 并开始解释执行合约的操作码。每一条指令都会扣减相应的能量。一旦虚拟机内的能量计数归零,TVM 抛出 OutOfEnergyException 并中断执行。
  6. 最终账单结算:交易完成后,系统根据实际消耗的能量进行扣费。优先扣除账户质押产生的可用能量(无 TRX 消耗),不足的部分则按照 sunPerEnergy 的单价燃烧 TRX 余额(受 fee_limit 上限保护)。

数学层面的完备性保证了无论执行何种逻辑,actualTRXBurned <= fee_limit 永远成立。

需要关注的底层特征与副作用

  • 只读模拟不要求交易级 fee_limit/wallet/triggerconstantcontract 在节点本地执行,不生成上链交易,也不消耗账户的实际 Bandwidth 或 Energy。当前接口请求参数不要求提供 fee_limit;模拟仍受节点配置和 TVM 执行时间等保护性限制,失败时应读取返回的 result 与错误信息。
  • 超时失败将全额扣除限额:与能量不足(OUT_OF_ENERGY 只扣减到出错点为止的实际消耗量)不同,如果交易执行时长超过了单区块执行的硬性时间窗限制(当前主网通常为 80 毫秒)而触发了 OUT_OF_TIME 异常,该失败在协议底层会被视作 assert 式的严重故障。这意味着交易设置的整个 fee_limit 额度折算出的能量都将被全额扣减。尽管对于设计良好的合约这极难发生,但如果您的合约包含对大数组的无界循环操作,便有触发此机制的潜在风险。参见 VM 异常处理 章节。

(注:系统扣费时,能量额度的计算也会受到用户和创建者账户中质押资源的比例以及全网实时空闲资源量的动态影响。)

设定 fee_limit 的三种开发策略

在实际开发中,并没有一个固定的“标准” fee_limit。具体的参数设定需要开发者在开发复杂度与扣款精确度之间进行权衡。以下是业界沉淀出的三种实用策略,按精度与实现难度由浅入深排列:

策略 1:基于 max_factor 计算(推荐作为默认方案)

TRON 网络中包含一个名为 max_factor 的链级参数,它代表动态能量模型(Dynamic Energy Model, DEM)的惩罚因子上限。将合约基础能耗估算值乘以 (1 + max_factor),可以覆盖当前链参数允许的动态能量惩罚上界:

fee_limit = base_energy_estimate × (1 + max_factor) × EnergyPrice
  • 优势:计算简单,无需频繁查询各个合约当前的 energy_factor;在基础能耗估算与链参数仍有效的前提下,可降低因动态能量惩罚导致的预算不足风险。
  • 缺点:设置的费用上限偏高,账户需要具备相应的可承担能力。正常执行或 revert 按协议规定的实际 Energy 结算;OUT_OF_TIME、非法指令等异常可能按本次交易允许的最大 Energy 扣费,因此不应仅因为“通常不会用满”就盲目设置过高上限。

该公式只覆盖动态能量因子的上界,不覆盖合约执行分支、链上状态、调用参数、账户余额或链参数变化。它适合作为偏保守的预算方法,而不是交易成功保证。

策略 2:按维护周期动态跟踪合约的 energy_factor

如果您的业务需要高频调用同一个特定的智能合约,并且希望尽可能压低用户的 fee_limit 授权额度,可以选择在每个维护周期(每 6 小时)动态查询该目标合约当前的 energy_factor 并套用公式:

fee_limit = base_energy_estimate × (1 + contract_energy_factor) × EnergyPrice

每个智能合约的 energy_factor 会基于其近期在主网的使用热度动态变动,其值始终处于 0max_factor 之间。您可以通过 /wallet/getcontractinfo 接口进行获取:

BASE_URL=https://api.trongrid.io   # 示例端点——可替换为实际可用的 TRON 节点
curl -X POST ${BASE_URL}/wallet/getcontractinfo \
  -d '{"value":"TG3XXyExBkPp9nzdajDZsozEu4BkaSJozs","visible":true}'
  • 优势:设定的 fee_limit 通常比策略 1 更紧凑,并能反映查询时该合约的动态能量因子。
  • 缺点:需要定期刷新参数;即使处于同一维护周期,合约状态、调用参数和账户资源变化仍可能使历史估算不再适用。

策略 3:在每次交易发起前实时估算

这是时效性更高、实现也更复杂的方案。在交易发送广播前,调用 /wallet/estimateenergy(或 /wallet/triggerconstantcontract)获取基于当前节点状态的预测值,并据此设定:

fee_limit = estimated_total_energy × EnergyPrice
  • 优势:能反映估算节点在请求时刻看到的合约状态和动态能量因子,通常比缓存值更接近实际执行成本。
  • 缺点:每次发送真实交易前需要额外的节点 RPC 请求,会增加业务时延。估算与上链之间的状态变化、维护周期切换、节点状态差异或执行分支变化仍可能造成偏差,因此应保留合理缓冲并处理失败重试。

开发策略选择指南

您的业务场景建议采用的策略
绝大多数普通场景(如钱包交互、低频合约调用、新合约部署等)策略 1(基于 max_factor 计算)
高频、重复调用已知的特定热门合约策略 2(基于单个合约的 energy_factor 动态更新)
对资金留存率要求极高、高并发且成本异常敏感的业务(如中心化交易所、大额自动支付系统)策略 3(交易发起前毫秒级实时估算)

如何在广播交易前进行能耗估算

在实际应用上述策略前,都需要首先获取合约执行的基础能耗估算值。 TRON 提供了以下两个主流的 HTTP 接口:

1. /wallet/triggerconstantcontract 接口(通用推荐)

这是最通用的模拟执行与估算接口。它在节点本地沙箱中试运行合约指令,完全不上链广播,并会在响应体的 energy_used 字段中返回如果交易成功执行所需的能量估算值。它对部署新合约以及调用现有合约的方法均通用。

合约调用模拟示例

BASE_URL=https://api.trongrid.io   # 示例端点——可替换为实际可用的 TRON 节点
curl -X POST ${BASE_URL}/wallet/triggerconstantcontract \
  -d '{
    "owner_address": "TTGhREx2pDSxFX555NWz1YwGpiBVPvQA7e",
    "contract_address": "TVSvjZdyDSNocHm7dP3jvCmMNsCnMTPa5W",
    "function_selector": "transfer(address,uint256)",
    "parameter": "0000000000000000000000002ce5...0000038d7ea4c68000",
    "visible": true
  }'

合约部署模拟示例:需要在 data 字段中传入编译生成并经过 Hex 编码的完整合约字节码。具体参数详情可参考 HTTP API —— triggerconstantcontract 接口文档:

BASE_URL=https://api.shasta.trongrid.io   # 示例端点——以 Shasta 测试网节点为例
curl --request POST \
 --url ${BASE_URL}/wallet/triggerconstantcontract \
 --header 'accept: application/json' \
 --header 'content-type: application/json' \
 --data '
{
  "owner_address": "TZ4UXDV5ZhNW7fb2AMSbgfAEZ7hWsnYS2g",
  "data":"608060405234801561000f575f80fd5b50d3801561001b575f80fd5b50d28015610027575f80fd5b5061015b806100355f395ff3fe608060405234801561000f575f80fd5b50d3801561001b575f80fd5b50d28015610027575f80fd5b506004361061004c575f3560e01c806360fe47b1146100505780636d4ce63c1461006c575b5f80fd5b61006a600480360381019061006591906100d2565b61008a565b005b610074610093565b604051610081919061010c565b60405180910390f35b805f8190555050565b5f8054905090565b5f80fd5b5f819050919050565b6100b18161009f565b81146100bb575f80fd5b50565b5f813590506100cc816100a8565b92915050565b5f602082840312156100e7576100e661009b565b5b5f6100f4848285016100be565b91505092915050565b6101068161009f565b82525050565b5f60208201905061011f5f8301846100fd565b9291505056fea26474726f6e58221220ca11b5749b47f126a08ed4dd6de453cf3e3e1d68c1105af0acdd8a38c18b37ac64736f6c63430008140033",
  "visible": true
}'

接口返回的 JSON 响应体中会包含 energy_used(执行所需的总能量)以及 energy_penalty(其中属于动态能量模型惩罚的那部分能耗)。如果您只想解析出合约自身最核心的基础能耗,只需用 energy_used 减去 energy_penalty 即可。

2. /wallet/estimateenergy 接口(特定高精场景)

此接口在 java-tron v4.7.0.1 版本中引入,旨在针对少数包含复杂控制流的边缘合约提供更为精准的能耗精算。它的请求参数和调用格式与 triggerconstantcontract 完全一致,但在返回结果中,对应能耗的字段名是 energy_required,而不是 energy_used

BASE_URL=https://api.trongrid.io   # 示例端点——可替换为实际可用的 TRON 节点
curl -X POST ${BASE_URL}/wallet/estimateenergy \
  -d '{
    "owner_address": "TTGhREx2pDSxFX555NWz1YwGpiBVPvQA7e",
    "contract_address": "TVSvjZdyDSNocHm7dP3jvCmMNsCnMTPa5W",
    "function_selector": "transfer(address,uint256)",
    "parameter": "0000000000000000000000002ce5...0000038d7ea4c68000",
    "visible": true
  }'

请注意,该接口在全节点(FullNode)上是可选开启的。节点运维人员必须在其配置文件中将 vm.estimateEnergyvm.supportConstant 两个属性同时设为 true 才会对外生效。公共 RPC 是否支持取决于运营方当前配置;客户端应处理 this node does not support estimate energy 错误,并回退使用 triggerconstantcontract

📘

接口选择建议

  • /wallet/triggerconstantcontract:常用的本地模拟接口,可返回 energy_used;节点仍需启用 vm.supportConstant
  • /wallet/estimateenergy:对极少数逻辑极其复杂的控制流合约能提供更逼真的模拟,但可能在某些自建节点上未被启用。

建议在默认情况下均采用通用度更高的 /wallet/triggerconstantcontract 接口,仅在测试中发现个别合约的实际能耗与该接口估算值存在明显偏差时,再针对性地切换为 /wallet/estimateenergy

动态能量模型:能耗偏差的主因

在实际开发中,有些开发者发现在 Shasta 测试网上测试一笔交易时,系统提示消耗了 31,000 能量,但将完全相同的合约部署到主网执行调用时,却被收取了 90,000 能量。这种现象正是由于 TRON 的**动态能量模型(Dynamic Energy Model)**在起作用。

动态能量模型对链上资源分配引入了惩罚机制:如果某合约在短期内被超高频地调用,系统会为该合约设置一个惩罚系数。该系数在每 6 小时一次的维护周期切换时由系统自动重新计算。合约交互时实际扣除的能耗公式如下:

actual_energy = base_energy × (1 + energy_factor)

其中:

  • base_energy:表示智能合约指令执行所产生的最基础、确定性的能量消耗。
  • energy_factor:针对单个合约在此维护周期内计算出的惩罚系数,其值介于 0max_factor 之间。

对于大多数调用频次较低的非热门合约,其 energy_factor 通常为 0,这意味着其实际消耗的能量即等于基础能量。然而,对于 USDT、热门 DEX 合约等极度繁忙的资源消耗大户,其 energy_factor 可能会被系统推高(历史上一些超高热度合约的惩罚系数常年在 0.5 到 1.5 之间漂移)。

能耗估算接口在执行模拟时,会带入估算节点当时看到的合约状态与 energy_factor。维护周期切换后该因子可能变化,因此跨周期缓存需要重新验证;即使没有跨周期,合约状态或调用参数变化也可能使缓存值不再适用。

关于该模型的更多机制,请参考 资源模型与动态能量模型 专题章节。

各种开发工具中的参数配置指南

请特别注意两个参数配置的生效时机与存放位置。fee_limit 属于单笔交易层面的通用参数;而 origin_energy_limitconsume_user_resource_percent 则专属于合约部署阶段。部署完成后若要对其进行调整,合约拥有者必须调用对应的系统合约交易进行更新(调用 /wallet/updateenergylimit 接口修改每次限额,调用 /wallet/updatesetting 接口修改用户分摊比率)。所有值在传递给 API 时均应采用各自的原始单位:fee_limit 必须以 sun 为单位;origin_energy_limit 以无单位的能量数值表示;consume_user_resource_percent 以代表百分比的整数(0–100)表示。

HTTP API

在部署时,以上三个参数均需包含在 /wallet/deploycontract 接口的请求体中:

BASE_URL=https://api.trongrid.io   # 示例端点——可替换为实际可用的 TRON 节点
curl -X POST ${BASE_URL}/wallet/deploycontract \
  -d '{
    "abi": "...",
    "bytecode": "...",
    "fee_limit": 1000000000,
    "consume_user_resource_percent": 100,
    "origin_energy_limit": 10000000,
    "owner_address": "TTGhREx2pDSxFX555NWz1YwGpiBVPvQA7e",
    "visible": true
  }'

若是对于部署后的常规合约调用交易(/wallet/triggersmartcontract),则请求参数中仅需传递 fee_limit 即可,另外两个限额参数将直接从合约已部署的链上属性中读取。

TronWeb SDK

在部署合约时指定参数:

const tx = await tronWeb.transactionBuilder.createSmartContract(
  {
    abi: contractAbi,
    bytecode: contractBytecode,
    feeLimit: 1000e6,                      // 1,000 TRX,单位为 sun
    userFeePercentage: 100,                // 对应 consume_user_resource_percent
    originEnergyLimit: 10_000_000          // 对应 origin_energy_limit
  },
  ownerAddress
);

而在正常的合约交互调用时,仅需指定 feeLimit

const result = await contract
  .transfer(toAddress, amount)
  .send({ feeLimit: 150e6 });              // 150 TRX,单位为 sun

Trident SDK (Java)

// 部署合约阶段
Response.TransactionExtention deployTx = wrapper.deployContract(
    "TokenContract",
    abiJson,
    bytecode,
    parameters,
    150_000_000L,      // feeLimit,单位为 sun
    100,               // consumeUserResourcePercent
    10_000_000L,       // originEnergyLimit
    0L,                // callValue
    "",                // tokenName
    0L                 // tokenValue
);

// 合约调用阶段(仅需提供 feeLimit)
Response.TransactionExtention callTx = wrapper.triggerContract(
    ownerAddress, contractAddress,
    "transfer(address,uint256)",
    parameter,
    150_000_000L,      // feeLimit,单位为 sun
    0L, "", 0L
);

TronBox 工具

tronbox-config.js 配置文件中针对特定的网络进行全局定义:

networks: {
  shasta: {
    privateKey: process.env.PRIVATE_KEY,
    feeLimit: 1000 * 1e6,                   // 1,000 TRX,单位为 sun
    userFeePercentage: 100,                 // 对应 consume_user_resource_percent
    originEnergyLimit: 10_000_000,          // 对应 origin_energy_limit
    fullHost: 'https://api.shasta.trongrid.io',
    network_id: '2'
  }
}

其中 userFeePercentageoriginEnergyLimit 参数仅在通过 tronbox migrate 部署合约时生效。调用已存在的合约方法时仅参考配置中的 feeLimit

TronIDE

Deploy & Run 的参数面板中,IDE 暴露了三个可配置字段:feeLimit(单位为 sun)、userFeePercentage(取值 0–100)和 originEnergyLimit(能量值)。由于 IDE 给出的默认初始参数通常较为保守,若您需要部署较为复杂的大型智能合约,请务必手动调高这些参数以防部署失败。

📘

合约部署后的参数调优

如果需要修改已上线合约的 consume_user_resource_percentorigin_energy_limit 设定,合约拥有者可以使用系统接口 /wallet/updatesetting(修改百分比)或 /wallet/updateenergylimit(修改调用上限限制)发起单独的控制参数交易。

开发者常见误区与防坑指南

  • 混淆 TRX 与 sun 单位:将 fee_limit 设为 100(原意是限制 100 TRX),结果在网络底层实际只授权了极小的 100 sun(等同于 0.0001 TRX),导致交易由于能量计算直接归零而立即以 OUT_OF_ENERGY 报错中断。
  • 误以为 fee_limit 总会被全额烧掉fee_limit 是调用方成本上限,不是每笔交易的固定费用。正常执行通常按实际 Energy 结算;但 OUT_OF_TIME、非法指令等异常可能按交易允许的最大 Energy 扣费,因此仍需根据风险设置合理上限。
  • 将“直接扣减 TRX 余额”视为底层第一逻辑:在上链合约交易中,TVM 会先将 fee_limit 折算为调用方可承担的最大 Energy,再执行合约操作码并按结果结算资源。这解释了为什么质押 Energy 与燃烧 TRX 都受同一调用方预算约束,以及为什么部分执行失败的交易仍会产生资源费用。
  • 硬编码 15,000 TRX 上限值或 100 sun 换算率:这两项参数均为 TRON 主网根据 SR 提案可动态投票修改的链上参数(getMaxFeeLimitgetEnergyFee)。在生产环境的代码库中,务必通过调用 /wallet/getchainparameters 接口动态获取,切勿在代码中硬编码固定数值。
  • 长期重用能耗估算结果:动态能量模型会在维护周期切换时重新计算 energy_factor,合约状态和调用参数也可能更早发生变化,历史估算因此可能不再适用。高频业务应在发送前重新估算,或采用带有 DEM 上界缓冲的保守预算策略。
  • 忽略合约部署阶段的超高能耗:部署一个全新合约往往是其生命周期中能耗最高的步骤。一个典型的标准 TRC-20 合约部署会产生约 200,000 到 500,000 的能量损耗(在主网无质押燃烧时约折合 20 到 50 TRX);逻辑复杂的特种合约或带复杂库的合约甚至可能产生超过 1,000,000 的能量消耗。
  • 混淆 OUT_OF_TIMEOUT_OF_ENERGY 的能耗结算区别:如果合约方法执行时间由于死循环或复杂的大型数组计算超过了单区块的时间窗口限制(80 毫秒),交易将触发 OUT_OF_TIME 超时错误。该错误在底层会被作为 assert 式的严重故障进行惩罚,导致扣除这笔交易对应的整个 fee_limit 换算的全部能量费用,而不是像 OUT_OF_ENERGY 那样仅按出错点为止的实际消耗量扣减。关于此机制的更多信息请查阅 VM 异常处理 章节。

相关资源