Alpha Homora V2 Hidden sUSD Pool / Zero-Share Debt Inflation Exploit (2021-02-13)
-
Name: Alpha Homora V2 hidden sUSD pool / zero-share debt inflation exploit
-
Date: 2021-02-13
-
Protocol: Alpha Homora V2
-
Chain: Ethereum
-
Category: debt-share desynchronization / zero-share borrow inflation / hidden-pool fresh-market exploit / bad-debt drain
-
Loss: about $38 million extracted, leaving Alpha Homora with tens of millions of dollars of debt to Iron Bank
-
Summary: The attacker found that Alpha Homora V2 had an unannounced
sUSDbank deployed onchain ahead of any public release. Because that bank had no existing borrowers, the attacker could become the sole debt-share holder, then repay down to1debt share and1mini-sUSD of debt. A publicly callableresolveReserve()call increasedtotalDebtwithout increasingtotalDebtShare, creating a state where borrowing amounts just belowtotalDebtmapped to0new debt shares after floor rounding. The attacker then repeatedly borrowed realsUSDwhile the protocol recorded no new borrower liability, used flash-swapped USDC to deepen Iron BanksUSDliquidity, and finally turned Alpha’s protocol-level Iron Bank credit line into about$38Mof realWETH,DAI,USDC, andUSDToutflows. -
Trigger / entry point:
- Alpha Homora V2 had an
sUSDpool at the contract level “in preparation for the upcoming release,” but it was neither exposed in the UI nor publicly announced. HomoraBankV2.execute()accepted arbitrary spells as long as the final collateral check passed, so the attacker could deploy a custom spell contract and route the whole exploit through it.- The attacker used a
UNI-WETHLP position deposited intoWERC20as collateral so the bank’s end-of-callcollateralValue >= borrowValuecheck would not block setup. - Because the
sUSDbank had no other borrowers, the attacker became the first and only debt-share holder.
- Alpha Homora V2 had an
-
Root cause:
- Alpha left an empty, reachable
sUSDdebt market live onchain before it was operationally hardened or publicly launched. - The debt-share accounting let a carefully chosen borrow amount round down to
0new debt shares whentotalDebthad been made much larger thantotalDebtShare. resolveReserve()could be called by anyone and increasedtotalDebtwithout increasingtotalDebtShare, which was exactly the mismatch the attacker needed.- The spell system treated arbitrary spell code as acceptable so long as the final collateral check passed, which let the attacker automate borrow / repay / transfer choreography inside Alpha’s privileged protocol context.
- Alpha’s integration with Iron Bank meant the accounting failure did not stay local: fake internal
sUSDborrowing power could be converted into real external assets borrowed on Alpha’s protocol credit line.
- Alpha left an empty, reachable
-
Exploit path:
- The attacker deployed an “evil spell” contract at
0x560a8e3b79d23b0a525e15c6f3486c6a293ddad2in tx0x2b419173c1f116e94e43afed15a46e3b3a109e118aba166fcca0ba583f686d23. - In tx
0x4441eefe434fbef9d9b3acb169e35eb7b3958763b74c5617b39034decd4dd3ad, the attacker swapped ETH intoUNI, formed aUNI-WETHLP position for collateral, swapped more ETH intosUSD, and depositedsUSDinto Cream’s Iron Bank to getcysUSD. - In tx
0xcc57ac77dc3953de7832162ea4cd925970e064ead3f6861ee40076aca8e7e571, they calledexecute()through the evil spell, opened position883, borrowed1000e18sUSD, and became the first borrower with1000e18debt shares. - In tx
0xf31ee9d9e83db3592601b854fe4f8b872cecd0ea2a3247c475eea8062a20dd41, they repaid1000000098548938710983sUSDagainst an actual debt of1000000098548938710984, leaving exactly1mini-sUSD of debt and1debt share. - In tx
0x98f623af655f1e27e1c04ffe0bc8c9bbdb35d39999913bedfe712d4058c67c0e, they calledresolveReserve()on thesUSDbank. That accrued19709787742196debt units to reserves while leavingtotalShareat1, producingtotalDebt = 19709787742197andtotalShare = 1. - With that skewed ratio in place, the attacker re-entered
execute()and repeatedly borrowedtotalDebt - 1-style amounts. Because each borrow was just below the debt-per-share threshold, the calculated borrow share rounded down to0, so Alpha transferred realsUSDbut recorded no new debt shares. Alpha’s postmortem highlights tx0x2e387620bb31c067efc878346742637d650843210596e770d4e2d601de5409e3as a 16-iteration doubling loop and tx0x64de824a7aa339ff41b1487194ca634a9ce35a32c65f4e78eb3893cc183532a4as a further 10-iteration doubling loop. - Once the attacker needed deeper
sUSDliquidity, they flash-borrowed USDC from Aave, swapped it intosUSD, deposited thatsUSDinto Iron Bank, and resumed the zero-share doubling path in txs0x7eb2436eedd39c8865fcc1e51ae4a245e89765f4c64a13200c623f676b3912f9,0xd7a91172c3fd09acb75a9447189e1178ae70517698f249b84062681f43f0e26e, and0xacec6ddb7db4baa66c0fb6289c25a833d93d2d9eb4fbe9a8d8495e5bfa24ba57. - After manufacturing enough apparent credit, the attacker borrowed real assets through Alpha’s Iron Bank relationship. Alpha’s postmortem attributes roughly
13,244.63 WETH,4.263M DAI,3.998M USDC,5.647M USDT, and an additional426,659 USDCdraw to the exploit, with the multi-asset cash-out centered in tx0x745ddedf268f60ea4a038991d46b33b7a1d4e5a9ff2767cdba2d3af69f43eb1b. - The attacker then moved stablecoins into Aave and Curve’s
a3Crvsystem, staked the LP in tx0xc60bc6ab561af2a19ebc9e57b44b21774e489bb07f75cb367d69841b372fe896, and later routed funds through Tornado Cash while sending1,000 ETHeach to Alpha’s and Cream’s deployer addresses.
- The attacker deployed an “evil spell” contract at
-
What the attacker needed to be true:
- The hidden
sUSDbank had to remain callable onchain even though Alpha had not surfaced it in the UI. - No other borrower could hold meaningful
sUSDdebt shares; otherwise the attacker could not cheaply dominatetotalDebtShare. - Borrow-share minting had to floor-round downward rather than forcing a minimum
1share for any nonzero debt increase. resolveReserve()had to be public and able to increasetotalDebtwithout increasingtotalDebtShare.- Alpha had to honor custom spells so long as the final collateral check passed.
- Iron Bank had to extend protocol-level credit to Alpha so the printed internal debt state could be externalized into real borrowable assets.
- The hidden
-
Technical anatomy worth remembering:
- This was not an oracle hack and not a classic callback bug. It was a share-accounting failure in the debt side of a lending market.
- The decisive invariant was
totalDebtstaying proportional tototalDebtShare. Once Alpha let reserves raisetotalDebtwhile the attacker still owned all shares, one extra debt share became extremely expensive and anything below that threshold could round to zero. - The hidden-pool detail matters. From Alpha’s UI perspective the
sUSDbank was not live; from Ethereum’s perspective it was live enough to become the exploit surface. - Alpha had recently added an
onlyEOAgate intended to hinder flash-loan-style attacks. That forced a multi-transaction exploit, but it did not fix the underlying accounting bug. - The exploit also shows how protocol-to-protocol credit lines magnify local bugs. Alpha’s broken debt accounting became Iron Bank bad debt.
-
Representative transactions and artifacts:
- Attacker EOA:
0x905315602ed9a854e325f692ff82f58799beab57 - Evil spell:
0x560a8e3b79d23b0a525e15c6f3486c6a293ddad2 - Alpha Homora V2 proxy:
0x090ece252cec5998db765073d07fac77b8e60cb2 - Spell deployment:
0x2b419173c1f116e94e43afed15a46e3b3a109e118aba166fcca0ba583f686d23 - Initial setup / collateral and
sUSDpreparation:0x4441eefe434fbef9d9b3acb169e35eb7b3958763b74c5617b39034decd4dd3ad - First borrow into position
883:0xcc57ac77dc3953de7832162ea4cd925970e064ead3f6861ee40076aca8e7e571 - Near-total repay to
1debt share:0xf31ee9d9e83db3592601b854fe4f8b872cecd0ea2a3247c475eea8062a20dd41 - Reserve-resolution skew step:
0x98f623af655f1e27e1c04ffe0bc8c9bbdb35d39999913bedfe712d4058c67c0e - Zero-share borrow doubling loops:
0x2e387620bb31c067efc878346742637d650843210596e770d4e2d601de5409e3,0x64de824a7aa339ff41b1487194ca634a9ce35a32c65f4e78eb3893cc183532a4,0x7eb2436eedd39c8865fcc1e51ae4a245e89765f4c64a13200c623f676b3912f9,0xd7a91172c3fd09acb75a9447189e1178ae70517698f249b84062681f43f0e26e,0xacec6ddb7db4baa66c0fb6289c25a833d93d2d9eb4fbe9a8d8495e5bfa24ba57 - Multi-asset cash-out:
0x745ddedf268f60ea4a038991d46b33b7a1d4e5a9ff2767cdba2d3af69f43eb1b - Curve gauge parking:
0xc60bc6ab561af2a19ebc9e57b44b21774e489bb07f75cb367d69841b372fe896
- Attacker EOA:
-
Response / mitigation:
- Alpha paused new borrowing on Homora V2 while investigations proceeded, and Iron Bank temporarily cut Alpha’s credit line to
0while pausing and then re-enabling its own markets after checking they were functioning normally. - Alpha’s patched bank restricted
execute()to whitelisted spells, maderesolveReserve()governor-only, and limited borrow / repay assets toETH,DAI,USDC, andUSDT. - The incident ultimately became a long-lived protocol-to-protocol debt problem between Alpha and Iron Bank, not just an instant one-block loss.
- Alpha paused new borrowing on Homora V2 while investigations proceeded, and Iron Bank temporarily cut Alpha’s credit line to
Three debt-authority promotions this incident collapsed
-
A hidden, not-yet-released bank still counted as live debt-share genesis state
- The dangerous fact was not merely that an
sUSDpool contract existed. It was that Alpha left an onchain-reachable bank with full borrowing semantics live before any honest borrower distribution, so the attacker could become the one address that defined what a debt share meant. - Once the attacker repaid down to
1debt share and1mini-sUSD of debt, the bank’s entire borrower-side denominator had effectively become attacker-owned singleton state.
- The dangerous fact was not merely that an
-
That attacker-owned singleton state was still promoted into canonical debt truth
resolveReserve()then raisedtotalDebtwithout raisingtotalDebtShare, which meant Alpha promoted a reserve-side bookkeeping mutation into canonical borrower-side debt meaning even though the only meaningful share owner was the attacker.- After that skew,
borrow()calls just below the one-share threshold could mint0new debt shares while still moving realsUSD, so Alpha was no longer publishingdebt amountanddebt ownershipas the same security object.
-
That published debt fiction still deserved external balance-sheet authority
- The exploit only became a protocol-scale loss because Alpha still treated the corrupted
sUSDdebt state as good enough to support real borrowing through its Iron Bank credit line. - In other words, a hidden bank with attacker-authored share semantics and zero-share borrow growth still retained authority to spend external
WETH,DAI,USDC, andUSDTliquidity rather than being quarantined as impossible local accounting.
- The exploit only became a protocol-scale loss because Alpha still treated the corrupted
The durable lesson is that the bank exists onchain, its debt/share ratio still defines canonical borrower truth, and that published truth still deserves upstream credit authority are three separate promotions. Alpha collapsed all three.
Why this incident matters
- It preserves a lending-market exploit family that is easy to miss if you only look for price or callback failures: the debt side can be just as forgeable as the collateral side.
- It is a strong example of hidden or prelaunch markets still being production attack surface. If users cannot see a pool but contracts can, attackers can usually reach it first.
- It shows how public reserve-sync helpers and share-based accounting compose into a mint primitive when market state is empty.
- It also broadens the empty-market corpus beyond supplier-side exchange-rate or
liquidityIndexinflation: here the attacker weaponized empty borrower state and debt-share math instead.
Pattern tags
- zero-share borrow inflation
- Fresh-market initialization failure
- Helper-path invariant reuse and fail-closed discipline
Why the exploit worked
- Alpha let an onchain-reachable but operationally “not live yet”
sUSDbank inherit full production authority before there was any honest borrower distribution, so the attacker could own essentially all debt-share meaning from the start. resolveReserve()was a real balance-sheet mutation, not harmless maintenance. Because it could raisetotalDebtwithout minting matchingtotalDebtShare, it created the exact impossible debt/share ratio that later borrows could exploit.- Borrow-share conversion used downward floor rounding with no
nonzero borrow => nonzero debt sharebackstop. Once one share represented an absurd amount of debt, Alpha still honored borrows that minted0new borrower liability. - The spell framework expanded blast radius because arbitrary attacker-written control flow could sequence borrow, repay, reserve-resolution, and collateral choreography inside Alpha’s privileged lending context as long as the final collateral snapshot looked acceptable.
- Iron Bank turned a local accounting lie into systemwide loss. Alpha did not merely misprice internal debt; it could externalize that fake capacity into real
WETH,DAI,USDC, andUSDTborrowed against its protocol credit line.
Detection and audit pivots
- Review every borrowable market that exists onchain but is hidden from the UI, paused, deprecated, or prelaunch. If contracts remain reachable, ask who can become the first or only meaningful debt-share holder before operators consider the market live.
- Trace all functions that can change aggregate debt outside the main borrow path, especially reserve syncs, fee accrual, socialized repayment helpers, and emergency debt tooling. Any path that mutates debt amount without proportional debt-share movement is solvency code.
- Fuzz
totalDebtShare == 1, dust debt, andborrow just below one-share thresholdstates. A single successfulnonzero borrow mints 0 debt sharestestcase should be treated as a critical failure. - Model plugin, spell, and strategy frameworks as privileged accounting surfaces rather than mere UX helpers. The right question is whether arbitrary user-authored call choreography can string together individually-valid debt mutations into one exploit bundle.
- Follow externalization paths, not just local pool math. If a manipulated local debt ledger can tap an upstream credit line, sibling pools, or shared treasury liquidity, the real risk is balance-sheet theft at protocol scope rather than isolated market bad debt.
Failure-prevention checklist
- Do not leave hidden, UI-dark, or prelaunch borrow markets callable with production semantics. If a bank is not live, borrowing, reserve helpers, and custom strategy entry should not be live either.
- Enforce
nonzero borrow => nonzero debt-share mintas a hard invariant. If a borrow amount would round to0debt shares, revert instead of lending anyway. - Treat reserve-accrual helpers like
resolveReserve()as balance-sheet code. If they can movetotalDebt, they must also preserve meaningfultotalDebt : totalDebtSharecoupling under singleton-share and dust-debt states. - Re-test debt accounting under adversarial first-borrower conditions: one borrower, one debt share, dust debt, and repeated
borrow(totalDebt - 1)-style thresholds should all fail safely. - Keep spell, plugin, and strategy systems on a whitelist once they can touch borrowing, repayment, or collateral state.
Final collateral check passedis not enough if arbitrary intermediate control flow can author impossible debt state. - Stage fresh-market activation in code rather than in ops lore: deploy, seed, constrain helpers, verify borrower-share behavior, then expose the asset through supported spells and interfaces.
- Put blast-radius limits under protocol-credit integrations. A hidden-market debt bug inside one partner should not be able to drain ordinary
WETH,DAI,USDC, andUSDTliquidity from an upstream line without earlier ceilings, isolation, or circuit breakers.
Audit prompts
- Can any path besides
borrow()andrepay()change aggregate debt for this market, and if so, what proves those paths preserve the same economic meaning oftotalDebtShare? - What exactly happens when
totalDebtShare == 1andtotalDebtis large? Does any positive borrow, however small, still mint at least one unit of borrower liability? - Are there any deployed-but-not-exposed markets, spells, or asset configs that remain callable onchain even though operators consider them
not launchedornot supported yet? - Can a user-authored spell sequence reserve sync, debt repayment, and later borrowing inside one privileged bank context while only an end-of-call collateral snapshot stands guard?
- If one market’s internal debt ledger becomes impossible, what external venue still trusts it enough to hand out real assets: partner credit lines, sibling pools, or treasury liquidity?
- Which monitoring would have paged earlier here: singleton debt-share ownership,
resolveReserve()on a dormant market,borrowShare == 0events, or hidden-bank activity before public launch? - Does the protocol assume UI invisibility or undocumented status reduces attackability anywhere in code or operational rollout?
Lessons that should already be standard
Not in the UI yetis not a launch control. If the contract is live, the market is live to attackers.- Debt-side share math deserves the same paranoia teams already apply to collateral exchange rates. A borrower ledger that can hand out assets while minting
0liability is already a mint bug. - Reserve collection and maintenance helpers are never neutral once they can change aggregate debt. If they touch
totalDebt, they are part of the lending security boundary. - Arbitrary strategy or spell systems do not need to break the final collateral check to be dangerous; they only need enough mid-flight authority to compose legitimate-looking steps into an impossible accounting state.
- Protocol-to-protocol credit turns local accounting mistakes into ecosystem balance-sheet events. Partner integrations need isolation and distrust, not just good intentions.
- First-borrower and dust-share states are as security-sensitive as first-depositor states. Fresh debt markets should launch with explicit singleton-state review, not with hope that nobody notices them first.
Internal linkages
- Closest recurring pattern: debt-share-desynchronization-zero-share-borrow-inflation.
- Launch-state companion: fresh-market-initialization-failure.
- Adjacent pattern: solvency-check-bypass.
- Adjacent empty-market family: empty-market-exchange-rate-inflation.
Control surface
-
The decisive leverage sat in hidden-market exposure, first-borrower share concentration, public reserve-accrual sync, downward debt-share rounding, spell admission, and Alpha’s protocol-level credit line into Iron Bank.
-
Alpha did not just have a bad
sUSDpool. It had a state machine where an empty debt market, one public accounting helper, and one floor division could be turned into external balance-sheet theft. -
Sources:
- https://blog.alphaventuredao.io/alpha-homora-v2-post-mortem/
- https://medium.com/cream-finance/alpha-homora-v2-exploit-post-mortem-344d277bdea6
- https://raw.githubusercontent.com/OriginProtocol/security/master/incidents/2021-02-13-Alpha-Homora-v2.md
- https://etherscan.io/address/0x905315602ed9a854e325f692ff82f58799beab57
- https://etherscan.io/address/0x560a8e3b79d23b0a525e15c6f3486c6a293ddad2
-
Last reviewed: 2026-08-05 UTC