Blueberry Protocol Lending Market PriceOracleProxy / CoreOracle Decimal-Mismatch Borrow Drain / Whitehat (2024-02-23)

  • Name: Blueberry Protocol lending market PriceOracleProxy / CoreOracle decimal-mismatch borrow drain / whitehat
  • Date: 2024-02-23
  • Protocol: Blueberry Protocol
  • Chain: Ethereum
  • Category: oracle configuration failure / unit-normalization failure / premature borrow activation / whitehat-preempted drain
  • Loss: about 91 ETH net lost to validator payments after whitehat interception; about 457 ETH was extracted in the rescue bundle and about 366.5 ETH was later returned
  • Summary: Blueberry unintentionally enabled borrowing before its planned launch and pointed the Compound-style lending market at the wrong oracle surface. The money-market PriceOracleProxy simply forwarded CoreOracle.getPrice() outputs that were normalized to 1e18, but Compound-family getUnderlyingPrice() consumers expect prices scaled by underlying-token decimals. That made sub-18-decimal borrow assets such as OHM (9 decimals), USDC (6), and WBTC (8) look drastically cheaper than they were. With only 1 WETH of collateral, the exploitable path could borrow roughly 8,616 OHM, 913,262 USDC, and 6.866901 WBTC. Whitehat c0ffeebabe.eth front-ran the blackhat using the same path, returned most funds, and the main unrecovered residue was the validator payment used to win ordering.
  • Trigger / entry point:
    • Blueberry’s team says borrowing was enabled unintentionally on 2024-02-22, earlier than the planned strategy launch.
    • The lending market used PriceOracleProxy from the money-market side, but that proxy simply returned baseOracle.getPrice(underlying) from the strategy-side CoreOracle.
    • Blueberry’s own postmortem says CoreOracle always returned prices scaled to 18 decimals, which was correct for the strategy system’s expectations but not for the Compound-style money market.
    • Because the proxy did not rescale by underlying-token decimals before handing the answer to the lending core, lower-decimal borrow assets were undervalued inside account-liquidity math.
    • The exploit did not require any market manipulation. Once the wrong oracle surface was live, ordinary supply-and-borrow flows monetized it immediately.
  • Root cause:
    • This was a deployment-side oracle integration failure, not a manipulation of external prices.
    • The decisive mistake was routing the money market through the strategy-side CoreOracle output format without adapting it to the money market’s dimensional expectations.
    • In practice, Blueberry mixed two internally valid oracle conventions: one interface that returned 1e18-scaled USD prices, and another lending path that expected price answers already normalized for underlying-token decimals.
    • The earlier-than-intended borrow enablement mattered because it exposed the bad oracle wiring before the team expected public borrowing or had fully completed the operational rollout around it.

Three oracle-authority promotions this incident collapsed

  1. A locally coherent strategy-side price object already existed

    • CoreOracle.getPrice() was not random garbage; it returned a consistent 1e18-scaled USD object that made sense inside Blueberry’s strategy-side accounting.
    • The weak first fact was only some internal price-producing object is live and reachable.
  2. That strategy-side object was over-promoted into canonical money-market borrow truth

    • PriceOracleProxy.getUnderlyingPrice() simply forwarded the strategy-side output as though the last dimensional translation step had already happened.
    • That stronger promotion silently answered the wrong question: not just do we have a USD price?, but do we already have the exact Compound-style underlying-decimal-normalized solvency object this market should honor for collateral and debt accounting?
  3. That accepted local truth still inherited immediate live borrowing authority

    • Borrowing went live before the protocol had quarantined or canary-checked the reused oracle path, so the dimensional mistake immediately became permissionless debt authority against real OHM, USDC, and WBTC pool liquidity.
    • The cash-out step was brutally ordinary: once the wrong local price shape counted as finished borrow truth, one normal collateral deposit and standard borrow flow were enough to drain the market.
  • Exploit path:
    1. Borrowing became live unintentionally before Blueberry’s intended launch window.
    2. A blackhat opportunity appeared: deposit a normal 18-decimal asset like WETH as collateral, then borrow underpriced lower-decimal assets whose oracle answers were missing the required decimal normalization.
    3. Whitehat c0ffeebabe.eth used the live exploit path first in tx 0xf0464b01d962f714eee9d4392b2494524d0e10ce3eb3723873afd1346b8b06e4.
    4. The bundle flash-borrowed 1 WETH, supplied it into Blueberry’s lending market, minted bWETH, and then borrowed approximately 8,616.071267266 OHM, 913,262.603416 USDC, and 6.866901 WBTC against what should only have been about 1 WETH of collateral.
    5. Those borrowed assets were swapped back into ETH, producing roughly 457 ETH of extracted value from the lending pools.
    6. Most funds were later returned to the Blueberry multisig, but about 91 ETH was consumed as validator payment needed to win ordering and keep the blackhat from landing first.
  • What the attacker needed to be true:
    • Borrowing had to be live before operators noticed the oracle mismatch and paused the market.
    • The money market needed to trust PriceOracleProxy.getUnderlyingPrice() as authoritative solvency truth without an end-to-end sanity check on decimal scale.
    • There had to be real pool liquidity in the undervalued borrow assets once the attacker posted correctly priced collateral.
    • The transaction had to land before remediation, which is why block ordering and validator payment became economically central to the outcome.
  • Technical anatomy worth remembering:
    • This is a clean wrong internal oracle surface incident. Blueberry did not need a manipulated market, a compromised feed, or a broken external publisher; it deployed the wrong local price shape into a live lending path.
    • It is also a useful contrast case for oracle-manipulation. The market prices for OHM, USDC, and WBTC were fine. The protocol’s own dimensional translation made them look cheap to borrow.
    • The bug preserved a classic Compound-family lesson: getUnderlyingPrice() is not just some USD number. The exact scale is part of the solvency boundary, and handing the lending core a syntactically valid but dimensionally wrong answer is enough to drain markets.
    • The incident also belongs in the launch / rollout discipline cluster. Blueberry’s own postmortem preserves that borrowing was enabled earlier than intended, so a configuration mismatch that might have remained an internal deployment bug became a live drain surface.
    • The whitehat outcome is operationally important too: even when defenders notice in time, saving users may still require paying validators so aggressively that part of TVL is burned as rescue cost.
  • Representative transactions and artifacts:
    • Whitehat rescue / exploit-path tx: 0xf0464b01d962f714eee9d4392b2494524d0e10ce3eb3723873afd1346b8b06e4
    • Whitehat address: 0xC0ffeEBABE5D496B2DDE509f9fa189C25cF29671
    • Exploit contract reported by third-party incident writeups: 0xE61ad5B0E40c856E6C193120Bd3fa28A432911B6
    • Borrowed assets called out by Blueberry’s postmortem: OHM, USDC, and WBTC
  • Response / mitigation:
    • Blueberry paused the affected lending markets after the incident.
    • The team publicly credited c0ffeebabe.eth, offered a 10% bounty on returned funds funded by Composable Corp rather than user balances, and said affected lenders would be made whole.
    • Blueberry’s remediation plan said the owner would be set immediately to multisig on deployment, the money-market oracle would be replaced with the proper implementation, and additional bug-hunting plus tighter monitoring would be added before relaunch.
    • The team also said borrowing would remain paused until the oracle issue was fixed and broader testing was complete.

Pattern tags

  • oracle-configuration-failure
  • unit-normalization-failure
  • The preserved failure shape is sharper than oracle glitch or borrowing opened too early: Blueberry’s money-market PriceOracleProxy forwarded a strategy-side getPrice() object that was locally valid at 1e18 precision but dimensionally wrong for Compound-style borrow accounting once the underlying token had fewer than 18 decimals.
  • This is also a clean wrong internal oracle surface became finished solvency truth exemplar. The protocol did not need manipulated prices or a broken external publisher; it selected the wrong local interface, then let that interface become live borrow authority before rollout safeguards caught up.

Why the exploit worked

  • Blueberry’s own postmortem and the published contracts make the trust boundary unusually legible. PriceOracleProxy.getUnderlyingPrice() simply returns baseOracle.getPrice(underlying), while the strategy-side CoreOracle documents getPrice() as a USD price multiplied by 10**18 and getTokenValue() then divides by the token’s own decimals. That is a coherent object for Blueberry’s strategy side, but not automatically the same object a Compound-style money market should consume as final borrow truth.
  • The exploit path therefore began with a local semantic mismatch, not with market manipulation. The money market treated the forwarded 1e18 price as if it already satisfied its own dimensional expectations, so lower-decimal borrow assets like OHM, USDC, and WBTC looked far cheaper than intended once account-liquidity math consumed the answer.
  • The early borrow enablement mattered because it removed the only remaining protection layer: operational sequencing. Blueberry’s postmortem says borrowing went live on 2024-02-22, before the intended Monday strategy launch, so the miswired oracle path moved straight from deployment mistake to public borrow surface.
  • That made the extraction path brutally ordinary. The whitehat only needed one flash-borrowed WETH, one collateral deposit, and the standard borrow flow. No callback trick, governance race, or external oracle move was required because the protocol itself had already published underpriced borrow-side assets into a live lending market.
  • The preserved design lesson is broader than check decimals. Internal interface selection, final price-object shape, and launch timing were one security object, and Blueberry treated them as separable implementation details until the money market started honoring the wrong one.

Detection and audit pivots

  • Reconstruct the exact final solvency object at the money-market boundary, not just the upstream oracle output. Here that means asking whether CoreOracle.getPrice(token) -> 1e18-scaled USD is the same dimensional object the lending side expects from getUnderlyingPrice(), especially for assets with non-18 decimals.
  • Audit interface reuse across protocol subsystems as if it were a price migration. A strategy-side oracle and a money-market oracle can both say USD price while still publishing different balance-sheet objects once one side assumes later decimal normalization and the other treats the answer as finished borrow truth.
  • Treat feature enablement as part of oracle review. If borrowing, liquidation, or leverage opens before the intended rollout window, a latent configuration mismatch has already become a live exploit surface even if the code itself was thought to be ready for Monday.
  • Compare final borrow-side prices against independent references before activation, with branch-local attention to low-decimal assets. If 1 WETH of collateral suddenly supports hundreds of thousands of dollars in USDC, WBTC, and OHM borrows, the system should fail closed before the first ordinary borrow succeeds.
  • Keep asset-local quarantine and fast pause controls close to fresh oracle paths. Blueberry’s own writeup preserves the operations lesson that a detector noticing the issue is not enough if public borrowing is already live and the rescue path must outbid the attacker by paying validator-level ordering costs.

Failure-prevention checklist

  • Write the money-market-side solvency object down explicitly before activation: for each listed asset, the protocol should be able to state that getUnderlyingPrice() means <underlying> / USD in the exact Compound-compatible scale implied by underlying decimals, not merely the proxy returns a USD price.
  • Treat internal oracle-surface reuse as a migration, even when both contracts live in the same codebase. A strategy-side getPrice() object should not become lending-side borrow truth until the adapter proves the same asset, quote, and decimal semantics the money market expects.
  • Keep branch-local unit tables and activation tests for low-decimal assets. USDC, WBTC, OHM, and other non-18-decimal assets should each prove their final borrow-side published value under the exact production path before borrowing opens.
  • Put plausible-magnitude checks at the publication boundary. If modest WETH collateral suddenly supports protocol-scale borrows of stronger assets, the system should fail closed before the first ordinary borrow succeeds.
  • Separate oracle wiring deployed successfully from borrowing may now go live. New oracle paths, reused oracle proxies, and launch-time parameter changes should clear a canary or borrow-disabled observation window before they receive ordinary debt authority.
  • Keep narrow emergency controls close to fresh oracle paths: per-market borrow disablement, collateral-factor zeroing, or asset-local pauses should beat a blackhat to the first ordinary extraction rather than forcing responders into validator-bribe triage.
  • Review emitted deployment artifacts and market-enable sequencing as security-critical inputs. A semantically wrong but ABI-valid oracle hookup plus an early borrow enable is already a balance-sheet publication event.

Audit prompts

  • What exact object does PriceOracleProxy.getUnderlyingPrice() publish into the Blueberry money market after every decimal and asset-identity assumption is resolved, and where is that object documented?
  • Starting from CoreOracle.getPrice(), which step is responsible for converting 1e18-scaled USD into the Compound-style underlying-decimal-normalized answer the lending side expects?
  • Do low-decimal assets like USDC, WBTC, and OHM each pass the same end-to-end solvency check under production wiring, or does one shared USD price label hide branch-local semantic drift?
  • If the upstream market prices are perfectly honest, what exact local mistake could still make borrowing unsafe here: wrong decimal exponent, wrong proxy surface, wrong asset binding, or borrow enablement before canary review?
  • What separates the oracle path now returns values from users may now borrow against those values at scale? If those are the same transition, what concrete control prevents one miswired launch from becoming immediate bad debt?
  • Which asset-local emergency control could have frozen the affected borrow markets faster than a searcher or whitehat bundle could consume the bad price path?
  • Are launch sequencing and feature toggles reviewed as part of oracle risk, or does the team only review contract math while assuming borrow enablement timing is an operational detail?

Lessons that should already be standard

  • Internal oracle interfaces are balance-sheet boundaries. Both return USD prices is not enough if one side still expects a different finished solvency object.
  • A valid upstream price plus the wrong local dimensional translation is still a full oracle exploit surface. The market can stay honest while the protocol lends against fiction.
  • Borrowing opened a little early is not merely bad rollout hygiene; if a fresh or miswired price path is live, early enablement is part of the exploit.
  • Shared interface names hide real semantic risk. getPrice() and getUnderlyingPrice() can look adjacent in code review while still describing different economic objects.
  • Low-decimal assets deserve explicit end-to-end tests at the lending boundary. The dangerous bug is often not price feed missing but published value is off by exactly the exponent the deepest pools care about.
  • Rescue outcomes do not erase the design failure. Blueberry still showed that a local oracle-wiring mistake can force defenders to pay validator-level ordering costs just to keep a blackhat from being first.
  • Activation sequencing is part of oracle correctness. A path that may be correct after one more review step is still unsafe if the protocol lets users borrow against it now.

Why this incident matters

  • Blueberry is a strong exemplar of oracle configuration failure where the protocol mixed two internal pricing conventions that were each locally reasonable but incompatible at the lending boundary.
  • It also preserves a useful rollout lesson: feature enabled early can be the exploit, even when the code path is already known and the final mitigation is straightforward.
  • The incident broadens the corpus’s oracle cluster with a case where the attacker did not need inflated collateral pricing; instead, the protocol cheapened the borrow side by under-scaling lower-decimal assets.

Internal linkages

Control surface