πŸ“”Storyline

Setup level 2

Clone the challenge repository (branch v3.0.0):

git clone https://github.com/tinchoabbate/damn-vulnerable-defi.git --branch v3.0.0

Open the damn-vulnerable-defi folder as a VSCode project and locate the following files:

.
β”œβ”€β”€ contracts
β”‚Β Β  β”œβ”€β”€ naive-receiver
β”‚Β Β  β”‚Β Β  β”œβ”€β”€ FlashLoanReceiver.sol        // Sample user's contract
β”‚Β Β  β”‚Β Β  └── NaiveReceiverLenderPool.sol  // The contract
β”œβ”€β”€ test
β”‚Β Β  β”œβ”€β”€ naive-receiver
β”‚Β Β  β”‚Β Β  └── naive-receiver.challenge.js

Install dependencies and run the challenge to verify it works:

yarn install
yarn naive-receiver

A correct output at this part would be:

  [Challenge] Naive receiver
    βœ” Execution
    1) "after all" hook for "Execution"


  1 passing (2s)
  1 failing

  1) [Challenge] Naive receiver
       "after all" hook for "Execution":

      AssertionError: expected 10000000000000000000 to equal 0. The numerical values of the given "ethers.BigNumber" and "number" inputs were compared, and they differed.
      + expected - actual

      -10000000000000000000
      +0
Understanding the challenge objective

In our naive-receiver.challenge.js we can inspect the conditions to complete the challenge:

const { ethers } = require('hardhat');
const { expect } = require('chai');

describe('[Challenge] Naive receiver', function () {
    let deployer, user, player;
    let pool, receiver;

    // Pool has 1000 ETH in balance
    const ETHER_IN_POOL = 1000n * 10n ** 18n;

    // Receiver has 10 ETH in balance
    const ETHER_IN_RECEIVER = 10n * 10n ** 18n;

    before(async function () {
        /** SETUP SCENARIO - NO NEED TO CHANGE ANYTHING HERE */
        [deployer, user, player] = await ethers.getSigners();

        const LenderPoolFactory = await ethers.getContractFactory('NaiveReceiverLenderPool', deployer);
        const FlashLoanReceiverFactory = await ethers.getContractFactory('FlashLoanReceiver', deployer);
        
        pool = await LenderPoolFactory.deploy();
        await deployer.sendTransaction({ to: pool.address, value: ETHER_IN_POOL });
        
        // redacted..

        receiver = await FlashLoanReceiverFactory.deploy(pool.address);
        await deployer.sendTransaction({ to: receiver.address, value: ETHER_IN_RECEIVER });
        // redacted..
    });

    // redacted..

    after(async function () {
        /** SUCCESS CONDITIONS - NO NEED TO CHANGE ANYTHING HERE */

        // All ETH has been drained from the receiver
        expect(
            await ethers.provider.getBalance(receiver.address)
        ).to.be.equal(0);
        expect(
            await ethers.provider.getBalance(pool.address)
        ).to.be.equal(ETHER_IN_POOL + ETHER_IN_RECEIVER);
    });
});

We can see that in the challenge setup code (on the before hook) a user deployed a contract with 10 ETH balance.

In the success condition, we can see an equality check that expects all the ETH to be drained from the receiver address, while the pool address has its original balance plus the amount drained from the receiver.

This implies a successful flash loan operation where the receiver ends up losing its ETH balance to the pool.

In other words, to pass the challenge we need to ensure that:

  • The user or "receiver" is left with a balance of 0 ETH.

  • The lending pool has it's original amount, plus the 10 ETH drained from the user.

Fake Product Landing Page / Info

In a proper audit, rather than a challenge, you'd have some marketing data / protocol RFC / whitepapers to research about before diving into the code.

Imagine the following as public information found about the solution.


Unlock the Power of Flash Loans in DeFi! 🌟

Join our Lender Pool, a groundbreaking platform where your crypto assets aren't just stored, they're strategically put to work!

We're thrilled to unveil our newest smart contracts, offering lightning-fast loans without the need for collateral.

They're a perfect tool for arbitrage, portfolio rebalancing, and various other financial strategies in the blockchain world.

You were called for a quick scoped audit on the NaiveReceiver protocol.

The scope is as follows:

In the scoping call, the client had described the product is especially reliant on these token contracts standards:

The client requested to ensure the lending protocol is implemented securely, and that user contracts using the pool are not vulnerable.

Please review the code and let us know if you discover any risks!

Last updated