Sonne Finance soVELO Empty-Market Donation Exploit (2024-05-14)

  • Name: Sonne Finance soVELO empty-market donation exploit
  • Date: 2024-05-14
  • Protocol: Sonne Finance
  • Chain: Optimism
  • Category: empty-market exchange-rate inflation / precision-loss redemption / governance-activated market drain
  • Loss: about $20 million
  • Summary: Sonne Finance listed VELO on Optimism through queued governance transactions that were permissionlessly executable after a two-day timelock. The team had been relying on a manual, sequence-sensitive hardening ritual — list the market with 0% collateral factor, seed it, burn the seed position, then later raise the collateral factor — to avoid the well-known Compound-v2 empty-market bug. That meant the market never had an irreversible seed floor; it only had a plan to create and remove one before outsiders could interfere. After the timelock expired, the attacker executed the queued market-creation and collateral-factor transactions first, minted just 2 wei of soVELO with 400000001 wei of VELO, then borrowed about 35,469,150 VELO from an AMM and donated it directly into the soVELO contract. That made each remaining wei of soVELO represent about 17,735,851 VELO on paper. Using that fake collateral, the attacker borrowed real assets from other Sonne markets, then called redeemUnderlying to pull out about 35,471,603 VELO while burning only 1 wei of soVELO instead of the economically required 1.999994 wei. They then re-expanded the market back to 2 wei of supply and repeated the drain path against deeper-liquidity pools.
  • Trigger / entry point:
    • The exploit centered on the newly listed soVELO market at 0x9560e827af36c94d2ac33a39bce1fe78631088db on Optimism.
    • Sonne’s queued governance actions were executable by anyone once the Optimism timelock expired, even though the team intended to execute a protective seed-and-burn sequence around the listing.
    • Compound-v2-style exchange-rate math in soVELO trusted raw token balance, so direct VELO transfers into the market inflated exchange rate without minting matching soVELO.
  • Root cause:
    • Sonne inherited the known empty-market / precision-loss weakness from Compound-v2 forks: a nearly zero-supply market could have its exchange rate inflated by direct underlying donations, and redemption math rounded in the attacker’s favor at that microscopic supply.
    • The distinctive operational failure was that Sonne’s mitigation was procedural rather than structural. Safety depended on privileged execution order around a temporary seed-and-burn sequence, but the queued listing and collateral-factor transactions were permissionlessly executable after timelock expiry.
    • That let the attacker activate a collateral-eligible VELO market before the team could stage even the temporary protective liquidity, and it also highlighted that the planned mitigation would not have left behind a durable non-burnable supply floor anyway.
    • Once soVELO supply sat at 2 wei, redeemUnderlying truncation turned printed exchange-rate value into redeemable VELO and borrowable solvency across sibling markets.
  • Exploit path:
    1. Sonne passed SIP-15 to add VELO markets on Optimism, then queued the relevant multisig / timelock transactions in 0x18ebeb958b50579ce76528ed812025949dfcff8c2673eb0c8bc78b12ba6377b7.
    2. After the two-day timelock expired, the attacker executed the queued market-activation path before the team could finish its intended hardening flow, including the collateral-factor-enabling transaction 0x45c0ccfd3ca1b4a937feebcb0f5a166c409c9e403070808835d41da40732db96.
    3. In the setup phase, the attacker supplied 400000001 wei VELO and minted only 2 wei of soVELO, creating the tiny residual supply state needed for the exploit.
    4. In the main exploit transaction 0x9312ae377d7ebdf3c7c3a86f80514878deb5df51aad38b6191d55db53e42b7f0, the attacker borrowed about 35,469,150 VELO from an AMM and donated it directly into soVELO.
    5. Because totalSupply remained 2 wei, the donation made 1 wei of soVELO represent about 17,735,851 VELO on paper.
    6. The attacker moved 2 wei of soVELO into a helper contract and used that fake collateral to borrow real assets, beginning with about 265 WETH and then extending the same pattern into other Sonne markets.
    7. The decisive redemption step was redeemUnderlying(35,471,603 VELO): economically it should have burned about 1.999994 wei of soVELO, but truncation rounded the burn down to 1 wei.
    8. After extracting the donated VELO back out, the attacker transferred another 100 VELO into soVELO, minted 1 wei more soVELO, restored market supply to 2 wei, and repeated the same inflate-borrow-redeem loop against markets holding USDC, USDT, WETH, WBTC, and wstETH liquidity.
  • What the attacker needed to be true:
    • The VELO market had to become collateral-eligible before a non-burnable protective seed position was established.
    • The Optimism timelock execution path had to remain permissionless so the attacker could control listing sequence rather than merely react to it.
    • Direct VELO donations into soVELO had to flow straight into exchange-rate math without minting new shares.
    • Redemption math had to truncate in the attacker’s favor when supply was pinned at 2 wei.
    • Sibling Sonne markets needed enough real liquidity for the fake soVELO collateral to be monetized into stronger assets before operators intervened.
  • Technical anatomy worth remembering:
    • Sonne is a useful post-Hundred case because the core bug was already known. The failure was not ignorance; it was an operational dependency on governance execution order that turned a known bug back on.
    • The memorable numbers are 2 wei of soVELO, roughly 35.47M donated VELO, and the 1.999994 -> 1 wei redemption truncation.
    • The exploit shows that empty-market hardening is not just contract math. Timelock design, who may execute queued actions, and how new markets become collateral also sit inside the security boundary.
    • It also shows how newly listed or freshly enabled markets are especially dangerous in Compound-style forks: the attacker does not need an abandoned legacy market if governance itself creates a brand-new empty one.

Three launch-authority promotions this incident collapsed

  1. A governance-approved launch artifact existed at all

    • SIP-15 had passed, the VELO-market transactions were queued, and the two-day timelock had matured.
    • That only proved a launch-shaped governance object exists and is now executable, not the market is already safely initialized.
  2. That executable launch artifact was over-promoted into finished market-initialization truth

    • Sonne’s own postmortem is unusually direct here: the team was relying on a manual 0% collateral factor -> add collateral -> burn it -> then raise c-factor ceremony, and on Optimism the multisig execution path was permissionless once the timelock expired.
    • The dangerous promotion was treating the queued launch can now be executed as if it already implied the seed hardening has happened, the denominator is durable, and the market is ready to matter as collateral.
    • This is the sharper design mistake than generic timelock race: the supposed hardening never existed as an irreversible onchain fact, only as an execution-order-dependent plan.
  3. That accepted launch state still inherited immediate cross-market reserve authority

    • Once the attacker executed the listing and c-factor path first, 2 wei of soVELO plus a direct VELO donation stopped being a weird local startup state and became real borrowing power against WETH, USDC, USDT, WBTC, and wstETH in sibling markets.
    • The same accepted state also inherited redemption authority, so the attacker could turn 1.999994 wei should burn into 1 wei did burn and withdraw the donated VELO back out.
    • Healthy launch design keeps governance artifact exists, market is safely initialized, and this market may already spend against the rest of the protocol as separate promotions instead of letting timelock maturity collapse them into one state.
  • Representative transactions and artifacts:
    • Governance scheduling tx: 0x18ebeb958b50579ce76528ed812025949dfcff8c2673eb0c8bc78b12ba6377b7
    • Collateral-factor / market-activation tx executed after timelock: 0x45c0ccfd3ca1b4a937feebcb0f5a166c409c9e403070808835d41da40732db96
    • Main exploit tx: 0x9312ae377d7ebdf3c7c3a86f80514878deb5df51aad38b6191d55db53e42b7f0
    • soVELO: 0x9560e827af36c94d2ac33a39bce1fe78631088db
    • soWETH: 0xf7b5965f5c117eb1b5450187c9dcfccc3c317e8e
    • soUSDC: 0x0b2c639c533813f4aa9d7837caf62653d097ff85
    • Related attacker addresses published by Sonne: 0xae4a7cde7c99fb98b0d5fa414aa40f0300531f43, 0x5d0d99e9886581ff8fcb01f35804317f5ed80bbb, 0x6277ab36a67cfb5535b02ee95c835a5eec554c07
  • Response / mitigation:
    • Sonne paused markets once the team realized the exploit was underway.
    • The team publicly framed the incident as the known Compound-v2 donation attack resurfacing through the VELO market listing path.
    • Community responders noticed the still-open setup quickly enough to save the remaining roughly $6.5M by adding about $100 worth of VELO to harden the market before the attacker could continue draining it.
    • The incident reinforced that manual launch procedures are weak mitigations when post-timelock execution can be taken over by arbitrary actors.

Pattern tags

  • empty-market-exchange-rate-inflation
  • fresh-market-initialization-failure
  • The preserved shape is sharper than generic governance listed a bad market: Sonne knowingly depended on a manual list with 0% c-factor -> seed -> burn -> later raise c-factor ceremony, but Optimism timelock execution let the attacker take the same market from just listed to borrow-relevant before any irreversible floor existed.
  • This is also a clean temporary seed ritual != durable bootstrap hardening failure. Even Sonne’s own pre-incident mitigation logic assumed the protective position could later be burned away, which means the protocol still had not encoded a lasting answer to what non-attacker-owned denominator keeps VELO collateral meaningfully initialized?

Why the exploit worked

  • Sonne’s own postmortem is unusually explicit that the protocol already knew about the Compound-v2 donation family and had been relying on a sequencing workaround rather than a structural fix. That makes the preserved lesson sharper than team forgot an old bug: the market launch only stayed safe if the right actor executed the right transactions in the right order before anyone else touched VELO.
  • Once the queued Optimism transactions became permissionlessly executable, market listed, temporary hardening, and collateral-enabled stopped being one trusted launch flow and became an attacker-controlled state machine. The exploiter did not need to bypass governance; they only needed to inherit governance’s unfinished launch surface faster than the team did.
  • The donation and truncation math were already familiar. The deeper failure was that the protocol let a brand-new VELO market become cross-market solvency truth before an irreversible floor, dead shares, or a still-binding collateral quarantine existed. In other words, Sonne treated someone intended to seed this as if it were the same thing as the market is now safely initialized.
  • The cash-out step then reused the classic Hundred-family path: with supply pinned at 2 wei, a huge VELO donation printed a fake exchange rate, and redeemUnderlying floor rounding turned nearly 2 wei of economically required burn into 1 wei. Borrowing from sibling markets only monetized a false collateral mark that governance sequencing had already made possible.
  • The preserved design lesson is therefore broader than protect timelock execution: launch sequencing, non-redeemable floor design, collateral-activation discipline, and redemption rounding were one security object, and Sonne split them apart.
  • Sonne is also a clean operations-boundary case: an authorized timelock / rollout path was still allowed to act like live balance-sheet authority. The queued launch bundle did not merely expose a new market; it exposed immediate borrow-relevant collateral truth before the opening denominator had been made durable.

Detection and audit pivots

  • Ask whether launch hardening is an encoded lifecycle or a team ritual. If the safe path is described as we list with collateral factor 0, seed it, burn it, then raise the c-factor, the next question should be: what exact onchain condition proves that sequence finished safely when the team is offline or an arbitrary executor races it?
  • Review timelock semantics together with market initialization. If queued listing and collateral-factor transactions become permissionlessly executable after expiry, then arbitrary actors control the most dangerous sequencing window unless the queued actions themselves leave no unsafe intermediate state.
  • Check whether a supposed seed is actually durable. A seed position that is meant to be burned away after launch does not create a lasting denominator; it only buys time, and only if no attacker can claim the same opening state first.
  • Fuzz the familiar 2 wei regime directly: microscopic receipt-token supply, direct donation into cash, and redeemUnderlying under extreme exchange rates. Known bug families should still be re-simulated on each new market listing path, not mentally waived away because the team has an ops runbook.
  • Look for a downstream distrust boundary. Can a just-listed market stay locally deployable while collateral factor remains 0, borrows stay off, or debt caps remain tiny until a non-redeemable floor and real supplier breadth exist? If not, the protocol is still letting launch-state fiction become sibling-market solvency.

Failure-prevention checklist

  • Do not let governance queue market exists and market may be collateral as separate publicly executable steps unless every intermediate state is already safe. If third parties may execute the timelock, launch ordering must be modeled as hostile.
  • Encode an irreversible seed floor before collateral admission. A temporary seed that operators plan to burn away after launch is not a defense; it only delays the same empty-market state until the next redemption or sequencing mistake.
  • Keep fresh listings locally reachable but systemically quarantined: collateral factor 0, tiny borrow caps, disabled recursive borrow routes, and no liquidation path into deeper sibling liquidity until seeded supply and market-state checks are complete.
  • Treat direct donations into Compound-style markets as adversarial accounting input, not benign surplus. If unsolicited VELO can instantly change cash without minting new soVELO, the protocol must separately decide whether that value affects collateral, reserves, redemption, or none of them.
  • Fuzz redeemUnderlying and related burn paths at microscopic receipt-token supply, especially after governance-created fresh listings. Known 2 wei states should be a mandatory pre-launch test, not a historical anecdote.
  • Add automatic launch-state alerts for totalSupply near dust, single-supplier markets, sudden exchange-rate jumps, or freshly enabled collateral markets whose protective floor has not yet been observed onchain.
  • Require explicit reclassification back into quarantine if a supposed seed floor is later removed, a market falls back toward dust supply, or supplier breadth collapses. Old market does not mean safe market.

Audit prompts

  • Which exact onchain fact proves the VELO market is past its dangerous opening state, and could any outsider reach meaningful interaction before that fact became true?
  • If the timelock is permissionless after expiry, what prevents an attacker from choosing the order of create market, seed market, and raise collateral factor transactions?
  • Was the intended seed actually non-redeemable, or did the design still assume the denominator could later return to attacker-owned dust once the ceremonial seed was burned away?
  • Which functions consume raw VELO balance as exchange-rate truth, and do any of those consumers become protocol-wide solvency inputs before launch hardening finishes?
  • What exact integer path turned 1.999994 wei of economically required burn into 1 wei, and where else does the same truncation become catastrophic under tiny supply?
  • Could the just-listed market have stayed deposit-live while borrow-side and liquidation authority remained disabled until a durable floor and real supplier breadth existed?
  • Which monitoring rule should have fired first: permissionless execution of the queued launch bundle, soVELO supply pinned at 2 wei, or the sudden VELO balance jump after donation?
  • If the team disappeared during the launch window, what contract state would still have forced the safe path instead of trusting the runbook?

Lessons that should already be standard

  • We know this bug family and have an ops runbook is not a mitigation if arbitrary actors can inherit the runbook’s sequencing rights.
  • A seed that is meant to be burned away was never the real defense. The real defense is an irreversible denominator that survives contact with production.
  • Governance listing is part of runtime security, not a harmless administrative prelude. The most dangerous launch state often begins exactly when the timelock matures.
  • Authorized rollout rights should stay narrower than live collateral authority. If a public timelock can take a market from queued to borrow-relevant before hardening is irreversible, the rollout path itself is part of the balance sheet.
  • Fresh-market risk is a solvency-boundary problem, not just a math problem. Listing order, collateral activation, donation handling, and burn rounding were one joined security object here.
  • New markets should earn systemwide trust in stages. Users can interact and sibling markets must trust this as collateral are different lifecycle states and should stay different.
  • Known exploit families need production-impossible-state monitoring, not just code comments and team memory. If 2 wei is enough to destroy the book, the protocol should scream before that state becomes borrow-relevant.

Why this incident matters

  • Sonne extends the empty-market exploit family beyond dormant legacy markets and LP-token corner cases into governance-created fresh listings.
  • It is a sharp reminder that we know this bug and have a runbook is not enough if the runbook depends on privileged sequencing that the system does not actually enforce.
  • It is also a strong negative exemplar for launch hardening that exists only as a ceremony. A seed position that can be skipped, or that the protocol plans to burn away immediately after launch, is not the kind of lasting supply floor this bug family actually requires.
  • The case broadens the historical record for Compound-v2 fork failures: not just empty markets, but empty markets that governance itself can accidentally expose at the exact moment they become borrow-relevant.

Internal linkages

Control surface