This is a lower bound: only assets with reliable historical USD prices are counted, so the actual loss may be higher.
0x6e9ebcdebbabda04fa9f2e3bc21ea8b2e4fb4bf4f4670cb8483e2f0b2604f4510x5a5755e1916f547d04ef43176d4cbe0de4503d5dOptimism0x35594e4992dfefcb0c20ec487d7af22a30bdec60OptimismOn 2023-04-15, the adversary drained multiple Hundred Finance Optimism markets by abusing the empty-market hWBTC listing as collateral. The public setup phase ended at block 90761917, where the attacker-controlled orchestrator already held the full hWBTC supply (1,503,167,295 raw shares) and the hWBTC market still held 30,064,194 satoshis of WBTC cash. In seed transaction 0x6e9ebcdebbabda04fa9f2e3bc21ea8b2e4fb4bf4f4670cb8483e2f0b2604f451 at block 90761918, the attacker flash-borrowed WBTC, manufactured dust hWBTC collateral, donated WBTC directly into the market to inflate the exchange rate, then borrowed or liquidated across hSNX, hUSDC, hDAI, hUSDT, hSUSD, hETH, and hFRAX.
The root cause is a Compound-v2-style empty-market donation flaw. Hundred's CErc20 implementation floors the number of cTokens burned when redeeming a chosen amount of underlying, and its stored exchange-rate calculation trusts raw underlying.balanceOf(address(this)). In a near-empty market that combination lets an attacker keep dust shares outstanding, make those shares appear extremely valuable with a direct donation, and then use the fictitious collateral value inside Hundred's borrow-capacity and liquidation math.
The exploit predicate in root_cause.json is strictly monetary. The adversary contract 0x978d0ce23869ec666bfde9868a8514f3d2754982 was valued at $9,128.5265130930 before the seed tx and $6,714,751.589791875073173541387 after it, for a net gain of $6,705,623.063278782073173541387 after of fees. The valuation note in the evidence attributes the before-state to the pre-seed hWBTC position and the after-state to the post-tx balances in SNX, USDC, DAI, USDT, sUSD, FRAX, ETH, and WBTC, all priced with Hundred's public oracle inputs at block .
$7,590.8753708028899190761917Hundred's Optimism money markets are Compound-v2-style cTokens governed by a Unitroller/Comptroller. The hWBTC market is the critical collateral market in this incident: the market proxy is 0x35594e4992dfefcb0c20ec487d7af22a30bdec60, its CErc20 implementation is 0x7100cbca885905f922a19006cf7fd5d0e1bbb26c, and the Comptroller implementation is 0x8279b109d3a8a61fcdb2532f08e14e763a064be1.
The important accounting rule is that exchangeRateStored() is derived from (cash + borrows - reserves) / totalSupply. In Compound-style markets, cash is whatever the underlying token contract reports as the cToken's current balance. That means a direct underlying transfer into the cToken contract raises cash even if no mint occurs. When totalSupply is tiny, each remaining raw share absorbs a disproportionate fraction of that donated value.
The attacker's public pre-state was built with four public transactions before the seed tx:
0xf479b1f397080ac01d042311ac5b060ceccef491867c1796d12ad16a8f12a47e: mint 3,190,800 raw hWBTC shares with 63,816 satoshis.0x771a16e02a8273fddf9d9d63ae64ff49330d44d31575af3dff0018b04da39fcc: mint 1,499,976,495 raw hWBTC shares with 30,000,000 satoshis.0xdeb82968c10dc0a42cfb0a8099dec22a63c5e09a731b4bf3ac137094e6d1c193: prepare the final exploit structure.0x7db7c6d1a9d8778c328ee926ff6662723d1577d9fdcb9a131ef531652b65b977: transfer the full 1,503,167,295 hWBTC-share balance into the orchestrator contract.At that point the exploit was permissionless. Any unprivileged searcher able to observe the same public state could submit the same seed transaction against the same public liquidity and listed markets.
This incident is an ATTACK, not a pure MEV arbitrage, because it exploits a broken collateral-accounting invariant inside Hundred's inherited Compound logic. The invariant that should have held is: a chosen redeem amount must burn enough cToken supply that the remaining supply still represents the remaining cash fairly, and direct donations must not turn dust shares into valid high-value collateral.
The verified CErc20 source shows exactly why the invariant fails:
function getCashPrior() internal view returns (uint) {
EIP20Interface token = EIP20Interface(underlying);
return token.balanceOf(address(this));
}
function redeemFresh(address payable redeemer, uint redeemTokensIn, uint redeemAmountIn) internal returns (uint) {
(vars.mathErr, vars.exchangeRateMantissa) = exchangeRateStoredInternal();
...
(vars.mathErr, vars.redeemTokens) =
divScalarByExpTruncate(redeemAmountIn, Exp({mantissa: vars.exchangeRateMantissa}));
...
}
getCashPrior() makes raw donations count as protocol cash, while divScalarByExpTruncate rounds the burn down when the attacker redeems by underlying amount. The verified exchange-rate function confirms that the donation feeds directly into collateral valuation:
function exchangeRateStoredInternal() internal view returns (MathError, uint) {
uint _totalSupply = totalSupply;
...
uint totalCash = getCashPrior();
(mathErr, cashPlusBorrowsMinusReserves) = addThenSubUInt(totalCash, totalBorrows, totalReserves);
(mathErr, exchangeRate) = getExp(cashPlusBorrowsMinusReserves, _totalSupply);
return (MathError.NO_ERROR, exchangeRate.mantissa);
}
Hundred's Comptroller then trusts that inflated exchange rate in both liquidity checks and liquidation math:
vars.tokensToDenom = mul_(mul_(vars.collateralFactor, vars.exchangeRate), vars.oraclePrice);
vars.sumCollateral = mul_ScalarTruncateAddUInt(vars.tokensToDenom, vars.cTokenBalance, vars.sumCollateral);
...
uint exchangeRateMantissa = CToken(cTokenCollateral).exchangeRateStored();
seizeTokens = mul_ScalarTruncate(ratio, actualRepayAmount);
That chain of logic is the concrete breakpoint. Once hWBTC supply falls to dust and raw WBTC is donated into the market, the Comptroller interprets dust shares as enormous collateral, authorizes full-market borrows, and later lets the attacker seize the final dust share for a negligible repayment.
The vulnerable components identified in the validated root cause are:
0x7100cbca885905f922a19006cf7fd5d0e1bbb26c via redeemFresh and exchangeRateStoredInternal0x8279b109d3a8a61fcdb2532f08e14e763a064be1 via getHypotheticalAccountLiquidityInternal and liquidateCalculateSeizeTokens0x35594e4992dfefcb0c20ec487d7af22a30bdec60, which remained listed as borrowable collateral despite effectively having no real liquidityThe security principles violated are equally concrete:
The exploit unfolds in three deterministic stages.
First, the orchestrator starts the seed transaction from the fully primed public state and redeems its preloaded 1,503,167,295 hWBTC shares for 30,063,816 satoshis of WBTC. That resets hWBTC supply to zero while leaving only 378 satoshis of residual market cash.
Second, each helper contract manufactures dust collateral. A representative helper mints hWBTC with 4 WBTC, receives 20,000,000,000 raw hWBTC shares, and then redeems almost all of them. The collector's helper timeline shows the critical dust state exactly:
{
"step": "hWBTC redeem by helper_1",
"logIndex": 16,
"details": {
"redeemAmount": "400000000",
"redeemTokens": "19999999998",
"redeemer": "helper_1"
},
"totalSupply": "2",
"cash": "378",
"helper_1_hWBTC_balance": "2"
}
After that large redeem, only two raw hWBTC shares remain. The helper then donates roughly 500.30063816 WBTC directly into hWBTC, which raises market cash to 50,030,064,194 satoshis while total supply stays at 2. No mint occurs during this step, so the stored exchange rate spikes artificially.
Third, the helper realizes the fake collateral value. In the hUSDC loop, the decoded receipt shows the exact borrow, dust redeem, and liquidation sequence:
[
{
"logIndex": 86,
"emitter_label": "hUSDC",
"event": "Borrow",
"borrowAmount": "1233516758776"
},
{
"logIndex": 91,
"emitter_label": "hWBTC",
"event": "Redeem",
"redeemAmount": "50030063816",
"redeemTokens": "1"
},
{
"logIndex": 100,
"emitter_label": "hUSDC",
"event": "LiquidateBorrow",
"repayAmount": "282",
"seizeTokens": "1",
"cTokenCollateral": "0x35594e4992dfefcb0c20ec487d7af22a30bdec60"
}
]
This is the decisive failure mode. The helper borrows the target market against the donated dust collateral, then uses redeemUnderlying(...) semantics to recover almost the full donated WBTC balance while burning only one raw share. That leaves the helper insolvent with one last dust share. The orchestrator repays a tiny amount in the borrowed market, seizes the final hWBTC share through liquidation, redeems that seized share, and forwards the recovered WBTC into the next helper loop. The same primitive repeats across hETH, hSNX, hUSDC, hDAI, hUSDT, hSUSD, and hFRAX.
The exploit conditions are also explicit from the evidence:
These conditions held on Optimism block 90761917, so the seed transaction was a live ACT opportunity from that public state.
The adversary cluster identified in the evidence is:
0x155da45d374a286d383839b1ef27567a15e67528: sent the setup transactions, deployed the orchestrator, and submitted the seed tx.0x978d0ce23869ec666bfde9868a8514f3d2754982: held the primed hWBTC balance, initiated the Aave flash loan, coordinated the helper loops, and retained stolen assets.0xd340f2208edbbbeca4fe4893d76eaaa5711e702b: executed the first dust-collateral loop against hETH.The validated victim-side addresses are:
0x5a5755e1916f547d04ef43176d4cbe0de4503d5d0x35594e4992dfefcb0c20ec487d7af22a30bdec60The end-to-end adversary lifecycle is:
0x6e9ebcdebbabda04fa9f2e3bc21ea8b2e4fb4bf4f4670cb8483e2f0b2604f451, which pulls an Aave WBTC flash loan.4 WBTC, redeems almost all shares, and leaves two dust shares behind.The stage evidence in root_cause.json is also consistent on timing and mechanics:
0xf479...47e, 0x771a...fcc, 0xdeb8...193, and 0x7db7...977 at blocks 89017327, 90301503, 90741684, and 9076076690761918The representative markets and helper outputs captured in the collector artifacts are consistent with the reported losses: hETH, hSNX, hUSDC, hDAI, hUSDT, hSUSD, and hFRAX were each drained down to dust or near-dust balances.
Hundred lost roughly $6.71 million in value, and the exploit left multiple helper borrowers with unrecoverable bad debt after their last hWBTC dust shares were seized. The raw on-chain loss amounts reported in the evidence are:
20000006040813679379832 with 18 decimals1233516758494 with 6 decimals842788494009886029179569 with 18 decimals1113430652679 with 6 decimals865142911064170347497066 with 18 decimals1021915074224867122534 with 18 decimals457286054364794404672674 with 18 decimals30063817 with 8 decimalsOperationally, the attack drained the lendable cash of at least seven Hundred borrow markets, left the protocol with bad debt tied to insolvent helper borrowers, and transferred the extracted assets into the adversary cluster after flash-loan repayment.
0x6e9ebcdebbabda04fa9f2e3bc21ea8b2e4fb4bf4f4670cb8483e2f0b2604f451/workspace/session/artifacts/collector/seed/10/0x6e9ebcdebbabda04fa9f2e3bc21ea8b2e4fb4bf4f4670cb8483e2f0b2604f451/metadata.json0x35594e4992dfefcb0c20ec487d7af22a30bdec600x7100cbca885905f922a19006cf7fd5d0e1bbb26c0x8279b109d3a8a61fcdb2532f08e14e763a064be1/workspace/session/artifacts/collector/iter_8/tx/10/0x6e9ebcdebbabda04fa9f2e3bc21ea8b2e4fb4bf4f4670cb8483e2f0b2604f451/pre_attack_sequence_summary.json/workspace/session/artifacts/collector/iter_8/tx/10/0x6e9ebcdebbabda04fa9f2e3bc21ea8b2e4fb4bf4f4670cb8483e2f0b2604f451/helper_loop_matrix.json/workspace/session/artifacts/collector/iter_8/tx/10/0x6e9ebcdebbabda04fa9f2e3bc21ea8b2e4fb4bf4f4670cb8483e2f0b2604f451/helper_hWBTC_state_timelines.json/workspace/session/artifacts/collector/iter_2/tx/10/0x6e9ebcdebbabda04fa9f2e3bc21ea8b2e4fb4bf4f4670cb8483e2f0b2604f451/receipt_decoded_grouped.json