All vulnerabilities
CRITICALWeb3exploited in the wildcurated

WEB3-CURVE-VYPER-2023

Web3 · Ethereum · Curve Finance (Vyper)

Summary

On 30 July 2023, several Curve Finance pools were drained of about $70 million, and the bug was not in Curve's code at all. It was in the compiler. Specific versions of Vyper, the programming language many Ethereum contracts are written in, generated a broken reentrancy guard, so the protection developers thought they had was silently not working. It is the rare and unsettling case of a reentrancy attack reaching contracts whose authors had correctly added the guard, because the tool that built them betrayed them. It put the spotlight on the compiler and the build pipeline as part of a smart contract's trust boundary.

How it happened

Curve's contracts used Vyper's @nonreentrant(key) decorator to prevent reentrancy, the exact bug class that destroyed The DAO in 2016. They did this correctly. The failure was underneath them, in the Vyper compiler itself (versions 0.2.15, 0.2.16, and 0.3.0; the flaw was fixed in 0.3.1, but vulnerable contracts stayed deployed for nearly two years).

A reentrancy lock works by setting a flag in one shared storage slot, so that any guarded function sees the flag and refuses to run while another is mid-execution. The buggy compiler gave each guarded function its own separate storage slot instead of sharing one slot per key. So every function got its own independent lock. The guard still stopped an attacker from re-entering the same function, but it completely failed to stop them re-entering a different guarded function, which is exactly what an attacker did, calling back into a second guarded function through the native-ETH transfer callback while balances were still mid-update, and draining the pool. Pools paired with wrapped ETH were safe because they have no such callback; the native-ETH pools were hit, with the biggest losses in the alETH/ETH pool (around $20 million), the CRV/ETH pool (around $18.5 million, exploited twice), pETH/ETH (around $11.5 million), and msETH/ETH, dragging in projects like Alchemix, JPEG'd, and Metronome.

The aftermath

Gross losses were around $70 million (some tallies put the total near $73.5 million once every affected project is summed). A large share was returned: white-hat actors and front-running (MEV) bots recovered funds, most famously the operator c0ffeebabe.eth, who captured 2,879 ETH (about $5.4 million) from the CRV/ETH pool through a flash-loan arbitrage that beat the attacker to it and handed it back to Curve, while the main attacker voluntarily returned millions more to Alchemix and JPEG'd, cutting net losses to roughly $52 million. There was a scarier second-order risk: the price of Curve's CRV token wobbled, threatening the roughly $110 million in loans its founder Michael Egorov had taken out against CRV collateral. The sharpest danger was a Fraxlend loan whose interest rate doubled every twelve hours, which would have forced a fire-sale of CRV and a cascading liquidation across DeFi; Egorov defused it by selling large blocks of CRV over-the-counter to a dozen buyers, and the cascade was narrowly avoided. It was a supply-chain attack in the truest sense, the compromised artifact was the compiler.

Why Curve still matters

Curve is the lesson where supply-chain risk meets reentrancy. The developers did everything right at the source level and were still exploited, because the compiler mis-generated their defence. The takeaway is that the compiler and build toolchain are part of your trust boundary, just like any dependency. Pin exact, audited compiler versions and verify the deployed bytecode against the audited source; track compiler security advisories and re-audit when a toolchain bug drops; treat the build pipeline as trusted supply chain with reproducible, signed builds; add explicit cross-function and read-only reentrancy guards rather than relying solely on a compiler-generated lock; and test reentrancy protection at the bytecode level, including native-ETH callback paths.

How to fix it

  • Identify and pause every contract built with the affected Vyper versions, and redeploy from a fixed compiler with verified bytecode.
  • Add explicit cross-function and read-only reentrancy protection rather than relying on the compiler-generated lock that failed.
  • Trace funds and coordinate with white-hat recoverers; a meaningful share of the Curve loss was returned.

How to avoid it

  • Pin exact, audited compiler versions and rebuild/verify deployed bytecode against the audited source before release.
  • Track compiler security advisories and re-audit or migrate contracts when a toolchain-level bug is disclosed.
  • Treat the compiler and build pipeline as part of the trusted supply chain: reproducible builds and signed releases.
  • Add explicit cross-function reentrancy guards and read-only reentrancy checks rather than relying solely on compiler-generated locks.
  • Test reentrancy protection at the bytecode level, including native-ETH transfer callback paths.

References

Related vulnerabilities

All Web3 →