Hundred Finance hWBTC Empty-Market Exchange-Rate Exploit (2023-04-15)

  • Name: Hundred Finance hWBTC empty-market exchange-rate exploit
  • Date: 2023-04-15
  • Protocol: Hundred Finance
  • Chain: Optimism
  • Category: empty-market exchange-rate inflation / precision-loss redemption / cross-market lending drain
  • Loss: about $7.4 million
  • Summary: The attacker targeted Hundred Finance’s unused Optimism hWBTC market, whose age did not stop it from becoming effectively fresh again once supply had collapsed to dust. They drove receipt-token supply down to almost zero, then donated roughly 500 WBTC directly into the hWBTC contract so the exchange rate seen by collateral checks became enormous. In Compound-v2 math that inflation flowed through getCash() into exchangeRateStored, and therefore into getAccountSnapshot() / borrow power, even though no new hWBTC had been minted. With that distorted collateral, the attacker borrowed from deeper-liquidity sibling markets. A truncation bug in the redeemUnderlying path then let the attacker pull the donated WBTC back out while burning only 1 wei of hWBTC instead of the nearly 2 wei economic reality required. The attacker finally liquidated the residual position to reset the market and repeated the pattern across other pools.
  • Trigger / entry point:
    • The key target was Optimism hWBTC at 0x35594E4992DFefcB0C20EC487d7af22a30bDec60, an effectively empty market that could be driven to a tiny totalSupply.
    • Hundred’s exchange-rate math trusted the market’s raw WBTC balance, so direct transfers into hWBTC increased getCash() and therefore the quoted exchange rate without minting new hWBTC.
    • The redeemUnderlying math rounded down when converting underlying amount to receipt tokens to burn, which became exploitable when the market had only a few wei of hWBTC outstanding.
  • Root cause:
    • Hundred left an effectively unused duplicate Optimism WBTC market live as borrow-relevant collateral even after it had drifted back into launch-dangerous dust supply, giving the attacker an isolated empty-state scaffold rather than a mature market with durable outside supply.
    • Its receipt-token exchange rate could be inflated by direct underlying transfers because raw balance changes flowed straight into solvency-critical accounting: getCash() fed exchangeRateStored, and exchangeRateStored fed getAccountSnapshot() for collateral value.
    • redeemUnderlying ultimately converted underlying amount back into hWBTC shares with floor-rounded Exp math (div_ / truncate() style behavior), so a required burn just under 2 wei became a burn of only 1 wei.
    • In short: the protocol let a nearly zero-supply market define borrow power for the rest of the system, then compounded that design flaw with rounding behavior that favored the attacker.
  • Exploit path:
    1. The attacker, publicly tracked at 0x155da45d374a286d383839b1ef27567a15e67528, opened with a 500 WBTC Aave v3 flash loan in transaction 0x6e9ebcdebbabda04fa9f2e3bc21ea8b2e4fb4bf4f4670cb8483e2f0b2604f451.
    2. They redeemed prior hWBTC so the market’s totalSupply returned to zero, then used a fresh helper contract to mint 200 hWBTC with 4 WBTC.
    3. They redeemed almost the entire position, leaving just 2 wei of hWBTC outstanding.
    4. They transferred roughly 500.30063816 WBTC directly into hWBTC, which did not mint any new shares but made each remaining wei of hWBTC represent an absurd amount of WBTC on paper.
    5. Using that distorted collateral value, the attacker entered the market through the Unitroller 0x5a5755E1916F547D04eF43176d4cbe0de4503d5d and borrowed from sibling pools, beginning with about 1021 ETH from hETH / crETH at 0x1A61A72F5Cf5e857f15ee502210b81f8B3a66263.
    6. The decisive cash-out came when the attacker called redeemUnderlying for roughly 500.30063815 WBTC. Economic reality required burning almost 2 wei of hWBTC, but truncation rounded the burn down to 1 wei.
    7. That left one residual wei of hWBTC, which the attacker then used in liquidation / reset steps so the market could be emptied again and the same pattern repeated against additional pools such as SNX, USDC, DAI, USDT, sUSD, and FRAX.
    8. A second major drain transaction, 0x15096dc6a59cff26e0bd22eaf7e3a60125dcec687580383488b7b5dd2aceea93, continued the extraction before the flash loan was repaid.
  • What the attacker needed to be true:
    • hWBTC had to be empty or controllably near-empty so tiny receipt-token balances could dominate the exchange-rate math.
    • Direct WBTC transfers into the market had to affect the exchange rate immediately through raw getCash() accounting.
    • Hundred had to accept inflated hWBTC collateral quickly enough to allow borrowing from deeper-liquidity sibling markets.
    • Redemption math had to truncate in the attacker’s favor once the market reached the extreme 2 wei supply state.
    • The sibling markets needed enough real liquidity to turn fake hWBTC solvency into withdrawable assets.
  • Technical anatomy worth remembering:
    • The donation step alone was not the whole exploit. The real money came from pairing exchange-rate inflation with precision-loss redemption and then using the manipulated market as collateral elsewhere.
    • This was not a conventional oracle attack. The protocol’s own internal exchange-rate formula became the printable price: getCash() increased after the donation, exchangeRateStored jumped, and getAccountSnapshot() treated the attacker’s dust hWBTC balance as massive collateral.
    • The decisive arithmetic edge sat in redeemUnderlying: after the donation, redeeming about 500.30063815 WBTC implied burning roughly 1.99999992 wei of hWBTC, but floor truncation rounded that down to 1.
    • Empty-market state was decisive. At normal supply levels, the same truncation would have been nuisance-level noise rather than a multi-million-dollar drain.
    • The exploit also shows why duplicate, unused, or operationally forgotten markets are dangerous in lending forks: they can become attack scaffolding for stealing from active pools.
  • Representative transactions and artifacts:
    • Attacker EOA: 0x155da45d374a286d383839b1ef27567a15e67528
    • First main exploit tx: 0x6e9ebcdebbabda04fa9f2e3bc21ea8b2e4fb4bf4f4670cb8483e2f0b2604f451
    • Second main exploit tx: 0x15096dc6a59cff26e0bd22eaf7e3a60125dcec687580383488b7b5dd2aceea93
    • hWBTC: 0x35594E4992DFefcB0C20EC487d7af22a30bDec60
    • hETH / crETH: 0x1A61A72F5Cf5e857f15ee502210b81f8B3a66263
    • Unitroller: 0x5a5755E1916F547D04eF43176d4cbe0de4503d5d
    • Aave v3 flash-loan pool used in public reproductions: 0x794a61358D6845594F94dc1DB02A252b5b4814aD
  • Response / mitigation:
    • Hundred publicly warned that the issue was a general Compound-v2-fork weakness rather than a one-off deployment error.
    • The incident quickly became the reference case for auditing empty markets, seed liquidity, and precision behavior across Compound-style forks.
    • Later fork incidents in 2023 and 2024 showed that many teams still had not hardened empty-market handling or tiny-supply redemption math.

Three solvency-authority promotions this incident collapsed

  1. An abandoned-looking market was still allowed to become attacker-owned denominator state

    • The dangerous fact was not merely that hWBTC existed. It was that an old side market could drift back into an effectively fresh state, return to microscopic supply, and still remain a live collateral surface.
    • Once the attacker reduced totalSupply to 2 wei, they effectively controlled the denominator that would decide what the next unit of WBTC meant for supplier-side value.
  2. That attacker-shaped denominator was still promoted into canonical collateral truth

    • Direct WBTC donation into hWBTC raised raw getCash().
    • Hundred then treated that raw-balance increase as ordinary exchange-rate truth through exchangeRateStored, and exported the result through getAccountSnapshot() as if the printed hWBTC mark were economically backed collateral rather than attacker-authored thin-market state.
  3. That published collateral truth still deserved immediate reserve-spending authority elsewhere

    • Liquid sibling markets accepted the distorted hWBTC mark quickly enough for the attacker to borrow real assets against it.
    • The later redeemUnderlying under-burn and liquidation/reset loop mattered because the protocol had already crossed the decisive line: an attacker-authored dust-market mark still retained live cross-market borrowing and reserve-extraction authority.

The durable lesson is that the market is almost empty again, the next balance change defines supplier truth, and that printed truth can still spend sibling liquidity are three separate security promotions. Hundred failed all three.

Pattern tags

  • empty-market-exchange-rate-inflation
  • fresh-market-initialization-failure
  • The preserved shape is sharper than generic donation attack or generic rounding bug: Hundred let an effectively fresh-again side market collapse to 2 wei of hWBTC supply, let direct raw-balance donation redefine the market’s printed exchange rate over that microscopic denominator, and then let the same printed mark become immediate borrow power against deeper sibling markets.
  • This was also a clean old market != mature market failure. The dangerous state was not brand-new deployment; it was dust-supply reserve whose next unit of donated balance still decides what collateral means.

Why the exploit worked

  • Hundred allowed an abandoned-looking side market to keep full cross-market collateral authority after it had already drifted back into an effectively fresh state. Once supply was reduced to 2 wei, the protocol no longer had a real outside depositor base; it had one attacker-owned denominator defining what the next unit of WBTC would mean.
  • In that state, direct donation stopped being passive balance noise and became solvency authorship. getCash() read the raw WBTC balance, exchangeRateStored transformed that into supplier-side value, and getAccountSnapshot() then exported the printed result as ordinary collateral truth to the rest of the protocol.
  • The rounding bug completed the cash-out path rather than standing alone. Without the under-burn in redeemUnderlying, the attacker could have printed absurd paper collateral but would have had a harder time recovering the donated WBTC; without the empty-market donation path, the truncation would have stayed economically small. The exploit only paid because the protocol joined tiny-supply state, raw-balance-sensitive pricing, and attacker-favorable burn math into one pipeline.
  • Downstream borrowing had no second distrust boundary. There was no rule saying an effectively fresh, dust-supply, or impossible-growth market could stay locally observable while still being unable to define borrow power for liquid sibling pools.
  • The preserved lesson is therefore broader than watch for donation attacks: market lifecycle, balance-accounting semantics, redemption arithmetic, and cross-market collateral admission all belonged to one security object, and Hundred split them apart.

Detection and audit pivots

  • Ask the lifecycle question directly: what onchain fact proves a market has durable supplier breadth, and what demotes it back into quarantine if supply later collapses toward dust? If the answer is only this market is old, the control does not exist.
  • Reconstruct the whole exchange-rate publication path, not only the redemption bug: underlying balance -> getCash() -> exchangeRateStored -> getAccountSnapshot() -> collateral checks should tell one coherent story about economically backed value before the market can matter system-wide.
  • Force direct donations into low-supply and duplicate markets during testing. If unsolicited balance increases can change borrowable exchange rate faster than the protocol can mint proportional shares, quarantine the market or separate donation handling from solvency math.
  • Simulate redeemUnderlying, liquidation, and seize flows at pathological denominators like 1 wei, 2 wei, and other dust-supply states. If the only thing keeping the bug small is nobody should ever get here, the exploit surface still exists.
  • Check for downstream distrust boundaries. Can a dust-supply market remain listed while collateral factor stays 0, borrow caps stay negligible, or emergency demotion paths automatically remove its ability to define cross-market solvency before one fake mark drains liquid sibling pools?

Failure-prevention checklist

  • Reclassify dormant markets as effectively fresh when supplier breadth collapses. If totalSupply or meaningful outside supply falls back toward attacker-ownable dust, automatically demote collateral factor, borrow caps, or market status before the next donation can become systemwide solvency truth.
  • Seed non-burnable minimum liquidity that cannot be redeemed away later. The protocol should never be able to return to a 2 wei denominator where one actor owns the meaning of the next donated unit of WBTC.
  • Keep raw-balance changes from directly redefining collateral truth. Donations, rescue transfers, and other off-path balance increases should either mint proportional claims, route to a quarantined bucket, or stay excluded from exchange-rate inputs until reviewed.
  • Treat redeemUnderlying burn math as treasury logic at pathological denominators. If redeeming attacker-sized underlying in a dust-supply market can round ~1.99999992 wei of required burn down to 1 wei, the market is not safe to leave borrow-relevant.
  • Separate local accounting observability from cross-market solvency authority. A market that is new, duplicate, dormant, or impossible-growth should be allowed to exist only behind collateral-factor 0, tiny borrow caps, or another borrow-side quarantine until the denominator is no longer attacker-owned.
  • Fuzz the full empty-market exploit pipeline, not isolated steps: supply collapse, direct donation, exchange-rate publication, sibling-market borrowing, redeem-under-burn, and liquidation/reset loops should all be tested as one joined cash-out path.
  • Inventory duplicate and forgotten markets as security liabilities. A side market that nobody uses is not harmless clutter if it can still publish collateral marks into active sibling pools.

Audit prompts

  • What exact onchain condition proves this market is mature enough to define borrow power, and what exact condition demotes it back into effectively-fresh quarantine if supply later collapses?
  • Can direct underlying transfers into the receipt-token contract still flow straight into getCash(), exchangeRateStored, account snapshots, or other borrow-relevant marks without minting proportional new supplier claims?
  • At 1 wei, 2 wei, or similarly tiny supply states, how many receipt-token units should redeemUnderlying burn economically, and where does floor division let the protocol burn less than that?
  • If the market is duplicate, long-unused, or operationally forgotten, why is it still allowed to define collateral truth for liquid sibling pools instead of staying locally visible but cross-market quarantined?
  • Which controls would stop the attack after the donation but before sibling-market borrowing: supply-floor guards, collateral-factor demotion, per-market borrow caps, impossible-growth alerts, or automatic pause on exchange-rate jumps?
  • Can liquidation or reset steps recycle the last residual receipt-token wei so the same market can be used repeatedly inside one exploit sequence?
  • What invariant proves that every meaningful increase in supplier-side collateral value came from matched accounting paths rather than an attacker-chosen denominator plus unsolicited balance growth?

Lessons that should already be standard

  • An old market can become a fresh-market bug again. Age is not a security property when supplier breadth has already collapsed back into dust.
  • Direct donation is already collateral authorship if raw balance feeds exchange rate. Calling it just a transfer is how protocols let attackers publish borrow truth without minting proportional claims.
  • Tiny-supply rounding is not a cosmetic precision issue. Once one or two receipt-token wei represent real money, floor division becomes treasury policy.
  • Cross-market lending turns local accounting lies into protocol-wide bad debt. A dust-supply side market should never get to define borrow power for the rest of the book without extra distrust boundaries.
  • Duplicate or forgotten listings are latent launch surfaces. If a market can be the first meaningful-supplier market again, it needs launch-grade hardening again.
  • The exploit family is one joined object: denominator control, exchange-rate publication, under-burn, and sibling-market liquidity access either fail together or defend together.

Why this incident matters

  • Hundred is the canonical demonstration that an almost-empty receipt-token market can become a protocol-wide solvency weapon, even when the market is old enough to look operationally harmless.
  • It ties together three lessons that often get reviewed separately: effective-freshness quarantine for dormant markets, raw-balance donation risk, and tiny-supply integer truncation.
  • It also helped surface a broader fork ecosystem problem: security assumptions that were tolerable in mature, liquid core Compound markets became dangerous when copied into thinly used side markets and then left borrow-relevant after drifting back into dust-supply conditions.

Internal linkages

Control surface