FeeLimit参数设置
FeeLimit 是智能合约交易中的一个重要参数,用于设定调用者可接受的能量(Energy)消耗上限。该参数以 sun 为单位(1 TRX = 1,000,000 sun),默认值为 0,目前允许的最大上限为 15,000 TRX。
在执行智能合约过程中,Energy 的消耗与扣费是逐条指令进行的。一旦实际消耗超出设定的 FeeLimit,合约交易将执行失败,且已消耗的 Energy 不予退还。因此,建议用户在部署或调用合约前,为交易设置一个合理的 FeeLimit 值,以确保合约能够顺利执行。
接下来的内容将围绕以下三方面,详细说明如何评估合约交易的能量消耗,并正确设置 FeeLimit 参数:
- 如何预估智能合约交易的能量消耗?
- 如何根据预估值确定合理的 FeeLimit 参数?
- 如何为交易设置 FeeLimit?
如何预估能量消耗?
在发起合约交易前,用户可以通过调用wallet/triggerconstantcontract 接口来预估合约调用所需的能量消耗。
下面通过示例说明如何预估合约调用或部署交易的能量消耗:
如何预估合约调用交易的能量消耗
$ curl -X POST https://nile.trongrid.io/wallet/triggerconstantcontract -d '{
"owner_address": "TTGhREx2pDSxFX555NWz1YwGpiBVPvQA7e",
"contract_address": "TVSvjZdyDSNocHm7dP3jvCmMNsCnMTPa5W",
"function_selector": "transfer(address,uint256)",
"parameter": "0000000000000000000000002ce5de57373427f799cc0a3dd03b841322514a8c00000000000000000000000000000000000000000000000000038d7ea4c68000",
"visible": true
}'
执行结果为:
{
……
"result": {
"result": true
},
"energy_used": 46236,
"energy_penalty": 32983,
……
}
在接口返回值中:
result.result字段为true表示本次预估执行成功;若为false,则表示执行失败,请检查请求参数是否设置正确,以确保合约调用的有效性。energy_used表示本次交易预估的总能量消耗,其中- 基础能量消耗量 =
(energy_used - energy_penalty) - 额外消耗量 =
energy_penalty
- 基础能量消耗量 =
注意:
triggerconstantcontract接口可用于预估链上绝大多数智能合约调用交易的能量消耗,例如USDD、USDT、USDC、TUSD等主流合约。自Java-tron 4.7.0.1起,新增了一个 estimateEnergy 接口, 相较于triggerconstantcontract, 该接口在预估极少数特殊合约的能量消耗时更为准确。需要注意的是,estimateEnergy属于可选接口,部分 FullNode 节点可能未开启此功能。若在调用过程中遇到错误提示this node does not support estimate energy,建议开发者退而使用triggerconstantcontract接口进行能量预估。
如何预估合约部署交易的能量消耗
对于构造函数无参数的合约,在预估其部署所需的能量时,只需将合约的字节码填入 wallet/triggerconstantcontract 接口的 data 字段中即可。而对于构造函数带有参数的合约,在部署时还需要传入相应的参数。这些参数同样通过 data 字段传递:参数需进行 ABI 编码,并附加在合约字节码之后。请参考以下示例。
- 预估部署构造函数无参数的合约所需的能量
合约示例:
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;
contract SimpleContract {
uint storedData;
function set(uint x) public {
storedData = x;
}
function get() public view returns (uint) {
return storedData;
}
}
合约编译后,得到如下字节码:
608060405234801561000f575f80fd5b50d3801561001b575f80fd5b50d28015610027575f80fd5b5061015b806100355f395ff3fe608060405234801561000f575f80fd5b50d3801561001b575f80fd5b50d28015610027575f80fd5b506004361061004c575f3560e01c806360fe47b1146100505780636d4ce63c1461006c575b5f80fd5b61006a600480360381019061006591906100d2565b61008a565b005b610074610093565b604051610081919061010c565b60405180910390f35b805f8190555050565b5f8054905090565b5f80fd5b5f819050919050565b6100b18161009f565b81146100bb575f80fd5b50565b5f813590506100cc816100a8565b92915050565b5f602082840312156100e7576100e661009b565b5b5f6100f4848285016100be565b91505092915050565b6101068161009f565b82525050565b5f60208201905061011f5f8301846100fd565b9291505056fea26474726f6e58221220ca11b5749b47f126a08ed4dd6de453cf3e3e1d68c1105af0acdd8a38c18b37ac64736f6c63430008140033
将合约字节码填入 wallet/triggerconstantcontract 接口的 data 字段中。预估能量消耗的命令如下:
curl --request POST \
--url https://api.shasta.trongrid.io/wallet/triggerconstantcontract \
--header 'accept: application/json' \
--header 'content-type: application/json' \
--data '
{
"owner_address": "TZ4UXDV5ZhNW7fb2AMSbgfAEZ7hWsnYS2g",
"data":"608060405234801561000f575f80fd5b50d3801561001b575f80fd5b50d28015610027575f80fd5b5061015b806100355f395ff3fe608060405234801561000f575f80fd5b50d3801561001b575f80fd5b50d28015610027575f80fd5b506004361061004c575f3560e01c806360fe47b1146100505780636d4ce63c1461006c575b5f80fd5b61006a600480360381019061006591906100d2565b61008a565b005b610074610093565b604051610081919061010c565b60405180910390f35b805f8190555050565b5f8054905090565b5f80fd5b5f819050919050565b6100b18161009f565b81146100bb575f80fd5b50565b5f813590506100cc816100a8565b92915050565b5f602082840312156100e7576100e661009b565b5b5f6100f4848285016100be565b91505092915050565b6101068161009f565b82525050565b5f60208201905061011f5f8301846100fd565b9291505056fea26474726f6e58221220ca11b5749b47f126a08ed4dd6de453cf3e3e1d68c1105af0acdd8a38c18b37ac64736f6c63430008140033",
"visible": true
}'
返回:
{
"result": {
"result": true
},
"energy_used": 69558,
......
}
- 预估部署构造函数带参数的合约所需的能量
合约示例:
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;
contract SimpleContract {
uint storedData;
uint public num;
constructor(uint _num) {
num = _num;
}
function set(uint x) public {
storedData = x;
}
function get() public view returns (uint) {
return storedData;
}
}
合约编译后,得到合约的字节码:
608060405234801561000f575f80fd5b50d3801561001b575f80fd5b50d28015610027575f80fd5b5060405161024f38038061024f8339818101604052810190610049919061008d565b80600181905550506100b8565b5f80fd5b5f819050919050565b61006c8161005a565b8114610076575f80fd5b50565b5f8151905061008781610063565b92915050565b5f602082840312156100a2576100a1610056565b5b5f6100af84828501610079565b91505092915050565b61018a806100c55f395ff3fe608060405234801561000f575f80fd5b50d3801561001b575f80fd5b50d28015610027575f80fd5b5060043610610057575f3560e01c80634e70b1dc1461005b57806360fe47b1146100795780636d4ce63c14610095575b5f80fd5b6100636100b3565b60405161007091906100e2565b60405180910390f35b610093600480360381019061008e9190610129565b6100b9565b005b61009d6100c2565b6040516100aa91906100e2565b60405180910390f35b60015481565b805f8190555050565b5f8054905090565b5f819050919050565b6100dc816100ca565b82525050565b5f6020820190506100f55f8301846100d3565b92915050565b5f80fd5b610108816100ca565b8114610112575f80fd5b50565b5f81359050610123816100ff565b92915050565b5f6020828403121561013e5761013d6100fb565b5b5f61014b84828501610115565b9150509291505056fea26474726f6e582212205d0adb1a1985b9d6f432a9defc416a17dfe1c931bfb8554978c0f37cfe4cc99f64736f6c63430008140033
该合约在部署时需要传入一个 uint 类型的参数。假设部署时传入的值为 2,则其 ABI 编码结果为
0000000000000000000000000000000000000000000000000000000000000002,
该编码结果应附加在合约字节码之后,组成完整的 data 字段值。预估能量消耗的命令如下:
curl --request POST \
--url https://api.shasta.trongrid.io/wallet/triggerconstantcontract \
--header 'accept: application/json' \
--header 'content-type: application/json' \
--data '
{
"owner_address": "TZ4UXDV5ZhNW7fb2AMSbgfAEZ7hWsnYS2g",
"data":"608060405234801561000f575f80fd5b50d3801561001b575f80fd5b50d28015610027575f80fd5b5060405161024f38038061024f8339818101604052810190610049919061008d565b80600181905550506100b8565b5f80fd5b5f819050919050565b61006c8161005a565b8114610076575f80fd5b50565b5f8151905061008781610063565b92915050565b5f602082840312156100a2576100a1610056565b5b5f6100af84828501610079565b91505092915050565b61018a806100c55f395ff3fe608060405234801561000f575f80fd5b50d3801561001b575f80fd5b50d28015610027575f80fd5b5060043610610057575f3560e01c80634e70b1dc1461005b57806360fe47b1146100795780636d4ce63c14610095575b5f80fd5b6100636100b3565b60405161007091906100e2565b60405180910390f35b610093600480360381019061008e9190610129565b6100b9565b005b61009d6100c2565b6040516100aa91906100e2565b60405180910390f35b60015481565b805f8190555050565b5f8054905090565b5f819050919050565b6100dc816100ca565b82525050565b5f6020820190506100f55f8301846100d3565b92915050565b5f80fd5b610108816100ca565b8114610112575f80fd5b50565b5f81359050610123816100ff565b92915050565b5f6020828403121561013e5761013d6100fb565b5b5f61014b84828501610115565b9150509291505056fea26474726f6e582212205d0adb1a1985b9d6f432a9defc416a17dfe1c931bfb8554978c0f37cfe4cc99f64736f6c634300081400330000000000000000000000000000000000000000000000000000000000000002",
"visible": true
}'
返回:
{
"result": {
"result": true
},
"energy_used": 99278,
......
}
在上述返回值中:
- 如果
result.result字段为 true,表示预估操作执行成功,energy_used字段即为该交易预估的能量消耗值。如果result.result字段为 false,则表示预估失败,请检查请求参数是否设置正确。
例如:
$ curl -X POST https://nile.trongrid.io/wallet/estimateenergy -d '{
"owner_address": "TTGhREx2pDSxFX555NWz1YwGpiBVPvQA7e",
"contract_address": "TVSvjZdyDSNocHm7dP3jvCmMNsCnMTPa5W",
"function_selector": "transfer(address,uint256)",
"parameter": "0000000000000000000000002ce5de57373427f799cc0a3dd03b841322514a8c00000000000000000000000000000000000000000000000000038d7ea4c68000",
"visible": true
}'
执行结果:
{
"result": {
"result": true
},
"energy_required": 34830
}
在接口返回值中:
result.result字段为true表示本次预估执行成功;energy_required字段表示该交易的能量消耗量的预估值,其中包含了执行该合约交易的能量基础消耗量和额外消耗量。
如何确定FeeLimit参数值?
由于动态能量模型机制,热门合约的能量消耗是动态变化的。因此,在不同时间段调用同一个合约,其能量消耗可能会有所不同。为确保交易能够顺利执行,调用热门合约时应根据实际情况设置合理的 feelimit 参数。
下面介绍三种常见的 feelimit 设置方法:
-
每次合约调用前预估能量消耗
在每次发送交易前,调用triggerconstantcontract接口预估该合约调用的总能量消耗量,并据此动态设置 FeeLimit 参数:合约交易的FeeLimit = 预估的总能量消耗量 * EnergyPrice该方法的优点是 FeeLimit 设置最为精确,可根据实时能量消耗进行动态调整。但缺点是操作繁琐,每次发送交易前都需要额外调用接口进行预估。
-
每个维护周期均获取一次合约
energy_factor
先通过triggerconstantcontract API预估或者结合历史数据,来确定合约函数的基础能量消耗量。然后,在每一个维护周期中,获取一次该合约当前的energy_factor参数:合约交易的FeeLimit = 基础能量消耗 * (1 + energy_factor)* EnergyPrice该方法在保持较高精度的同时,减少了调用频率,只需每个维护周期(约6小时)获取一次合约的 energy_factor 参数;但仍然存在一定的操作成本。
-
使用链上全局的
max_factor设置feelimit
预估基础能量消耗后,结合链上的max_factor参数来计算 Feelimit。max_factor是能量惩罚系数的最大值,无论合约多热门,其能量消耗涨幅都不会超过该上限:合约交易的FeeLimit = 基础能量消耗 *(1 + max_factor)* EnergyPrice该方式最大的优点是实现简单,无需频繁获取链上参数或进行动态计算,适用于对操作便利性要求较高,或需要确保交易 100% 成功的场景。然而,由于
max_factor表示能量惩罚系数的最大值,通常高于实际的energy_factor,因此基于此计算出的 FeeLimit 往往偏高。
如何为交易设置 FeeLimit?
在交易结构体中,fee_limit 字段位于 raw_data 字段内部,结构示例如下所示:
{
"raw_data": {
...
"fee_limit": 10000000000
},
"signature": [
"47b1f77b3e30cfbbfa41d795dd34475865240617dd1c5a7bad526f5fd89e52cd057c80b665cc2431efab53520e2b1b92a0425033baee915df858ca1c588b0a1800"
]
}
在预估并确定了交易的 FeeLimit 值后,可以通过多种方式为交易设置 FeeLimit 。根据创建交易的方式不同,设置 FeeLimit 的方法也有所区别:
通过节点 HTTP API 创建交易
如果使用 /wallet/triggersmartcontract 或 /wallet/deploycontract 接口创建交易,fee_limit 是接口的必填参数之一,只需将其设置为合适的数值即可(单位为 sun)。
使用 TronWeb 创建交易
使用 send() 方法
在调用 send() 方法时,可通过传入包含 feeLimit 的可选项参数来设置交易的能量费用上限。例如,将 feeLimit 设置为 150 TRX(即 150e6 sun):
let abi = [...];
let contract = await tronWeb.contract(abi, 'contractAddress');
let result = await contract.function_name(param1, param2, ...).send({
feeLimit: 150e6, // 设置为 150 TRX
});
使用 triggerSmartContract() 方法
在调用 triggerSmartContract() 方法时,第三个参数为可选项配置,可以在其中设置 feeLimit。例如,如果希望将 feeLimit 设置为 150 TRX(即 150e6 sun),则可按如下方式设置:
const functionSelector = 'transfer(address,uint256)';
const parameter = [
{ type: 'address', value: 'ACCOUNT_ADDRESS' },
{ type: 'uint256', value: 100 }
];
const tx = await tronWeb.transactionBuilder.triggerSmartContract(
'USDT_ADDRESS',
functionSelector,
{ feeLimit: 150e6 },
parameter
);
使用 Trident 创建交易
通过 triggerContract 或 deployContract 构建交易
在 Trident 中使用 triggerContract 或 deployContract 构建智能合约交易时,由于接口本身包含 feeLimit 参数,因此只需在调用时直接传入即可。
// Transfer tokens
Function trc20Transfer = new Function(
"transfer",
Arrays.asList(
new Address(toAddress),
new Uint256(BigInteger.valueOf(10).multiply(BigInteger.valueOf(10).pow(6))) // decimals
),
Collections.singletonList(new TypeReference<Bool>() {})
);
String encodedHex = FunctionEncoder.encode(trc20Transfer);
TransactionExtention transactionExtention = client.triggerContract(
fromAddr, // 发起地址
contractAddress, // 合约地址
encodedHex, // 编码后的函数调用数据
0, // callValue
0, // tokenValue
null, // tokenId
150_000_000L // feeLimit,单位为sun
);
// 签名并广播
Transaction signedTxn = client.signTransaction(transactionExtention);
String txid = client.broadcastTransaction(signedTxn);
使用 TransactionBuilder 为交易设置 feeLimit
如果交易已通过 Trident 构建完成,也可以使用 TransactionBuilder 单独设置 feeLimit 或添加其他可选参数,如备注等。
TransactionBuilder builder = new TransactionBuilder(transaction); // transaction 为 transactionExtention.getTransaction()
builder.setFeeLimit(100_000_000L); // 设置 feeLimit,仅智能合约交易需要
Transaction transaction = builder.build();
使用 TronBox 创建交易
在使用 TronBox 部署或调用合约时,feeLimit 一般统一配置在 TronBox 的配置文件中,所有合约部署和调用交易将共用该 feeLimit 设置。但是也可以在交易中单独设置,具体配置方式请参考 TronBox 配置文档。
使用 TronIDE 创建交易
在 TronIDE 中部署或调用智能合约时,可通过界面中的 FEE LIMIT 输入框设置交易的 feeLimit 值。只需填写一个合适的数值,即可应用于本次交易。
Updated 4 days ago