Moonwell cbETH/USD Oracle Misconfiguration / Bad-Debt Liquidations (2026-02-15)

  • Name: Moonwell cbETH/USD oracle misconfiguration / bad-debt liquidations
  • Date: 2026-02-15
  • Protocol: Moonwell
  • Chain: Base core market, via governance changes that activated additional OEV wrappers across Base and Optimism
  • Category: oracle configuration failure / governance misconfiguration / liquidation cascade / bad debt
  • Loss: approximately $1.78M of protocol bad debt and 1,096.317 cbETH seized from borrowers
  • Summary: On 2026-02-15, Moonwell executed governance proposal MIP-X43, which activated Chainlink OEV wrapper configurations for remaining markets. One Base-side cbETH configuration treated the raw cbETH / ETH exchange rate feed as though it were already a USD price. That collapsed Moonwell’s reported cbETH price from roughly $2,200 to about $1.12, and liquidation bots immediately converted the error into mass collateral seizures. Borrowers lost most or all of their cbETH collateral while still retaining residual debt, a smaller set of opportunistic users borrowed underpriced cbETH against trivial collateral, and the protocol was left with about $1.78M of bad debt.
  • Trigger / entry point:
    • MIP-X43 executed at 2026-02-15 18:01 UTC, enabling Chainlink OEV wrapper contracts across Moonwell core markets on Base and Optimism.
    • The proposal bundle is worth reading at the commit level, not just the merged PR headline. In commit 0898069b7e49b60e98cca0903c04f0ef96695ca3, proposals/ChainlinkOracleConfigs.sol already contained the Base mapping OracleConfig("cbETHETH_ORACLE", "cbETH", "MOONWELL_cbETH") while the same config family still showed the intended Optimism-side CHAINLINK_cbETH_USD_COMPOSITE_ORACLE path.
    • In later commit 318d67818a01ecc4404e90e67fc37e46db27fe40, Moonwell commented out several composite-oracle routes with the note Composite oracles don't support latestRound(), deferred to follow-up, but the Base cbETHETH_ORACLE -> MOONWELL_cbETH mapping remained live.
    • mip-x43.sol then read OracleConfig[] memory oracleConfigs = getOracleConfigurations(...), deployed cbETHETH_ORACLE_OEV_WRAPPER, and wired CHAINLINK_ORACLE.setFeed(symbol, wrapperAddress) for the market symbol. In other words, the proposal mechanically proved wrapper deployment and feed wiring, then published the wrong semantic object with no extra normalization step.
    • That left the Base cbETH market wired to a single-feed OEV wrapper around the raw cbETH / ETH exchange-rate oracle rather than to a composed cbETH / ETH * ETH / USD dollar price.
    • The resulting oracle answer was about 1.12, which was correct as a cbETH / ETH ratio but catastrophically wrong as a USD price.
  • Root cause:
    • Moonwell misconfigured a composite asset price during an oracle-wrapper migration. The protocol needed cbETH / ETH * ETH / USD and instead used only the first leg.
    • This was not a case of attackers manipulating the underlying cbETH market. The false price came from Moonwell’s own configuration and became authoritative because the lending markets treated it as final collateral truth.
    • The failure is best understood as an oracle-integration and governance-review breakdown: a lower-level exchange-rate feed was substituted into a slot that required a normalized dollar price.
    • The proposal’s validation path was also semantically too weak for this migration. mip-x43.sol validated that each symbol pointed at an OEV wrapper and that each wrapper pointed at the configured source feed, but those checks only proved wiring correctness; they did not prove that MOONWELL_cbETH resolved to a USD-denominated price rather than an exchange-rate ratio.
    • The commit history sharpens the review failure further: the broader config family already contained composite-oracle entries, and the later proposal revision explicitly deferred several of them because they lacked latestRound(). That means the dangerous tradeoff was not hidden at runtime; it was visible in source as we are shipping the simpler lower-level feed now and postponing the semantically safer composite path.
    • The damage was amplified by governance timing. Moonwell could quickly reduce caps, but correcting the oracle itself still required a five-day governance vote and timelock.
  • Exploit path:
    1. Moonwell governance executed MIP-X43, activating additional OEV wrappers and deploying the faulty cbETH oracle configuration.
    2. Moonwell’s cbETH market on Base began valuing cbETH at about $1.12 instead of roughly $2,200.
    3. Liquidation bots immediately repaid tiny amounts of user debt and seized cbETH collateral at a near-total discount, effectively paying about $1 of debt to seize 1 cbETH.
    4. Borrowers lost most or all of their cbETH collateral, yet many positions still retained residual debt because the liquidation repayment amount was tiny relative to the true borrowed value.
    5. A smaller set of opportunistic users moved the other direction, supplying minimal collateral and borrowing cbETH at the fake oracle price, which created additional cbETH-denominated bad debt.
    6. At 18:05 UTC, Moonwell’s monitoring stack and Anthias Labs detected the discrepancy and reduced the cbETH market’s supply and borrow caps to 0.01, preventing new meaningful borrowing and deposits.
    7. The wrong oracle price itself could not be corrected immediately because governance still needed to push a follow-up proposal through a five-day vote and timelock.
  • What the attacker needed to be true:
    • The protocol had to accept the misconfigured cbETH answer as the solvency-critical price for collateral and borrowing.
    • Liquidations and borrowing had to remain permissionless, so bots could monetize the wrong price faster than governance could patch it.
    • The cbETH market had to have enough open borrowing and collateral activity for the false price to crystallize into both liquidations and bad debt.
    • Moonwell needed no onchain sanity check that a cbETH price of $1.12 was dimensionally absurd relative to the market and to the prior answer.
  • Technical anatomy worth remembering:
    • The decisive mistake was dimensional, not economic: Moonwell used a ratio feed where it needed a dollar price.
    • MIP-X43 is a useful source artifact because the proposal code shows the exact configuration surface that changed. In ChainlinkOracleConfigs.sol, Base added OracleConfig("cbETHETH_ORACLE", "cbETH", "MOONWELL_cbETH") while composite oracle routes were deferred.
    • That detail matters because it shows the real integration hazard: OEV-wrapper rollout was not just a generic wrapper activation, but a migration that swapped in lower-level oracle primitives with different semantics.
    • The proposal’s own validation logic in mip-x43.sol only checked that CHAINLINK_ORACLE.getFeed(symbol) now pointed to the new wrapper and that each wrapper’s priceFeed() matched the configured oracle source. That meant the governance pipeline could successfully prove cbETH -> cbETHETH_ORACLE_OEV_WRAPPER -> cbETHETH_ORACLE while still missing the more important semantic question: whether the terminal value was actually a USD price.
    • The incident also preserves a second-order lesson about governance latency. Moonwell could freeze market activity quickly by cutting caps, but could not repair the broken price path at the same speed because the fix itself lived behind governance.
    • The protocol’s own postmortem records two distinct monetization paths: unfair liquidations against existing borrowers and over-borrowing by new opportunistic users exploiting the underpriced cbETH market.
    • This is also a useful no single exploit contract case study. The value extraction was spread across permissionless liquidators and opportunistic borrowers using ordinary protocol entrypoints after governance itself had already published the wrong price as collateral truth. The highest-signal forensic artifacts are therefore the proposal commits, Moonwell’s public bad-debt spreadsheet, and the remediation dashboard rather than one canonical attacker transaction.
  • Representative contracts and change surfaces:
    • Governance proposal: MIP-X43 / GitHub PR #578 — Add MIP-X43: Activate OEV wrappers for all remaining markets
    • Proposal config file: proposals/ChainlinkOracleConfigs.sol
    • Proposal execution / validation file: proposals/mips/mip-x43/mip-x43.sol
    • Pre-merge proposal commit showing both the bad Base mapping and the intended composite-oracle model elsewhere in the config family: 0898069b7e49b60e98cca0903c04f0ef96695ca3
    • Later proposal commit that kept OracleConfig("cbETHETH_ORACLE", "cbETH", "MOONWELL_cbETH") while commenting out several composite routes as deferred to follow-up: 318d67818a01ecc4404e90e67fc37e46db27fe40
    • Base configuration path introduced in the proposal: OracleConfig("cbETHETH_ORACLE", "cbETH", "MOONWELL_cbETH")
    • Base OEV wrapper address added in chains/8453.json: cbETHETH_ORACLE_OEV_WRAPPER = 0x67996D1ff7a3711a91E2839d1059Fcb950C0495D
    • Proposal merge commit: 9a6738e189fa903bffa0abf10b9dd7ed2fe73a18
    • Proposal wiring step worth preserving: _wireCoreFeeds() pushed CHAINLINK_ORACLE.setFeed(symbol, wrapperAddress) for each configured symbol after wrapper deployment
    • Market-freeze response: cbETH market supply and borrow caps reduced to 0.01 after detection
    • Public forensic spreadsheet referenced by Moonwell / Anthias: cbETH incident 2/15/26
    • Public remediation site referenced by Moonwell / Anthias: https://cbeth-remediation.vercel.app/
  • Impact summary worth preserving:
    • 1,096.317 cbETH seized by liquidators
    • Total bad debt: about $1,779,044.83
    • Full asset-level bad-debt breakout published in Moonwell’s incident summary:
      • 467.7555896 cbETH / $1,033,393.71
      • 239.6643802 WETH / $478,998.02
      • 232,607.2797 USDC / $232,584.02
      • 9,719.35397 EURC / $11,566.03
      • 0.16685141 cbBTC / $11,442.17
      • 5,481.145179 cbXRP / $7,947.66
      • 1,520.178946 DAI / $1,520.03
      • 1,053.312414 USDS / $1,052.15
      • 642.4149872 AERO / $204.87
      • 126.2302566 MORPHO / $171.67
      • 0.06824221 wstETH / $164.49
    • Moonwell and Anthias also published a spreadsheet of materially impacted user positions, unfair liquidations, and cbETH over-borrow addresses plus a dedicated remediation dashboard, which makes this incident unusually transparent despite not having one canonical exploit transaction.
  • Response / mitigation:
    • Anthias Labs and Moonwell detected the discrepancy within about four minutes and cut the affected market’s supply and borrow caps to 0.01.
    • Moonwell prepared a follow-up governance patch to correct the faulty oracle path, but the actual oracle repair still had to wait for governance voting and timelock completion.
    • The incident later fed into remediation planning, user-impact dashboards, and community reimbursement discussions.

Pattern tags

Why the exploit worked

  • The migration validated the wrong property. MIP-X43’s checks proved that MOONWELL_cbETH now pointed at an OEV wrapper and that the wrapper pointed at the intended underlying feed, but they never proved that the final answer was denominated in USD rather than in cbETH / ETH.
  • Moonwell let a newly rewired price path become immediate collateral, borrow, and liquidation truth. There was no canary period, shadow-mode comparison, or borrow-side quarantine between new wrapper is live and liquidators may trust this output.
  • The protocol had no strong semantic or range sanity check at the publication boundary. A collateral asset worth about $2,200 was suddenly accepted at about $1.12, and the system treated that as a normal price update instead of a category error.
  • Liquidators and opportunistic borrowers did not need to manipulate cbETH, ETH, or the oracle network. Once Moonwell itself published the wrong number as money-good truth, permissionless actors only had to use the protocol as designed.
  • Governance latency finished the exploit path. Caps could be cut quickly, but the actual oracle correction still lived behind a slower vote and timelock, so the protocol’s fastest response path was containment rather than immediate truth repair.

Detection and audit pivots

  • For every listed collateral asset, ask what the final solvency unit is supposed to be: USD price, ETH ratio, stETH ratio, share price, exchange rate, or something else. Then verify that the object actually published into liquidation logic matches that unit exactly.
  • Treat wrapper migrations and feed substitutions as semantic migrations, not just address rewires. Test the composed path end to end against expected quote currency, decimals, reciprocal behavior, and economically plausible range.
  • Keep a shadow oracle or pre-migration reference path running long enough to diff new output versus old output before granting the new path full borrow and liquidation authority.
  • Add publication-boundary sanity checks for category errors: if a large-cap collateral asset suddenly reprices from four digits to about 1, or a ratio feed is being consumed where a dollar feed is expected, fail closed rather than publish.
  • Separate feed changed successfully from market may now liquidate on it. Borrow caps, collateral factors, or liquidation permissions should stay constrained until the new path survives monitoring and reconciliation.
  • Review governance validation code for semantic completeness. wrapper points to source feed is not the same as market receives the right economic object.

Failure-prevention checklist

  • Treat composite-oracle rollout as a balance-sheet migration, not a wiring chore. Governance proposals should prove the final published object, quote currency, decimals, and branch-local semantics before activation, not only that wrappers and source feeds are connected.
  • Keep ratio feeds and dollar feeds in different typed configuration lanes where possible. If a market expects asset / USD, a raw asset / ETH or asset / wrapper answer should fail schema validation unless the missing quote leg is explicitly composed.
  • Gate new oracle paths behind canary or shadow mode before they receive full liquidation authority. A newly wired wrapper should first survive reference diffs, plausibility bands, and operator review while borrow caps, collateral factors, or liquidation permissions remain constrained.
  • Encode dimensional sanity checks near publication. Large-cap collateral should not be allowed to reprice from the low thousands to about 1 without failing closed, paging operators, or automatically freezing the market.
  • Review governance validation code for semantic completeness. wrapper points to feed and feed address is correct are necessary but insufficient if the market still expects a different economic object at the output boundary.
  • Keep emergency containment faster than semantic repair. If the real oracle fix must traverse vote and timelock latency, the protocol still needs a scoped fast path to freeze borrow and liquidation relevance immediately when a new answer is category-wrong.
  • Rehearse wrapper migrations against independent references and historical output snapshots. The useful question is not only does the new path return a number but does it still return the same market truth as the old path under realistic states.

Audit prompts

  • What exact object is this market supposed to publish into solvency logic: cbETH / USD, cbETH / ETH, wrapper share price, capped ratio, or something else?
  • Which contract or governance artifact is responsible for composing the final quote leg, and what proves that composition was not silently skipped during rollout?
  • If a wrapper path is changed today, where does the protocol compare old answer, new answer, and an independent reference before liquidations are allowed to trust the new branch?
  • Can governance or deployment automation bind a semantically valid lower-level feed into a slot that expected a different final unit object while still passing validation?
  • What range checks make dimensionally absurd but syntactically valid outputs unpublishable, especially for blue-chip or large-cap collateral?
  • Which emergency controls can immediately narrow borrow caps, collateral factors, or liquidation permissions when a newly deployed oracle path diverges from prior truth?
  • Does the protocol distinguish oracle branch is live from oracle branch is allowed to trigger liquidations, or are those still the same state transition?
  • If a migration defers a safer composite path because it lacks one helper interface such as latestRound(), what alternate proof shows the fallback branch still publishes the right market object rather than a locally correct but economically incomplete ratio?

Lessons that should already be standard

  • A correctly addressed feed can still be the wrong answer. The decisive question is the final economic object the market publishes, not whether the source contract was real.
  • Oracle-wrapper migrations are solvency events. Swapping a composite path for a lower-level primitive can liquidate users even when no external market was manipulated.
  • Governance review must prove semantics, not only plumbing. wrapper -> source feed validation is weaker than market still receives the intended USD collateral price.
  • Newly activated oracle paths should not get instant liquidation authority. Shadow mode, canaries, caps, and borrow-side quarantine are not optional ceremony when the migration can change units or quote legs.
  • Category errors deserve fail-closed treatment. A collateral asset repricing from thousands of dollars to about 1 is usually not volatility; it is a dimensional mistake until proven otherwise.
  • Fast containment and slow repair are different controls. Moonwell could shrink caps quickly, but the deeper lesson is that every governance-bound oracle system still needs a faster path to stop wrong truth from liquidating users.
  • Permissionless users do not need to manipulate a market if the protocol misprices itself. Once Moonwell published fiction as liquidation truth, bots only had to act normally.

Why this incident matters

  • It is a clean example of a protocol self-inflicting an oracle disaster without any underlying market manipulation.
  • It shows how wrapper migrations and oracle-upgrade programs can silently change the dimensional meaning of a feed, even when the code review topic sounds operationally routine.
  • It also preserves the governance-time mismatch: once a wrong price is live, permissionless liquidators run on millisecond time, while DAO repair often runs on day-scale time.

Internal linkages

Control surface