First of all, let's create a basic re-entrancy vulnerable contract:
contract Safe {
mapping(address => uint) public userBalances;
function deposit() public payable {
userBalances[msg.sender] += msg.value;
}
function withdraw(uint _amount) public {
require(userBalances[msg.sender] >= _amount, "low balance");
payable(msg.sender).call({value: _amount});
userBalances[msg.sender] -= _amount;
}
}
As you can see we didn't set the max gas that call
can use. What does it mean? You can restrict the gas amount that external function calls can use. Take a look at here. What if you did not set this? It can use all over of your gas (nearly 1m gas). Is it bad? It can be. Fallback functions are for what should contract do when it receives ether
(not just that but it is enough for now to now that).
We are a popular DeFi project. So, there will be thousands of ether in our contract.
This attacker creates a contract for attack to our Safe contract. And this contract has this function:
function attack() public onlyOwner {
Safe.deposit({value: 1 ether});
Safe.withdraw(1 ether);
}
This function is basically to deposit and withdraw 1 ether for the attacker's smart contract. And our attacker's smart contract also has a fallback function:
fallback() external payable {
Safe.withdraw(1 ether);
}
Okay, let's take a deep breath and look step by step what are we doing:
- Create an attacker contract
- Call attack function - Which means deposit and withdraw 1 ether.
- When we try to withdraw, the Safe contract tries to call our fallback function. And that function also tries to call withdraw function.
Can you see that? There is a loop. This loop will continue until all Safe contract funds are over. Because there will be an error. After then we will receive all the funds. Because our funds didn't decrease (because of our loop).
But if you'll change the call function line to this:
payable(msg.sender).call({value: _amount, gas: 30000});
Probably it will be okay (depending the gas calculation). But the best solution is first decrease userBalances, and after then send his money.