v4-security-foundations
Security-first Uniswap v4 hook development. Use when user mentions "v4 hooks", "hook security", "PoolManager", "beforeSwap", "afterSwap", or asks about V4 hook best practices, vulnerabilities, or audit requirements.
Works with
---
name: v4-security-foundations
description: Security-first Uniswap v4 hook development. Use when user mentions "v4 hooks", "hook security", "PoolManager", "beforeSwap", "afterSwap", or asks about V4 hook best practices, vulnerabilities, or audit requirements.
license: MIT
---
# v4 Hook Security Foundations
Security-first guide for building Uniswap v4 hooks. Hook vulnerabilities can drain user funds—understand these concepts before writing any hook code.
## Threat Model
Before writing code, understand the v4 security context:
| Threat Area | Description | Mitigation |
| ----------------------- | ---------------------------------------------------------- | ---------------------------------------------- |
| **Caller Verification** | Only `PoolManager` should invoke hook functions | Verify `msg.sender == address(poolManager)` |
| **Sender Identity** | `msg.sender` always equals PoolManager, never the end user | Use `sender` parameter for user identity |
| **Router Context** | The `sender` parameter identifies the router, not the user | Implement router allowlisting |
| **State Exposure** | Hook state is readable during mid-transaction execution | Avoid storing sensitive data on-chain |
| **Reentrancy Surface** | External calls from hooks can enable reentrancy | Use reentrancy guards; minimize external calls |
## Permission Flags Risk Matrix
All 14 hook permissions with associated risk levels:
| Permission Flag | Risk Level | Description | Security Notes |
| --------------------------------- | ---------- | --------------------------- | ----------------------------- |
| `beforeInitialize` | LOW | Called before pool creation | Validate pool parameters |
| `afterInitialize` | LOW | Called after pool creation | Safe for state initialization |
| `beforeAddLiquidity` | MEDIUM | Before LP deposits | Can block legitimate LPs |
| `afterAddLiquidity` | LOW | After LP deposits | Safe for tracking/rewards |
| `beforeRemoveLiquidity` | HIGH | Before LP withdrawals | Can trap user funds |
| `afterRemoveLiquidity` | LOW | After LP withdrawals | Safe for tracking |
| `beforeSwap` | HIGH | Before swap execution | Can manipulate prices |
| `afterSwap` | MEDIUM | After swap execution | Can observe final state |
| `beforeDonate` | LOW | Before donations | Access control only |
| `afterDonate` | LOW | After donations | Safe for tracking |
| `beforeSwapReturnDelta` | CRITICAL | Returns custom swap amounts | **NoOp attack vector** |
| `afterSwapReturnDelta` | HIGH | Modifies post-swap amounts | Can extract value |
| `afterAddLiquidityReturnDelta` | HIGH | Modifies LP token amounts | Can shortchange LPs |
| `afterRemoveLiquidityReturnDelta` | HIGH | Modifies withdrawal amounts | Can steal funds |
### Risk Thresholds
- **LOW**: Unlikely to cause fund loss
- **MEDIUM**: Requires careful implementation
- **HIGH**: Can cause fund loss if misimplemented
- **CRITICAL**: Can enable complete fund theft
## CRITICAL: NoOp Rug Pull Attack
The `BEFORE_SWAP_RETURNS_DELTA` permission (bit 10) is the most dangerous hook permission. A malicious hook can:
1. Return a delta claiming it handled the entire swap
2. PoolManager accepts this and settles the trade
3. Hook keeps all input tokens without providing output
4. User loses entire swap amount
### Attack Pattern
```solidity
// MALICIOUS - DO NOT USE
function beforeSwap(
address,
PoolKey calldata,
IPoolManager.SwapParams calldata params,
bytes calldata
) external override returns (bytes4, BeforeSwapDelta, uint24) {
// Claim to handle the swap but steal tokens
int128 amountSpecified = int128(params.amountSpecified);
BeforeSwapDelta delta = toBeforeSwapDelta(amountSpecified, 0);
return (BaseHook.beforeSwap.selector, delta, 0);
}
```
### Detection
Before interacting with ANY hook that has `beforeSwapReturnDelta: true`:
1. **Audit the hook code** - Verify legitimate use case
2. **Check ownership** - Is it upgradeable? By whom?
3. **Verify track record** - Has it been audited by reputable firms?
4. **Start small** - Test with minimal amounts first
### Legitimate Uses
NoOp patterns are valid for:
- Just-in-time liquidity (JIT)
- Custom AMM curves
- Intent-based trading systems
- RFQ/PMM integrations
But each requires careful implementation and audit.
## Delta Accounting Fundamentals
v4 uses a credit/debit system through the PoolManager:
### Core Invariant
```text
For every transaction: sum(deltas) == 0
```
The PoolManager tracks what each address owes or is owed. At transaction end, all debts must be settled.
### Key Functions
| Function | Purpose | Direction |
| ---------------------------- | ----------------------------------- | ---------------------- |
| `take(currency, to, amount)` | Withdraw tokens from PoolManager | You receive tokens |
| `settle(currency)` | Pay tokens to PoolManager | You send tokens |
| `sync(currency)` | Update PoolManager balance tracking | Preparation for settle |
### Settlement Pattern
```solidity
// Correct pattern: sync before settle
poolManager.sync(currency);
currency.transfer(address(poolManager), amount);
poolManager.settle(currency);
```
### Common Mistakes
1. **Forgetting sync**: Settlement fails without sync
2. **Wrong order**: Must sync → transfer → settle
3. **Partial settlement**: Leaves transaction in invalid state
4. **Double settlement**: Causes accounting errors
## Access Control Patterns
### PoolManager Verification
Every hook callback MUST verify the caller:
```solidity
modifier onlyPoolManager() {
require(msg.sender == address(poolManager), "Not PoolManager");
_;
}
function beforeSwap(
address sender,
PoolKey calldata key,
IPoolManager.SwapParams calldata params,
bytes calldata hookData
) external override onlyPoolManager returns (bytes4, BeforeSwapDelta, uint24) {
// Safe to proceed
}
```
### Why This Matters
Without this check:
- Anyone can call hook functions directly
- Attackers can manipulate hook state
- Funds can be drained through fake callbacks
## Router Verification Patterns
The `sender` parameter is the router, not the end user. For hooks that need user identity:
### Allowlisting Pattern
```solidity
mapping(address => bool) public allowedRouters;
function beforeSwap(
address sender, // This is the router
PoolKey calldata key,
IPoolManager.SwapParams calldata params,
bytes calldata hookData
) external override onlyPoolManager returns (bytes4, BeforeSwapDelta, uint24) {
require(allowedRouters[sender], "Router not allowed");
// Proceed with swap
}
```
### User Identity via hookData
```solidity
function beforeSwap(
address sender,
PoolKey calldata key,
IPoolManager.SwapParams calldata params,
bytes calldata hookData
) external override onlyPoolManager returns (bytes4, BeforeSwapDelta, uint24) {
// Decode user address from hookData (router must include it)
address user = abi.decode(hookData, (address));
// CAUTION: Router must be trusted to provide accurate user
}
```
### msg.sender Trap
```solidity
// WRONG - msg.sender is always PoolManager in hooks
function beforeSwap(...) external {
require(msg.sender == someUser); // Always fails or wrong
}
// CORRECT - Use sender parameter
function beforeSwap(address sender, ...) external {
require(allowedRouters[sender], "Invalid router");
}
```
## Token Handling Hazards
Not all tokens behave like standard ERC-20s:
| Token Type | Hazard | Mitigation |
| ------------------- | ------------------------------------ | ----------------------------------- |
| **Fee-on-transfer** | Received amount < sent amount | Measure actual balance changes |
| **Rebasing** | Balance changes without transfers | Avoid storing raw balances |
| **ERC-777** | Transfer callbacks enable reentrancy | Use reentrancy guards |
| **Pausable** | Transfers can be blocked | Handle transfer failures gracefully |
| **Blocklist** | Specific addresses blocked | Test with production addresses |
| **Low decimals** | Precision loss in calculations | Use appropriate scaling |
### Safe Balance Check Pattern
```solidity
function safeTransferIn(
IERC20 token,
address from,
uint256 amount
) internal returns (uint256 received) {
uint256 balanceBefore = token.balanceOf(address(this));
token.safeTransferFrom(from, address(this), amount);
received = token.balanceOf(address(this)) - balanceBefore;
}
```
## Base Hook Template
Start with all permissions disabled. Enable only what you need:
```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import {BaseHook} from "v4-periphery/src/base/hooks/BaseHook.sol";
import {Hooks} from "v4-core/src/libraries/Hooks.sol";
import {IPoolManager} from "v4-core/src/interfaces/IPoolManager.sol";
import {PoolKey} from "v4-core/src/types/PoolKey.sol";
import {BeforeSwapDelta, BeforeSwapDeltaLibrary} from "v4-core/src/types/BeforeSwapDelta.sol";
contract SecureHook is BaseHook {
constructor(IPoolManager _poolManager) BaseHook(_poolManager) {}
function getHookPermissions() public pure override returns (Hooks.Permissions memory) {
return Hooks.Permissions({
beforeInitialize: false,
afterInitialize: false,
beforeAddLiquidity: false,
afterAddLiquidity: false,
beforeRemoveLiquidity: false,
afterRemoveLiquidity: false,
beforeSwap: false, // Enable only if needed
afterSwap: false, // Enable only if needed
beforeDonate: false,
afterDonate: false,
beforeSwapReturnDelta: false, // DANGER: NoOp attack vector
afterSwapReturnDelta: false, // DANGER: Can extract value
afterAddLiquidityReturnDelta: false,
afterRemoveLiquidityReturnDelta: false
});
}
// Implement only the callbacks you enabled above
}
```
See [references/base-hook-template.md](references/base-hook-template.md) for a complete implementation template.
## Security Checklist
Before deploying any hook:
| # | Check | Status |
| --- | ----------------------------------------------------- | ------ |
| 1 | All hook callbacks verify `msg.sender == poolManager` | [ ] |
| 2 | Router allowlisting implemented if needed | [ ] |
| 3 | No unbounded loops that can cause OOG | [ ] |
| 4 | Reentrancy guards on external calls | [ ] |
| 5 | Delta accounting sums to zero | [ ] |
| 6 | Fee-on-transfer tokens handled | [ ] |
| 7 | No hardcoded addresses | [ ] |
| 8 | Slippage parameters respected | [ ] |
| 9 | No sensitive data stored on-chain | [ ] |
| 10 | Upgrade mechanisms secured (if applicable) | [ ] |
| 11 | `beforeSwapReturnDelta` justified if enabled | [ ] |
| 12 | Fuzz testing completed | [ ] |
| 13 | Invariant testing completed | [ ] |
## Gas Budget Guidelines
Hook callbacks execute inside the PoolManager's transaction context. Excessive gas consumption can make swaps revert or become economically unviable.
### Gas Budgets by Callback
| Callback | Target Budget | Hard Ceiling | Notes |
| ----------------------------- | ------------- | ------------ | ----------------------------- |
| `beforeSwap` | < 50,000 gas | 150,000 gas | Runs on every swap; keep lean |
| `afterSwap` | < 30,000 gas | 100,000 gas | Analytics/tracking only |
| `beforeAddLiquidity` | < 50,000 gas | 200,000 gas | May include access control |
| `afterAddLiquidity` | < 30,000 gas | 100,000 gas | Reward tracking |
| `beforeRemoveLiquidity` | < 50,000 gas | 200,000 gas | Lock validation |
| `afterRemoveLiquidity` | < 30,000 gas | 100,000 gas | Tracking/accounting |
| Callbacks with external calls | < 100,000 gas | 300,000 gas | External DEX routing, oracles |
### Common Gas Pitfalls
1. **Unbounded loops**: Iterating over dynamic arrays (e.g., all active positions) can exceed block gas limits. Cap array sizes or use pagination.
2. **SSTORE in hot paths**: Each new storage slot costs ~20,000 gas. Prefer transient storage (`tstore`/`tload`) for data that doesn't persist beyond the transaction. Requires Solidity >= 0.8.24 with EVM target set to `cancun` or later.
3. **External calls**: Each cross-contract call adds ~2,600 gas base cost plus the callee's execution. Batch calls where possible.
4. **String operations**: Avoid `string` manipulation in callbacks; use `bytes32` for identifiers.
5. **Redundant reads**: Cache `poolManager` calls — repeated `getSlot0()` or `getLiquidity()` reads cost gas each time.
### Measuring Gas
```bash
# Profile a specific hook callback with Foundry
forge test --match-test test_beforeSwapGas --gas-report
# Snapshot gas usage across all tests
forge snapshot --match-contract MyHookTest
```
---
## Risk Scoring System
Calculate your hook's risk score (0-33):
| Category | Points | Criteria |
| --------------------- | ------ | ---------------------------------------- |
| **Permissions** | 0-14 | Sum of enabled permission risk levels |
| **External Calls** | 0-5 | Number and type of external interactions |
| **State Complexity** | 0-5 | Amount of mutable state |
| **Upgrade Mechanism** | 0-5 | Proxy, admin functions, etc. |
| **Token Handling** | 0-4 | Non-standard token support |
### Audit Tier Recommendations
| Score | Risk Level | Recommendation |
| ----- | ---------- | ------------------------------ |
| 0-5 | Low | Self-audit + peer review |
| 6-12 | Medium | Professional audit recommended |
| 13-20 | High | Professional audit required |
| 21-33 | Critical | Multiple audits required |
## Absolute Prohibitions
**Never do these things in a hook:**
1. **Never trust `msg.sender` for user identity** - It's always PoolManager
2. **Never enable `beforeSwapReturnDelta` without understanding NoOp attacks**
3. **Never store passwords, keys, or PII on-chain**
4. **Never use `transfer()` for ETH** - Use `call{value:}("")`
5. **Never assume token decimals** - Always query the token
6. **Never use `block.timestamp` for randomness**
7. **Never hardcode gas limits in calls**
8. **Never ignore return values from external calls**
9. **Never use `tx.origin` for authorization** - It's a phishing vector; malicious contracts can relay calls with the original user's `tx.origin`
## Pre-Deployment Audit Checklist
| # | Item | Required For |
| --- | ----------------------------------------- | ------------------------ |
| 1 | Code review by security-focused developer | All hooks |
| 2 | Unit tests for all callbacks | All hooks |
| 3 | Fuzz testing with Foundry | All hooks |
| 4 | Invariant testing | Hooks with delta returns |
| 5 | Fork testing on mainnet | All hooks |
| 6 | Gas profiling | All hooks |
| 7 | Formal verification | Critical hooks |
| 8 | Slither/Mythril analysis | All hooks |
| 9 | External audit | Medium+ risk hooks |
| 10 | Bug bounty program | High+ risk hooks |
| 11 | Monitoring/alerting setup | All production hooks |
See [references/audit-checklist.md](references/audit-checklist.md) for detailed audit requirements.
## Production Hook References
Learn from audited, production hooks:
| Project | Description | Notable Security Features |
| -------------- | --------------------- | ----------------------------- |
| **Flaunch** | Token launch platform | Multi-sig admin, timelocks |
| **EulerSwap** | Lending integration | Isolated risk per market |
| **Zaha TWAMM** | Time-weighted AMM | Gradual execution reduces MEV |
| **Bunni** | LP management | Concentrated liquidity guards |
## External Resources
### Official Documentation
- [v4-core Repository](https://github.com/Uniswap/v4-core)
- [v4-periphery Repository](https://github.com/Uniswap/v4-periphery)
- [Uniswap v4 Docs](https://docs.uniswap.org/contracts/v4/overview)
- [Hook Permissions Guide](https://docs.uniswap.org/contracts/v4/concepts/hooks)
### Security Resources
- [Trail of Bits Audits](https://github.com/trailofbits/publications)
- [Certora v4 Analysis](https://www.certora.com/)
- [ABDK Consulting](https://abdk.consulting/)
### Community
- [v4-hooks-skill by @igoryuzo](https://github.com/igoryuzo/uniswapV4-hooks-skill) - Community skill that inspired this guide
- [v4hooks.dev](https://www.v4hooks.dev) - Community hook resources
---
## Additional References
- [Base Hook Template](references/base-hook-template.md) - Complete implementation starter
- [Vulnerabilities Catalog](references/vulnerabilities-catalog.md) - Common patterns and mitigations
- [Audit Checklist](references/audit-checklist.md) - Detailed pre-deployment checklistMore General & Other skills
find-skills
vercel-labs/skills
Helps users discover and install agent skills when they ask questions like "how do I do X", "find a skill for X", "is there a skill that can...", or express interest in extending capabilities. This skill should be used when the user is looking for functionality that might exist as an installable skill.
grill-me
mattpocock/skills
A relentless interview to sharpen a plan or design.
grill-with-docs
mattpocock/skills
A relentless interview to sharpen a plan or design, which also creates docs (ADR's and glossary) as we go.

