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()butexchangeRatestill reads rawcash - 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 changeliquidity()or borrowable balance - Aave-style reserves where
supply()is the only sanctioned way to enlarge scaled supplier state, but direct transfers to theaTokencan 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 trustsbalanceOf(address(this)), cached NAV, or another wider quantity
- Compound-style markets where supply caps constrain
- Exploit sequence:
- The attacker identifies the state variable that downstream solvency or payout logic really trusts.
- They identify the protocol rule that is supposed to constrain that state, but notice the rule only wraps one official entrypoint.
- They find an off-path route that still mutates the trusted state without passing through the guarded verb.
- They use that route to push the trusted state beyond the intended policy boundary.
- Downstream logic honors the mutated state as collateral value, borrowable liquidity, accrued yield, or redeemable balance.
- 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
-
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.
- The protocol has a visible rule on some blessed verb:
-
The economically trusted state still means
admitted valueunder 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.
- The harder question is whether the state later read by exchange-rate math,
-
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 somehowis still weaker thanthe 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 againstexchangeRate,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 supplyorlimit donationas 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 trustedandwhat path is guardedtogether; 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 safestory:a route is constrained,the wider state read later by solvency logic still means the same admitted quantity, andthat 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, orthis 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 movedis 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, andthat state deserves downstream monetary authorityshould 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, andcollateral authorityobjects 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, orthat 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 increasedas equivalent touser deposited,collateral posted, orliquidity suppliedwithout 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 changedbutexpected 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 guardedis not yet the same thing asthe trusted state still reflects that guard, and that is still not the same thing asdownstream 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 trustedas 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
requirein 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
THEsupply-cap-bypass donation / price-manipulation bad-debt incident (2026-03-15)]] — the cleancap on mint path, no cap on raw-balance exchange-rate pathcase: Venus bounded formalvTHEsupply but still let directTHEtransfers inflate thecashand 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 residuescase: 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 mutablecase: 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
liquidityIndexexploit (2025-02-23)]] — the sharp Aave-v3-stylepolicy on supply path, broader state trusted by flash loans and collateral mathcase: direct PT-LBTC transfers to theaTokenenlarged real flash-loanable balance without minting scaled supplier liquidity, so premiums compounded intoliquidityIndexagainst a dust denominator and the same widenedrayDiv()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 directcbBTCtransfer to theaTokenwidened the gap between real reserve balance and scaled-liquidity denominator while flash-loan fees inflatedliquidityIndexover 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.isWithdrawomission hollow-order borrow drain (2022-06-27)]] — the withdrawn-collateral variant:withdrawNFT()removed the pledged BAYC from custody, butborrowAllowed()/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.
- [[../../../incidents/oracle-market-abuse/venus-protocol-thena-the-supply-cap-bypass-donation-price-manipulation-bad-debt-2026-03-15|Venus Protocol THENA
Internal linkages
- Best balance-sheet case study: venus-protocol-thena-the-supply-cap-bypass-donation-price-manipulation-bad-debt-2026-03-15.
- Best Aave-style reserve-accounting case study: zerolend-pt-lbtc-actual-balance-scaled-liquidity-liquidityindex-exploit-2025-02-23 — useful when the policy gap is specifically
supply()constrains booked supplier state while directaTokentransfers still enlarge the raw reserve balance trusted by flash loans, fee routing, or collateral readers. - Best withdrawn-collateral case study: xcarnival-xnft-withdrawn-nft-order-borrowallowed-iswithdraw-omission-hollow-order-borrow-drain-2022-06-27 — useful when the policy gap is not about raw balances or donations, but about an order object that stays borrow-authoritative after the real pledged asset has already left custody.
- Positive-design companion: collateral-liveness-and-right-consumption-discipline — useful when the healthy-side question becomes what exact state transition should retire borrow authority the moment an order, receipt, or async adapter object stops having live backing.
- Adjacent pattern: donation-share-price-manipulation — use this when the sharper question is how exchange-rate or assets-per-share inflation was printed after the policy gap opened.
- Adjacent pattern: utilization-boundary-failure — use this when the sharper question is how off-ledger liquidity pushed utilization above its safe domain and turned interest accrual into the payout rail.
- Adjacent pattern: balance-delta-provenance-failure — use this when the deeper analytical mistake is that raw balance change itself was treated as proof of a specific accounted action.
- Adjacent pattern: solvency-check-bypass — use this when the sharper question is not just that policy sat on the wrong path, but that the protocol kept lending against state whose real backing had already been removed or invalidated.
- Adjacent pattern: fresh-market-initialization-failure — use this when the policy gap mattered mainly because launch-state or dust-state economics made the off-path mutation cheap enough to dominate the system.
- Adjacent pattern: supplier-index-inflation — use this when the off-path state mutation lands specifically inside a rebasing reserve, where the same gap between real balance and scaled supplier accounting lets flash-loan fees or other reserve revenue print indexed collateral.
Control surface
-
The decisive leverage sits at the seam between
which path is guardedandwhich state is later trusted. -
These incidents are easy to summarize as
direct transfer bug,donation attack, orcap bypass, but the reusable design failure is sharper: the protocol bound policy to one verb while solvency logic trusted a wider state object. -
A useful review question is brutally simple: if an attacker never called the blessed entrypoint, could they still move the variable that borrow, liquidation, redemption, or valuation logic treats as truth? If yes, the policy is only decorative.
-
Sources:
- https://community.venus.io/t/the-market-incident-post-mortem/5712.json
- https://code4rena.com/reports/2023-05-venus
- https://www.infect3d.xyz/blog/wise-lending-post-mortem
- https://www.euler.finance/blog/exchange-rate-manipulation-in-erc4626-vaults
- https://immunefi.com/blog/bug-fix-reviews/silo-finance-logic-error/
- https://www.certora.com/blog/silo-finance-post-mortem
- https://www.kyp.one/notes/zerolend-liquidity-index-manipulation/
- https://rekt.news/zero-to-lend
- https://docs.dtrinity.org/security/incident-disclosures
- https://etherscan.io/tx/0x8d33d688def03551cb77b0463f55ae5a670f5ebf3bbb5b8aa0e284c040ae7139
- https://slowmist.medium.com/xcarnival-nft-lending-protocol-vulnerability-analysis-583474cede61
-
Last reviewed: 2026-07-30 UTC