v4-hook-reentrancy-via-unlocklisted
Install: claude install-skill iktok90-design/ai-smart-contract-auditor
# 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)
```solidity
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).
### Recurs