3分钟Solidity: 11.18 燃气规则

19 阅读2分钟

欢迎订阅专栏3分钟Solidity--智能合约--Web3区块链技术必学

如需获取本内容的最新版本,请参见 Cyfrin.io 上的63 / 64gas规则(代码示例)

根据63/64gas规则,外部调用最多只能接收调用合约中剩余gas的63/64。

漏洞

那些退还已使用 gas 的合约必须考虑到这 1/64 未被消耗的 gas。

以下是一个简化版的退还 gas 合约示例。

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;

contract A {
    function f(address b) external {
        uint256 gasStart = gasleft();
        B(payable(b)).g(msg.sender, gasStart);
    }
}

contract B {
    event Log(uint256 gas);

    mapping(address => bool) public authorized;

    constructor() {
        authorized[msg.sender] = true;
    }

    receive() external payable {}

    function setAuth(address addr, bool auth) external {
        require(authorized[msg.sender], "not authorized");
        authorized[addr] = auth;
    }

    // 发送 9000000000000000000 gas 以耗尽 ETH
    function g(address receiver, uint256 gasStart) external {
        require(authorized[msg.sender], "not authorized");

        uint256 gasNow = gasleft();
        uint256 gasUsed = gasStart - gasNow;
        // Fix
        //uint256 gasUsed = gasStart - (gasNow / 63) - gasNow;
        (bool ok,) = receiver.call{value: gasUsed}("");
        require(ok, "send failed");

        emit Log(gasUsed);
    }
}

/*
# 63 / 64 gas 规则
外部调用最多接收当前合约中剩余 gas 的 63/64
当前合约保留 1/64 的 gas

g0 = 在A中某处调用gasleft()
g1 = 在B中某处调用gasleft()
g* = 调用B之前实际的剩余gas量

  g*    63/64 g*
A |---->| B
|         |
g0        g1

# Gas 消耗
dg = 从g0到g1之间使用的gas
   = g0 - g* + 63/64 g* - g1
   = g0 - g1 - 1/64 g* >= 0

# 问题
-   g0 到 g1 的退款多支付了 1/64 g*
-   通过发送大量 gas,g* 可能会变得很大

# 修复
g1 <= 63/64 g* <= g0
g1/63 <= 1/64 g* <= g0/63
g0 - g1 - g1/63 >= g0 - g1 - 1/64 g* >= g0 - g1 - 1/63 g0 = 62/63 g0 - g1
                                     >= 0

退款 g0 - g1 - g1/63
*/

Try on Remix