Skip to content

The Curve Finance · Vyper compiler hack — $69.3M lost

Loss$69.3M
Date30 Jul 2023
ChainEthereum
Failure classCompiler / toolchain + Reentrancy
In assetsnobody's source code was wrong
TargetalETH, msETH, pETH, CRV/ETH
1

What happened

Every audited line was correct. Vyper versions 0.2.15, 0.2.16 and 0.3.0 contained a bug in set_storage_slots that gave each @nonreentrant('lock') its own storage slot instead of one shared slot per lock name. Two functions declaring the same named lock therefore did not share it. Source code that read as fully protected compiled to bytecode that had no cross-function protection at all. The attacker re-entered through the raw-ETH callback in remove_liquidity and drained four pools.

2

How the attack ran

  1. Both functions declare the same lock@nonreentrant('lock') — correct source
  2. Vyper 0.2.15–0.3.0 compiles itGiving each function its own storage slot
  3. The shared lock is not sharedAudited source, unprotected bytecode
  4. Re-enter via the ETH callbackFour pools drained — $69.3M
3

The code

the source passed every review — the bytecode did not
# What every auditor read, and correctly approved:
@external
@payable
@nonreentrant('lock')
def add_liquidity(...): ...

@external
@nonreentrant('lock')
def remove_liquidity(_amount: uint256, _min_amounts: uint256[N]):
    ...
    raw_call(msg.sender, b"", value=eth_amount)   # attacker's fallback runs here
    self.balances = ...                        # state written after

# In Vyper 0.2.15 – 0.3.0 those two 'lock's compiled to
#   add_liquidity    → storage slot A
#   remove_liquidity → storage slot B
# One lock per function is the same as no lock at all.

→ fixed in Vyper 0.3.1+ · "audited source" ≠ "audited bytecode"
4

What would have caught it

What an audit looks for: the compiler version is part of the threat model. Pin it, check it against known-bad releases, and verify that the deployed bytecode matches the source that was reviewed. This is also why we insist on on-chain verification: the source on the explorer is a claim until the bytecode agrees with it.
6

Sources

Every figure on this page comes from the post-mortems above, not from us. Losses are US dollars at the time of the incident.

Check your own contract for this

Compiler / toolchain + Reentrancy is one of the 203 classes the SaferICO scanner checks for. It will not review your signing process — but it will read your Solidity.

Run the scanner See how it is attacked Read the docs