example-fork-detection
TEMPLATE — replace with the description of your rule. Should activate on the specific code patterns your fork has. Activate on…
Detect Uniswap V4 hooks whose address-encoded permission flags don't match the callbacks the hook actually implements. In V4 the hook's permissions live in the low bits of its deployed address (mined via CREATE2 salt) and must agree with getHookPermissions(); a callback the hook
Claude Code
$ npx -y skills add omermaksutii/RugProof --skill v4-hook-permission-flags-mismatch --agent claude-code
Codex
$ npx skills add omermaksutii/RugProof --skill v4-hook-permission-flags-mismatch -a codex -y
Cursor
$ npx skills add omermaksutii/RugProof --skill v4-hook-permission-flags-mismatch -a cursor -y
Gemini CLI
$ npx skills add omermaksutii/RugProof --skill v4-hook-permission-flags-mismatch -a gemini-cli -y
GitHub Copilot
$ npx skills add omermaksutii/RugProof --skill v4-hook-permission-flags-mismatch -a github-copilot -y
OpenCode
$ npx skills add omermaksutii/RugProof --skill v4-hook-permission-flags-mismatch -a opencode -y
Windsurf
$ npx skills add omermaksutii/RugProof --skill v4-hook-permission-flags-mismatch -a windsurf -y
name: v4-hook-permission-flags-mismatch description: Detect Uniswap V4 hooks whose address-encoded permission flags don't match the callbacks the hook actually implements. In V4 the hook's permissions live in the low bits of its deployed address (mined via CREATE2 salt) and must agree with getHookPermissions(); a callback the hook implements but whose flag bit is unset is never invoked, and a flag set without a real implementation makes pool initialization revert in Hooks.validateHookPermissions. Activate on any BaseHook/IHooks contract, getHookPermissions overrides, or hook address mining.
Trigger on any of:
function getHookPermissions() public pure override returns (Hooks.Permissions memory) {
return Hooks.Permissions({ beforeSwap: true, afterSwap: false, /* ...all else false */ });
}
function afterSwap(...) external override returns (bytes4, int128) {
_accrueFees(...); // ← real logic, but afterSwap flag is FALSE
return (this.afterSwap.selector, 0);
}The pool reads permissions from the hook *address bits*, not from the function table. With the `AFTER_SWAP` bit unset, the PoolManager never calls `afterSwap`; `_accrueFees` silently never runs. **Signal:** a callback is implemented (non-reverting body) but its corresponding permission is `false` / the address bit is unmined.
return Hooks.Permissions({ beforeAddLiquidity: true, /* ... */ });
// but beforeAddLiquidity is NOT overridden → BaseHook reverts HookNotImplemented`Hooks.validateHookPermissions` checks that each set address bit corresponds to an implemented callback; a set flag with no override makes `PoolManager.initialize` revert, bricking the pool. **Signal:** permission `true` (or address bit set) with no matching overridden function, or the default `BaseHook` stub left in place (reverts `HookNotImplemented`).
beforeSwapReturnDelta: false // but beforeSwap returns a non-zero BeforeSwapDelta
If `beforeSwap` returns a non-zero delta while `BEFORE_SWAP_RETURNS_DELTA` is unset, the manager ignores the delta (or reverts), stranding the accounting the hook tried to apply. **Signal:** a callback returns a non-zero `BeforeSwapDelta`/`int128` while its `*ReturnDelta` permission is false.
address hook = address(uint160(0x...0040)); // only BEFORE_SWAP bit // getHookPermissions() also claims afterSwap → mismatch at validateHookPermissions
**Signal:** the mined deployment address low bits don't equal the `getHookPermissions()` struct.
| Pattern | Severity | Notes | |---|---|---| | Implemented callback with unset flag → silently skipped | **High** | Fees/limits/guards never run | | Flag set, no implementation → init reverts | **High** | Pool un-initializable (DoS) | | returnDelta flag mismatch → delta ignored | **High** | Stranded accounting, see [[v4-hook-delta-accounting]] | | Address bits disagree with getHookPermissions | **High** | Deterministic init revert | | Cosmetic flag set but callback is a true no-op | **Low** | Wasted gas only |
1. **Single source of truth** — derive the deploy salt from `getHookPermissions()` (e.g. `HookMiner.find` with the exact flag set) so address bits and the struct can't drift. 2. **Assert at construction** — `Hooks.validateHookPermissions(this, getHookPermissions())` in the constructor to fail fast on a wrong address. 3. **Implement exactly the flagged callbacks** — every `true` has an override; every override has a `true`. Remove dead callbacks or set their flag. 4. **Set the matching `*ReturnDelta` flag** whenever a callback can return a non-zero delta. 5. **Test against the real PoolManager** init path, not a mock that skips validation.
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
TEMPLATE — replace with the description of your rule. Should activate on the specific code patterns your fork has. Activate on…
Detect unsafe assumptions about Solady's gas-optimized ERC20/ERC2612 permit and DN404 metadata. Solady's ERC20 uses custom…
Detect front-runnable ownership initialization in Solady Ownable / OwnableRoles. Solady's `_initializeOwner` is a guarded…
Detect Solady SafeTransferLib calls that assume the token has code. SafeTransferLib.safeTransfer/safeTransferFrom/safeApprove…
Detect Uniswap V4 hooks that fail to settle currency deltas with the PoolManager. Every credit/debit a hook creates…
Detect reentrancy in Uniswap V4 hooks via the PoolManager unlock/lock callback. V4 uses a singleton PoolManager with transient…
Use for authorized security assessment of REST, GraphQL, WebSocket, or SOAP APIs, including discovery, authentication,…
Extract and decrypt Windows DPAPI-protected secrets (Credential Manager, browser logins/cookies, Wi-Fi credentials, KeePass keys)…
Clean and finalize authorized natural-language text intended for readers by auditing suspicious invisible Unicode and rewriting…