transfer和send会固定最多能使用2300个gas,如果调用者也是合约代码,我们称为合约代码A(企业A),存在漏洞的合约称为合约代码B(企业B)
如果B合约向A合约转账(如果此时A合约方法里有receive 或 fallback,接收到接受过来的转账后,就会触发这两个方法),那么此时大概率都会失败,因为receive 或 fallback接受基本都需要超过2300GAS才能执行,因此会产生dos类的问题,企业A的合约将永远无法收到这笔转账。
contract SimpleBank {
mapping(address => uint) private balances;
function deposit() public payable {
balances[msg.sender] += msg.value;
}
function getBalance() public view returns (uint) {
return balances[msg.sender];
}
function withdraw(uint amount) public {
require(balances[msg.sender] >= amount);
balances[msg.sender] -= amount;
// the issue is here
payable(msg.sender).transfer(amount);
}
}
payable(msg.sender).transfer(amount);
-
如果接收方是普通 EOA 钱包:
transfer
成功,因为 EOA 没有 receive
/fallback
逻辑,Gas 消耗极低。-
如果接收方是
Receiver Contract
: transfer
触发 receive
函数,但 Gas 不足(需 3000,上限 2300)。- 结果
转账失败,ETH 未发送, withdraw
函数回滚。
contract ContractTest is Test {
SimpleBank SimpleBankContract;
FixedSimpleBank FixedSimpleBankContract;
function setUp() public {
SimpleBankContract = new SimpleBank();
FixedSimpleBankContract = new FixedSimpleBank();
}
function testTransferFail() public {
SimpleBankContract.deposit{value: 1 ether}();
assertEq(SimpleBankContract.getBalance(), 1 ether);
vm.expectRevert();
SimpleBankContract.withdraw(1 ether);
}
function testCall() public {
FixedSimpleBankContract.deposit{value: 1 ether}();
assertEq(FixedSimpleBankContract.getBalance(), 1 ether);
FixedSimpleBankContract.withdraw(1 ether);
}
receive() external payable {
//just a example for out of gas
SimpleBankContract.deposit{value: 1 ether}();
}
}
04
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
// 定义合约 A 的接口
interface ISimpleBank {
function deposit() external payable;
function withdraw(uint amount) external;
function getBalance() external view returns (uint);
}
contract ContractB {
// 合约 A 的实例
ISimpleBank public immutable contractA;
// 初始化时绑定合约 A 的地址
constructor(address _contractA) {
contractA = ISimpleBank(_contractA);
}
// 存款到合约 A
function depositETH() external payable {
// 将 ETH 存入合约 A(msg.value 是发送的 ETH 金额)
contractA.deposit{value: msg.value}();
}
// 从合约 A 中提取 ETH
function withdrawETH(uint amount) external {
contractA.withdraw(amount);
}
// 接收 ETH 的 fallback 函数(当合约 A 的 transfer 发送 ETH 到此合约时触发)
receive() external payable {
contractA.deposit{value: msg.value}();
}
// 获取合约 B 的 ETH 余额
function getBalance() external view returns (uint) {
return address(this).balance;
}
// 获取合约 B 在合约 A 中的存款余额
function getDepositInContractA() external view returns (uint) {
return contractA.getBalance();
}
}
Note: The called function should be payable if you send value and the value you send should be less than your current balance. If the transaction failed for not having enough gas, try increasing the gas limit gently.
符合预期,因为是合约调用合约,然后触发fallback函数
(bool success, ) = payable(msg.sender).call{value: amount}("");
contract FixedSimpleBank {
mapping(address => uint) private balances;
function deposit() public payable {
balances[msg.sender] += msg.value;
}
function getBalance() public view returns (uint) {
return balances[msg.sender];
}
function withdraw(uint amount) public {
require(balances[msg.sender] >= amount);
balances[msg.sender] -= amount;
(bool success, ) = payable(msg.sender).call{value: amount}("");
require(success, " Transfer of ETH Failed");
}
}
原文始发于微信公众号(Ice ThirdSpace):DeFiVulnLabs靶场全系列详解(四十二)转账函数固定2300个gas导致合约可用性遭到破坏
免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论