Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.phylax.systems/llms.txt

Use this file to discover all available pages before exploring further.

This case study explains how the first depositor bug works and which assertion pattern can constrain the invalid state transition.

What Happened

Background: https://akshaysrivastav.hashnode.dev/first-deposit-bug-in-compoundv2-and-its-forks The First Depositor Attack in Compound / Aave v2 lending protocols: Key points:
  • First depositor supplies a tiny amount (e.g. 1 wei) of assets
  • First depositor donates a large amount directly to the lending pool (bypassing supply)
  • This artificially inflates the exchange rate between shares and assets
  • Next depositor will receive very few shares for their deposit
  • If next depositor’s amount is smaller than exchange rate, they will receive 0 shares due to rounding error
  • First depositor owns all shares, so they can withdraw all assets
Example:
  1. Attacker deposits 1 wei, gets 1 share
  2. Attacker donates 1000 tokens directly
  3. Exchange rate is now 1000.00..01 tokens : 1 share
  4. Victim deposits 100 tokens, gets only ~0.1 shares due to inflated rate -> will be rounded down to 0
  5. Attacker withdraws 1 share and gets 1100 tokens, stealing all of victim’s deposit

Why This Matters

Traditional prevention:
  • Attack only makes sense if attacker owns all shares (or close to it)
    • If he owns all shares, donating tokens to the pool will still be owned by the attacker
  • Protocols typically deposit with opening the market atomically
    • This means not all shares are owned by the attacker
    • Adjustments to the protocol could mint tokens to 0x0 upon first mint/deposit
  • Many forks fail to combine these actions

Assertion Pattern

  • History has shown that an old vulnerability has found its way into many protocols
  • Many succesfull attacks leveraged this vulnerability
    • Sonne Finance
    • Radiant Capital
    • Hundred Finance
  • Assertions can be used to easily prevent known vulnerabilities in forked protocols
    • Enforce existing workarounds
    • I.e. Sonne Finance planned to execute deploying the market and depositing atomically, however, their governance process let the attacker execute the transactions out of order
  • Low entry barrier to enable publicly known assertions for famous protocols to your own fork

Example Assertion: Enforce atomically opening the market and depositing first assets

function assertionHasMinimumSupply() public view {
    Market market = Market(address(0x1234));
    require(market.totalSupply() > MINIMUM_SUPPLY, "Market has no supply");
}

Example Assertion: Enforce exchange rate < deposit amount

function assertionExchangeRate() public view {
    Market market = Market(address(0x1234));

    PhEvm.CallInputs[] memory inputs = ph.getCallInputs(address(cToken), cToken.mint.selector);

    for (uint256 i = 0; i < inputs.length; i++) {
        PhEvm.CallInputs memory input = inputs[i];
        (uint256 amount) = abi.decode(input.input, (uint256));
        require(amount / market.exchangeRate() > 0, "Did not receive any shares");
    }
}