欢迎订阅专栏: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
*/