Securityadvancedarticle25 min read

Compound Finance Hack: How a Governance Bug Cost $147M | VCC

On September 29, 2021, Compound Finance — the DeFi lending protocol that invented liquidity mining — upgraded its own money-distribution contract through a routine governance vote. The upgrade, Proposal 062, changed about ten lines of Solidity. Within hours, any Compound user who touched certain

Vietcardano teamTeam

Đội ngũ biên tập VietCardanoCommunity — các chuyên gia blockchain và Cardano, cung cấp nội dung giáo dục và nghiên cứu chuyên sâu cho cộng đồng.

Published September 14, 2026

On September 29, 2021, Compound Finance — the DeFi lending protocol that invented liquidity mining — upgraded its own money-distribution contract through a routine governance vote. The upgrade, Proposal 062, changed about ten lines of Solidity. Within hours, any Compound user who touched certain lending markets could claim COMP governance tokens equal to their entire deposit balance — not a sliver of yield, but a 1:1 copy of their collateral in COMP. By morning, roughly $80 million in excess COMP had been claimed; another $67 million sat in the protocol's Reservoir vault waiting for the next person to call a function. The headline loss converged on $147 million (up to $162 million depending on COMP's price that week). The fix — Proposal 063 — could not execute for seven days because Compound's own timelock, designed to protect users from malicious governance, now protected the bug from being patched.

This compound finance hack was not a private-key theft, a flash loan, or a bridge exploit. It was a governance-induced distribution bug — the protocol's own upgrade was the exploit, its own users were the beneficiaries, and its own safety delay was the reason the bleeding lasted a week. Understanding what went wrong clarifies why every major lending protocol now treats governance changes to distribution logic as a critical attack surface.

What Compound Finance Does and Why Its Governance Matters

Compound is an Ethereum lending protocol launched in 2020. Users deposit assets (USDC, ETH, DAI, USDT, etc.) into cToken markets — cUSDC, cETH, cDAI — and earn interest paid by borrowers. In June 2020, Compound introduced COMP, its governance token, distributed pro-rata to suppliers and borrowers as a liquidity-mining reward. By September 2021, Compound held roughly $10 billion in total value locked and COMP traded around $320–$400.

Three design choices made the incident possible. First, governance can upgrade the Comptroller — the single contract that tracks who is owed how much COMP. Compound's Governor Bravo lets COMP holders vote on proposals that, after a timelock delay, can replace Comptroller logic entirely. Second, COMP distribution is mediated through a Reservoir — a vault holding ~280,000 COMP (~$90 million) that the Comptroller draws from whenever a user claims rewards. One storage layout error in the Comptroller therefore gives access to the entire Reservoir. Third, there was no pause button on distribution. Compound could pause individual cToken markets, but not the global claimComp() path — so once the bug shipped, there was no way to stop it without another governance proposal.

Diagram - Compound governance stack: Governor Bravo, Timelock, Comptroller, cTokens, Reservoir, and the COMP distribution path that the bug weaponized

Timeline: 9 Days From Quiet Upgrade to Patch

The exploit window was governed less by an attacker's speed than by Compound's governance clock.

September 29, 2021, ~20:00 UTC — Proposal 062 executed after passing with overwhelming support. Its stated purpose was narrow: fix the compSpeed configuration for the cCOMP market (a self-referential market where COMP itself can be supplied). The new Comptroller deployed; no alarms fired for several hours.

September 29, 21:30 – September 30, 03:00 UTC — On-chain watchers (@banteg, @samczsun, @transmissions11) flagged outsized claimComp() receipts. A user who had supplied 1,000 cUSDC received 1,000 COMP. DeFi Twitter threads reverse-engineered the supplyIndex == 1e36 condition within hours. MEV bots began automating drip() + claimComp() loops.

September 30, ~09:00 UTC — Compound founder Robert Leshner confirmed the bug in a tweet thread: ~$80 million already claimed, ~$67 million still at risk in the Reservoir. The team disclosed that the Comptroller's drip() function — which anyone can call to initialize accrual for a market — was now the cheapest exploit trigger, because it eagerly set a global index without initializing per-user state.

September 30 — Proposal 063 was submitted to fix the Comptroller logic (revert the compInitialIndex handling and correct the supplierIndex == 0 guard). But Compound governance requires a 3-day voting period plus a 2-day timelock after queuing, so the earliest execution was seven days out.

October 1–6 — The bug continued to pay. The Reservoir bled roughly 120,000 COMP (~$40 million) to about 18 addresses. The largest single claim — 202,472 COMP (~$65 million) — came from a vault-like address that supplied then immediately claimed across cUSDC and cUSDT. Several MEV bots spent ~$2k in gas each to net $500k–$2M. One address tagged as Justin Sun–linked claimed ~37,000 COMP (~$12 million), later returning ~10,000 COMP after public appeal.

October 8, ~02:00 UTC — Proposal 063 executed after the timelock elapsed. The distribution bug was patched; the remaining Reservoir balance was secured. Compound also added a returnComp(uint amount) goodwill function so claimants could voluntarily return excess COMP.

October 8–18 — Community recovery: roughly $30–40 million in excess COMP was returned via returnComp and direct transfers (about 35% of excess claimed). The remainder — $50–60 million — was kept by claimants and never recovered. Compound Labs published a post-mortem on October 18, treating the incident as a distribution error, not theft; no law enforcement action was pursued.

Timeline - 9-day Compound hack timeline from Proposal 062 execution (Sep 29) through outsized claims, Proposal 063 submission, Reservoir drainage, and patch (Oct 8)

The Numbers: How $147M Moves When the Bug Is the Protocol

At COMP ~$320 on September 30, the at-risk quantity and its dollar translation depended heavily on when you measured:

BucketCOMP (approx)USD at $320/COMPStatus
Excess claimed by Sep 30~240,000 COMP~$80MIn claimants' wallets
Reservoir at risk (Sep 30)~280,000 COMP~$90MStill in vault, distributable
Total at-risk headline~520,000 COMP$147–162MNews consensus (price-sensitive)
Largest single claim202,472 COMP~$65MOne address, one burst
Gas to exploit—<$5kclaimComp() is permissionless
Returned via goodwill~100,000 COMP~$30–40M~35% of excess
Kept by claimants~150,000 COMP~$50–60MNever recovered

Three subtleties matter. First, the USD headline is reflexive: COMP fell ~30% during the week (from ~$400 to ~$300 as claimants sold), so a later dollar quote understates the same COMP quantity. Always anchor on COMP count, not USD. Second, the exploit was permissionless and cheap — claimComp() and drip() require no privileged role and cost a single transaction's gas. Any Ethereum address could participate, which is why MEV bots, not a single sophistication hacker, captured much of the Reservoir. Third, the cCOMP self-market that Proposal 062 intended to fix was itself the canary: once its compSpeed was corrected, every zero-speed market inherited the index bug via the shared drip() path, multiplying a single-market fix into a cross-market vulnerability.

Root Cause: When supplyIndex Became 1e36 and supplierIndex Stayed 0

The compound finance hack is a textbook state-initialization mismatch — two storage slots that must move in lockstep were updated by different code paths. The invariant violated is simple: no user should ever receive COMP equal to their cToken balance.

How COMP Distribution Normally Works

For each cToken market, the Comptroller tracks:

  • compSupplyState[cToken] = { index: uint224, block: uint32 } — a global accumulator for that market. Each block, deltaBlocks * compSpeed is added to index.
  • compSupplierIndex[cToken][supplier] = uint — per-user snapshot of the global index at the user's last interaction.

A user's pending COMP is:

` deltaIndex = supplyIndex - supplierIndex pending = supplierTokens * deltaIndex / 1e36 `

A new supplier starts with supplierIndex == 0. On first interaction, the protocol sets supplierIndex = compInitialIndex (1e36) so that deltaIndex == 0 and no retroactive reward is minted — the user only earns going forward.

What Proposal 062 Changed

The proposal's diff touched _setCompSpeed / updateCompSupplyIndex and the drip() helper. For markets that had never distributed COMP — compSpeed == 0 and supplyIndex == 0 (several cToken markets had zero speed at that time) — the new code eagerly set:

` compSupplyState[cToken].index = compInitialIndex // 1e36 `

…on the first drip() or speed-update call. But it did not touch compSupplierIndex for existing suppliers in those markets. Those suppliers still had supplierIndex == 0.

So on the supplier's next interaction — claimComp(), but equally mint, redeem, borrow, or repayBorrow — the calculation became:

` deltaIndex = 1e36 - 0 = 1e36 pending = supplierBalance * 1e36 / 1e36 = supplierBalance `

A holder of 1,000 cUSDC (representing ~$21,000 in underlying USDC) received 1,000 COMP (~$320,000). Scale that to a large supplier with 500,000 cUSDC and a single call mints 500,000 COMP. Because the Comptroller's Reservoir held 280,000 COMP and the pending formula is additive per block, repeated claims across multiple markets compounded the windfall.

Why drip() Was the Real Weapon

Most coverage says "users claimed COMP." The cheaper exploit path was drip(cToken). Anyone — not even a supplier — could call drip() on a zero-index market to trigger the supplyIndex = 1e36 transition. That call set the trap; the next supplier to touch the market tripped it. An attacker could drip() all affected markets in one transaction, then claim on each sequentially, without ever needing a sophisticated payload — just a loop of permissionless calls.

Why Audits and Tests Missed It

CertiK and OpenZeppelin had audited earlier Comptroller versions; the diff was ~10 lines. The violated invariant — "supplier pending must be << supplier balance" — was not in the audit spec, and no existing test exercised the (supplyIndex == 1e36) ∧ (supplierIndex == 0) state combination because all test markets had non-zero supplier indexes from prior accrual. A single state-seeded property test — "for any market, after any drip(), pending for an old supplier < 1 COMP" — would have caught it.

Diagram - Compound root cause: Proposal 062 sets supplyIndex to 1e36 while supplierIndex stays 0, so pending equals the full cToken balance

Why the Timelock and Governance Delays Made It Worse

Compound's Governor Bravo + Timelock enforces a 48-hour delay between a proposal passing and execution. By design this is a transparency window: users can exit if they disagree with a proposal before it goes live. On September 30, that same delay became the enemy.

The bug was already live. The fix — Proposal 063 — needed 3 days of voting plus 2 days of timelock, totaling 7 days before it could patch an actively bleeding contract. During those 7 days, the bug continued to drip COMP roughly every block to anyone who interacted with affected markets.

Compound Labs considered alternatives and found none viable:

  • No global pause. Compound could pause individual cToken markets (supply/borrow), but not the claimComp() distribution path globally. Proposing a pause would itself require governance — and hit the same timelock.
  • No guardian fast-track. Unlike later protocols that added a guardian multisig with emergency pause or fix authority, Compound governance was fully timelocked with no bypass. OpenZeppelin's post-incident note captured it crisply: "The Timelock is not a security module; it is a transparency module. Coupling it to an immutable distribution contract without a pause role turns every governance bug into a week-long open vault."
  • No clawback. Once claimComp() transferred COMP from the Reservoir to a claimant, no on-chain mechanism could recover it. The contract had no time-locked vesting, no reclaim window, no optimistic distribution — transfers were final.

This is the compound comp token bug as a governance lesson: a timelock protects against malicious governance (a hostile proposal to steal funds can be seen and exited), but it amplifies damage from buggy governance (a well-intentioned upgrade with a logic error cannot be quickly reverted). Any protocol that lets governance upgrade distribution logic without a scoped pause or a guardian escape hatch is running the same risk Compound ran.

Who Benefited — And Who Returned

Unlike bridge hacks dominated by a single exploiter address, the governance attack defi narrative does not fit here — there was no governance takeover. The beneficiaries were simply existing Compound users (and bots watching the mempool) who understood the math first.

Roughly 18 addresses claimed excess COMP by October 6, per Etherscan and Dune:

  • The largest claim — 202,472 COMP (~$65M) — came from one address supplying then immediately claiming across cUSDC and cUSDT in a single burst.
  • A Justin Sun–tagged address claimed ~37,000 COMP (~$12M) via the cTUSD market and later returned ~10,000 COMP after public appeal and on-chain returnComp calls (October 3).
  • Several MEV / searcher bots automated drip() then claimComp() loops, spending ~$2k in gas each to net $500k–$2M.

Proposal 063's returnComp(uint amount) is itself a governance precedent worth noting: it treats excess distribution as a social debt, not a smart-contract debt. Calling it is voluntary; the contract does not compel returns. It worked partially — ~35% (~$30–40M) was returned by October 18 — because claim sizes were large and many claimants were doxxable or reputationally sensitive. The remaining ~60% (~$50–60M) was kept. Had the COMP flowed through a mixer, the goodwill mechanism would have failed entirely — which is precisely why Compound and peers later added a formal pause guardian rather than relying on voluntary returns again.

Fund Flow: From Reservoir to Wallets

Diagram - Fund flow: Reservoir 280k COMP and claimed 240k COMP split into kept, returned, and secured buckets, with COMP price reflexivity noted

The passthrough is simpler — and in some ways more instructive — than a typical cross-chain laundering flow. There were no bridges, no mixers, no cross-chain hops:

  1. Source — The Reservoir address held ~280k COMP earmarked for distribution. The Comptroller is its sole spender; any authorized claimComp draws from this vault.
  2. Distribution overshoot — claimComp() computed pending == balance and transferred that amount from Reservoir → claimant. Each call was legitimate from the EVM's perspective — the function executed correctly, only the state it read was wrong.
  3. Claimant behavior — Some claimants immediately swapped COMP for stables on Uniswap/Sushi (which is why COMP's price fell), some held, some returned. On-chain Swap events show ~100k COMP swapped within 48 hours.
  4. Recovery path — Returns flowed via two methods: direct transfer to the Reservoir / Timelock, and returnComp() addition in Proposal 063 that emits a CompReturned event for accounting. The remaining Reservoir balance was secured when Proposal 063 patched the index logic on October 8.

What did not happen is as telling: no funds were frozen by an issuer (COMP is not freezable like USDT), no chain was halted, and no funds were rolled back at the consensus layer. The entire compound 147 million saga resolved through social coordination layered on top of immutable EVM execution.

Lessons: 5 Defenses That Would Have Stopped the Compound Finance Hack

Every lending protocol that shipped a governance-upgradeable Comptroller before October 2021 carried some version of this risk. Compound's patch and the ecosystem response since have converged on five defenses.

Checklist - 5 defense layers: invariant tests, scoped pause guardian, Reservoir rate-limit, emergency timelock bypass, and diff-scoped audit for every Comptroller proposal

1. Invariant tests and formal verification on distribution math. The single property "for any market and any supplier, pending < supplierBalance" would have failed on day one of a forked testnet with Proposal 062's code. Certora, Halmos, or even a Foundry invariant harness seeded with (supplyIndex=0, supplierIndex=0, speed>0) would have flagged the 1e36 delta. Distribution logic deserves its own verification spec, not just generic reentrancy checks.

2. Scoped pause guardian — not full governance for emergencies. A guardian multisig (e.g., 3-of-5) with only the ability to pause claimComp / drip — not to move funds — would have frozen the bleed within hours, without giving anyone custody of the Reservoir. This is the standard Compound later adopted (and Aave, Uniswap, and others copied): pause authority is narrow, revocable, and faster than the timelock.

3. Per-block and per-claim rate limits on the Reservoir. A circuit breaker — e.g., any single claimComp > 10k COMP or aggregate > 50k COMP per block triggers an automatic pause and Timelock review — would have capped the blast radius to one anomalous transaction instead of a week-long drainage. Stablecoin-heavy thefts show that volume-based halts are the highest-leverage single control.

4. Timelock bypass for emergency patches. Keep the transparency window for new features and parameter changes, but allow a guardian or a fast-track proposal path (shorter voting + shorter timelock, gated by a security council) for bug-fix proposals when a prior proposal is actively leaking funds. The 7-day fix delay is the purest lesson: governance latency should never exceed time-to-exploit for a known-vulnerable live contract.

5. Diff-scoped audit for every Comptroller proposal. Every governance proposal that touches distribution, Comptroller, or Reservoir code should get an independent review — even a 10-line diff. OpenZeppelin's post-incident recommendation was a standing engagement where any proposal's diff, not just the full contract, is reviewed before it reaches on-chain voting. The cost of reviewing 10 lines is trivial compared to the cost of shipping them unreviewed.

These five together eliminate the specific failure mode: (1) catches the logic error before it ships, (2)+(4) shorten the window if it slips through, (3) caps the damage per block while it is live, and (5) ensures step (1) is not skipped when the change looks small. The timelock governance defi lesson is now canonical: a timelock is a transparency feature, not a security boundary, and coupling it to an unpausable distributor turns every governance bug into an open vault.

Frequently Asked Questions

How much was stolen in the Compound Finance hack? Between $147 million and $162 million depending on COMP's price when measured. By September 30, ~$80 million (240k COMP) had been claimed in excess, with ~$67–90 million (280k COMP) still at risk in the Reservoir — the $147M headline counts both realized and at-risk; $162M counts future accrual during the 7-day fix window. About $30–40M (~35%) was voluntarily returned; $50–60M was kept.

How did the Compound Finance hack work? It was not an external hack but a governance bug. Proposal 062 upgraded the Comptroller and changed how COMP distribution indexes are initialized. For markets that had never distributed COMP, the global supplyIndex was set to 1e36 while existing suppliers' stored indexes stayed at 0, so the next reward calculation yielded pending = supplierBalance * 1e36 / 1e36 = supplierBalance — minting COMP equal to the user's entire cToken balance per claim. Anyone could trigger it by calling claimComp() or drip().

Was the Compound hack a governance attack? No. A governance attack (e.g., Beanstalk, where an attacker used a flash loan to seize voting power and pass a malicious proposal) involves hostile takeover of voting. Compound's was a governance bug: the community legitimately voted to pass Proposal 062, but the code it installed contained a logic error. The timelock and voting process worked as designed; the contract they authorized did not.

Why did it take 7 days to fix the Compound bug? Compound governance requires a 3-day voting period plus a 2-day Timelock delay after queuing — about 7 days end-to-end. The fix (Proposal 063) was submitted September 30 but could not execute until October 8. During those 7 days, the buggy Comptroller continued to distribute excess COMP each block. Compound had no global pause on claimComp() and no guardian fast-track, so no faster path existed.

Did Compound users get their money back? The "stolen" funds were COMP from the Reservoir that should not have been distributed — not user deposits (cTokens and underlying collateral were never moved). About 35% of excess COMP was voluntarily returned via direct transfers and a returnComp() function added in Proposal 063. The rest was kept by claimants. User deposits and borrowing were unaffected; the loss was dilutionary (extra COMP supply) rather than custodial.

Has anything like the Compound bug happened since? Yes — governance-upgradeable distribution bugs recurred (e.g., Audius, Build Finance governance proposals), and the broader category of timelock governance defi failures now routinely includes a pause-guardian and diff audit as standard defenses. Compound itself later added a pause guardian and now subjects every Comptroller proposal diff to independent review before it reaches on-chain voting.

Conclusion

The compound finance hack endures not because it was the largest DeFi loss of 2021 — Poly Network's $611M and later Ronin's $625M dwarfed it — but because it exposed governance itself as an attack surface. A ten-line upgrade, passed by the community's own vote, turned the protocol's reward distributor into an open faucet; a two-day transparency delay, designed to protect users from malicious proposals, protected the bug from its fix for a full week; and recovery depended not on cryptography but on social coordination — a returnComp() function that only worked because claimants chose to call it. The technical takeaway is precise: never ship distribution logic without the invariant "pending < balance", never couple an unpausable distributor to a fully timelocked governance path, and never treat a small diff as too small to audit. The governance takeaway is more durable: in DeFi, the vote is the code, and the timelock is not a security boundary — it is a clock that ticks for bugs and fixes alike.

Tags:

compound finance hackcompound comp token buggovernance attack deficompound 147 milliontimelock governance defi