Active-Liquidity and Idle-Balance Ledger Coherence Discipline

  • Name: active-liquidity and idle-balance ledger coherence discipline
  • Category: AMM-and-solvency reference / balance-bucket coherence / active-versus-idle inventory discipline
  • Summary: Some AMMs, vaults, and hybrid liquidity systems split one pool’s inventory into several operational buckets such as active liquidity, idle balance, buffer, reserve, or undeployed inventory. That architecture is not safe merely because total balance remains conserved. It is only safe if the bucket split stays economically coherent across every mint, burn, withdraw, swap, and estimator update. A design is broken when a locally conservative adjustment to one bucket can later make the protocol believe far less or far more liquidity is active than the same underlying inventory could really support. Healthy systems treat which units are active right now, which are idle right now, and which estimator is allowed to speak for them as one security object.
  • Why this note exists:
    • Bunni V2 preserves the clean negative exemplar: floor rounding in withdraw() looked locally conservative for idle-balance updates, but the active/idle split plus later estimator switching let the attacker monetize a rebound in quoted liquidity.
    • Reviews often check only does total inventory stay conserved? and miss the harder question does the split between active and idle inventory remain monotone and economically meaningful under adversarial sequencing?
    • The useful design habit is to review bucket accounting, selector rules, and downstream trading or collateral authority as one joined state machine.

Core claim

If a protocol splits one inventory into active and idle lanes, then the bucket split itself is part of solvency truth. Conserving total balance is not enough if attacker-controlled transitions can make the active bucket temporarily understate or overstate what the system will later let users trade against.

Three inventory promotions active/idle reviewers should not collapse

  1. Inventory still exists somewhere in the system
  • The pool, vault, or wrapper still physically holds the tokens, and total balance may even reconcile perfectly.
  • This is only a custody or gross-inventory fact. It does not yet say which portion is currently executable liquidity, which portion is idle inventory, or which estimator is allowed to speak for either one.
  1. The protocol published one active/idle split or one authoritative liquidity estimate locally
  • The system has decided how much inventory counts as active, how much counts as idle, or which branch now represents executable depth.
  • This is already a stronger claim than the tokens are still there, but it is still not the same thing as downstream money-moving logic should immediately trust that split at full size.
  1. Downstream paths may spend against that published split
  • Swaps, collateral reads, reserve refreshes, redemptions, or other balance-moving paths are now allowed to act as if the freshly published active/idle view is money-good state.
  • This last promotion is where many composed accounting failures actually pay: the protocol did not merely publish an unstable split, it let later users trade or borrow against that instability before reconciliation.

The design target is to keep these promotions explicit. A healthy active/idle system should be able to answer three separate questions: what inventory is present?, what active/idle split or estimator is currently published locally?, and which downstream rights are already allowed to spend against that publication?

The main design rules

1. Name one canonical inventory equation before reviewing any bucket math

  • Reviewers should be able to state the exact shared object all bucket updates are supposed to preserve.
  • Healthy review sentences look like:
    • total token0 balance = active token0 inventory + idle token0 balance
    • quoted pool liquidity is a function of active balances only, while idle balance remains inventory but not executable depth
  • If the team cannot say which bucket carries executable liquidity versus merely stored inventory, the rest of the math review is already under-specified.

2. Bucket transitions must preserve economic meaning, not just arithmetic conservation

  • total balance unchanged is a weak guarantee.
  • The stronger question is whether a mint, burn, withdrawal, or sync keeps the active bucket proportional to the rights actually created or destroyed.
  • A locally conservative rounding choice can still be globally unsafe if it changes bucket composition enough to distort the later active-liquidity estimate, swap path, or collateral mark.
  • Review every active <- total - idle, idle <- idle +/- delta, or liquidity <- min/max(branches) path as economic policy, not bookkeeping.

3. Dust states and near-empty active balances are part of the main security envelope

  • Many bucket-split systems look harmless in ordinary size regimes and become dangerous only when one active side approaches dust.
  • Near-zero active balances, branch outputs near zero, or denominator singularities should trigger bounded behavior, not ordinary quoting.
  • If tiny repeated burns or withdrawals can walk an active balance from 28 wei to 4 wei, that is not numerical trivia. It is the protocol handing an attacker control over which inventory bucket defines price or liquidity truth.

4. Bucket accounting and estimator selection must be reviewed together

  • If the protocol derives tradable liquidity from several bucket-aware estimates and then picks one with min, max, or another selector rule, bucket math and selector math are one problem.
  • The security question is not just is the active-balance update conservative?
  • It is also what later state change can flip the authoritative estimator, and what money-moving rights remain live after that flip?
  • This is the bucket-accounting cousin of multi-estimator-monotonicity-and-selector-switch-discipline.

5. Fresh bucket transitions should not become immediate trading authority by default

  • A protocol can update internal bucket views quickly without immediately letting those views price swaps, collateral, redemptions, or reward issuance at full size.
  • This is the same promotion boundary preserved above: published one active/idle split locally is still weaker than downstream paths may spend against that split right now.
  • Safer shapes include:
    • capped or disabled trading when bucket disagreement becomes extreme
    • delayed use of fresh active-liquidity estimates
    • reconciliation before a new bucket split becomes authoritative
    • one-way degraded modes when active balances fall below minimum usable thresholds
  • updated the split should not automatically mean safe to trade against the split right now.

6. Reversible attacker paths matter more than steady-state proofs

  • Many systems pass forward-only tests but fail the reversible path:
    1. push one side of active liquidity near dust,
    2. perform repeated tiny bucket-changing operations,
    3. make the protocol publish a new active-liquidity state,
    4. trigger a selector or boundary flip,
    5. trade or redeem against the rebound.
  • Good testing must cover distort -> publish -> flip -> cash out -> unwind, not only isolated swaps or isolated withdrawals.

What sound design looks like

1. One inventory story, explicit bucket semantics

  • The protocol can explain what each bucket means, when inventory moves between them, and which downstream rights each bucket is allowed to inform.
  • Idle, active, buffer, and reserve are not interchangeable labels. Each should have one operational meaning and one explicit set of consumers.

2. Bucket updates remain monotone under adversarial sequencing

  • A user should not be able to make active liquidity dramatically smaller or larger than their economic rights justify merely by repeating tiny burns, partial withdrawals, or edge-state updates.
  • If the system uses conservative rounding, it should prove that repeated application cannot build a later monetizable rebound.

3. Dust-state behavior is explicit and boring

  • When active balances approach unusably small values, the protocol narrows what remains possible: freeze or cap swaps, stop using that branch for ordinary quoting, or force reconciliation.
  • The design should not rely on nobody will push it there or flash liquidity probably is not big enough as the main defense.

4. Bucket publication and bucket monetization are separated

  • The system can observe a pathological active/idle split before it grants that split full authority over trading, collateral, or accounting.
  • Large bucket disagreement or branch instability becomes a containment signal, not a profit surface.
  • In other words: inventory exists, a split was published, and that split may already move money stay visibly separate instead of collapsing into one ambient pool state changed event.

5. Selector flips cannot turn a conservative underestimate into a spendable rebound

  • If the protocol computes several bucket-aware estimates, the switch between them is monotone with respect to value extraction.
  • A later branch change should not let the attacker recover more than the earlier conservative update removed.

Negative exemplar cluster

  • liquidity-rebound sandwich drain (2025-09-02) — the clean preserved case where locally conservative idle-balance rounding, dust active balances, and estimator switching became one attacker-controlled liquidity-rebound state machine.
  • The preserved lesson is not merely rounding direction was wrong. It is active-versus-idle inventory truth and downstream liquidity authority were not reviewed as one security object.

Detection heuristics

  • Track total inventory and bucket composition separately in tests. Conservation of totals should not be allowed to hide economically absurd active/idle splits.
  • Log when the protocol crosses each promotion boundary: inventory present, active/idle split published, and downstream money-moving authority enabled. Active/idle bugs often pay at the last step, not the first one.
  • Fuzz repeated micro-withdrawals, dust burns, partial exits, and near-zero active-balance states rather than only representative user sizes.
  • Assert monotonicity of executable liquidity after bucket-changing operations. If a tiny withdrawal can dramatically reduce and later indirectly restore quoted active liquidity, the system likely has a rebound surface.
  • Monitor when one bucket approaches zero while total inventory remains large. That often signals the protocol is about to let a tiny arithmetic choice dominate price or liquidity truth.
  • Log which estimator or bucket-aware branch is authoritative after every update. Bucket-state exploits often cash out through the later selector flip, not the initial arithmetic error.

Red flags

  • Total balance is conserved, so the bucket math is safe.
  • We round down, so the pool is protected.
  • If one active-side estimate gets weird, the other branch will take over.
  • Idle balance is just accounting and cannot affect solvency.
  • This only matters in dust states.
  • comments that reason about one withdrawal or one burn locally while ignoring repeated edge-state sequencing

Failure-prevention checklist

  • Define one canonical inventory equation for every token and every pool state transition: which portion is active liquidity, which portion is idle inventory, and which downstream rights each portion may inform.
  • Make bucket updates economically directional, not just arithmetically convenient. Rounding choices in burn, withdraw, sync, and rebalance paths should be justified against the later executable-liquidity meaning of the buckets.
  • Add explicit dust-state handling. When active balances, branch outputs, or bucket-derived liquidity estimates approach unusable ranges, narrow or halt the actions that would otherwise trade against those states.
  • Review bucket accounting and selector logic together. If multiple bucket-aware estimators exist, prove that branch flips cannot convert a conservative underestimate into a later profitable rebound.
  • Separate fresh bucket publication from full trading or collateral authority. Extreme bucket splits should trigger caps, cooldowns, or reconciliation before ordinary swaps, borrows, or redemptions continue.
  • Make the three-step ladder machine-legible where possible: raw inventory present, split published locally, and spend authority enabled should be distinguishable in code paths, events, or monitoring.
  • Differential-test against a simpler reconciled model that computes one canonical active-liquidity object from total inventory and compare downstream outcomes, not only local bucket arithmetic.
  • Fuzz reversible adversarial sequences such as push to dust -> tiny withdraws -> estimator flip -> reverse trade and thin one side -> partial burn -> quote -> unwind.
  • Alert on bucket divergence and branch instability in production: near-zero active balance with large total inventory, sudden authoritative-estimator changes, or quoted liquidity rebounding after repeated tiny exits.
  • Document who or what can publish bucket state, sync bucket caches, or override branch choice, because those actions are already part of the solvency boundary.

Audit prompts

  • What exact economic object does active liquidity represent here, and how is it supposed to differ from idle balance, buffer, or reserve inventory?
  • Which state transitions move value between buckets, and do those transitions preserve proportional economic rights or only raw token totals?
  • Can repeated tiny burns, withdrawals, or partial exits move the active bucket disproportionately once one side is near dust?
  • If the protocol derives several bucket-aware liquidity or price estimates, what exact conditions switch authority among them, and can an attacker intentionally trigger that switch after first distorting one bucket?
  • What happens when active balances become too small to justify ordinary quoting? Does the protocol narrow behavior, or does it keep emitting tradable liquidity numbers anyway?
  • Can a user both create a pathological active/idle split and immediately trade, borrow, or redeem against the freshly published state before reconciliation?
  • Where does the protocol distinguish inventory is still here, one active/idle split was just published, and downstream paths may now spend against that published split at full size?
  • Are bucket transitions and authoritative-estimator changes observable in logs, tests, and monitoring, or would operators only notice indirectly through price impact?
  • If the quoted active liquidity vanished immediately after the attacker stopped cooperating, what exact downstream path would still honor it as real value?
  • Which code paths can mutate idle balance, active balance, or branch selection, and are those paths tested together as one state machine rather than one function at a time?

Lessons that should already be standard

  • Conserving total balance is not enough. If the split between active and idle inventory can become economically absurd, the protocol can still price or lend against fiction.
  • Round down for safety is not a proof. Local conservatism often becomes global overpayment once later selectors, swaps, or redemptions reuse the distorted state.
  • Dust states are not edge trivia when bucket splits drive price or liquidity truth. They are often the cheapest way to seize authorship over the next active-liquidity quote.
  • Bucket selection and estimator selection are one security boundary whenever downstream code trades against whichever branch is live.
  • A protocol should be able to explain when inventory is merely present versus when it is genuinely executable liquidity. If those meanings blur, solvency review will blur with them.
  • Inventory still exists, the protocol published one active/idle split, and that split may already move money are three different claims. Good designs keep them separate instead of letting every bucket refresh become full trading authority by default.
  • The safest active/idle systems get boring at the tails: near-zero active balances narrow permissions, selector flips are monotone, and pathological bucket states are published as warnings rather than monetized as quotes.

Where this note stops

How to use this note with the exploit corpus

Internal linkages

Control surface