Moonwell: donation inflated exchange rate on thin-liquidity oracle
Published Oct 20, 2026
- Project
- Moonwell
- Chain
- Base
- Date
- Aug 27, 2026
- Loss
- $8.70M
- Class
- Oracle And Exchange Rate Manipulation
- Basis
- Project post-mortem or audit firm analysis
What happened
On August 27, 2026, Moonwell's MAMO lending market on Base was exploited for approximately $8.7 million. The attacker sent MAMO tokens directly to the mMAMO receipt-token contract instead of depositing through the normal mint function. This bypassed the protocol's supply cap and inflated the exchange rate, making each existing mMAMO share represent far more underlying MAMO. Simultaneously, the attacker bought MAMO across decentralized exchanges with very thin liquidity, driving the Chainlink oracle price up roughly 3,700%. By combining an inflated exchange rate with an inflated oracle price, the attacker created over $22 million in paper collateral and borrowed $11 million in cbBTC, WETH, USDC, and wstETH before liquidations could respond.
Why it was possible
Two independent design gaps compounded into a single failure. First, the supply cap introduced under governance proposal MIP-B48 only checked tokens flowing through the deposit function. It did not account for tokens transferred directly to the receipt-token contract, leaving the exchange rate unprotected against unsolicited donations. Second, the oracle used to price MAMO collateral derived its feed from the same thin on-chain liquidity the attacker was trading against. No secondary price source, time-weighted average, or deviation check existed to contest a sudden spike. Because both halves of the collateral calculation—the exchange rate and the asset price—could be manipulated by the same wallet in the same sequence of transactions, the protocol accepted fabricated collateral value as real borrowing power.
How to protect your protocol
- Enforce caps on actual token balance, not just deposits. A supply cap that only guards the mint path is incomplete. Before any operation that reads the exchange rate, verify that the receipt-token contract's actual underlying balance does not exceed the configured cap, regardless of how those tokens arrived.
- Cross-check spot prices against a TWAP or secondary oracle. Never rely solely on a spot-derived feed for collateral valuation when the underlying market has limited depth. Require a time-weighted average price or an independent secondary oracle, and halt borrowing if the two sources diverge beyond a safe threshold.
- Tie collateral factors and caps to measured market depth. A high collateral factor amplifies any price distortion. Set collateral factors, supply caps, and borrow caps proportionally to the asset's verifiable on-chain liquidity so that moving the price enough to matter requires capital exceeding the potential borrow.
- Alert on exchange-rate jumps without corresponding mints. Monitor the receipt-token exchange rate continuously. If it increases significantly without proportional deposit activity, pause borrows automatically and flag the event for review before further withdrawals are permitted.
- Sweep unsolicited transfers before they inflate shares. If direct transfers to the receipt-token contract cannot be blocked, sweep unexpected balances into a separate reserve or fee pool at the start of every state-changing function so they never enter the exchange-rate calculation.
Check your own code
- Does the supply cap check the contract's actual token balance rather than only the deposit function input?
- Is the collateral price feed sourced from a market deep enough to resist single-transaction manipulation?
- Is there a TWAP, secondary oracle, or deviation bound that can override a sudden spot-price spike?
- Are collateral factors and borrow caps sized relative to the asset's measurable on-chain liquidity?
- Can an external actor inflate the receipt-token exchange rate by sending tokens directly to the contract?
- Do monitoring systems alert when the exchange rate changes without corresponding mint or redeem events?
- Are unsolicited token transfers swept out of the accounting path before the exchange rate is read?
Sources
How this was prepared: compiled from the sources above with the help of automated tools, then reviewed by the Foothold security team before publication.
Give Atlant a task
Could the same thing happen in your code?
Get it checked →