
To build secure Uniswap v4 hooks, treat the audited core as a safe floor, not a safety net: gate every callback so only the pool contract can call it, allowlist the specific pools your hook trusts, keep funds and fees in separate labeled buckets, and fuzz-test malicious pools and tokens before you deploy. The core protocol won’t catch your logic bugs.
That last point is where the money went. More than $20 million has already been drained from Uniswap v4 hooks, and not one dollar of it came from a flaw in the PoolManager — the shared engine every v4 pool runs on. The Cork exploit (~$12M, May 2025) and the Bunni exploit ($8.4M, September 2025) both passed straight through code that the core protocol was never responsible for checking. If you’re a protocol team shipping a hook, that gap is your problem to close, and this is where you find out whether you’ve closed it.
What does the Uniswap v4 PoolManager actually guarantee?
Uniswap v4 flipped the old model. In v3, every pool was its own contract. In v4, all pool state lives inside one shared contract called the PoolManager, and pools plug custom logic into it through hooks — small independent contracts that run at set moments in a swap or a liquidity change. Dynamic fees, custom accounting, external integrations: hooks are how you add them.
The PoolManager enforces the protocol’s own rules — pool math, the order in which hook callbacks fire, and a settlement check at the end of every session that reverts unless all currency balances net to zero. That settlement guarantee is strong, and it’s also the trap. It only proves the session balanced. It says nothing about whether your hook’s internal bookkeeping was correct. A hook can leak value on every transaction while the PoolManager waves each one through as perfectly settled. The core is audited; your logic on top of it is not, unless you make it so.
So the real question a v4 builder faces isn’t “is the protocol safe” — it is. The question is which assumptions your hook adds beyond what the protocol checks. Everything below is that list.
Why the hook address is part of the pool’s identity
A pool in v4 is defined by a PoolKey — its two currencies, its fee, its tick spacing, and its hook address. Change any one of those fields and you are talking to a different pool. The hook address is baked into the pool’s identity on purpose.
Creating a pool through the PoolManager is permissionless by default. Anyone can spin up a pool that points at your hook address, with two tokens they picked and parameters they control. If your hook trusts a user-supplied PoolKey without checking it, an attacker routes your logic through a pool they built to be hostile. That’s not theoretical: in Trail of Bits’ review of Semantic Layer’s SVFHook, an addLiquidity function let callers name their own PoolKey, so an attacker could deposit through a custom malicious pool and farm points below the intended cost. Trusting the wrong PoolKey means trusting the wrong pool, full stop.
The seven failure patterns behind every hook exploit
After analyzing dozens of findings across Trail of Bits audits, other firms’ public reports, and the Solodit vulnerability database, its researchers grouped hook-level bugs into seven recurring patterns. Each one is a place the PoolManager can’t help you.
Anyone can call your hook. Hook callbacks are public functions. Skip the caller check and an attacker calls them directly with poisoned inputs. The Cork exploit turned on exactly this — an access-control gap let untrusted data reach redemption logic, and combined with a pricing issue, it drained roughly $12M. The fix is boring and non-negotiable: inherit from BaseHook, use SafeCallback for the unlock path, and confirm the caller is the PoolManager on anything those don’t already cover.
Treating any pool as legitimate. Covered above — bind your hook to canonical pools at deployment or keep a strict allowlist, and re-derive and re-check the PoolId on every user-controlled path.
Custom accounting leaks value. A delta is a signed balance change your hook owes or is owed. A wrong sign, a rounding error, or mixing LP funds with fees can bleed value silently — because settlement only checks the session nets to zero, never that your math was right. The Bunni exploit was precisely this: a rounding bug in idle-balance accounting. The attacker nudged the price with a flash loan, then made 44 tiny withdrawals that each shrank the active balance more than the shares burned should have allowed, and every one of those transactions settled cleanly. $8.4M gone, core protocol untouched.
Right logic, wrong hook. beforeSwap sees pre-swap state; afterSwap sees the result. Code that’s correct in one is dangerous in the other. Auditors keep finding logic that needs the final swap result sitting in beforeSwap, operating on stale data that looks fine in isolation.
Address bits are part of the API. The hook’s own address encodes which callbacks the PoolManager will invoke. Get the permission bits out of sync with the functions you actually implement and things break — in the Sorella Angstrom finding, a missing return-delta permission meant every swap reverted with CurrencyNotSettled() the moment the fee turned on.
Hook failures block pool actions. Callbacks run in the same transaction as the pool action. If your reward distribution or dust cleanup reverts inside afterRemoveLiquidity, users can’t exit their positions. Keep non-essential code out of the critical path, wrap optional external calls so they can fail without trapping funds, and never silently trust stale price data.
State changes mid-callback. One hook contract can serve many pools, and a nested action can rewrite shared storage before your outer callback finishes. Don’t stash scratch state globally; key temporary data by pool and caller, reject overlapping operations, and clear it when done.
A pre-launch checklist you can run before you ship
Before deployment, a v4 hook team should be able to check off all eight. Gate every callback and unlock path. Allowlist specific pools, not just tokens. Label every balance and delta — who owns it, who can move it, which token behaviors the accounting supports. Keep LP funds, fees, and incentives in separate buckets. Keep non-essential code away from the main user flow. Verify the address permission bits match your declared callbacks, and if the hook is upgradeable, review the proxy and upgrade controls as their own security boundary. Fuzz nested callbacks, extreme fees, malicious pools, and non-standard tokens with tools like Echidna and Medusa — adversarial scenarios, not just the happy path. Isolate callback state per pool and caller.
The checklist is useful even when you pass it — it shows you where your assumptions live. That map is exactly what a good smart contract audit works from, and it’s what separates a review that finds real bugs from one that reformats your code comments.
When should you bring in an external audit?
Bring in outside eyes the moment your hook moves value it didn’t create in the same call — return-delta hooks, custom AMM behavior, dynamic fees, cross-pool accounting. Those are where Cork and Bunni lived, and they’re the hardest to reason about alone, because the very check you’d lean on (settlement) stays green while the bug runs. If your hook is upgradeable, audit the upgrade path as its own surface. And if the code custodies user funds at all, treat an independent review as a launch requirement, not a nice-to-have.
As more capital flows into v4, hook bugs — not core bugs — will account for essentially all of the losses, because the audited singleton simply doesn’t have the surface to fail the way each team’s bespoke logic does. Teams that self-assess against these seven patterns and get an external review before mainnet will be the ones whose names don’t appear in next year’s incident reports. If you’re preparing a hook for launch, our engineers can run a Uniswap v4 hook security review against exactly these failure modes before you ship.
FAQ
Q: Is the Uniswap v4 PoolManager secure, or is it the risk? A: The PoolManager and v4 core are audited and enforce protocol-level guarantees — pool math, callback ordering, and end-of-session settlement. Every publicly reported v4 loss so far, including Cork and Bunni, came from application-specific hook code built on top of it, not from the core. The risk lives in your hook, not the protocol.
Q: Why did the Bunni exploit succeed if the transactions all settled correctly? A: Because settlement only verifies that a session’s currency balances net to zero — it never checks that a hook’s internal accounting is right. The Bunni exploit ($8.4M, September 2025) used a rounding bug in idle-balance accounting; each of the attacker’s 44 withdrawals satisfied settlement while quietly extracting value.
Q: What’s the single most common Uniswap v4 hook vulnerability?
A: Missing caller checks. Hook callbacks are public functions, so without an onlyPoolManager-style guard an attacker can call them directly with malicious parameters. Inheriting from BaseHook and using SafeCallback closes most of this surface — the Cork exploit (~$12M) hinged on this class of gap.
Key Takeaways
- The audited v4 core protects protocol mechanics, not your hook’s authorization, pool selection, or accounting — assume every guarantee stops at the PoolManager boundary.
- Settlement passing is not proof your accounting is correct; test conservation, round-trip value, and internal-vs-actual balance as separate invariants.
- Treat the hook address, permission bits, and any proxy upgrade path as part of your public API and your security boundary, not deployment trivia.
- Fuzz adversarial cases — malicious pools, fee extremes, non-standard tokens, nested cross-pool callbacks — because the exploits that hit real protocols never take the happy path.
- Any hook that custodies funds or moves value it didn’t create in the same call should get an independent review before mainnet; self-assessment finds the easy bugs, outside eyes find the ones that cost $8 million.








