Technique · OAK-T9.016 · emerging
OAK-T9.016 — Contract Accounting / State-Invariant Flaw
Description
The contract lets a legitimate caller perform a legitimate call, and the call leaves the contract's books wrong in the caller's favour. No role is bypassed and no check of who is asking fails. What fails is the contract's model of how much — a balance, a reserve, a share price, a debt position, a claim record, a state flag — so that the attacker ends the transaction owed, or holding, more than they put in.
T9.016 exists because OAK previously filed these cases under T9.004 (Access-Control Misconfiguration), which described itself as "the residual category" for logic flaws. That grouping made the corpus's most-cited Technique mean two different things and pointed defenders at the wrong control: an access-control finding asks "who may call this?", while every T9.016 case had the right caller and the wrong arithmetic or state transition. The 2026-10 split moved them here.
Recurring sub-shapes in the anchor set:
- Missing solvency / health check on a value-moving path — an action that should be refused when it leaves the actor undercollateralised is permitted on one path the designers did not guard (Euler 2023
donateToReserves; Platypus 2023 emergency withdrawal; Cypher 2023 aggregate sub-account debt; Rhea 2026 collateral taken from a user-supplied route claim). - State not cleared or updated after payout — a record that should be consumed is left live and can be replayed (DoinGud 2026
acceptBid; Dream Health 2026 award reset; Likwid 2026leverage = 0path skipping the reserve update). - Self-referential or aliasing accounting — the same slot is read before and written after a mutation, or two claims are created on one asset (Miner ERC-X 2024 self-transfer cached balance; Yearn 2023 stale share-token mapping).
- Invariant check computed wrong — the constant-product or equivalent check exists but is miscalculated, overflows, or reads balances after an internal hook has moved the protocol's own money in (Uranium 2021; KyberSwap 2023 tick boundary; EtherVista 2026 uint112 product; MALT 2026 treasury DAI counted as trader input; GoldPesa 2026 unsettled hook deltas).
- Edge-case branches in share math — zero liquidity, zero supply, or first-depositor branches that stop being pro-rata (BitBay StableVault 2026).
- Mispriced internal valuation — the contract's own pricing function values an attacker-shaped input above its backing (Lien Finance 2026).
- Distribution / reward accounting errors — rewards accrue or distribute against the wrong basis (Compound 2021 Comptroller).
Observed indicators
- A single transaction (often flash-loan funded) whose net effect is that the caller's withdrawable claim on the protocol rises by more than the value they supplied, through only public entrypoints.
- The same function called repeatedly in one transaction with the protocol's internal accounting not changing between calls (replayed claims, stale reserves).
- Calls on rarely-used branches: emergency withdrawal, zero-leverage paths, migration helpers, first-deposit or empty-pool states, self-transfers.
- Post-event: the root-cause analysis names an invariant ("total claims ≤ total assets", "debt ≤ collateral", "k never decreases") rather than a missing role.
Detection signals
- Pre-deployment invariant testing / formal verification (OAK-M16): state the conservation invariants explicitly (assets vs. liabilities, k monotonicity, claim consumption) and fuzz every public entrypoint against them, including degenerate states (zero supply, self-transfer,
from == to, zero leverage). - Per-transaction balance-sheet monitoring: compare protocol assets and liabilities before and after each transaction; alert when a caller's net claim grows faster than their deposit, or when total liabilities exceed assets after a public call.
- Repeated-call heuristics: the same payout entrypoint invoked more than once per transaction by the same caller against the same record.
- Rate limits / per-block caps (OAK-M11): do not detect the bug but bound how much one transaction can extract against it.
Real-world examples
examples/2023-03-euler-finance.md— Euler Finance, 2023-03-13, ~$197M (returned). Missing health check ondonateToReserves. Canonical anchor for the missing-solvency-check sub-shape.examples/2023-11-kyberswap.md— KyberSwap Elastic, 2023-11, ~$48M. Tick-boundary liquidity double-count; canonical for the invariant-computation sub-shape.examples/2023-02-platypus.md— Platypus, 2023-02. Emergency withdrawal skipped the open-debt check.examples/2026-09-doingud-dormant-diamond-acceptbid-bid-reuse-drain.md— DoinGud, 2026-09. Bid record never cleared after payout.
Additional incidents
examples/2021-05-bogged-finance.md— Bogged Finance, 2021-05 — staking-reward logic let a flash-loaned position mint ~15M BOG (previously misfiled as a governance attack).examples/2021-04-uranium-finance.md— Uranium Finance, 2021-04 — swap invariant used 1000 where 10000 was required.examples/2021-09-compound.md— Compound Comptroller distribution bug, 2021-09.examples/2023-02-yearn-v2.md— Yearn iearnUSDT stale share-token mapping, 2023-02.examples/2023-08-cypher.md— Cypher, 2023-08 — per-sub-account checks, no aggregate check.examples/2024-02-miner-erc-x-self-transfer-cached-balance-double-spend.md— Miner ERC-X, 2024-02 — self-transfer minted.examples/2026-04-rhea-finance-margin-parser-exploit.md— Rhea Finance, 2026-04 — collateral quantity from a user-supplied route claim.examples/2026-07-lien-finance-bond-rate-calculation-overvaluation-drain.md— Lien Finance, 2026-07 — internal bond valuation.examples/2026-09-dream-health-chain-award-reset-repeat-claim.md— Dream Health Chain, 2026-09 — award reset permitted a repeat claim.examples/2026-09-likwid-margin-position-zero-leverage-stale-reserves-borrow-loop.md— Likwid, 2026-09 — zero-leverage path skipped the reserve update.examples/2026-10-malt-swap-rebalance-treasury-dai-counted-as-trader-input.md— MALT, 2026-10.examples/2026-10-goldpesa-gpxhooks-rebalance-unvalidated-currency-delta.md— GoldPesa, 2026-10.examples/2026-10-bitbay-stablevault-zero-liquidity-withdraw-full-balance.md— BitBay StableVault, 2026-10.examples/2026-10-ethervista-pair-uint112-k-invariant-overflow-swap-drain.md— EtherVista, 2026-10.
Reference implementations
mg-detectors-rs— coverage gap. Balance-sheet delta monitoring per transaction is the generic detector; protocol-specific invariants require per-protocol configuration.
Mitigations
- OAK-M16 (Pre-Deployment Audit and Formal Verification): highest leverage. Every T9.016 anchor violates an invariant that could have been written down and fuzzed.
- OAK-M11 (Rate-Limiting and Per-Block Caps): bounds single-transaction extraction.
- OAK-M34 (Pause-by-Default / Emergency Pause): limits the second and later transactions once monitoring fires.
Discussion
Boundary with T9.004. Ask whether the attacker needed to be someone they were not. If the exploit works only because the contract accepted a caller, signer, account, module or counterparty it should have rejected — including a counterfeit object presented as genuine (Cashio's fake LP account, Crema's fake tick array, Exactly's fake market, FlashLoopAdapter's fake Safe) — it is T9.004. If any caller could have done it through public entrypoints with honest inputs and the contract simply computed the wrong result, it is T9.016. Cases with both (an unrestricted registration function and double-counted shares, as in Redsonic 2026-09) take T9.004 as primary when the missing gate was load-bearing, and cross-reference T9.016.
Boundary with T9.011. Rounding direction under integer division stays in T9.011. Wrong constants, overflow, missing updates and wrong branches are T9.016, even when they are arithmetic.
Maturity. Minted as emerging with seventeen re-homed anchors spanning 2021–2026; it inherits an established class rather than introducing a new one, so promotion is a question of the multi-source attestation bar, not of anchor count.
Parent Tactics
Worked examples (18)
- MALT — the swap's own rebalancing pulled DAI from the treasury, and the swap counted it as the trader's payment — MALT / EVM — 2026-10-03
- GoldPesa — a hook's reBalance() trusted currency deltas it never settled through the shared PositionManager — GoldPesa (GPX) / Base — 2026-10-02
- EtherVista — the K check multiplied uint112 reserves and overflowed, so a smaller product passed as a larger one — EtherVista / EVM — 2026-10-09
- BitBay StableVault — with liquidity forced to zero, one share redeemed the whole vault — BitBay StableVault (UsdcDaiV4Vault) / Polygon — 2026-10-09
- Likwid — the zero-leverage borrow path never updated reserves, so every borrow got the same quote — Likwid / BNB Smart Chain — 2026-09-18
- Dream Health Chain — awards were recorded without locking the funds to pay them, and a claimed award could be reset and claimed again — Dream Health Chain / BNB Chain — 2026-09-05
- DoinGud — accepting a bid paid out and left the bid on the books, so the same bid could be accepted twice — DoinGud (dormant NFT platform) / Polygon — 2026-09-21
- Lien Finance — an internal rate function prices attacker-crafted bonds far above the collateral actually backing them — Lien Finance (Ethereum) — 2026-07-24
- Rhea Finance Margin-Parser Exploit — Apr 2026 — $18.4M
- MINER — the transfer function checked that neither address was null but not that they were different, so sending tokens to yourself doubled your balance — MINER (ERC-X) / Ethereum — 2024-02
- KyberSwap Elastic tick-state-manipulation exploit — multi-chain — 2023-11-22
- Cypher Protocol sub-account isolation flaw — Solana — 2023-08-07
- Euler Finance lending exploit — Ethereum — 2023-03-13
- Yearn iearnUSDT v1 deprecated-vault misconfiguration — Ethereum — 2023-02-02
- Platypus Finance emergency-withdrawal logic flaw — Avalanche — 2023-02-16
- Compound Comptroller distribution bug — Ethereum — 2021-09-30
- Bogged Finance flash-loan staking-reward mint exploit — BNB Chain — 2021-05 (reported 2021-05-24)
- Uranium Finance migration-pair arithmetic error — BSC — 2021-04-28