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.
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.
The sender parameter is the router, not the end user. For hooks that need user identity:
Allowlisting Pattern
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
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
// 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");
}
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
Unbounded loops: Iterating over dynamic arrays (e.g., all active positions) can exceed block gas limits. Cap array sizes or use pagination.
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.
External calls: Each cross-contract call adds ~2,600 gas base cost plus the callee's execution. Batch calls where possible.
String operations: Avoid string manipulation in callbacks; use bytes32 for identifiers.
Redundant reads: Cache poolManager calls — repeated getSlot0() or getLiquidity() reads cost gas each time.
Measuring Gas
# 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:
Never trust msg.sender for user identity - It's always PoolManager
Never enable beforeSwapReturnDelta without understanding NoOp attacks
Never store passwords, keys, or PII on-chain
Never use transfer() for ETH - Use call{value:}("")
Never assume token decimals - Always query the token
Never use block.timestamp for randomness
Never hardcode gas limits in calls
Never ignore return values from external calls
Never use tx.origin for authorization - It's a phishing vector; malicious contracts can relay calls with the original user's tx.origin