Install
$ agentstack add skill-omermaksutii-rugproof-v4-hook-delta-accounting ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
Uniswap V4 hook delta-accounting detection
When this applies
Trigger on any of:
- Callbacks returning
BeforeSwapDeltaor a non-zeroint128hookDeltafromafterSwap/afterAddLiquidity/afterRemoveLiquidity - Calls to
poolManager.take,settle,sync,mint,burn,donate,clear - Custom
unlockCallbackthat moves currency in/out of the manager - Hooks that charge custom fees, skim, or rebate by adjusting deltas
currencyDeltareads, or accounting that must net to zero beforeunlockreturns- 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 settled. 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
- Net every delta to zero before unlock returns — for each currency the hook touches, pair
takewithsettle(or a correct returnedhookDelta). - sync → transfer → settle — call
poolManager.sync(currency), transfer the tokens in, thensettle()so the manager credits the exact owed amount. - Use the official delta helpers —
toBeforeSwapDelta(specified, unspecified)with correct argument order and signs; reconcilespecifiedagainstparams.amountSpecified. - Assert flatness in tests — after a swap, assert
poolManager.currencyDelta(hook, currency) == 0for every currency. - Settle donations — every
donatemust be followed by transferring + settling the donated amounts.
False-positive notes
- A hook returning
BeforeSwapDeltaLibrary.ZERO_DELTA/0hookDelta 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]]
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: omermaksutii
- Source: omermaksutii/RugProof
- License: MIT
- Homepage: https://omermaksutii.github.io/RugProof
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.