Target class: Uniswap-V4-style singleton AMMs - one Vault/PoolManager, transient-storage flash-accounting, pluggable user-supplied hooks, before/after return-deltas. This class is proliferating (V4 forks); these primitives transfer across all of them.
PoolKey. A malicious/buggy hook draining users of
ITS OWN pool = documented, accepted, OUT OF SCOPE. Do not stage it.lock() runs the callback, then asserts the transient
unsettled-delta counter == 0 at unlock, else revert (CurrencyNotSettled). Every take/mint
accounts a negative delta for the caller; you cannot extract free value without an offsetting
credit that the gate forces you to settle.reservesOfApp[app][ccy]
ledger. _accountDeltaForApp does reserve -= uint128(delta) on withdraw - underflow reverts,
so an app/pool can never pay out more than was deposited into IT. This is app-isolation that
V4 itself lacks.The canonical "can a malicious hook mint value from the Vault?" reduces to two questions:
1. Does the hook's returned delta become the HOOK'S OWN settle-obligation on the vault ledger?
(In Infinity: yes - accountAppDeltaWithHookDelta charges address(key.hooks) the hookDelta,
and delta = delta - hookDelta shifts the cost to the swap caller, who also must settle.)
2. Can the hook delta overflow int128 to wrap-mint? (In Infinity: no - BalanceDelta.add/sub
route through SafeCast.toInt128(), so an oversized hook delta REVERTS.)
Also: beforeSwap bounds the specified-delta with HookDeltaExceedsSwapAmount (can't flip
exact-in/exact-out sign).
If BOTH hold, a malicious hook can only hurt its own pool's opt-in users (fenced), not the Vault.
If EITHER fails on a fork, that is a real core finding.
donate: must guard liquidity == 0 => revert (V4's donate-to-empty fix). Present in Infinity.swapFee == protocolFee (lpFee=0 => whole fee to protocol)._settle trusts msg.value for native (no balance-delta measure);
collectFee excludes native from the synced-currency guard - consistent, not a bug alone.setLocker reverts if a non-zero locker is overwritten with another non-zero.When driving the Vault directly with a mock app/locker: the take RECIPIENT and the delta
settler must be the SAME address (the locker/router). If they differ, the recipient's positive
delta is never offset and you get a FALSE CurrencyNotSettled that looks like a bug but is just
router-flow mis-modeling. Model the locker as both settler and taker-caller.
$1M+ multi-audited V4 forks are saturation-likely => frame as hunt-with-LEARN-fallback.
WALK CLEAN with the banked lens IS the win; only stage a finding you can PoC at the Vault level.
Banked Foundry harness: cycle-230-pcs-infinity/repo/test/cycle230/FlashAccounting230.t.sol.