本文详细介绍了Solidity中的gasleft()
函数的作用及其应用场景,包括防止交易耗光Gas、代码执行成本基准测试、转发所有Gas到实现合约以及防止中继器拒绝服务攻击等。
本文的目的是描述 Solidity 的 gasleft()
函数的行为及其用途。
它是一个内置函数,用于检查合约调用过程中剩余的 Gas。它是始终存在于全局命名空间中的特殊变量和函数,因此无需导入。gasleft()
在 Solidity 版本 0.4.21 之前被称为 msg.gas
(msg.gas)。
本文由 Jesse Raymond 合著 (LinkedIn, Twitter),作为 RareSkills 技术写作计划的一部分。
智能合约所使用的 Gas 量取决于正在运行的代码的复杂性以及在合约调用期间处理的数据量。
如果提供的 Gas 不足,交易将因 “Gas 不足” 错误而回滚。正确使用 gasleft() 函数可以防止合约交易耗尽 Gas 的情况。让我们在下一部分看一个例子。
通过循环将以太币发送到多个地址的智能合约可能会非常昂贵,特别是处理大型地址数组时。
如果执行交易所使用的 Gas 量不足,函数将如前所述,以 “Gas 不足” 错误失败。
然而,gasleft()
函数可以用来确保剩余的 Gas 足以进行下一个转账,否则提前退出。
以下代码演示了这一点:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
contract GasConsumer{
uint constant MINIMUM_AMOUNT = 10_000;
// 此代码仅用于说明目的,非生产用途
function distributeEther(address[] calldata receivers) external {
for (uint i = 0; i < receivers.length; i++) {
payable(receivers[i]).transfer(1 ether);
if (gasleft() < MINIMUM_AMOUNT) {
return;
}
}
}
receive() external payable {}
}
上面合约中的“distributeEther
”函数接受一个地址数组,使用 for 循环遍历该数组,向每个地址发送 1 ether,使用 “transfer” 函数。
“if 语句”检查每次以太转账后的剩余 Gas 是否足以进行下一次转账,方法是检查转账后“剩余 Gas”是否小于 10,000(其中 9,000 用于转移 Eth,额外 1,000 用于其他小的操作码)。如果少于 这些,交易将结束而不会回滚之前的转账。
(请注意,除非地址是可信的,否则不应该随意发送以太币。恶意接收者可能会提交一个会在接收以太币时回滚的智能合约地址)。
另一个例子是使用 gasleft()
函数测量一段代码所使用的总 Gas 量。
以下是在 remix 中的一个示例:
基准测试 Solidity 代码与 gasleft
在这种情况下,gasleft()
函数用于找出当通过 “updateArray
” 函数向 “numArr
” 数组追加一个数字时,使用了多少 Gas。这并不是该函数使用的 Gas 的总量,而是在代码 numArr.push(_num)
第 30 行追加一个数字之前和之后所使用的 Gas 量。
我们将“gasUsed
”设为公共变量,以便在函数执行后能够轻松看到其内容。通过部署“GasCalc
”合约并调用“updateArray
”函数进行测试。
该函数返回一个元组,第一项的结果是 80,348,即“initialGas
”。
元组中的第二项是“finalGas
”,值为 35,923,这包括gasleft()
函数本身的 Gas 成本。
通过将最终的 Gas 从初始 Gas 中相减,我们确定第 30 行的执行费用是 44,425。
Solidity 是一种高级语言,它被编译为在以太坊虚拟机(EVM)上执行的字节码。
gasleft()
函数的操作码是 GAS(字节码 0x5A),根据 以太坊文档 其成本为 “2 Gas” 。
在 yul 中使用 gasleft
gasleft()
函数也可以通过 yul(内联汇编)在 Solidity 智能合约中访问,作为 gas()
。
OpenZeppelin 代理合约是这方面的一个优秀示例。它用于代理用来调用实现合约的 “delegatecall” 函数。gas()
是指定使用最大可用 Gas 的便利方法。
在 delegate call 中转发所有 Gas 与 gasleft Solidity
“中继者”是一个链外实体,支付其他用户交易的 Gas,交易被发送到“转发器”合约以执行该交易。
当用户向中继者发送请求时,用户指定要包含在交易中的 Gas 数量,并数字签名其请求。
然而,中继者可能不遵守用户请求的 Gas 限制,并发送更低的金额。这种攻击在 SWC 注册表 中被记录为 SWC-126。
这导致了一种 Gas 干扰攻击。如果中继者对转发合约的调用成功,但用户想要的子调用失败,那么中继者可以“指责”用户发送了一个因为真实原因是子调用因 Gas 不足而回滚的交易。
子调用因为回滚或 Gas 不足而失败,但通常不会给出失败原因,只是布尔变量成功返回 false。基于此,我们无法知道子调用是因为 Gas 不足还是原发送者传递了错误指令而失败。
我们可以使用“gasleft”来判断是哪种情况。
当执行到“call” 时,只有 63/64 的 Gas 被转发。这个 63/64 限制是在 EIP 150 中引入的。
当子调用完成后,剩余的 Gas 应该至少是用户指定原始限制的 1/64。
如果子调用后剩余的 Gas 少于 1/64,那么我们就知道中继者没有发送他们应该发送的所有 Gas。
这里的转发合约检查剩下至少 1/63 的原始限制作为安全边际。
无效代码被用于让中继者的交易失败,从而明确交易是由于中继者失败,而不是子调用失败。你可以在 这里 了解更多关于 Gas 干扰攻击的信息。
这是本文第一个示例的一个实际应用,即在循环中分发以太币。该代码相比之前有更多的业务逻辑,但如果我们突出“gasleft()
” 检查,该检查导致提早退出循环,我们会发现其基本设计是相同的。
Chainlink 的 “VRFCoordinatorV2” 智能合约是一个“可验证随机函数”协调器,用于在区块链上生成密码学 安全的随机数。
该智能合约是智能合约请求和接收随机性的预言机。(见这里:VRFCoordinatorV2)。
在合约的“calculatePaymentAmount
” 函数中使用 gasleft()
函数,以便在节点需支付更多 Gas 以满足随机性时向用户收取更高的费用。
在这种情况下,gasleft()
越低,费用就会越高,因为(startGas
- gasleft()
)随着 Gas 使用的增加而增加。
链接:Chainlink VRFCoordinatorV2 合约
在本文中,我们讨论了 gasleft()
的各种用例。这些用例包括防止 Gas 不足错误、基准测试 Solidity 代码执行成本、将所有 Gas 转发到实现合约以及防止中继者 DOS。
请查看我们的高级 区块链训练营 产品,以了解有关我们提供的专家级开发者培训的更多信息。
最初发布于 2023 年 2 月 4 日
- 原文链接: rareskills.io/post/solid...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!