Uniswap v4 went live on January 31, 2025. Nine independent audits covered the core and periphery contracts, and a bug bounty of up to $15.5 million sat on top of that. Four months later, a protocol built on v4 hooks lost roughly $11 million in one exploit chain.
That gap is the whole story of v4. The core was built carefully. The extension surface was handed to everyone, and nobody was forced to build it carefully.
This article walks through how v4 and hooks work from the user’s click to final settlement, why the PoolManager exists, what actually changed since v3, and why hook security deserves its own discipline. We’ll use the Cork and Bunni exploits as the case studies, because they show two very different ways a hook can fail. At PandaSecurity we’ve spent a lot of time on this class of bugs, so we’ll also say where we think the industry’s habits are wrong.
Start from the user’s side. You sign one transaction, and it doesn’t go to a pool. It goes to a router. That contract calls PoolManager.unlock(data), and the PoolManager immediately calls back into unlockCallback(data) on the same contract. Everything interesting happens inside that callback.

Inside the callback, the router does roughly this:
swap, modifyLiquidity, or donate. Each one moves pool state and creates a delta.settle, take, mint, burn, clear. These are how the router pays what it owes or collects what it’s owed.When the callback returns, the PoolManager looks at every delta. If any of them is non-zero, the whole transaction reverts with CurrencyNotSettled. Otherwise everyone has paid or been paid correctly, and the result goes back to the user.
Deltas follow a simple sign convention from Uniswap’s own docs: a negative delta means the PoolManager is owed tokens, a positive one means the PoolManager owes tokens to someone. A swap typically leaves you negative on the token you sold and positive on the token you bought. Adding liquidity leaves you negative on both sides.
Hooks sit inside this loop at fixed points.

For a swap, beforeSwap runs against pre-swap state and can return a delta of its own plus a fee override. afterSwap runs against post-swap state and can return an adjustment too. Liquidity changes follow the same shape with before and after callbacks for add and remove, and initialize and donate get their own pairs. Whatever deltas the hook returns are folded into the same ledger and have to net to zero by the end, like everything else.
One detail that matters later: which callbacks a hook implements isn’t configured in a settings file. It’s encoded in the bits of the hook’s deployed address. The PoolManager reads those bits to decide what to call. Keep that in mind, because it’s a recurring source of bugs.
In Uniswap v3, every pool is its own contract, deployed by a factory. A swap that routes through three pools touches three contracts, and tokens physically move between them along the way. Every hop costs gas, and every integration has to reason about which contract holds what at which moment.
v4 replaces that with a singleton. One contract, the PoolManager, holds the state for every pool. A “pool” is no longer a deployment; it’s an entry in the PoolManager’s storage, keyed by the two currencies, the fee, the tick spacing and the hook address. Creating a pool becomes a storage write instead of a contract deployment, which is why Uniswap Labs says pool creation is up to 99.99% cheaper than before.
The PoolManager also does something a pile of separate pool contracts never could: it keeps a running ledger. That’s flash accounting. Every balance-changing operation updates a net delta per currency and per address, and the deltas live in transient storage (the EIP-1153 feature that arrived with the Dencun upgrade). Real token transfers only happen for the net result at the end. According to Uniswap’s docs, a multi-hop swap needs just two transfers regardless of how many hops it has, the input token in and the output token out.
Here’s our opinion, and we’ll defend it: the biggest difference between v3 and v4 is not hooks. It’s that v4 changed who you talk to and when money moves. In v3 you talk to a pool and tokens move on every call. In v4 you talk to a ledger, and tokens move once. Hooks are the feature people tweet about, but they’re only practical because of this accounting model. If every hook had to move real tokens at every step across many pools, nobody would be able to afford using them, and the reentrancy surface would be unmanageable.
The tradeoff is real, though. All pools now share one vault. That makes the core’s correctness more valuable, and it makes every hook that touches the ledger a little more dangerous, because it speaks directly to the same shared accounting.
Strip away the marketing and v4 brings six genuinely new things:
BeforeSwapDelta that consumes the whole specified amount turns the swap into a no-op against the pool’s own curve, so the hook becomes the AMM. This is how people build custom curves on top of v4.mint and burn let you hold balances inside the PoolManager instead of transferring tokens out, which helps when the same funds will be used again soon.The periphery adds a subscriber mechanism too: a position owner can register a contract that gets notified when liquidity or ownership changes, so staking or incentive programs don’t require handing over the position NFT.
What does it add up to? Our reading is that Uniswap stopped being a product and became a platform. The liquidity layer is the protocol. The strategy layer is yours. That’s a powerful idea, and Uniswap Labs said more than 150 hooks already existed at launch, across ten chains.
It’s also a transfer of risk. Uniswap audited the platform. It can’t audit what people build on it.
Let’s be precise about where the losses have landed. As far as public incident reports go, the large v4-related losses so far came from hooks and integrations, not from the PoolManager itself. That’s a credit to the core. It’s also an uncomfortable fact for anyone who thinks “built on audited v4” is a security statement. It isn’t. It’s a statement about the floor of the building, not the rooms people built on top.
Dedaub’s write-up and BlockSec’s analysis give the clearest picture. Reported figures range from about $11 million to $12 million of wstETH depending on who’s counting.
Cork’s hook had a beforeSwap function with no check on who was calling it. In normal operation only the PoolManager should call a hook callback. Here, anyone could call it directly, with any parameters they liked, including a pool ID of their own choosing and crafted hookData. The hook also didn’t maintain an allowlist of pools it was supposed to serve.
The attacker used that to steer the hook into a path that credited a deposit that never happened, minting tokens out of nothing. A second weakness, a manipulable price ratio near expiry, let them turn those tokens into real wstETH. BlockSec describes it as two independent flaws that combined into one chain.
According to Dedaub, four audit firms worked on Cork, but three of them didn’t cover the hook contracts. The fourth likely had the hook in scope and made suggestions that weren’t enough to prevent the exploit.
What stands out to us isn’t how clever the exploit was. It’s how basic the root cause was. “Only the PoolManager may call hook callbacks” is the first thing you write on the whiteboard when you design a hook, and a missing modifier turned it into an eight-figure loss. The audit scoping story is equally telling: the hook was the highest-risk code in the system and got the least attention.
The Bunni incident is the opposite lesson. There was no missing access control and no forged call. Every transaction settled cleanly, every delta netted to zero, and the PoolManager had no objection to anything. The books inside the hook were simply wrong.
BlockSec’s breakdown is the best public analysis we’ve seen. Bunni v2 is an AMM built as a v4 hook that adds rebalancing and rehypothecation on top of concentrated liquidity. It tracks an “idle balance” for each pool. When liquidity is withdrawn, the idle-balance update used a rounding direction that overstated the idle amount. That made the pool’s computed available balance of a nearly empty token too small, and the liquidity function then reported less effective liquidity than the pool really had, which makes swaps move the price much further.
The attacker first pushed the pool into a state where one token’s active balance was down to 28 wei. They then made 44 tiny withdrawals that dragged it to 4 wei, an 85.7% drop, while burning a negligible share of LP tokens. After that, a pair of swaps in opposite directions exploited the undervalued liquidity and drained value from two pools, a USDC/USDT pool on Ethereum and a weETH/ETH pool on Unichain.
This is the lesson we most want builders to absorb: delta settlement proves that the hook didn’t print tokens at the PoolManager boundary. It says nothing about whether the hook’s internal accounting is correct. Trail of Bits makes the same point in their guide to building secure v4 hooks: settlement checks that deltas net to zero, not that your sign conventions, rounding or balance buckets are right. A hook with a rounding bug will settle perfectly, right up to the moment it bleeds.
Cork and Bunni are the big ones, but the same Trail of Bits write-up lists patterns that keep showing up in reviews:
afterSwapReturnDelta bit made every swap revert with CurrencyNotSettled once a dynamic protocol fee was switched on.PoolKey. Pool creation is permissionless, so an attacker can attach your hook to a pool they control, with tokens that run malicious code. A points-farming hook in their examples let deposits route through a malicious pool because it accepted a caller-specified key.beforeSwap sees pre-swap state and afterSwap sees post-swap state. A just-in-time liquidity penalty hook was bypassed because the penalty was computed after the user had already collected fees through a tiny separate liquidity increase.afterRemoveLiquidity, can lock users in.Our review of a hook-based system doesn’t start with the swap math. It starts with the boundaries:
unlockCallback must verify the caller too.PoolKey, hookData, fee inputs and token behavior, including fee-on-transfer and rebasing tokens.Fuzzing against adversarial pools and tokens belongs on that list too, because several of these bugs are only reachable through sequences no unit test would try.
One more opinion. Teams often ask whether their hook is “safe because Uniswap is audited”. We tell them to flip the question. Treat the hook as a standalone protocol that happens to be wired into an AMM. It holds funds, it has an admin, it has its own accounting, and it faces an attacker who can create pools, choose tokens and send dust. That’s a full protocol audit scope, and the Cork story shows what happens when it’s treated as an appendix.
If you’re building on v4 and want a second pair of eyes on your hook before it holds real money, that’s what the PandaSecurity team does. Bring us in before launch, not after the post-mortem.
What is the PoolManager in Uniswap v4? It’s the singleton contract that stores the state of every v4 pool and keeps the ledger of what each address owes or is owed during an unlock session. It replaces the one-contract-per-pool design of v3.
What is the biggest difference between Uniswap v3 and v4? Architecture. v4 uses a single PoolManager and flash accounting, so tokens move once per unlock instead of at every step. Hooks, native ETH and custom accounting build on that foundation.
Are Uniswap v4 hooks safe? Not automatically. The core contracts were heavily audited, but each hook is separate code with its own accounting and access control. The Cork and Bunni exploits both came from hook-level flaws, not from the PoolManager.
UNCX's token vesting contract lost roughly $87.7K when an attacker exploited a share-accounting flaw in release(), topUpVesting(), and cancel(). We break down the root cause, the exact attack sequence, and what builders should take away from it — without the jargon.
Autheo’s THEO token lost 92% after attackers used a Hyperlane TrustedRelayerIsm misconfiguration to mint 19 million unbacked tokens on Base and extract roughly $1.76 million. Here’s how the attack worked and why single-key security models remain dangerous for cross-chain assets.
An attacker forced a Uniswap V4 vault into a zero-liquidity state, redeemed one minimal share unit, and withdrew roughly 14,838.47 DAI. Panda Security breaks down the transaction, vulnerable code, root cause, and practical fixes.
GoldPesa’s Uniswap v4 hook GPXHooks on Base rebalanced protocol-owned GPX/USDC liquidity through a shared PositionManager without checking that GPX/USDC currency deltas were zero inside the caller’s unlock. An attacker left an unsettled negative delta, triggered rebalance, and netted the hook’s burn credit, withdrawing about 115K USDC from the PoolManager. This note covers the contract’s role, v4 flash accounting, the root cause, and the transaction path.