All audits
FailedOptimismIndependent

Sonne Empty Market (2024)

Smart contract security audit by TierZero · 2024-05-15

Independent research. This is an unsolicited security analysis published by TierZero — not a commissioned audit, and no certificate is issued. It reflects our own review of public code and on-chain events.

Findings summary

1
Critical
1
High
2
Medium
1
Low
1
Informational

Independent research — not a commissioned audit. Prepared from public post-mortems, on-chain transaction data, and community writeups; no non-public information from the Sonne team was used.

What the protocol did

Sonne Finance was a Compound V2-style money market deployed on Optimism, later extended to Base. Users supplied assets into isolated cToken markets, received interest-bearing receipt tokens in return, and could borrow against that collateral up to a market-specific loan-to-value ratio. Like most Compound forks, each market's share price — its "exchange rate" between the receipt token and the underlying asset — was derived directly from the market's on-chain balance sheet rather than from an external price feed. That design choice is standard for Compound V2 and normally safe once a market has meaningful, organic supply. In May 2024, an attacker found a market where that precondition didn't hold.

The vulnerability

The bug class here is the empty-market share-price (exchange-rate) inflation attack, the same primitive behind first-depositor attacks on ERC-4626 vaults. In Compound V2's cToken model:

// simplified Compound V2 pattern
exchangeRate = (getCashPrior() + totalBorrows - totalReserves) / totalSupply;

When totalSupply of a market's receipt token is at or near zero — a newly listed market, or one that has been fully redeemed — this formula becomes unstable. An attacker who holds a trivially small amount of the receipt token can inflate getCashPrior() (the market's raw underlying balance) by transferring tokens directly into the market contract, bypassing the normal mint() path entirely. Because the transfer never mints new receipt tokens, totalSupply stays tiny while cash jumps, and exchangeRate spikes by orders of magnitude. Anyone still holding even a dust amount of the receipt token now controls collateral that the protocol values far above what was actually deposited.

// pseudocode: the donation
market.mint(1 wei)                 // totalSupply = 1
underlying.transfer(market, HUGE)  // cash += HUGE, totalSupply unchanged
// exchangeRate = cash / totalSupply  -> blown out of proportion

This is fundamentally an invariant failure: the protocol implicitly assumes totalSupply and cash move together, and never checks that assumption before using their ratio to price collateral.

How the exploit worked

  1. The attacker identified a market with negligible totalSupply — a low-liquidity or newly seeded market, the classic "empty market" condition.
  2. They minted a minimal position in that market, then donated a large amount of the underlying asset directly to the market contract via a raw transfer, inflating cash without touching totalSupply.
  3. The resulting exchange-rate spike meant the attacker's small receipt-token balance was now valued by the protocol at many multiples of what was actually deposited.
  4. Using that inflated collateral valuation, the attacker borrowed heavily against other, healthy markets in the same pool — draining real assets that legitimate suppliers had deposited elsewhere.
  5. Funds were moved out; public reporting placed assets at risk in the region of $20M at the time of the incident, with a portion later returned under a negotiated white-hat arrangement and net losses reported materially lower. Exact final figures vary by source and this report does not restate disputed numbers as fact.

The mechanics mirror other Compound-fork incidents in the same period — the common thread is always a market whose share-price formula was trusted before it had enough organic depositors to make manipulation economically irrational.

How an audit catches this

This is not a subtle bug once you know to look for it, which is exactly why it belongs in a standard checklist rather than relying on discovery by chance:

  • Invariant test: for every market, assert that exchangeRate cannot move by more than a bounded percentage per block from a transfer-only donation with no corresponding mint. Fuzz totalSupply down to 1 wei and confirm the formula degrades safely, not catastrophically.
  • Minimum-liquidity seeding: require protocol-owned or locked initial liquidity in every new market before it accepts external deposits or is usable as collateral — mirroring the dead-share pattern now standard in ERC-4626 implementations.
  • Direct-transfer resistance: verify the market's cash accounting uses a tracked internal balance rather than token.balanceOf(address(this)), so unsolicited transfers can't feed the exchange-rate calculation at all.
  • Market listing review: treat every new market addition as a privileged, security-relevant action requiring the same scrutiny as a contract upgrade, not a routine governance formality.
  • Cross-market blast-radius modeling: since isolated markets in a shared pool still share solvency, model what happens to every other market if one market's price oracle or exchange rate is fully corrupted.

We run this exact class of check as a standing item in our smart contract audit engagements, and it's one of the first things we test on any lending or vault fork before looking at anything protocol-specific. The same root cause — trusting a ratio before its denominator has enough real participants — shows up outside EVM lending markets too; see our note on cross-program invocation risk in Solana DeFi for the equivalent failure mode in a different execution environment.

Remediation

The durable fix is architectural, not a patch: new markets should launch with an irrevocable minimum deposit that establishes a non-trivial totalSupply floor before the market is usable as collateral, and cash accounting should track deposits internally instead of reading raw token balances. Where a fork inherits the original Compound V2 exchange-rate formula unmodified, treat every newly listed market as untrusted collateral until it has accumulated organic supply past a defined threshold, and cap the LTV of thinly supplied markets separately from mature ones. Teams shipping permit-based approval flows alongside lending markets should also revisit our writeup on Permit2 gasless approvals and EVM sniping-bot exposure — the underlying lesson is the same: any mechanism that lets value move without the normal accounting path deserves a dedicated adversarial review before mainnet.

If you're forking a lending protocol or adding new markets to one already live, get the exchange-rate and listing logic in front of us before you ship — that's exactly the kind of check a smart contract audit is built to catch.

Need your contracts audited?

Manual review + tooling across TON, Solana and EVM — certificate and full report included.

Get an audit