Install
$ agentstack add skill-omermaksutii-rugproof-v4-hook-reentrancy-via-unlock ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo 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.
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 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(...)orswap/modifyLiquidity/take/settlere-entrantly - Custom
unlockCallbackimplementations - Hook state (fee accumulators, TWAP buffers, custom accounting) read/written across an external call within one callback
safeTransfer/transferFromof 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
- CEI inside the hook — finalize hook state (zero out pending, update accumulators) before any external call within a callback.
- Per-hook reentrancy guard — a transient (
tstore) guard on callbacks; the PoolManager's own lock does NOT protect hook-internal state. - Avoid callback-bearing tokens in callbacks, or pull/settle through the manager (
take/settle) rather than raw transfers to arbitrary recipients. - Guard view functions that expose hook state used as a price, or serve a "settled" snapshot that only updates outside callbacks.
- 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.
- 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.