跨链 Gas 费用后置处理
在后置处理钩子中,如果支付的费用不足以覆盖中继器的预期成本,InterchainGasPaymaster
合约将会回滚。在 dispatch
时计算的 gas 报价必须与中继器的预期成本相符。
Gas 限制
gasLimit
是根据在目标链上调用给定消息的 handle
的成本来设置的。这可能会根据消息内容和处理程序的逻辑而有所不同。
用于计量 handle 调用的默认 gasLimit
是静态的 50_000
gas。这对于简单操作来说已经足够,但对于更复杂的 handle
函数可能不够。
如果你的 handle
函数执行复杂操作或需要更多 gas,你必须在元数据中覆盖默认的 gasLimit
以避免交易回滚。在单元测试中对你的 handle
实现进行基准测试,以确定合理的 gasLimit
。
元数据
此 hook 需要 打包编码 格式的 StandardHookMetadata
。查看 Mailbox dispatch 重载了解如何传递元数据覆盖。
- Solidity
struct StandardHookMetadata {
uint16 variant;
uint256 msgValue;
uint256 gasLimit;
address refundAddress;
}
StandardHookMetadata
结构定义了元数据编码所需的字段:
variant
: 指定元数据格式版本msgValue
: 随消息发送的原生代币数量gasLimit
: 目标链上handle
函数的 gas 限制。确保这与你的模拟结果相匹配refundAddress
: 退还未使用 gas 费用的地址
要编码此元数据,请使用 StandardHookMetadata.formatMetadata
库函数。Solidity 不支持使用 abi.encodePacked
直接编码结构体。
使用示例
- Solidity
// 示例: 使用 StandardHookMetadata 编码元数据
bytes memory metadata = StandardHookMetadata.formatMetadata(
0, // ETH 消息值
200000, // 自定义 gas 限制
address(this), // 退款地址
bytes("") // 可选的自定义元数据
);
确定和覆盖 Gas 限制
- 模拟和基准测试 Gas 使用量:
- 使用 Tenderly 或 Foundry 等工具模拟消息接收者的
handle
函数。确保from
地址设置为你链上的 Mailbox 合约。 - 如果 gas 使用量超过
50,000
,计算适当的gasLimit
并更新你的元数据。 - 使用 Hyperlane Explorer 中的操作按钮 从消息详情模拟交易。
- 更新你的元数据:
- 根据模拟结果计算所需的
gasLimit
。 - 在元数据中传入更新后的
gasLimit
,确保中继器能够传递你的消息。
目标 Gas 配置
对于每个远程域,InterchainGasPaymaster 设置域 gas 配置。
struct DomainGasConfig {
IGasOracle gasOracle;
uint96 gasOverhead;
}
Gas 开销
gas 开销是目标 gas 配置的一部分。这对应于在目标链上处理消息的运营成本。
- 你应该确保
gasOverhead
足以覆盖目标链上的 ISM 范围。 - 由于你可以为不同的消息类型配置不同的 ISM,每个 ISM 的
verify
函数可能有不同的 gas 开销。
Gas 预言机
跨链 Gas 费用要求是使用预言机提供的 gas 价格和原目标链之间的汇率计算的。
IGP 合约可以配置 gas 预言机,负责跟踪远程代币 gas 价格和汇率。开发者应该使用 Mailbox 合约上的 quoteDispatch
函数来计算 gas 费用。quoteDispatch
考虑了系统级开销,确保整个 dispatch
过程的费用计算准确。
- 汇率和 gas 价格由中继器决定。可能会收取价差以应对价格波动和运营成本。
最终,中继器将能够自动更新其 gas 预言机,以确保其 IGP 始终为远程 gas 报出公平价格。
getExchangeRateAndGasPrice
- Solidity
function getExchangeRateAndGasPrice(
uint32 _destinationDomain
)
public
view
override
returns (uint128 tokenExchangeRate, uint128 gasPrice)
{
IGasOracle _gasOracle = destinationGasConfigs[_destinationDomain]
.gasOracle;
if (address(_gasOracle) == address(0)) {
revert(
string.concat(
"Configured IGP doesn't support domain ",
Strings.toString(_destinationDomain)
)
);
}
return _gasOracle.getExchangeRateAndGasPrice(_destinationDomain);
}
参数
destinationDomain
: 消息的目标域
返回值
tokenExchangeRate
: 原链和目标链 gas 代币之间的汇率gasPrice
: 目标链的 gas 价格
quoteGasPayment
quoteGasPayment
函数计算中继器预期成本的费用。
- Solidity
function quoteGasPayment(
uint32 _destinationDomain,
uint256 _gasLimit
) public view virtual override returns (uint256) {
// Get the gas data for the destination domain.
(
uint128 _tokenExchangeRate,
uint128 _gasPrice
) = getExchangeRateAndGasPrice(_destinationDomain);
// The total cost quoted in destination chain's native token.
uint256 _destinationGasCost = _gasLimit * uint256(_gasPrice);
// Convert to the local native token.
return
(_destinationGasCost * _tokenExchangeRate) /
TOKEN_EXCHANGE_RATE_SCALE;
}
参数
destinationDomain
: 消息的目标域gasLimit
: 用于计量handle
调用的 gas 限制
返回值
fee
:postDispatch
成功所需的支付金额