Skip to content
Security
Skill

/v4-hook-delta-accounting

Detect Uniswap V4 hooks that fail to settle currency deltas with the PoolManager. Every credit/debit a hook creates (BeforeSwapDelta, afterSwap hookDelta, take/mint, donate, settle/sync) is tracked in the manager's transient nonzeroDeltaCount; if the books aren't flat when

From plugin
rugproof
952 skills23 agents45 commands4 hooks
Install
$ npx -y skills add omermaksutii/RugProof --skill v4-hook-delta-accounting --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/v4-hook-delta-accounting

Context preview

The summary Claude sees to decide when to auto-load this skill.

Detect Uniswap V4 hooks that fail to settle currency deltas with the PoolManager. Every credit/debit a hook creates (BeforeSwapDelta, afterSwap hookDelta, take/mint, donate, settle/sync) is tracked in the manager's transient nonzeroDeltaCount; if the books aren't flat when

SKILL.md

v4-hook-delta-accounting.SKILL.md
name: v4-hook-delta-accounting
description: Detect Uniswap V4 hooks that fail to settle currency deltas with the PoolManager. Every credit/debit a hook creates (BeforeSwapDelta, afterSwap hookDelta, take/mint, donate, settle/sync) is tracked in the manager's transient nonzeroDeltaCount; if the books aren't flat when unlock returns, the whole transaction reverts (CurrencyNotSettled), and mismatched take/settle/donate either strands hook funds in the manager or lets a swap leave with unpaid debt. Activate on hooks returning deltas, calling take/settle/mint/burn/donate, or custom unlockCallback accounting.

Uniswap V4 hook delta-accounting detection

When this applies

Trigger on any of:

  • Callbacks returning `BeforeSwapDelta` or a non-zero `int128` `hookDelta` from `afterSwap`/`afterAddLiquidity`/`afterRemoveLiquidity`
  • Calls to `poolManager.take`, `settle`, `sync`, `mint`, `burn`, `donate`, `clear`
  • Custom `unlockCallback` that moves currency in/out of the manager
  • Hooks that charge custom fees, skim, or rebate by adjusting deltas
  • `currencyDelta` reads, or accounting that must net to zero before `unlock` returns
  • Donations to a pool, or take/settle pairs that should balance

Detection patterns

Hook takes currency but never settles (HIGH)

function afterSwap(address, PoolKey calldata key, ..., BalanceDelta, bytes calldata)
    external override returns (bytes4, int128)
{
    poolManager.take(key.currency0, address(this), feeAmount);  // ← creates a -debt for the hook
    return (this.afterSwap.selector, 0);                         // ← returns 0 delta, never settles
}

`take` debits the hook's currency balance in the manager; with no matching `settle`/returned delta, `nonzeroDeltaCount != 0` and the entire `unlock` reverts `CurrencyNotSettled` — every swap on the pool reverts. **Signal:** `take`/`mint` without a balancing `settle`/`burn` or a non-zero returned `hookDelta` accounting for it.

Returned delta not backed by a real transfer (HIGH)

return (this.afterSwap.selector, int128(feeAmount));   // claims to owe the pool feeAmount...
// ...but the hook never `sync` + `settle`s those tokens into the manager

A positive `hookDelta` says "the hook owes the pool X"; if the hook never actually transfers and settles X, the books don't flatten → revert, or in the inverse direction the swapper leaves with unpaid debt. **Signal:** non-zero returned delta with no corresponding `sync`/`settle` (or `take`) of the same currency/amount.

Donate / take imbalance (HIGH)

poolManager.donate(key, amount0, amount1, "");  // adds to pool reserves...
// hook forgot to settle the donated tokens it owes

`donate` increases what the hook owes the pool; the tokens must be `settle`d. Imbalanced donate strands funds or reverts. **Signal:** `donate` without settling the donated amounts, or `take` of donated funds with no offsetting credit.

BeforeSwapDelta sign/units error (HIGH)

BeforeSwapDelta d = toBeforeSwapDelta(int128(amtSpecified), 0);  // wrong: specified vs unspecified swapped

`BeforeSwapDelta` packs (specified, unspecified) deltas; swapping the two halves or the sign mis-accounts the swap and either reverts or hands the swapper free output. **Signal:** `toBeforeSwapDelta` arguments in the wrong slot/sign, or specified-delta not reconciled with the actual swap amount.

Severity rubric

| Pattern | Severity | Notes | |---|---|---| | take/mint with no matching settle → CurrencyNotSettled | **High** | Pool-wide swap DoS | | Returned hookDelta not backed by settle → revert or free funds | **High** | Loss or DoS | | Donate without settling owed tokens | **High** | Stranded funds / revert | | BeforeSwapDelta sign/slot error | **High** | Mis-accounted swap, value leak | | Hook over-settles (pays more than owed) | **Medium** | Hook self-loss, no swapper gain | | Deltas always net to zero within callback | **Info** | Correct accounting |

Remediation patterns

1. **Net every delta to zero before unlock returns** — for each currency the hook touches, pair `take` with `settle` (or a correct returned `hookDelta`). 2. **sync → transfer → settle** — call `poolManager.sync(currency)`, transfer the tokens in, then `settle()` so the manager credits the exact owed amount. 3. **Use the official delta helpers** — `toBeforeSwapDelta(specified, unspecified)` with correct argument order and signs; reconcile `specified` against `params.amountSpecified`. 4. **Assert flatness in tests** — after a swap, assert `poolManager.currencyDelta(hook, currency) == 0` for every currency. 5. **Settle donations** — every `donate` must be followed by transferring + settling the donated amounts.

False-positive notes

  • A hook returning `BeforeSwapDeltaLibrary.ZERO_DELTA` / `0` hookDelta and never calling take/settle/donate has nothing to settle — Info.
  • Settlement done inside a shared internal helper called at the end of the callback may look unbalanced locally — trace the full callback before flagging.
  • Over-settling (hook pays extra) is a hook self-loss, not a protocol-loss — Medium, not High.

Related

  • [[v4-hook-permission-flags-mismatch]] — returnDelta flags must be set for deltas to apply
  • [[v4-hook-reentrancy-via-unlock]] — settlement ordering vs. reentrancy
  • [[unchecked-calls]]
Read more
Ships withrugproof

Rugproof your code before someone else does. 🌐 Live site: omermaksutii.github.io/RugProof 📦 Latest: v1.0.0 — 45 commands · 23 agents · 45 skills · 13 MCP servers · tested, offline-first, with rule packs, a benchmark, non-EVM coverage, and post-deploy

Get the whole plugin

Other skills on rugproof.