チャレンジ #1 - 止まらない#
コントラクト#
- ReceiverUnstoppable:IERC3156FlashBorrower コントラクトを継承し、フラッシュローンを実行した後のコールバックを行うためのものです。
- UnstoppableVault:IERC3156FlashLender、ERC4626 を継承した金庫コントラクトで、フラッシュローンをサポートしています。
スクリプト#
- DamnValuableToken、UnstoppableVault コントラクトを順番にデプロイします。
- TOKENS_IN_VAULT の数だけトークンを金庫に預け入れ、INITIAL_PLAYER_TOKEN_BALANCE の数だけトークンをプレイヤーのアカウントに転送します。
- ReceiverUnstoppable コントラクトをデプロイします。
- 攻撃スクリプトを実行します。
- ReceiverUnstoppable が実行されたフラッシュローンのトランザクションが revert されることを期待します。
解答#
攻撃の目標は、ReceiverUnstoppable コントラクトから実行される executeFlashLoan メソッドが revert されることです。まず、executeFlashLoan の呼び出しフローを分析します。
重要なのは、UnstoppableVault.flashLoan メソッドで、以下の操作が行われます。
- フラッシュローン開始前の残高を計算する:totalAssets ()
- 現在のシェアを計算する:convertToShares (totalSupply) が先に計算した残高と一致するかどうか
- フラッシュローン手数料を計算する:flashFee
- amount 個のトークンを receiver に転送し、receiver の onFlashLoan メソッドを呼び出してコールバックを実行する
- amount+fee の数だけトークンを receiver から返す
- fee を feeRecipient アカウントに転送して、フラッシュローンを完了する
トランザクションを revert させるためには、convertToShares(totalSupply) != totalAssets()
となるようにする必要があります。
これらの関数は、ERC4626 で定義されています。このプロトコルについては、以下の記事を参照してください:
WTF-Solidity/51_ERC4626/readme.md at main · WTFAcademy/WTF-Solidity
簡単に言えば、ERC20 の組み合わせで、アセットトークンとシェアトークンがあります。アセットを預け入れたり引き出したりすると、対応する数のシェアトークンが鋳造または破棄されます。
totalAssets()
:現在の金庫のアセットトークンの数を計算します。convertToShares(totalSupply)
:totalSupply は総シェアトークンの数です(デポジットまたはミント時にのみ生成されます)。convertToShares は、assets * totalSupply /totalAssets () を計算するものです。
これらの関数を一致しないようにするには、UnstoppableVault にトークンをデポジットまたはミントする必要がありません。したがって、攻撃スクリプトの内容は以下の通りです:
it('Execution', async function () {
/** CODE YOUR SOLUTION HERE */
const dvtForPlayer = token.connect(player);
await dvtForPlayer.transfer(vault.address,1);
});