Solvency Check Bypass / Accounting Desynchronization
- Name: Solvency check bypass / accounting desynchronization
- Category: exploit pattern / lending-accounting failure / bad-debt creation / liquidation-path abuse
- Summary: These attacks break lending systems by finding a state-changing path that mutates collateral, reserves, share balances, or debt without preserving the same solvency invariants the main borrow / withdraw / transfer paths enforce. The attacker first builds leverage inside the protocol’s normal rules, then uses the unchecked accounting path to desynchronize assets from liabilities, intentionally create bad debt, and finally monetize that mismatch through liquidation, withdrawal, redemption, or later borrowing against a ledger that no longer describes real liabilities.
- Quick routing rule: Euler is the unchecked reserve-donation / self-liquidation exemplar, Platypus the stale pre-withdraw emergency-exit exemplar, Abracadabra Cauldron V4 the aggregate debt-ledger corruption exemplar, and Abracadabra GMX V2 the phantom-collateral liquidation exemplar. Stay here when the main question is
which balance-sheet mutation escaped the canonical post-state solvency boundary? - What it is:
- A lending protocol tracks collateral and liabilities through internal accounting tokens or balances.
- Most user-facing actions are expected to preserve a health-factor or liquidity invariant after they execute.
- One secondary path — such as reserve donation, fee skim, share burn, internal transfer, or bookkeeping helper — updates part of the balance sheet without re-running the same invariant checks.
- The attacker uses that path to reduce realizable collateral, increase liabilities, or otherwise break the protocol’s assumptions about account health.
- A second module, usually liquidation or redemption, then converts the accounting mismatch into real profit.
- Typical preconditions:
- The protocol allows large leverage before the bug is triggered, often through same-asset looping or aggressive collateral factors.
- At least one state transition can materially change collateral, reserves, or debt without a post-action solvency check.
- Liquidation, withdrawal, or redemption logic assumes the broken state cannot arise except in narrower benign cases.
- The attacker can either self-liquidate through a controlled second account or otherwise capture the value released by the broken state.
- There is enough pool liquidity left for discounted collateral or withdrawal rights to cash out into real assets.
- Common vulnerable surfaces:
- reserve-donation functions that burn or move collateral without checking borrower health
- accounting helpers that update one side of the balance sheet but not the other
- internal transfer / transferFrom variants whose safety checks diverge from the main path
- fee, rebate, or penalty logic that can erase user equity while liabilities remain untouched
- debt-management helpers that can corrupt aggregate debt state without repairing the matching share or liability record
- liquidation engines that assume collateral shortfall can only arise from ordinary market movement
- redemption math that honors receipt tokens produced by a prior solvency-breaking state transition
- Exploit sequence:
- The attacker opens a large leveraged position while remaining nominally solvent.
- The attacker calls the unchecked path to reduce collateral, move value to reserves, or otherwise desynchronize accounting.
- The account becomes underwater in a way the protocol designers did not expect users to be able to manufacture.
- The attacker liquidates the position from a controlled address or uses some other downstream path that prices the broken state as if it were legitimate.
- Discounted collateral, withdrawal rights, or reserve claims are redeemed for real assets.
- The protocol is left with bad debt or depleted pools once the attacker exits.
- Variants:
- Unchecked reserve donation — collateral is donated or burned into reserves while debt remains unchanged.
- Equity burn without liability burn — one tokenized representation of the position is reduced but the matching liability side is not.
- Helper-path invariant gap — a convenience function skips the health checks present on the core path it economically imitates.
- Self-liquidation extraction — the attacker controls both the violator and liquidator so liquidation discounts stay inside the attack bundle.
- Debt-ledger corruption — a helper such as
repayForAll()or reserve sync leaves aggregate debt amount and debt-share state disagreeing, so later borrows or solvency checks run against meaningless liabilities. - Redemption after desync — a later withdraw or redeem path cashes out receipt tokens created or acquired from the broken accounting state.
- Cross-module debt-ledger contamination — one module mutates a shared borrow ledger, another module later interprets the resulting zero-debt state as sufficient settlement proof, and collateral is released even though no single canonical close path reconciled the whole position.
Three solvency-boundary promotions reviewers should not collapse
-
A helper, reserve, liquidation-side, or migration path can touch economically relevant state
- A side path may move collateral, burn reserve-backed claims, rewrite debt ledgers, consume pending-order value, or release entitlements.
- That fact alone does not yet mean the protocol has accepted the resulting post-state as canonical solvency truth.
-
The protocol still interprets that side path’s output as canonical balance-sheet meaning
- Downstream code treats the helper-authored combination of collateral, debt, reserves, shares, or pending assets as if it were a state the canonical close / borrow / withdraw core could also have produced.
- This is the dangerous promotion: a weaker mutation path has been allowed to publish state with the same semantic rank as the main mutation-and-validate boundary.
-
That accepted state still deserves monetization authority
- Liquidation, redemption, collateral release, or fresh borrowing is still willing to spend against the helper-authored state as if it were economically settled truth.
- This is where accounting drift becomes cash loss: the protocol does not just store the impossible state, it pays out against it.
Healthy design keeps those three steps separate. A helper changed something, the protocol accepted that changed thing as one coherent balance sheet, and downstream paths may now move real assets against it are different approvals, not one vague missing solvency check story.
- Detection heuristics:
- Compare every collateral-changing path against the canonical
checkLiquidity/ health-factor enforcement path; look for helpers that mutate balances without the same guard. - Ask whether any function can reduce user equity, move value into reserves, consume pending collateral, or burn position tokens while leaving debt untouched.
- Write invariants around realizable collateral, not just token balances: can debt exceed what liquidation can actually seize because of an internal bookkeeping path?
- Also write invariants around aggregate debt semantics: if a helper changes
totalBorrows,elastic, debt shares, reserve balances, or pending-order collateral, can a later canonical path still interpret the resulting state as if those fields were mutually consistent? - Simulate attacker-controlled violator and liquidator accounts together; many designs look safe until self-liquidation is modeled explicitly.
- Review reserve-management and fee-management code with the same seriousness as borrow and liquidation logic.
- Compare every collateral-changing path against the canonical
- Defenses:
- Enforce the same solvency checks on every path that changes user collateral, debt, or economically equivalent balances.
- Centralize accounting mutations so helper functions cannot silently bypass health checks.
- Require zero debt or explicit safe-state conditions before permitting reserve donations or similar balance-destructive actions.
- Test liquidation and redemption logic against adversarially manufactured insolvency, not just honest market-driven insolvency.
- Add invariants asserting that internal state transitions cannot create unbacked debt except through explicitly handled bad-debt resolution flows.
What sound design looks like
- A side path being able to touch economically relevant state does not automatically promote that path into a balance-sheet author. Helper, reserve, emergency, migration, and liquidation-side code should only mutate through the same post-state mutation-and-validate core that owns canonical solvency meaning.
- Canonical balance-sheet truth should be explicit and unique: aggregate debt amount, debt shares, reserve balances, pending collateral, and release rights should remain mutually interpretable as one position state rather than loosely synchronized side tables.
- Monetization authority should lag helper-side state mutation. Even if a side path has touched collateral, reserves, or debt, liquidation, redemption, collateral release, and fresh borrowing should not trust the result until the protocol has re-proved that the post-state still means one coherent solvency object.
- No path should let value leave, equity be erased, or reserve balances be repurposed while live debt still depends on the thing being removed, unless the protocol is intentionally entering an explicit bad-debt-resolution workflow.
- When helper output, module topology, or aggregate ledger state stops composing cleanly, the protocol should fail closed rather than silently promote an impossible state into economic truth and let downstream paths spend against it.
Failure-prevention checklist
- Keep the three promotions mechanically separate in code and reviews:
a helper can touch state,that touched state counts as canonical solvency meaning, anddownstream flows may monetize itshould each require an explicit reason, not one inherited assumption. - Route every collateral-, reserve-, debt-, or entitlement-mutating helper through one post-state mutation-and-validate core instead of letting
donate,sync,emergency,repayForAll,payoff, or adapter-specific helpers open-code weaker sequencing. - Require explicit safe-state gates on destructive helper paths:
debt == 0, pending-order value consumed, reserve accounting synchronized, aggregate debt/share pairs mutually meaningful, and no cross-module ledger still interpreting the soon-to-be-released object as backing. - Treat helper, migration, reserve, liquidation-side, and adapter-side code as balance-sheet code; give it the same invariant suite as ordinary borrow / withdraw / liquidation flows.
- Make liquidation, redemption, closeout, and fresh-borrow logic prove they cannot monetize attacker-manufactured insolvency states or stale entitlement objects from a prior helper-path desynchronization.
- Assert impossible-state combinations at the aggregate ledger layer and fail closed when they appear instead of allowing later user-facing paths to keep pricing corrupted debt or collateral state as canonical truth.
Audit prompts
- Which functions can reduce realizable collateral, move value into reserves, consume pending-order assets, or mutate aggregate debt without reusing the exact post-state solvency boundary of the canonical core path?
- If a helper changes
totalBorrows,elastic, debt shares, reserve balances, pending-order collateral, or lender-side debt records, what later path still interprets those fields as if they were authored together by one canonical close or borrow routine? - Do emergency, migration, rescue, liquidation-settlement, or admin convenience functions read pre-state comfort (
solvent before) when the real safety question is post-state meaning (safe after collateral or debt changed)? - Can an attacker control both the unhealthy account and the monetization path — self-liquidation, redemption, or helper-assisted closeout — so the protocol’s own discount or payout logic becomes the extraction leg?
- What helper or legacy path was written before collateral reuse, leverage, asynchronous orders, or cross-module debt ledgers existed, and why is it still allowed to mutate those objects without fresh requalification?
Lessons that should already be standard
-
A helper can touch balance-sheet state,that helper-authored state still counts as canonical solvency truth, andthe protocol may now pay against itare three different approvals. -
A helper that can move collateral, reserves, or debt is not secondary code; it is an alternative economic finality boundary and should be designed like one.
-
Solvent before this actionis not a substitute forsafe after this action; post-state meaning beats pre-state reassurance. -
Aggregate debt amount, debt shares, reserve balances, pending-order collateral, and closeout entitlements must stay mutually interpretable as one balance sheet or the protocol should stop and fail closed.
-
Liquidation logic is part of the exploit surface, not just the cleanup path; if users can manufacture insolvency and also capture the liquidator side, the discount itself becomes attacker financing.
-
Legacy helpers should narrow or revert when architecture changes broaden what their touched objects can back, settle, or release.
-
High-signal incidents:
- bad-debt exploit (2023-03-13) — the cleanest major exemplar: an unchecked reserve-donation path let the attacker delete collateral, preserve debt, self-liquidate at the protocol’s own discount, and drain multiple markets.
- Platypus Finance USP emergencyWithdraw solvency-check bypass (2023-02-16) — the collateral-escape-hatch variant:
emergencyWithdraw()checked stale pre-withdraw collateral state, ignored the meaning of live debt, and let the attacker pull LP collateral out while keeping newly minted USP outstanding. - [[../../../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 debt-ledger-corruption variant: a legacy helper zeroed aggregate debt amount without clearing the matching debt-share state, and tiny follow-on borrow/repay loops turned that impossible ledger into about$6.5Mof unbacked MIM borrowing power. - [[../../../incidents/lending-liquidations/openleverage-openlevv1-opborrowing-shared-ltoken-debt-accounting-liquidation-payofftrade-drain-2024-04-01|OpenLeverage OpenLevV1 / OpBorrowing shared-LToken debt-accounting liquidation +
payoffTradedrain (2024-04-01)]] — the shared-ledger cross-module variant: liquidation-side repayment mutated the lender’s debt book more aggressively than the surviving trade record expected, and a laterpayoffTrade()treatedborrowBalanceStored == 0as enough proof to release collateral. - Abracadabra GMX V2 Cauldron RouterOrder phantom-collateral self-liquidation drain (2025-03-25) — the asynchronous-order collateral variant: liquidation consumed real RouterOrder assets without consuming the same pending-order value still used by
_isSolvent(), so the protocol kept lending against collateral that had already been seized once.
Internal linkages
- Best concrete case study: euler-finance-donatetoreserves-self-liquidation-2023-03-13.
- Strong collateral-escape variant: platypus-finance-usp-emergencywithdraw-solvency-check-bypass-2023-02-16 — useful when the question is how a helper withdrawal path, rather than reserve donation or self-liquidation discounting, can let collateral leave the system while debt stays live.
- Strong debt-ledger-corruption variant: abracadabra-cauldron-v4-repayforall-base-elastic-desynchronization-borrow-inflation-2024-01-30 — useful when the protocol does not delete collateral at all, but instead corrupts aggregate debt accounting and then keeps lending against the broken ledger.
- Strong shared-ledger cross-module variant: openleverage-openlevv1-opborrowing-shared-ltoken-debt-accounting-liquidation-payofftrade-drain-2024-04-01 — useful when one module mutates a shared lender debt book and a later helper close path mistakes the resulting zero-debt reading for proof that the whole leveraged position is safely settled.
- Strong asynchronous-order collateral variant: abracadabra-gmx-v2-cauldron-routerorder-phantom-collateral-self-liquidation-drain-2025-03-25 — useful when the protocol’s extra collateral comes from an external-order adapter and liquidation can empty that adapter without consuming the same pending-order value still trusted by solvency checks.
- Positive-design companion: helper-path-invariant-reuse-and-fail-closed-discipline — use this when the sharper design question is why a helper, legacy, emergency, or bulk-management path was allowed to define a weaker economic boundary than the canonical mutation-and-validate core.
- Positive-design companion: debt-zero-emergency-exit-discipline — use this when the narrower design question is what an emergency or helper collateral-release path should require before any debt-bearing position may exit at all.
- Adjacent pattern: self-liquidation-monetization — use this when the main analytical question is not just how the bad debt was manufactured, but how the attacker also captured the liquidator side and internalized the protocol’s own liquidation incentive.
- Adjacent pattern: debt-share-desynchronization-zero-share-borrow-inflation — pivot there when collateral never meaningfully leaves or gets consumed, and the sharper failure is that aggregate debt amount and debt-share math drift apart until later borrows mint too few shares.
- Adjacent pattern: donation-share-price-manipulation — both families weaponize accounting paths, but donation/share-price attacks inflate collateral value whereas solvency-check-bypass attacks delete, relocate, or desynchronize liabilities against the state later trusted by borrow and liquidation logic.
- Adjacent pattern: entrypoint-only-policy-off-path-state-mutation — pivot there when the sharper failure is not a missing solvency check on one mutation path, but that a cap or policy protected one verb while a broader trusted state could still move elsewhere.
- Adjacent pattern: reentrancy — both often look like
missing checks, but here the core issue is balance-sheet desynchronization rather than callback ordering.
Control surface
-
The leverage sits in helper functions, reserve-accounting paths, liquidation assumptions, collateral escape hatches like
emergencyWithdraw(), and any place protocol designers quietly assumethis state cannot happenbecause ordinary UI flows would never create it. -
The sharp control-surface question is not just
can a side path touch collateral or debt?butwhen does that side path become a balance-sheet author, and when do downstream paths still honor its output as spendable solvency truth? -
The danger is not just a missing require statement. It is a balance-sheet mutation path that the rest of the protocol prices as if it were economically valid.
-
Sources:
- https://medium.com/@omniscia.io/euler-finance-incident-post-mortem-1ce077c28454
- https://www.certik.com/blog/euler-finance-incident-analysis
- https://www.chainalysis.com/blog/euler-finance-flash-loan-attack/
- https://medium.com/@omniscia.io/platypus-finance-incident-post-mortem-7b71a0a47a5e
- https://www.certik.com/blog/platypus-finance-incident-analysis
- https://rekt.news/platypus-finance-rekt
- https://github.com/euler-xyz/euler-contracts/blob/fa9398728165676a5666939d8c34a7578d8e1919/contracts/modules/EToken.sol#L356-L386
-
Last reviewed: 2026-07-30 UTC