Debt-Share Desynchronization / Zero-Share Borrow Inflation
- Name: Debt-share desynchronization / zero-share borrow inflation
- Category: exploit pattern / share-based lending accounting failure / debt rounding exploit / empty-borrower-state failure
- Summary: This exploit family hits share-based lending systems that represent borrower liabilities as a pro-rata claim on aggregate debt through variables like
totalDebt,totalDebtShare,base, orelastic. If an attacker can first dominate an empty debt market, then push aggregate debt amount materially out of sync with debt-share state, a later borrow can mint0new debt shares after floor rounding or otherwise add grossly understated liability. The protocol hands out real assets, but the borrower’s recorded share liability does not keep up. Public reserve-accrual helpers, hidden prelaunch markets, and debt-management paths that mutate only one side of the debt rebase all make the bug much easier to weaponize. - What it is:
- In share-based debt accounting, user debt is usually stored as
debtShare, while market debt lives in aggregate variables liketotalDebtandtotalDebtShare. - The design assumes debt and debt-share growth stay meaningfully coupled: every economically real borrow should mint a nonzero amount of new debt shares.
- If a protocol lets aggregate debt amount diverge from debt-share state — for example by increasing
totalDebtwithout increasingtotalDebtShare, or by zeroingelasticwhilebaseremains nonzero — the debt-per-share conversion can stop representing real liability. - Once that happens, a nonzero borrow smaller than one full debt share can round down to
0shares, or later borrow-part math can otherwise undercharge the attacker relative to the assets leaving the pool. The transfer is real; the recorded liability is not. - The attacker then repeats the under-share borrow or uses tiny borrow/repay loops to amplify the broken ratio until the protocol’s pool or downstream credit line is empty.
- In share-based debt accounting, user debt is usually stored as
- Typical preconditions:
- The attacker can become the only or dominant borrower in a fresh, hidden, or otherwise empty debt market, or can otherwise drive the market into a state where debt shares are concentrated and aggregate debt math is fragile.
- Debt is tracked via floor-rounded share math or debt-rebase accounting rather than exact per-user principal updates.
- Some path can increase debt amount without increasing debt-share state, or can zero / distort one side of the debt rebase while leaving the other side live.
- The protocol does not enforce a minimum
1debt share for any nonzero debt increase, or otherwise fails to reject impossible aggregate debt states before new borrows continue. - Borrowed assets can be monetized locally or through a deeper downstream venue before governance or operators intervene.
- Common vulnerable surfaces:
- lending banks that deploy or initialize new borrowable assets before there is any honest borrower base
- hidden or prelaunch debt markets that are live onchain but assumed to be harmless because the UI does not expose them yet
- reserve-resolution or fee-accrual functions callable by arbitrary users
- debt-management helpers such as
repayForAll()-style paths that can mutate aggregate debt amount and debt shares asymmetrically - protocol-to-protocol credit systems where a local accounting bug can be converted into external borrowable assets
- spell / strategy / plugin systems that let user-supplied code choreograph borrow and collateral state as long as one end-state invariant appears healthy
- Exploit sequence:
- The attacker finds an empty or near-empty debt market and becomes the only meaningful debt-share holder.
- They reduce their position to the smallest nonzero debt-share state that still preserves control of
totalDebtShare, often1share. - They trigger a function that increases debt amount without increasing debt shares, or otherwise corrupts the aggregate debt rebase so amount and share state stop describing the same market.
- The debt-per-share ratio jumps so high that a carefully chosen nonzero borrow now maps to
0new debt shares after floor division, or later borrow-part calculations otherwise stop charging economically meaningful liability. - The attacker repeatedly borrows real assets or uses tiny borrow/repay loops while their recorded debt-share liability barely changes or does not change at all.
- They cash out through the same protocol’s liquid assets or via an external venue, leaving bad debt once the accounting fiction is recognized.
- Variants:
- Hidden-pool prelaunch case — the vulnerable debt market exists onchain before operators consider it “live,” so the attacker becomes the first borrower without competition.
- Public reserve-sync amplifier — a callable reserve or interest-accrual helper creates the debt/share mismatch on demand.
- Debt-rebase corruption — a helper such as
repayForAll()leaves one side of aggregate debt accounting live after the other has been zeroed or distorted, so later borrow-part math is no longer economically meaningful. - Protocol-credit externalization — fake internal debt capacity is converted into real assets through a partner credit line or upstream lender.
- End-state-invariant evasion — a plugin or spell framework allows attacker-written control flow so long as the final collateral check passes, hiding the real exploit in intermediate share math.
Three debt-ledger promotions reviewers should not collapse
-
An aggregate debt object exists at all
totalDebt/totalDebtShareorelastic/basecontain nonzero state, and the market can quote a borrower liability object.- In thin or hidden markets this is already a privileged moment, because the first borrower may control nearly all debt-share meaning.
-
That aggregate debt object still deserves to count as economically real borrower liability
- This is the harder question: did every path that touched aggregate debt keep amount and share state coupled tightly enough that the market still means what later borrow math thinks it means?
- Alpha’s hidden
sUSDcase and Abracadabra’srepayForAll()case both fail here. The ledger still looked populated, but the state no longer encoded a truthful exchange rate between real debt and recorded shares.
-
A later borrow or helper path still deserves to spend against that state right now
- Even if the market exposes some internally consistent-looking debt object, the protocol still needs a separate boundary before a new borrow, reserve sync, spell step, or partner-credit draw is allowed to monetize it.
- The recurring mistake is silently promoting
aggregate debt object existsintonew borrowing authority remains safe, even after helper paths or dust-share states have already made that object economically meaningless.
The family gets much easier to classify once those promotions stay separate: hidden-market exploits usually fail between steps one and two because the first borrower gets to define the debt-share regime cheaply, while helper-path incidents like Abracadabra fail between steps two and three because later borrowing continues after the debt ledger has already stopped meaning what it claims.
- Detection heuristics:
- Ask whether any nonzero borrow can ever mint
0debt shares. If yes, treat that as a critical solvency bug, not a harmless rounding quirk. - Model empty-market borrower states explicitly: who owns
totalDebtSharewhen a borrowable asset first becomes reachable? - Trace every path that changes aggregate debt amount and verify whether debt-share state stays synchronized, especially across helper paths, reserve syncs, and legacy repayment flows.
- Stress-test debt-share math around
totalDebtShare == 1, dust debt, post-accrual states, and impossible rebase combinations likeelastic == 0 && base > 0. - Review hidden, paused, or not-yet-announced markets as live attack surface whenever contracts already accept calls against them.
- Ask whether any nonzero borrow can ever mint
- Defenses:
- Enforce
borrowShare > 0for every nonzero borrow, or revert when share math would round a real debt increase to zero. - Assert and preserve aggregate debt invariants on every helper path: debt amount and debt-share state must remain mutually meaningful after
repayForAll(), reserve syncs, liquidations, or emergency management calls. - Do not expose new debt markets until safe initial state, supported assets, and borrower-share hardening are already onchain.
- Restrict reserve-resolution and similar debt-growing sync functions to trusted control, or redesign them so they cannot create debt/share divergence in empty markets.
- Seed or quarantine new borrowable assets so no attacker can become the sole meaningful debt-share holder before launch.
- Treat plugin / spell systems as privileged surfaces and narrow them to audited strategies instead of arbitrary user code.
- Enforce
What sound design looks like
- A healthy debt-share system makes
economically real debtandrecorded borrower liabilitythe same state transition everywhere. If assets leave the pool, the protocol either mints nonzero borrower shares or refuses the action; there is no helper, reserve-sync, launch-state, or emergency path allowed to author a debt amount that later borrow math interprets without matching debt-share growth. - Aggregate debt objects should fail closed as one security object, not drift as loosely related fields. If
totalDebt/totalDebtShareorelastic/basecan no longer describe the same market after a helper call, a healthy design stops new borrowing immediately and routes through a bounded repair path instead of letting later users discover the impossible state through0-share borrows. The positive-design companion there is debt-zero emergency-exit discipline. - Helper paths inherit the same ledger semantics as the canonical borrow path. Reserve resolution, bad-debt socialization,
repayForAll(), migration helpers, strategy callbacks, and spell systems should reuse one accounting boundary or lose authority to mutate production debt altogether. The sharper control companion there is helper-path invariant reuse and fail-closed discipline. - Launch-state debt markets should stage exposure rather than publishing full borrow authority on day zero. A hidden bank, UI-dark market, or freshly listed debt asset should not be reachable until borrower-share floors, helper-path constraints, and safe initial-state checks are already enforced onchain. The nearby rollout companion is fresh-market seeding and activation discipline.
- The clean design question is:
what exact invariant guarantees that every unit of debt handed out still exists as borrower liability after any path that can change aggregate debt?If the answer only coversborrow()but not reserve helpers, launch-state reachability, or emergency debt tooling, the system is still trusting syntax more than balance-sheet truth.
Failure-prevention checklist
- Make
nonzero debt increase => nonzero debt-share increasea hard invariant, not a hoped-for consequence of normal market size. If rounding would mint0shares, revert. - Fail closed on impossible aggregate debt states such as
base > 0 && elastic == 0, dust-share singleton markets, or reserve-sync outcomes that change debt amount without changing debt-share meaning. - Reuse one canonical debt-accounting boundary across user-facing borrows, liquidations, reserve resolution, socialized repayment helpers, migrations, and emergency tooling. If a helper path cannot preserve the same ledger semantics, it should not be able to mutate production debt.
- Treat hidden, deprecated, paused, prelaunch, or UI-dark markets as live attack surface whenever contracts are callable and liquidity can still be externalized.
- Gate new debt markets with launch sequencing that is enforced in code: deploy, seed / sanity-check, constrain helpers, then expose borrowing. Do not rely on
the frontend is not listing it yetas a control. - Quarantine debt markets whose first borrower or first meaningful debt-share owner can still be attacker-controlled dust. Empty borrower state is as privileged as empty supplier state.
- Treat reserve-accrual, bad-debt socialization, and
repayForAll()-style helpers as solvency code. Review them with the same paranoia asborrow()itself. - Stress-test dust states explicitly:
totalDebtShare == 1, tinybase, tinyelastic, post-helper rebase states, and nonzero borrows just below one-share thresholds. - Constrain plugin / spell / strategy frameworks so arbitrary user code cannot compose benign-looking debt helpers into privileged exploit choreography under one end-state check.
- Put blast-radius controls under the market anyway: borrow caps, per-market debt ceilings, isolated liquidity, and fast pause paths should stop one debt-ledger lie from turning into systemwide bad debt.
Audit prompts
- Can any economically real borrow, fee accrual, reserve resolution, liquidation, or helper call change aggregate debt amount without changing debt-share state proportionally?
- What exact state transition guarantees that every nonzero borrow mints at least one unit of borrower liability? Where is that guarantee enforced in code?
- If a market is fresh, hidden, deprecated, or off-UI, who can become the first or only meaningful debt-share holder before operators consider the market live?
- Which functions can author debt state other than
borrow()andrepay()? Are those functions provably ledger-equivalent to the main path, or can they leavebase/elastic-style variables in combinations the main path would never create? - Do reserve-sync helpers, socialized repayment helpers, or emergency debt-management tools stay safe when
totalDebtShareis1, when debt is dust-sized, or when the market has no honest borrower distribution? - Can arbitrary spell, plugin, or callback-controlled code sequence helper calls and borrows in one privileged frame while only an end-state solvency check stands guard?
- If the local market ledger becomes distorted, what downstream venue turns that accounting fiction into real assets: an upstream credit line, sibling pools, shared treasury liquidity, or bridgeable stablecoins?
- What monitoring would catch this before a full drain: zero-share borrow attempts, singleton debt-share ownership, impossible debt-rebase combinations, or helper calls that move
elasticwithout correspondingbasemovement? - Is there any path where a market operators think is
not launched yetis still borrowable, repayable, or helper-callable onchain? - What invariant proves that aggregate borrower liability after any helper path still equals the economically real debt the protocol has handed out?
Lessons that should already be standard
-
Empty borrower state is a privileged accounting regime, not a harmless low-usage corner. Whoever owns the dust debt shares often defines what later debt math means.
-
Borrow rounds to zero sharesis never a cosmetic precision issue. It is the protocol handing out assets without minting matching liability. -
Helper functions are part of the solvency perimeter. A bulk repayment or reserve-sync path that can author ledger states the main borrow path would reject is already a lending bug.
-
Hidden markets are not hidden from attackers. If contracts are live, the launch has happened from a security perspective.
-
Aggregate debt variables need semantic coupling, not just syntactic coexistence.
base,elastic,totalDebt, andtotalDebtShareare only safe when every path preserves their shared economic meaning. -
End-state solvency checks do not rescue arbitrary intra-transaction authority. A spell or plugin system can still weaponize intermediate debt-state transitions long before the final snapshot looks healthy enough.
-
Protocol-to-protocol credit lines magnify accounting mistakes. A local debt-share bug becomes a treasury event once fake borrow capacity can be externalized into deeper liquidity.
-
Launch sequencing is debt security. Seeding, helper hardening, asset allowlisting, and market exposure order matter just as much on the borrower side as they do for fresh collateral markets.
-
High-signal incidents:
- zero-share debt inflation exploit (2021-02-13) — the canonical empty-market case where an unannounced
sUSDbank, publicresolveReserve(), and floor-rounded debt-share math let the attacker borrow real assets while minting0new debt shares. - [[../../../incidents/lending-liquidations/abracadabra-cauldron-v4-repayforall-base-elastic-desynchronization-borrow-inflation-2024-01-30|Abracadabra Cauldron V4
repayForAll()base/elastic desynchronization borrow inflation (2024-01-30)]] — the helper-path cousin whererepayForAll()lefttotalBorrow.elastic == 0whilebaseremained live, and later tiny borrow/repay loops turned that impossible debt-rebase state into real MIM outflows.
- zero-share debt inflation exploit (2021-02-13) — the canonical empty-market case where an unannounced
Internal linkages
- Best current exemplars: alpha-homora-v2-hidden-susd-pool-zero-share-debt-inflation-2021-02-13 for the empty hidden-market
0-share borrow path, and abracadabra-cauldron-v4-repayforall-base-elastic-desynchronization-borrow-inflation-2024-01-30 for the debt-rebase-corruption helper-path cousin. - Launch-state companion: fresh-market-initialization-failure — use that note when the main lesson is unsafe market activation or prelaunch exposure; use this note when the crucial exploit primitive is debt-side share math.
- Positive-design companion: helper-path-invariant-reuse-and-fail-closed-discipline — useful when the sharper design question is how reserve helpers, bulk repayment tools, and emergency wrappers should preserve the same ledger truth as the main borrow path.
- Positive-design companion: debt-zero-emergency-exit-discipline — useful when the question is how a system should safely unwind or socialize debt without leaving
base/elastic-style residue that later borrowing can weaponize. - Adjacent family: solvency-check-bypass — both end in fake borrowing power, but this family gets there by forging debt-share accounting rather than by directly deleting liabilities or bypassing a post-borrow solvency gate.
- Adjacent family: empty-market-exchange-rate-inflation — both are empty-market lending exploits, but that note centers supplier-side collateral inflation while this one centers borrower-side debt-share under-accounting.
Control surface
-
The leverage sits in hidden debt-market reachability, who controls the first debt shares, which functions can grow aggregate debt, which helper paths can desynchronize
base/elastic-style rebase state, how rounding treats sub-share borrows, and whether downstream liquidity lets the attacker externalize the fake capacity. -
The protocol does not fail because it uses debt shares. It fails because it lets economically real debt increments disappear at the share-conversion boundary or keeps lending after helper-path accounting has already made aggregate debt state meaningless.
-
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://paragraph.com/@abracadabra-money/resilience-rising-abracadabra-dao-next-steps-full-speed-ahead
- https://extropy-io.medium.com/breaking-down-the-6-5m-abracadabra-money-hack-a-detailed-analysis-of-the-cauldron-v4-exploit-44100da10896
-
Last reviewed: 2026-07-30 UTC