挑战 #1 - 不可阻挡#
合約#
- ReceiverUnstoppable:繼承 IERC3156FlashBorrower 合約,用於發起閃電貸款,執行閃電貸款後的回調
- UnstoppableVault:金庫合約,繼承 IERC3156FlashLender、ERC4626,支援閃電貸款
腳本#
- 依次部署 DamnValuableToken、UnstoppableVault 合約
- 存入 TOKENS_IN_VAULT 數量的 token 到金庫中,轉入 player 使用者 INITIAL_PLAYER_TOKEN_BALANCE 數目的 token
- 部署 ReceiverUnstoppable 合約
- 執行攻擊腳本
- 期望 ReceiverUnstoppable 執行閃電貸款的交易被 revert
題解#
攻擊目標是使得通過 ReceiverUnstoppable 合約發起的 executeFlashLoan 方法被 revert,首先分析 executeFlashLoan 的調用流程
重點在 UnstoppableVault.flashLoan 方法,分別會進行以下操作:
- 計算閃電貸款開始前的餘額:totalAssets ()
- 計算當前的 share:convertToShares (totalSupply) 是否與前面計算出來的餘額一致
- 計算閃電貸款手續費:flashFee
- 轉移 amount 個 token 到 receiver,再調用 receiver 的 onFlashLoan 方法執行回調
- 從 receiver 方轉回 amount+fee 數目的 token
- 將 fee 轉移給 feeRecipient 帳戶,完成本次閃電貸款
若要使交易 revert,關鍵的校驗點在於使得:convertToShares(totalSupply) != totalAssets()
這兩個函數都是 ERC4626 中的定義,關於此協議可參考下面的文章:
WTF-Solidity/51_ERC4626/readme.md at main · WTFAcademy/WTF-Solidity
簡單來說就是 ERC20 的組合:資產代幣 asset 和份額代幣 share,存入資產或提取資產時都會相對應的鑄造或銷毀對應數目的 share 代幣
totalAssets()
:計算的是當前金庫中的資產代幣數目convertToShares(totalSupply)
:totalSupply 是總的 share 代幣數目(只有 deposit 或 mint 時才會產生),convertToShares 就是計算:assets * totalSupply /totalAssets ()
要想使得兩者不一致,只要不通過 depost 或 mint 方法向 UnstoppableVault 中轉入 token 即可,因此攻擊腳本內容如下:
it('Execution', async function () {
/** CODE YOUR SOLUTION HERE */
const dvtForPlayer = token.connect(player);
await dvtForPlayer.transfer(vault.address,1);
});