Collateral-Membership Desynchronization

  • Name: collateral-membership desynchronization
  • Category: exploit pattern / lending-state desynchronization / collateral-eligibility race / stale-solvency escape
  • Summary: Many lending systems track two separate truths about an asset: the account owns it and the account has elected or is still allowed to count it as collateral. If a callback window, sibling entrypoint, or stale-state read lets an attacker edit that collateral-membership truth before debt, withdrawal, liquidation, or oracle state is fully settled, the protocol can do something much worse than misprice an asset. It can first rely on the asset to justify debt and then later forget that the same debt still depended on that asset. This family pays when solvency is broken not by forging balances, but by desynchronizing the set of assets the protocol still recognizes as supporting the account.
  • What it is:
    • A lending or margin system tracks collateral membership through flags such as enterMarket, exitMarket, usingAsCollateral, vault-collateral booleans, or equivalent receipt-token state.
    • One path creates, maintains, or evaluates debt while assuming the current collateral-membership set is stable.
    • A callback, sibling entrypoint, or mid-transition oracle window lets the attacker change that membership set before the first path has finalized accounting.
    • Later health checks, withdrawal permissions, or liquidation rules trust the edited membership state as if it had always been true.
    • The attacker then withdraws assets, preserves borrowed funds, or self-liquidates from a balance sheet that only looks valid because the protocol forgot which collateral was still required.
  • Typical preconditions:
    • The protocol lets users opt assets in or out of collateral treatment instead of deriving collateral membership from one immutable rule.
    • Solvency depends on a mutable per-account asset-membership set shared across several entrypoints or modules.
    • At least one path can hand away control or trust transient state before debt, collateral flags, liquidation state, or oracle safety has fully settled.
    • Membership-changing functions are not all covered by the same reentrancy lock, oracle-validation guard, or in-flight state fence.
    • The protocol has enough liquidity or withdrawal value that deleting one collateral requirement becomes monetizable.
  • Common vulnerable surfaces:
    • exitMarket() or equivalent market-membership removers callable during borrow, redeem, or ETH-send callbacks
    • setUserUseReserveAsCollateral() / collateral-toggle helpers that skipped newer oracle-safety or reentrancy hardening
    • NFT collateral withdrawal or liquidation flows that transfer the NFT before clearing or sealing usingAsCollateral-style flags
    • optimistic vault-management flows that temporarily move assets out while a sibling liquidation or debt-mutating path can still rewrite whether the vault needs collateral support
    • deprecated collateral-management entrypoints that remain live after the main borrow/withdraw paths received additional guardrails
  • Exploit sequence:
    1. The attacker establishes debt or a position whose solvency genuinely depends on some collateral asset.
    2. They open a state window where debt, collateral balances, or oracle truth is not yet final.
    3. During that window, they call a collateral-membership mutator such as exitMarket, setUsingAsCollateral(false), or a logically equivalent path.
    4. The protocol accepts the edited collateral-membership set against stale debt, stale pricing, or unfinished withdrawal / liquidation state.
    5. The attacker withdraws the newly freed collateral, keeps borrowed assets live, or routes the position into a self-liquidation path that now pays from false premises.
    6. When the original call stack finishes, the protocol has a debt position that only looked healthy because collateral membership and debt state no longer describe the same moment.
  • Variants:
    • Callback-time market exit — a borrow or redeem path transfers ETH or tokens first, and a callback calls exitMarket() before the debt that depended on that market is recorded.
    • Receiver-hook collateral-flag clearing — an NFT or receipt-token transfer during withdraw / liquidation hits onERC721Received() or a similar hook before usingAsCollateral state is finalized. Omni is the sharp exemplar because the liquidation cleanup only cleared collateral membership on the last NFT in reserve branch, which let the attacker re-enter exactly as the protocol was about to retire the final collateral bit and then resume into stale cleanup after re-supplying and re-borrowing.
    • Transient-price collateral-toggle bypass — a read-only reentrancy or oracle window temporarily overstates one collateral asset, and the attacker uses that fake headroom only to disable a different collateral asset that they then withdraw at normal prices.
    • Optimistic-management liquidation desync — one module deliberately allows temporary undercollateralization during a batched management action, while a sibling liquidation path rewrites debt or auction state against that temporary view.
  • Detection heuristics:
    • Enumerate every function that can change whether an asset counts toward solvency and verify they all sit behind the same reentrancy, oracle-safety, and in-flight-state guards as borrow, withdraw, redeem, and liquidation paths.
    • Search for state transitions where asset transfer or external calls happen before usingAsCollateral, market-membership, or vault-collateral flags become final.
    • Fuzz borrowed accounts through callbacks that attempt exitMarket, collateral toggles, or liquidation while borrow / withdraw / liquidation flows are mid-flight.
    • For read-only reentrancy defenses, explicitly test the less glamorous collateral-management functions; this family often survives because teams guarded borrow() and missed setCollateral().
    • Treat deprecated or low-traffic collateral-toggle functions as first-class attack surfaces until they are removed, not merely documented as legacy.
  • Defenses:
    • Put every collateral-membership mutation behind one shared solvency gate with the same reentrancy, oracle-context, and settled-state assumptions used by borrow and withdraw flows.
    • Make in-flight debt, withdrawal, and liquidation phases explicitly non-overlappable with membership edits on the same account or vault.
    • Finalize or seal collateral-membership state before handing execution to token callbacks, NFT receiver hooks, raw ETH sends, multicall helpers, or liquidation side modules.
    • Recompute health from one settled snapshot immediately after any membership edit; do not let stale debt or transient prices stand in for the post-edit state.
    • Remove or fully harden deprecated collateral toggles instead of relying on callers to avoid them.
    • Treat the question which assets currently count toward solvency? as a security-critical state object, not just UI metadata.

What sound design looks like

  • One canonical collateral-membership object exists per account or vault. Borrow paths, withdraw paths, liquidation paths, toggles, and helper modules should all be reading and mutating the same settled answer to which assets support this debt right now?, not parallel approximations that drift by call order.
  • One shared safety perimeter governs every way that answer can change. If borrow, redeem, liquidation, and oracle-sensitive paths require a guard, then exitMarket, setUsingAsCollateral, legacy toggles, and vault-manager helpers should inherit the same guard instead of operating as lighter side doors. helper-path-invariant-reuse-and-fail-closed-discipline is the broader positive-design companion when the sharper question becomes why a helper or legacy path was allowed to define a weaker economic boundary at all.
  • One state transition finishes before outside code or sibling modules can reinterpret it. A healthy design retires collateral membership, debt dependence, or liquidation eligibility before token callbacks, receiver hooks, multicall targets, liquidation sidecars, or optimistic management modules can act on the same account again.
  • One post-edit solvency snapshot is authoritative. Health should be recomputed from fully settled balances, debt, prices, and membership state after the edit rather than inferred from cached debt, pre-withdraw balances, optimistic temporary states, or transient oracle windows.
  • One module should not be allowed to temporarily ignore a collateral dependency unless every sibling path has been explicitly designed around that temporary state. If management flows, migration helpers, or staged withdrawals need a transitional phase, that phase should be machine-checkable and non-overlappable with liquidation or collateral-disablement rights.
  • One callback-specific lesson should already be standard: if collateral leaves through an ERC-721 / ERC-1155 receiver hook or other callback-bearing delivery path, delivery happened and this asset no longer counts toward solvency must already agree before the hook runs. receiver-hook-staged-delivery-and-single-accounting-ownership is the narrower positive-design companion for that delivery-boundary problem.
  • The sharper design-side question usually deserves its own explicit object: which exact account, asset set, debt dependence, mutation lane, and post-edit solvency proof should have stayed fused while collateral membership changed at all? collateral-membership-truth-security-object is the positive-design companion for that full answer.
  • The clean design target is stronger than health passes eventually. No callback, stale-read window, or sibling helper should ever be able to create a world where debt is still real but the protocol’s collateral-membership story has already forgotten which assets that debt depended on.

Three collateral-authority promotions this pattern collapses

  1. A mutable collateral-membership surface exists at all

    • The first fact is only that some path can still say this asset counts or this asset no longer counts for one account: exitMarket, setUsingAsCollateral(false), an NFT-collateral cleanup branch, a liquidation side effect, or a manager helper.
    • By itself that should not mean debt, withdrawal rights, or liquidation state have already moved with it. It is only a candidate edit to the solvency story.
  2. That edited membership answer is treated as canonical solvency truth for the same debt snapshot

    • The dangerous jump is from this flag changed to this account's collateral set already describes the same moment as its debt, balances, and oracle state.
    • In practice the bug appears when a callback window, stale read, or sibling helper lets the protocol accept a new collateral-membership set against old debt, old prices, unfinished delivery, or optimistic management state.
  3. Downstream value-moving paths still spend against that half-settled answer

    • Once the protocol publishes the edited membership set as if it were already money-good, ordinary withdrawals, extra borrows, liquidations, self-liquidations, or debt migrations can monetize the mismatch.
    • This is where a bookkeeping drift becomes loss: the system first forgets which assets still support the debt, then still lets some other path cash out based on that amnesia.

Failure-prevention checklist

  • Inventory every path that can change whether an asset counts toward solvency: enterMarket, exitMarket, collateral toggles, vault collateral setters, liquidation side effects, migration helpers, emergency exits, keeper flows, and any deprecated compatibility wrapper.
  • Put those paths behind one shared safety perimeter. If borrow, withdraw, redeem, and liquidation are oracle-validated or reentrancy-guarded, collateral-membership mutators should inherit the same guarantees rather than lighter legacy logic.
  • Treat collateral-membership state as a per-account state machine, not as a loose boolean. A position should not be able to move from debt relies on asset to asset no longer counts while another flow for that same account is still unsettled.
  • Seal membership before handing away control. Token transfers, ETH refunds, multicall adapters, NFT receiver hooks, leverage helpers, and liquidation modules should never observe a state where collateral is provisionally removed but debt or liquidation state still describes the old world.
  • Recompute health from a single settled snapshot immediately after any membership edit. Do not let cached debt, stale oracle reads, temporary price inflation, or optimistic management assumptions stand in for the post-edit balance sheet.
  • Delete or hard-disable deprecated collateral toggles once newer guardrails exist elsewhere. Sturdy is the reminder that nobody should use this anymore is not a security control.
  • Make liquidation and auction modules prove they cannot clear, rewrite, or migrate debt while assets are temporarily out of the vault or while collateral flags are in transition.
  • Add throttles that make accidental membership mistakes less catastrophic: borrow caps, isolated markets, withdrawal cool-downs after collateral disablement, and explicit pause authority over collateral toggles.
  • Fuzz with malicious receivers and sibling entrypoints that try to disable collateral, exit markets, or trigger liquidation during borrow / withdraw / management flows, including read-only reentrancy windows rather than only write-time recursion.
  • Preserve an operator-visible audit trail for collateral-membership changes so incident responders can reconstruct which exact path detached an asset from solvency.

Audit prompts

  • Which exact functions can change whether an asset counts toward solvency for an account or vault, including deprecated and low-traffic paths?
  • Are all of those functions covered by the same reentrancy lock, oracle validation, pause scope, and settled-state assumptions as borrow, withdraw, and liquidation?
  • Can any callback-capable surface — ETH send, token hook, safe NFT transfer, multicall target, flash-loan callback, or liquidation side module — reach a collateral-membership mutator while debt or asset balances are mid-transition?
  • After a membership edit, does the protocol recompute health from one coherent snapshot, or can stale debt, stale prices, cached balances, or optimistic temporary states still authorize withdrawal or liquidation?
  • Can liquidation, migration, or rescue paths erase or migrate debt while collateral assets are already outside the account, making the final health check validate an impossible moment?
  • Does any path allow disable collateral first, withdraw later using assumptions that were only true under a transient price, transient asset balance, or pre-borrow state?
  • Are legacy compatibility functions missing newer controls that were added to headline entrypoints after an earlier audit or incident?
  • If an account currently has debt, what exact proof prevents the protocol from forgetting one required collateral asset before the debt that depended on it is reduced or closed?
  • Could an attacker profit even if the collateral asset itself is priced correctly, simply because the protocol’s membership set and debt state describe different moments in time?
  • What adversarial tests show that no sequence of nested calls can make the protocol first honor an asset as collateral and then silently stop counting it before settlement finishes?

Lessons that should already be standard

  • Use as collateral is not UI metadata. It is part of the protocol’s solvency truth and deserves the same hardening as borrow and liquidation code.

  • Legacy paths are where these bugs like to survive. Teams add guardrails to obvious flows and leave one old toggle or helper operating on yesterday’s trust assumptions.

  • Read-only reentrancy often pays through collateral-membership seams rather than through direct price theft. The transient mark matters because it authorizes detaching some other asset from solvency.

  • Liquidation code is not merely a cleanup path; it can become the reentrancy gadget that rewrites debt against an optimistic or half-finished vault state.

  • A protocol should be able to answer which assets support this debt right now? from one settled snapshot. If the answer depends on call ordering across modules, the design is already fragile.

  • Borrow-side math can be perfectly fine and the system can still fail if collateral membership is mutable at the wrong times. These incidents are about temporal coherence, not just arithmetic.

  • The clean design goal is stronger than health check passes at the end: no intermediate callback or sibling module should be able to create a world where debt and collateral membership belong to different moments.

  • When teams decompose positions across vaults, managers, liquidators, and toggle helpers, the security task is to keep one ownership and solvency story intact across the whole graph, not to make each contract locally reasonable.

  • Where this pattern stops:

    • If the exploit mainly came from a false price read during a callback window, start with read-only-reentrancy; use this note when that false price mattered because it authorized a collateral-membership edit.
    • If the exploit mainly came from arbitrary nested control before accounting finished but without any special collateral-membership mutation, stay with reentrancy.
    • If the protocol never had a special collateral-membership surface and simply passed an impossible balance sheet as healthy, solvency-check-bypass is usually the better parent note.
    • If the exploit pays because the attacker controls the liquidation side of the false state they just created, pair this note with self-liquidation-monetization.
  • High-signal incidents:

    • Fei Fuse pools reentrancy (2022-04-30) — the CEther ETH-send callback let the attacker call exitMarket() before Fuse had finalized the borrow that depended on that collateral membership.
    • NFTX safeTransferFrom double reentrancy (2022-07-10) — Omni transferred NFT collateral out before sealing usingAsCollateral, and the liquidation path’s balance == 1 cleanup branch let ERC-721 receiver callbacks catch the account at the exact last collateral bit about to be cleared moment, re-supply and re-borrow, then resume into stale debt-detaching cleanup.
    • collateral-toggle bypass (2023-06-12) — a transiently inflated Balancer LP mark was used mainly to disable steCRV collateral through an unguarded toggle path, after which the attacker withdrew the freed collateral at normal prices.
    • Arcadia Finance vaultManagementAction liquidation reentrancy drain (2023-07-10) — the same which assets still need to support this debt? truth was split across an optimistic management flow and a sibling liquidation path, letting liquidation rewrite debt state while the assets were already outside the vault.

Internal linkages

Control surface