AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL verified MIT Self-run

V4 Hook Reentrancy Via Unlock

skill-omermaksutii-rugproof-v4-hook-reentrancy-via-unlock · by omermaksutii

Detect reentrancy in Uniswap V4 hooks via the PoolManager unlock/lock callback. V4 uses a singleton PoolManager with transient lock state; all pool mutations happen inside an unlockCallback. A hook that makes external calls during beforeSwap/afterSwap/before*Liquidity (to tokens with hooks, arbitrary routers, or user-controlled contracts) can be re-entered, and because the manager is already unlo…

No reviews yet
0 installs
19 views
0.0% view→install

Install

$ agentstack add skill-omermaksutii-rugproof-v4-hook-reentrancy-via-unlock

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/skill-omermaksutii-rugproof-v4-hook-reentrancy-via-unlock)

Reliability & compatibility

Security review passed
0 installs to date
no reviews yet
1mo ago

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

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 →
Are you the author of V4 Hook Reentrancy Via Unlock? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

Uniswap V4 hook reentrancy via unlock detection

When this applies

Trigger on any of:

  • Hook callbacks (beforeSwap, afterSwap, beforeAddLiquidity, afterRemoveLiquidity, beforeDonate, ...) that perform external calls
  • External calls to ERC-777 / ERC-1155 / callback-bearing tokens, arbitrary routers, or user-supplied addresses inside a callback
  • A hook that itself calls poolManager.unlock(...) or swap/modifyLiquidity/take/settle re-entrantly
  • Custom unlockCallback implementations
  • Hook state (fee accumulators, TWAP buffers, custom accounting) read/written across an external call within one callback
  • safeTransfer / transferFrom of tokens that invoke recipient hooks during settlement

Detection patterns

External call before state finalize inside a callback (HIGH)

function afterSwap(address, PoolKey calldata key, ..., int128) external override returns (bytes4, int128) {
    uint256 reward = _pending[key.toId()];
    rewardToken.safeTransfer(msg.sender, reward);  // ← ERC-777 hook re-enters here
    _pending[key.toId()] = 0;                       // ← cleared AFTER the external call
    return (this.afterSwap.selector, 0);
}

During the transfer the recipient re-enters swap (manager is unlocked), triggering afterSwap again while _pending is still non-zero → double reward. Signal: hook state mutated after an external call inside a callback, with the PoolManager unlocked (CEI violated in hook context).

Recursive unlock / nested swap (HIGH)

function beforeSwap(...) external override returns (bytes4, BeforeSwapDelta, uint24) {
    router.call(userData);   // user contract calls poolManager.swap again, re-entrant
    ...
}

The transient lock is already held; the manager permits nested operations, so the hook's pre-swap invariants can be violated mid-flight. Signal: arbitrary/user-controlled external call inside a callback whose result feeds the same swap's accounting.

Read-only reentrancy on hook-exposed price (HIGH)

function getTwap(PoolKey calldata key) external view returns (uint256) {
    return _twap[key.toId()];   // read by a victim mid-callback, before this hook updates it
}

A consumer reads the hook's oracle while the hook is mid-callback and its accumulator is stale. Signal: a public view exposing hook state that is updated inside a callback, readable during reentrancy. See [[reentrancy]].

Severity rubric

| Pattern | Severity | Notes | |---|---|---| | State cleared after external call in callback → double-spend | High | Direct value extraction | | User-controlled call inside callback enabling nested swap | High | Invariant break mid-swap | | Read-only reentrancy on hook oracle/TWAP | High | Often missed, see [[oracle-manipulation]] | | External call to a fixed, trusted contract only | Medium | Bounded by trust assumption | | Callback with no external calls / pure accounting | Info | No reentrancy surface |

Remediation patterns

  1. CEI inside the hook — finalize hook state (zero out pending, update accumulators) before any external call within a callback.
  2. Per-hook reentrancy guard — a transient (tstore) guard on callbacks; the PoolManager's own lock does NOT protect hook-internal state.
  3. Avoid callback-bearing tokens in callbacks, or pull/settle through the manager (take/settle) rather than raw transfers to arbitrary recipients.
  4. Guard view functions that expose hook state used as a price, or serve a "settled" snapshot that only updates outside callbacks.
  5. No arbitrary external calls inside callbacks; restrict to allowlisted, non-reentrant targets.

False-positive notes

  • A callback that performs no external calls and only mutates its own storage has no reentrancy path — Info.
  • Calls strictly to the PoolManager's take/settle/sync (no recipient hooks) don't re-enter the hook.
  • A callback already wrapped in a transient reentrancy guard with state finalized first — downgrade.

Related

  • [[reentrancy]]
  • [[v4-hook-delta-accounting]] — settlement ordering interacts with reentrancy
  • [[oracle-manipulation]] — read-only reentrancy on hook TWAP
  • [[token-compatibility]] — ERC-777 hooks widen the surface

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.