All audits
FailedEthereumIndependent

Cream Finance yUSD (2021)

Smart contract security audit by TierZero · 2023-03-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
2
High
2
Medium
1
Low
1
Informational

Independent research — not a commissioned audit. This report is TierZero's own post-mortem analysis of a publicly documented incident, prepared for educational purposes. We had no involvement with Cream Finance and no non-public information.

What the protocol did

C.R.E.A.M. Finance (Cream) was a Compound-derived money market: users supplied assets into pooled cTokens, borrowed against them, and interest accrued algorithmically. Where Cream differed from Compound was risk appetite — it listed long-tail and "exotic" collateral that mainline markets wouldn't touch, including LP tokens and yield-bearing vault shares from Yearn. One of those listings was crYUSD, a Cream market collateralized by Yearn's yUSD vault token, which itself wrapped the LP token of Curve's Y pool (a basket of yDAI/yUSDC/yUSDT/yTUSD).

Every layer here compounds pricing complexity: the value of one yUSD share depends on the vault's pricePerShare, which depends on the Curve pool's internal balances, which depend on the pool's bonding curve math. Cream's oracle had to resolve all of that down to a single USD figure it could trust inside one lending transaction. In February 2021, it didn't resolve it safely, and an attacker used that gap to drain roughly the mid-tens-of-millions of dollars from Cream's pools in a single transaction.

The vulnerability

The bug class is spot-price collateral oracle manipulation via flash loan — one of the most consistently exploited patterns in DeFi lending, and the direct ancestor of many issues we still flag in smart contract audits today. The flaw is architectural, not a typo: the oracle computed collateral value by reading a derived, on-chain, real-time price (the vault's share price) at the moment of the borrow, with no time-weighting, no bounds checking, and no resistance to the caller temporarily distorting the inputs within the same transaction.

// Illustrative — the flawed pattern, not the literal Cream source
function getUnderlyingPrice(CToken cToken) external view returns (uint256) {
    address underlying = cToken.underlying();          // yUSD vault share
    uint256 pricePerShare = IYearnVault(underlying).getPricePerFullShare();
    uint256 basePrice = getCurveVirtualPrice(curvePool); // also spot-read
    return (pricePerShare * basePrice) / 1e18;            // no TWAP, no sanity bound
}

Both getPricePerFullShare() and the Curve pool's virtual price are legitimate, non-buggy functions in isolation. The problem is that Cream treated their instantaneous output as ground truth for collateral valuation, when both values can be pushed within a single block by anyone with enough capital — and flash loans make "enough capital" free.

How the exploit worked

  1. The attacker took out a large flash loan of stablecoins/ETH — no upfront capital required.
  2. Using the borrowed funds, they interacted with the underlying Curve pool and Yearn vault in a sequence that skewed the pool's internal balance ratio and, downstream, the reported share price / virtual price that Cream's oracle consumed.
  3. With the price now inflated, the attacker supplied a comparatively small amount of yUSD collateral into Cream's crYUSD market.
  4. Cream's oracle read the manipulated price in the same transaction and valued that collateral far above its real market worth.
  5. Against this phantom collateral value, the attacker borrowed the maximum available liquidity across Cream's other lending pools — draining real, unrelated assets other users had supplied.
  6. The attacker unwound the pool manipulation and repaid the flash loan within the same transaction. Collateral value snapped back to normal; the borrowed assets did not.
  7. Cream was left holding an under-collateralized position and a large bad debt that legitimate liquidators could never realistically close, because the "collateral" backing it had already reverted to its true, much lower value.

The entire attack was atomic — one transaction, no intermediate risk, no chance for anyone to intervene mid-exploit.

How an audit catches this

This is exactly the class of finding that separates a checklist pass from a real review. Concretely, we check for:

  • Oracle source classification. Every price feed a lending or margin system depends on gets traced to its root: is it a Chainlink-style external feed, a TWAP, or a spot read off an AMM/vault contract that the caller can influence in-transaction? Any spot-read collateral price is flagged regardless of how many wrapper layers sit on top of it.
  • Single-transaction invariant tests. We write fork tests that simulate a flash-loan-funded actor manipulating the target pool/vault immediately before a borrow call, then assert the protocol either rejects the borrow or bounds the price move. This is the same discipline we describe for state-machine and account-integrity invariants in our piece on formal verification for trading programs — model the adversarial transaction, don't just test the happy path.
  • Composability depth review. Each additional wrapped layer (vault → LP → pool → underlying) is an additional manipulation surface, and we explicitly enumerate all of them rather than trusting the outermost interface. The same "trust boundary per hop" logic applies whether the composability risk sits in an EVM vault stack or, as we cover separately, in cross-program invocation chains on Solana — the chain differs, the failure mode doesn't.
  • Borrow-cap and utilization sanity checks. Even with a perfect oracle, a market that lets a single borrower drain the majority of pooled liquidity in one call is a design smell worth flagging as a standalone finding.

Remediation

The fix pattern that the ecosystem converged on after this incident (and its many successors) is now table stakes: use time-weighted or externally attested prices for any collateral whose valuation depends on AMM or vault internals, reject price inputs that move beyond a sane bound within a single block, and cap the value any single position can borrow regardless of reported collateral worth. Protocols that list composable, yield-bearing collateral — the exact pattern we also discuss in the context of gasless approval flows and EVM MEV exposure — need an oracle review that goes one layer deeper than "does this feed exist," because the question that matters is "can this feed be moved by the caller before the feed is read."

If your protocol prices collateral off an LP token, vault share, or any other on-chain derived value, this is precisely the failure mode a proper smart contract audit is built to catch before deployment.

Need your contracts audited?

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

Get an audit