Balance-Delta Provenance Failure / Ambient-Balance Attribution

  • Name: balance-delta provenance failure / ambient-balance attribution
  • Category: exploit pattern / accounting provenance failure / balance-delta ambiguity / ambient-balance attribution / semantic-source confusion
  • Summary: These exploits happen when a protocol treats a raw token balance change as proof that one specific economic event occurred. balanceAfter - balanceBefore can show that assets moved, but it does not by itself prove why they moved, which call frame caused them, which user / market / position should get credit, whether that balance increase was ever explicitly recognized by the right ledger, or whether the newly observed backing even arrived through the wrapper or reserve boundary that outstanding claims were supposed to rely on. Attackers exploit that gap by pre-seeding contract balances, re-entering through another path during the measurement window, shuttling the same asset in through a different semantic route, or inflating an upstream recipient-owned position one layer below the object being priced. The protocol then mints shares, credits rewards, or records locked value against assets that did not truly arrive from the action or issuance path it thinks it is accounting for. Spartan Protocol, Predy Finance, and ParaSpace preserve three especially useful siblings where balances stayed real but provenance did not: Spartan let ambient pool balances masquerade as already-recognized LP backing, Predy kept aggregate pool balances constant while silently migrating which pair ledger owned them, and ParaSpace treated third-party APE deposited into address(cAPE)’s staking position as if existing cAPE / pcAPE shares had legitimately earned more backing.
  • Fast routing: start with Mirror when ambient balances were already parked in the contract before accounting ran; Spartan when live pool balances were treated as already-recognized LP backing even though reserve accounting had not actually absorbed them; Predy when aggregate pool conservation hid which pair ledger still owned the assets; Penpie when the decisive question is which public action or market hook caused the observed balance increase; Akropolis when one real deposit lands in a later nested frame and one earlier outer frame remints against it on unwind; Grim when several earlier frames keep reminting against the same bottom-of-stack deposit after repeated checkpoint resets; and Origin Dollar when one path already minted against the inflow and a later rebasing or presentation layer re-reads that same now-live backing into a second balance expansion.
  • What it is:
    • A contract derives a deposit amount, reward amount, locked amount, or other entitlement from a token balance delta.
    • The code assumes that observed delta uniquely came from the intended action being processed.
    • In reality, the same token balance can change for many reasons: direct transfers, nested calls, other entrypoints, malicious token callbacks, or attacker-controlled market hooks.
    • The attacker makes the contract observe a real balance increase in the wrong accounting frame or attribute it to the wrong object.
    • The protocol turns that semantic mistake into redeemable claims, overstated rewards, or oversized unlock rights.
  • Typical preconditions:
    • The protocol uses before/after balanceOf measurements as its main proof that value was delivered or earned.
    • External calls, token transfers, market hooks, or user-chosen integration targets happen between the balance snapshots.
    • The protocol does not maintain a tighter source-specific accounting record for the event it is trying to measure.
    • The same token can arrive through another path during the measurement window, or already sit in the contract before the accounting function runs.
    • Any claims minted from the misattributed delta remain redeemable for real assets or durable accounting power.
  • Common vulnerable surfaces:
    • vault and savings-pool share minting based on balanceAfter - balanceBefore
    • reward-harvest functions that treat generic token-balance growth as proof of earned rewards
    • lock / vesting / custody helpers that infer one position’s amount from ambient contract balances
    • AMM liquidity-mint / burn paths that price LP claims from live pool balances instead of from synchronized reserve state
    • rebasing wrappers or collateral adapters that derive share value from upstream staking / reward positions whose balances can be increased by third parties outside the wrapper’s own mint path
    • deposit routers that accept user-chosen token or market contracts before finalizing accounting
    • bridge or settlement adapters that credit deposits from loose balance observations rather than source-specific state
  • Exploit sequence:
    1. The attacker finds a function that maps a raw balance delta to one specific meaning such as new deposit, harvested reward, or this position's locked amount.
    2. They identify another way to move that same token balance: direct transfer, re-entrant deposit, malicious market callback, or pre-seeded ambient balance.
    3. They trigger the victim function and inject or expose the balance change inside the victim’s accounting window.
    4. The victim function observes a genuine balance increase, but attributes it to the wrong source, wrong position, or wrong call frame.
    5. The protocol mints shares, credits rewards, or records entitlement value against that misattributed delta.
    6. The attacker withdraws, redeems, or otherwise cashes out the false accounting claim against real protocol assets.
  • Variants:
    • Ambient-balance attribution — unsolicited or previously parked tokens already in the contract get assigned to the next position or event that consults live balances. Mirror is the clean lock-accounting exemplar.
    • Callback double observation — nested execution lets two or more frames observe the same economically real inflow and each treat it as their own deposit. Akropolis is the clean two-frame version: one real deposit lands in the later nested frame, then one earlier outer frame resumes and remints against that same inflow on unwind. Grim is the repeated-checkpoint-reset multi-frame vault cousin, where several earlier frames keep re-measuring the same bottom-of-stack LP deposit.
    • Semantic-source confusion — a contract watches balances around one action such as redeemRewards() but the observed increase actually came from another path such as depositMarket(). Penpie is the sharp reward-accounting exemplar.
    • Cross-ledger provenance loss under aggregate conservation — pool-wide or contract-wide balances end where they started, but the protocol never proves which internal pair, tranche, or position still owns them. Predy is the clean pair-ledger exemplar, and Spartan is a reserve-accounting cousin where ambient balances were treated as if the LP ledger had already recognized them.
    • Rebase / balance-presentation amplification — a misattributed balance increase is fed not only into explicit minting but also into elastic-supply, rebase, or presentation-layer balance math, so the same backing inflates wallet balances twice. Origin Dollar is the clean rebasing-stablecoin exemplar: unlike Akropolis and Grim, the second monetization step does not come from an earlier frame reminting on unwind; the outer frame already minted against the DAI, then a later re-entrant frame re-read that same already-arrived DAI inside _rebase() and expanded balances again. A useful classifier from PeckShield’s walkthrough is that the hostile fake-token leg can be rendered as effectively zero-valued: its job is to reopen the accounting frame, not to contribute another economically meaningful inflow.
    • Cross-entrypoint contamination — one public function can move the same tokens another public function is measuring, and the protocol never proves which path actually caused the balance increase.
    • Upstream-recipient attribution failure — the measured backing sits one layer below the wrapper or collateral token, and third parties can increase that upstream recipient-owned staking / reward position without crossing the wrapper’s own issuance boundary. ParaSpace is the clean reminder that the wrapper's underlying position got larger is still not proof that existing shares legitimately earned more claim on reserves.
  • Quick classifier:
    • If one real inflow lands at the bottom of a nested stack and earlier frames still mint against it on the way back out, start with Akropolis or Grim.
    • If one path already minted against the inflow and a later frame or presentation layer turns that same now-live backing into a second balance expansion, start with Origin Dollar.
    • If the critical mistake is that ambient balances were already sitting in the contract before the accounting function ran, start with Mirror; if those balances lived in an AMM pool and were wrongly treated as already-recognized LP backing under stale reserve accounting, start with Spartan.
    • If the critical mistake is not which frame owned the balance increase but which economic ledger owned it, start with Predy when aggregate conservation hid cross-ledger ownership reassignment and with Spartan when stale reserve accounting let old LP shares cash out against ambient balances before the reserve ledger had really adopted them.
    • If the critical mistake is which public action or market hook caused the observed increase, start with Penpie.
    • If the callback mattered but the same inflow was not being claimed twice by different frames or meanings, move back toward reentrancy; this note is for who gets to claim this balance increase?, not every stale-state callback bug.

Three provenance questions healthy accounting should not collapse

  1. Did inventory really move?

    • This is the raw observation question: did token balance, pool inventory, or upstream recipient-owned backing actually increase?
    • It is the weakest claim in the ladder. Real balances can move for many reasons.
  2. Did the correct ledger explicitly recognize that movement?

    • This is the accounting-adoption question: did the right pair, tranche, reserve bucket, lock record, wrapper, or reward ledger deliberately absorb that movement as canonical backing or entitlement input?
    • Spartan and Predy are the strong reminders that tokens are here is still not the same thing as this exact internal ledger now owns them.
  3. Did this exact claim path earn the right to consume that recognized movement?

    • This is the entitlement-consumption question: may this mint, reward settlement, unlock, rebase, or withdrawal path use that recognized quantity right now, and has no other path already consumed it?
    • Akropolis, Grim, Origin, and Penpie are useful reminders that one economically real inflow can still be over-consumed by several claim narratives.
  • Healthy systems answer those questions separately and in order. They do not let inventory changed silently imply the right ledger adopted it, and they do not let the ledger adopted it silently imply this path may now mint, reward, unlock, or rebase against it.

  • Detection heuristics:

    • Whenever code says received = balanceAfter - balanceBefore, ask what excludes every other source of that same token during the measurement window.
    • Look for external calls between the baseline snapshot and the final accounting write.
    • Review any path that accepts user-chosen token contracts, reward tokens, markets, or adapters before finishing accounting.
    • Test unsolicited direct token transfers into the contract before calling accounting-sensitive functions.
    • Fuzz nested or re-entrant flows where a sibling function can move the same asset while the first function is still live.
    • Trace backing one layer downstream whenever a wrapper or collateral token prices itself from an upstream staking / reward position. Ask who else can increase that recipient-owned balance without minting proportional shares.
    • If the protocol has rebasing, changeSupply, share-price caching, or other presentation-layer balance math, ask whether one live balance increase can first justify an explicit mint and then justify a second supply / balance expansion in the same transaction.
    • Ask whether the contract is proving amount received only, or incorrectly proving amount received from this exact source for this exact purpose.
  • Defenses:

    • Prefer source-specific accounting over generic balance-delta inference whenever the economic meaning matters.
    • Seal balance-sensitive accounting windows with reentrancy protection and tighter state checkpoints.
    • Track accounted balances internally so unsolicited transfers cannot silently become someone else’s credited deposit or lock amount.
    • Restrict or deny attacker-chosen integration surfaces when downstream accounting assumes honest token or market behavior.
    • Attribute rewards, deposits, and lock amounts from explicit receipts, known state transitions, or isolated vaults rather than from ambient balances alone.
    • Keep rebase / elastic-supply updates out of any state where a just-arrived deposit has not yet been source-validated and fully committed; the same token inflow should never be eligible for both explicit minting and a later balance-presentation expansion in the same transaction.
    • Add adversarial tests for pre-seeded balances, nested calls, same-token sibling entrypoints, malicious callback behavior, and rebasing / presentation-layer accounting that re-reads live balances mid-flow.

What sound design looks like

  • A healthy accounting system treats token inventory changed, the right ledger adopted that movement, and this user / pair / reward bucket may now consume it as three different facts that must be explicitly fused by one journaled state transition. Real balances may move for many reasons; only a narrower receipt, settlement record, or pair-local ledger update should be allowed to decide who now owns the new claim. The positive-design companion there is reserve-backed claim truth as a single security object.
  • Live balances should stay inventory, not ambient authority. Donations, prefunds, stale leftovers, callback-delivered assets, and third-party increases to upstream recipient-owned staking positions should sit outside recognized reserves, locked amounts, or reward entitlement until a dedicated reconcile step adopts them under the correct position- or market-local identity.
  • Recognition should happen at the right ledger granularity. Contract balance increased is weaker than pair 2 owns more reserves, this lock record owns more backing, or this wrapper-recognized reserve bucket increased under policy. Spartan, Predy, and ParaSpace are the standing reminders that accounting truth often breaks exactly at that promotion boundary.
  • Callback and helper paths should inherit the same provenance boundary as the canonical path instead of getting to reinterpret balances on the fly. If a nested frame, router hook, or market callback can still move the same token, the system should either close the accounting window first or carry a frame-local identity object that proves which inflow belongs to which unfinished action. The sharper control companion there is callback frame truth as a single security object.
  • Reward recognition, reserve sync, and presentation layers should consume each economically real inflow once. A healthy design does not let a deposit first justify explicit minting, then later justify harvested rewards, reserve adoption, rebasing, or wallet-balance expansion because a different subsystem re-read the same now-live balance. The nearby publication / recognition companions are sync, harvest, and cache-write authority discipline and reward-recognition, presentation, and collateral-admission separation.
  • The clean design question is: what exact object proves that this balance increase came from this source, was adopted by the correct ledger, and has not already been consumed by another claim path? If the answer is only the token balance went up while this function was running, the protocol is still publishing ownership fiction rather than accounting truth.

Failure-prevention checklist

  • Decide explicitly what each observed balance delta is allowed to prove. Separate inventory moved, ledger recognized it, and this claim path may consume it instead of inferring all three from one contract-wide token balance.
  • Keep a separate internal ledger for accounted assets, pending settlement, locked amount, recognized reserves, or pair-level ownership. Live token balances are inventory; they are not automatically ownership truth.
  • Treat unsolicited transfers, rescue top-ups, prefunds, stale ambient balances, and third-party top-ups of upstream staking / reward recipients as hostile until a dedicated sweep / skim / reconcile path assigns them. Never let the next business action or existing wrapper share base silently inherit them.
  • Close accounting windows before any attacker-influenced external call, or split execution into phases where the amount to credit is fixed before callbacks, adapters, or market hooks can move the same asset.
  • If callbacks are unavoidable, verify provenance at the right ledger granularity afterward: not just contract balance is unchanged, but the same pair / tranche / position / reward bucket still owns the same assets it owned before.
  • Prevent one economically real inflow from being creditable by more than one subsystem. Deposit minting, reward settlement, reserve sync, rebasing, and balance-presentation updates must all agree on whether that inflow is still unrecognized or has already been consumed.
  • For reward harvesters and routers, bind the credited asset source to a known market, adapter, or claim path. token balance increased during this call is not enough when users can choose the market contract.
  • For wrappers and rebasing collateral, bind backing growth to the wrapper’s own issuance or reward-recognition boundary. the upstream staking recipient balance increased is not enough if outsiders can donate into that recipient directly.
  • For AMMs and lending adapters, reconcile against synchronized reserve / ledger state rather than raw pool balances so ambient inventory cannot masquerade as already-recognized backing.
  • Fuzz the combined edge cases, not only each one alone: pre-seeded balances plus reentrancy, same-token sibling entrypoints plus callbacks, aggregate-balance conservation plus internal-ledger drift, and explicit minting plus rebasing in one transaction.
  • Monitor for balance delta without journal entry conditions in production: token balances rising without matching deposit receipts, pair-level ownership moving without expected settlement events, or rebases firing immediately after unusually large inbound transfers.

Audit prompts

  • What exact economic fact is balanceAfter - balanceBefore supposed to prove here: merely inventory moved, or also this ledger adopted it, or even this exact mint / reward / unlock / rebase path may now consume it? Could the same observed delta come from any other path?
  • Between the first snapshot and the final accounting write, which external actors can still move the same asset: token hooks, user-chosen markets, router callbacks, bridge adapters, strategy hooks, or sibling public entrypoints?
  • If tokens were already sitting in the contract before this function ran, what stops the function from assigning those ambient balances to the current user, pair, or claim?
  • Does the protocol reconcile at the right scope? Predy-style issues survive when contract-wide conservation passes but pair-level ownership changed underneath it.
  • Can one inflow first justify explicit minting or reward credit and then later justify reserve sync, rebase, changeSupply, or some other balance-presentation expansion in the same transaction or epoch?
  • Are any user-selectable integrations trusted as if they were pure accounting helpers? Penpie-style reward accounting breaks when the credited market itself can perform deposits during the harvest window.
  • If a wrapper or collateral token derives value from an upstream staking or reward position, who else can add assets to that recipient position, and what proves the increase came through the wrapper’s own issuance path rather than from a third-party top-up?
  • When AMM balances or vault balances change, does the protocol know whether those assets were actually recognized into reserves / shares already, or is it reading ambient inventory as if the ledger had adopted it? Spartan is the warning sign here.
  • If the protocol uses reentrancy guards, do they actually cover the whole accounting window, or can a nested path still mutate the same token balance through another contract or another entrypoint?
  • What invariant proves that each economically meaningful inflow can be consumed exactly once by the accounting system, even if callbacks, rebases, or reserve sync logic run in unusual order?
  • Which operational alerts would catch this early: pre-seeded-balance alerts, unexpected token-balance growth without receipts, pair-ledger mismatch alarms, or rebase immediately after deposit anomalies?

Lessons that should already be standard

  • We saw tokens arrive is evidence of movement, not evidence of recognition or claim authority. Security-critical accounting has to prove source, ledger adoption, purpose, and ownership, not just amount.

  • Contract-wide balances are almost always too coarse. If value is owned by markets, tranches, pairs, vault shares, or lock positions, the security boundary lives at that finer ledger, not at IERC20.balanceOf(address(this)).

  • Aggregate conservation is not enough. A protocol can finish with the same total balance and still be insolvent if ownership silently migrated to an attacker-controlled sub-ledger.

  • Reentrancy is often just the transport. The lasting lesson in this family is provenance: one real inflow was allowed to satisfy more than one claim narrative.

  • Ambient balances should be treated like untrusted input. Prefunds, donations, and leftovers are not free accounting convenience; they are attack surface.

  • Reward harvesters, sync functions, rebasing logic, and wrapper-backed collateral adapters are solvency code when they can reinterpret recent balance changes into user credit.

  • Backing increased is not the same claim as share-backed value increased. If the increase arrived one layer below the wrapper through a third-party recipient rail, the protocol still has a provenance problem.

  • If a design depends on no one will move this token for any other reason during this window, then the window itself is part of the threat model and must be closed technically, not socially.

  • Journaled internal accounting is not optional complexity. It is the mechanism that keeps a real token movement from being re-labeled into multiple redeemable claims.

  • The dangerous collapse is usually three-step, not one-step: inventory exists, the protocol recognized it, and this path may spend or mint against it. Healthy accounting keeps those promotions explicit.

  • High-signal incidents:

    • Predy Finance PredyPool callback-locker cross-pair liquidity migration drain (2024-05-14) — Predy’s callback finalization accepted same total pool balances as proof of honest settlement even though the attacker had migrated liquidity from one pair ledger into another pair they controlled; balances stayed real, but pair-level provenance did not.
    • Mirror Protocol lock contract duplicate unlock (2021-10-08) — the lock contract inferred one position’s locked_amount from live USTC balance deltas, so attacker-preseeded ambient funds were assigned to that position before the later duplicate-unlock bug multiplied the payout.
    • poolToken over-mint (2020-11-12) — the clean two-frame share-mint version, where one real DAI deposit landed in the nested frame and one earlier outer fake-token frame resumed and minted a second set of redeemable poolTokens against that same inflow on unwind.
    • [[../../../incidents/accounting-issuance-claims/origin-dollar-ousd-mintmultiple-missing-validation-reentrancy-supply-inflation-2020-11-16|Origin Dollar OUSD mintMultiple() missing-validation reentrancy / supply inflation (2020-11-16)]] — the rebasing-stablecoin cousin, where the outer frame first minted against real DAI and a later re-entrant frame then re-read that same already-arrived DAI inside _rebase(), inflating elastic balances instead of merely minting another share token.
    • Grim Finance SPIRIT-LP vault reentrancy (2021-12-18) — the repeated-checkpoint-reset multi-frame vault version, where several earlier callback frames kept making the same bottom-of-stack SPIRIT-LP deposit look fresh on unwind.
    • Spartan Protocol current-balance liquidity-share inflation drain (2021-05-01) — Spartan redeemed LP burns against live pool balances, so attacker-transferred SPARTA and WBNB first inflated the payout on an old LP position and then reappeared as fresh liquidity input because cached reserves never cleanly absorbed them; balances stayed real, but reserve-level provenance did not.
    • cAPE third-party ApeStaking recipient inflation near miss (2023-03-17) — ParaSpace is the one layer below the wrapper provenance cousin: outsiders could increase the cAPE staking position’s APE backing through depositApeCoin(..., recipient = address(cAPE)), and the lending layer treated that upstream recipient-balance increase as if outstanding cAPE / pcAPE shares had legitimately earned more collateral value.
    • [[../../../incidents/callbacks-reentrancy/penpie-malicious-pendle-market-batchharvestmarketrewards-reentrancy-2024-09-03|Penpie malicious Pendle market batchHarvestMarketRewards reentrancy (2024-09-03)]] — Penpie measured amountAfter - amountBefore as harvested rewards even though the observed balances came from attacker-supplied legitimate market-token deposits during the callback window.

Internal linkages

Control surface