Entrypoint-Only Policy / Off-Path State Mutation

  • Name: entrypoint-only policy / off-path state mutation
  • Category: exploit pattern / policy-to-state mismatch / accounting-boundary failure / control-surface misbinding
  • Summary: Some protocols put the safety rule on the official verb (deposit, mint, borrow, donate, claim) instead of on the economic state later trusted by collateral, liquidity, rate, or accounting logic. The result is a brittle split: the guarded entrypoint may stay capped, throttled, or permissioned, while the state variable that actually prices solvency can still move through direct token transfers, rounding residues, delegated helper flows, or other off-path mutations. When downstream logic trusts the broader state instead of the guarded path, attackers do not need to break the rule directly. They route around the verb, mutate the trusted state anyway, and make the protocol enforce policy on syntax rather than on value.
  • What it is:
    • A protocol intends to constrain some economic quantity with a cap, throttle, admission rule, or accounting invariant.
    • The enforcement lives on one canonical entrypoint such as mintAllowed(), deposit(), or a direct-donation guard.
    • The downstream consumer that matters — exchange-rate math, liquidity accounting, collateral valuation, utilization, NAV, or balance presentation — reads a broader state source than that guarded entrypoint controls.
    • The attacker finds another way to move the trusted state: direct transfer, stealth donation from rounding, helper-mediated flow, delegated borrow loop, or some other path outside the guarded verb.
    • The policy still looks present in code, but the economically relevant state has already moved past it.
  • Typical preconditions:
    • The protocol has a visible guardrail such as a supply cap, donation throttle, deposit ledger, borrow guard, or entrypoint-local allowlist.
    • A downstream state variable with real balance-sheet authority is derived from raw balances, cached NAV, utilization math, exchange-rate math, or another source broader than the guarded path.
    • The attacker can reach an off-path mutation route cheaply enough that the guarded path is no longer the only way to change the trusted state.
    • Another protocol module quickly consumes the mutated state as collateral truth, borrowable liquidity, accrued value, or redeemable balance.
    • The protocol lacks a reconciliation step that either neutralizes the off-path state change or applies the same policy to it.
  • Common vulnerable surfaces:
    • Compound-style markets where supply caps constrain mint() but exchangeRate still reads raw cash
    • vaults that throttle direct donations but still let rounding residues or stealth donations raise assetsPerShare
    • lending markets where deposit() updates booked deposits but direct transfers still change liquidity() or borrowable balance
    • Aave-style reserves where supply() is the only sanctioned way to enlarge scaled supplier state, but direct transfers to the aToken can still enlarge real flash-loanable balance or reserve cash consumed by downstream fee and solvency logic
    • systems that guard one accounting verb but let helper contracts, delegated borrowers, or callbacks mutate the same economic state through another route
    • protocols whose comments or risk settings reason about deposited value, while implementation later trusts balanceOf(address(this)), cached NAV, or another wider quantity
  • Exploit sequence:
    1. The attacker identifies the state variable that downstream solvency or payout logic really trusts.
    2. They identify the protocol rule that is supposed to constrain that state, but notice the rule only wraps one official entrypoint.
    3. They find an off-path route that still mutates the trusted state without passing through the guarded verb.
    4. They use that route to push the trusted state beyond the intended policy boundary.
    5. Downstream logic honors the mutated state as collateral value, borrowable liquidity, accrued yield, or redeemable balance.
    6. The attacker monetizes the gap by borrowing, draining, self-liquidating, or leaving bad debt behind.
  • Variants:
    • Cap-on-mint / uncapped raw-balance state — a market supply cap constrains formal deposits, but direct transfers still inflate the raw-balance quantity used by exchange-rate or collateral math.
    • Throttle-on-donation / stealth-donation bypass — the protocol limits explicit donations, but rounding residues or sibling accounting paths still move the same assets-per-share state.
    • Deposits-ledger / live-liquidity split — borrowable liquidity comes from live token balance while utilization or depositor accounting comes from a narrower booked-deposits ledger.
    • Helper-path policy bypass — the guarded surface is safe in isolation, but delegated helper contracts or sibling flows can still exercise the same economic authority through another path.
    • Withdrawn-collateral / live-order split — an order, pledge, or receipt object remains borrow-authoritative even after a sibling withdrawal path has already removed the real collateral the object was supposed to prove.
    • Asynchronous-order residual-state bypass — an adapter exposes a pending external order as collateral or value, but liquidation, refund, or extraction paths consume the real backing assets without consuming the same pending-order value still trusted downstream.
    • Policy-on-verb / policy-not-on-state — the general form: the protocol constrains one syntactic route but not the state object that later defines solvency.

Three policy-to-state promotions reviewers should not collapse

  1. A guarded route exists

    • The protocol has a visible rule on some blessed verb: mint() obeys a cap, deposit() updates the deposits ledger, withdrawNFT() is supposed to retire borrow authority, or a donation helper enforces a throttle.
    • This is only the weakest fact: one syntactic lane is constrained.
  2. The economically trusted state still means admitted value under that same rule

    • The harder question is whether the state later read by exchange-rate math, liquidity(), liquidityIndex, NAV, order value, or collateral checks can still move outside the guarded lane.
    • Venus, Wise, Silo, ZeroLend, XCarnival, and Abracadabra each break here in different ways: raw balance, stealth donation residue, live borrowable liquidity, or surviving order state drifted away from the narrower rule the protocol thought it had enforced.
  3. That trusted state still deserves downstream monetary authority

    • Even if some broader state object exists, the protocol still has to decide whether borrow, liquidation, redemption, flash-loan, or collateral paths should already honor it as money-good truth.
    • This is where quarantine, derecognition, caps, and fail-closed behavior matter: state moved somehow is still weaker than the rest of the balance sheet may now spend against it.

Healthy review improves once teams stop flattening those three promotions into one vague sentence like the cap exists or the deposit path is guarded.

  • Detection heuristics:
    • For every cap, throttle, or guardrail, ask: what exact state variable does this rule constrain, and is that the same variable downstream logic later trusts?
    • Compare guarded-path state updates with off-path balance changes such as direct ERC-20 transfers, rounding leftovers, callback-driven state moves, or delegated helper actions.
    • Diff mintAllowed() / deposit() / donation-guard inputs against exchangeRate, liquidity(), totalAssets, balanceOf, utilization, or NAV inputs.
    • Fuzz off-path state mutations and assert that the guarded economic quantity cannot move past policy limits without the official entrypoint firing.
    • Treat comments like cap total supply or limit donation as suspicious until the same invariant is proven on the actual balance-sheet state consumed later.
  • Defenses:
    • Enforce policy on the economically trusted state, not only on one public verb.
    • If raw balances may change off-path, either ignore them for solvency-critical math, sweep them into a neutral reserve bucket, or mint non-user-owned shares against them.
    • Make downstream consumers fail closed when a supposedly constrained state changes without the corresponding accounting path or event.
    • Reconcile entrypoint-local limits with broader state reads before borrow, liquidation, redemption, or collateral admission proceeds.
    • Add invariants that prove policy survives direct transfers, rounding residues, delegated helper routes, and other off-path mutations.
    • Review what state is trusted and what path is guarded together; if those answers differ, treat the gap as a live exploit surface.

What sound design looks like

  • A healthy system preserves three separate gates instead of one vague the guarded path is safe story: a route is constrained, the wider state read later by solvency logic still means the same admitted quantity, and that state is actually allowed to gain downstream monetary authority. If those gates are not explicit, the protocol is usually enforcing policy on syntax while trusting value through a broader side channel.
  • A healthy system preserves one explicit balance-sheet object from admission or deposit path through downstream solvency use: if the protocol says this cap limits collateral, this ledger records deposits, or this order proves backing, then borrow, liquidation, exchange-rate, utilization, and redemption math should all consume that same constrained object rather than a wider proxy. The positive-design companion there is helper-path invariant reuse and fail-closed discipline: helper paths, rescue paths, and sibling modules should inherit one state boundary, not quietly author a looser one.
  • Unsolicited or off-path state changes should fail closed by default. Direct ERC-20 transfers, stealth-donation residues, withdrawn-but-still-referenced collateral, pending-order artifacts, and delegated helper mutations should land in a quarantined or non-authoritative state until a reconciliation step proves what they mean. The adjacent accounting companion is balance-delta provenance failure: the balance moved is not yet proof that a user performed the accounted action the rest of the system wants to trust.
  • Pricing and collateral authority should only attach after provenance and backing still line up. If a live balance, cached NAV, pending order, or receipt object can outlive the asset or accounting path that was supposed to justify it, the safer design is to zero, quarantine, or derecognize that object before it gains borrow or redemption authority. The nearby companions there are vault-share truth as a single security object for balance-sheet meaning and exit-right consumed-state binding when payout or withdrawal paths can consume backing without consuming the same right downstream code still honors.
  • Launch and thin-state workflows should stage trust rather than publishing full solvency meaning immediately. If a market, vault, or async settlement object is cheap to dominate in dust or bootstrap conditions, healthy systems keep borrowing, cross-collateral use, or downstream pricing disabled until that state is seeded, reconciled, and monitored. The strongest rollout companion there is collateral admission lag and borrow-side quarantine: a locally updated balance should not instantly become global collateral truth.
  • Reward, yield, and accounting presentation should stay weaker than collateral authority unless real backing has already been proven. If an off-path mutation can raise assets-per-share, utilization-driven yield, or cached value display, healthy systems may still present or park that state without letting it immediately support new borrowing. The adjacent positive-design companion is reward-recognition, presentation, and collateral-admission separation.
  • The clean design question is blunt: what exact state object is allowed to make money move, and what off-path mutation would have to happen before we stop trusting it? If the answer names a narrower guarded verb than the downstream state readers actually honor, the protocol is still enforcing policy on syntax rather than on value.

Failure-prevention checklist

  • For every cap, throttle, quota, or admission rule, document the full promotion ladder explicitly: guarded route exists, trusted state still means admitted quantity, and that state deserves downstream monetary authority should be separate checks in code, monitoring, and incident response.
  • For every cap, throttle, quota, or admission rule, name the exact balance-sheet variable it is meant to constrain and ensure downstream solvency logic reads that same variable rather than a wider proxy.
  • Enumerate all off-path mutations of the trusted state: direct ERC-20 transfers, wrapper donations, rounding leftovers, delegated helper flows, liquidation side effects, migration adapters, async order objects, and operator rescue paths.
  • Decide explicitly how unsolicited or off-path assets are handled. Either quarantine them from pricing math, convert them into non-user-owned reserves, or force a reconciliation step before they gain collateral or liquidity authority.
  • Make borrow, redeem, liquidate, and collateral-admission paths assert that state moved through the expected accounting rail. If the trusted state changed without the matching ledger update, pause or neutralize it.
  • Fuzz and invariant-test policy on state, not just policy on verb: direct transfer before borrow, stealth donation before liquidation, withdrawn-collateral order reuse, helper-mediated liquidity changes, and partial-settlement async paths.
  • Keep live balance, booked deposits, pending orders, and collateral authority objects distinct unless the code proves they cannot drift apart.
  • Add monitoring for unexplained jumps in exchange rate, utilization, liquidity, NAV, or collateral value that were not preceded by the expected entrypoint or event.
  • When a mitigation relies on rate limits or supply caps, confirm the cap still holds under raw-balance change, residual dust accumulation, and cross-module state reuse.

Audit prompts

  • Which promotion is happening at each step here: one blessed route is guarded, the broader state still counts as policy-compliant admitted value, or that state is already allowed to move money elsewhere?
  • What exact variable does the risk setting claim to constrain, and what exact variable do borrow, liquidation, redemption, or collateral checks later trust?
  • If an attacker never calls the canonical entrypoint, can they still move the state that downstream math prices as assets, liquidity, utilization, or collateral?
  • Which off-path routes can mutate the trusted state: direct token transfer, callback, fee-on-transfer residue, helper contract, migration adapter, settlement queue, or admin recovery path?
  • Does the protocol ever interpret balance increased as equivalent to user deposited, collateral posted, or liquidity supplied without proving provenance?
  • Can a pending order, pledge record, receipt NFT, or cached balance remain borrow-authoritative after the real backing assets have already been withdrawn, redeemed, or diverted elsewhere?
  • Are supply caps, donation throttles, or deposit ledgers enforced before the exact state used in exchange-rate, utilization, or NAV math is recomputed?
  • If the protocol wants to tolerate unsolicited transfers, where are those assets parked, and why can they not leak into user-share pricing or borrowable liquidity?
  • What invariant fires when trusted state changed but expected accounting path did not?
  • Do tests cover the ugly surfaces: empty-market conditions, dust state, first-depositor state, direct donations, async settlement, and helper-mediated state mutation?

Lessons that should already be standard

  • The blessed path is guarded is not yet the same thing as the trusted state still reflects that guard, and that is still not the same thing as downstream modules should already lend or liquidate against it.

  • A guard on the public verb is not a guard on the protocol if economic authority lives in broader state. Reviewers should treat path guarded != state trusted as an immediate red flag.

  • Raw balance is a dangerous truth source. If solvency-critical math reads balanceOf(this) directly, the protocol should justify exactly why unsolicited or misproven balance changes cannot become spendable authority.

  • Supply caps, donation guards, and deposit ledgers are accounting statements, not UI hints. If they do not survive direct transfers and sibling flows, they are decorative.

  • Async objects deserve the same suspicion as balances. A receipt, order, or pledge that remains authoritative after backing assets leave custody is just a slower version of the same bug.

  • Quarantine beats interpretation. When the protocol cannot prove what an inbound balance change means, the safest default is to exclude it from user-facing solvency math until reconciled.

  • Most of these exploits are not about a missing require in isolation; they are about a protocol telling one module a tighter story than the rest of the balance sheet is actually enforcing.

  • The standard review question should be blunt: what exact state makes money move, and can anything outside the blessed path mutate it?

  • Off-path mutation bugs are especially dangerous in low-liquidity, bootstrap, or dust states because a small unsolicited change can dominate the whole accounting surface.

  • High-signal incidents:

    • [[../../../incidents/oracle-market-abuse/venus-protocol-thena-the-supply-cap-bypass-donation-price-manipulation-bad-debt-2026-03-15|Venus Protocol THENA THE supply-cap-bypass donation / price-manipulation bad-debt incident (2026-03-15)]] — the clean cap on mint path, no cap on raw-balance exchange-rate path case: Venus bounded formal vTHE supply but still let direct THE transfers inflate the cash and exchange-rate state that collateral math actually trusted.
    • share-price manipulation (2024-01-12) — the strong donation throttle on the obvious path, same economics still reachable through rounding residues case: Wise tried to bound explicit donation effects, but stealth donations still moved the assets-per-share state that sibling lending pools honored as collateral truth.
    • Silo Finance WETH donated-liquidity utilization inflation near miss (2023-04-28) — the clean deposit ledger guarded, live-liquidity state still mutable case: off-ledger WETH donations became borrowable liquidity even though the deposits book feeding utilization and interest logic never moved.
    • [[../../../incidents/lending-liquidations/zerolend-pt-lbtc-actual-balance-scaled-liquidity-liquidityindex-exploit-2025-02-23|ZeroLend PT-LBTC actual-balance / scaled-liquidity liquidityIndex exploit (2025-02-23)]] — the sharp Aave-v3-style policy on supply path, broader state trusted by flash loans and collateral math case: direct PT-LBTC transfers to the aToken enlarged real flash-loanable balance without minting scaled supplier liquidity, so premiums compounded into liquidityIndex against a dust denominator and the same widened rayDiv() windows later helped drain PT-LBTC and print LBTC-facing collateral.
    • dTRINITY dLEND cbBTC fresh-market liquidityIndex inflation exploit (2026-03-17) — the fresh-reserve hybrid: supply() was supposed to define supplier-side accounting, but a direct cbBTC transfer to the aToken widened the gap between real reserve balance and scaled-liquidity denominator while flash-loan fees inflated liquidityIndex over dust supply, so downstream borrowing consumed a broader state object than the guarded deposit path ever authorized.
    • [[../../../incidents/lending-liquidations/xcarnival-xnft-withdrawn-nft-order-borrowallowed-iswithdraw-omission-hollow-order-borrow-drain-2022-06-27|XCarnival XNFT withdrawn-NFT order borrowAllowed() / _order.isWithdraw omission hollow-order borrow drain (2022-06-27)]] — the withdrawn-collateral variant: withdrawNFT() removed the pledged BAYC from custody, but borrowAllowed() / orderAllowed() still treated the surviving order record as borrow authority because they never checked whether the NFT had already been withdrawn.
    • Abracadabra GMX V2 Cauldron RouterOrder phantom-collateral self-liquidation drain (2025-03-25) — the asynchronous-order variant: gmCauldrons trusted orderValueInCollateral() as pending collateral truth even after liquidation had already extracted the RouterOrder’s real backing assets through a separate path.

Internal linkages

Control surface