TL;DR
At 13:05:51 UTC on October 2, 2026, one transaction on Base pulled 114,999.999186 USDC out of the Uniswap v4 PoolManager at the expense of GoldPesa's GPXHooks contract. Defimon put the loss at $114K and SlowMist at about $114.9K. The hook rebalances GoldPesa's protocol-owned GPX/USDC liquidity from inside beforeSwap, and it does so through the Uniswap v4 PositionManager without opening an unlock of its own. The attacker opened the unlock, minted a WETH/USDC position through the same PositionManager without paying for it, then swapped on the GPX pool to set off the rebalance. The hook burned its position for 148,868.60 USDC, but the PositionManager's balance is one shared ledger per unlock, so the attacker's 115,000 USDC debt was netted out first and the hook received 33,868.60 USDC. No admin key or signer was involved. GoldPesa had not published a postmortem as of October 7, as far as we could find. A v4 hook that takes its "full credit" from the PositionManager inside someone else's unlock is drawing on a balance the caller has already spent.
What Happened to GoldPesa?
GoldPesa runs a GPX/USDC pool on Uniswap v4 on Base whose liquidity is owned and managed by its own hook contract, GPXHooks. On October 2 that rebalance was turned against it.
The attack transaction landed in block 52078502. Defimon's alert classed it as a logic error and summed it up: "In effect the hook paid for the attacker's position." SlowMist's alert on October 3 named the same root cause, a missing check that the PositionManager's currency deltas were zero before the hook relied on them. A reconstruction merged into DeFiHackLabs as pull request #1288 reproduces the transaction against a Base fork and matches both headline figures to the microunit.
The Crypto Times contacted GoldPesa for comment and reported no response. Coinfomania reported GPX trading volume at $0 after the alert.
How Does Uniswap v4 Settle Balances, and Why Does It Matter Here?
Uniswap v4 does not move tokens on every operation. Inside an unlock, each swap or liquidity change only updates a running net balance called a delta, and tokens move once at the end. Uniswap's flash accounting documentation describes it this way: "each balance-changing operation (e.g. swap and liquidity modification) updates an internal net balance known as delta." The deltas are held in transient storage (EIP-1153), and before the unlock closes the PoolManager checks that every balance is resolved, with nothing owed to or from it.
The PoolManager tracks deltas per address. When you mint or burn a position through the PositionManager, the delta is recorded against the PositionManager's address, not yours. The PositionManager source implements TAKE_PAIR as "take my full credit" in each currency, and the DeltaResolver helper computes that credit as poolManager.currencyDelta(address(this), currency). That is one number, shared by every caller routed through the PositionManager in that unlock.
The normal entry point, modifyLiquidities, opens a fresh unlock with a clean ledger. The other entry point, modifyLiquiditiesWithoutUnlock, joins whatever unlock is already open. Its interface comment says it "must be called by a contract that has already unlocked the v4 PoolManager." A hook running inside beforeSwap has not unlocked anything. The swapper did.
How Did the GoldPesa Exploit Work?
The verified GPXHooks source has three properties that combine into the bug.
The rebalance is reachable from any swap. beforeSwap runs reBalanceRoutine() whenever block.timestamp - lastRebalance >= 1 hours, and the routine then checks a price condition. Nothing restricts who the swapper is.
The rebalance burns the old position with modifyLiquiditiesWithoutUnlock(BURN_POSITION + TAKE_PAIR), sending the proceeds to the hook. Because it runs inside the swapper's unlock, TAKE_PAIR withdraws the PositionManager's net credit across everything that has happened in that unlock, including the swapper's own PositionManager actions.
And the hook never checks what actually arrived. It sets a 1% slippage floor on the burn (minUSDCExpected = usdcInsideUniswap * 99 / 100). The burn returned the full amount, so that floor passed. The shortfall came one action later, in TAKE_PAIR, and no check covers that step. The code that follows does handle one mismatch: if the hook holds more USDC than it expected, it treats the excess as a donation and sends it to the owner. There is no branch for holding less. It simply re-mints the new position with whatever USDC it has.
So the attacker only needed a negative USDC delta sitting on the PositionManager at the moment the hook burned. That is free to create. Mint a position through the PositionManager and do not settle it. The debt did have a ceiling: the DeltaResolver's _getFullCredit reverts when the delta is negative, so a debt above the hook's 148,868.602189 USDC credit would have reverted the hook's TAKE_PAIR. The attacker took about 77% of that ceiling. Why it left the rest is not in the public record.
The Attack, Step by Step
All figures below are from the transaction's token transfers and match the DeFiHackLabs reconstruction.
| # | Action | Effect on the PositionManager's USDC delta |
|---|---|---|
| 1 | Flash-borrow 175,000 USDC from Morpho Blue | None. Working capital only |
| 2 | Open a PoolManager unlock and mint a WETH/USDC position below spot through the PositionManager, with no settle | -114,999.999187 USDC owed |
| 3 | Buy GPX with 5,500 USDC. The hook runs its rebalance check, which does not fire yet | Unchanged |
| 4 | Buy GPX with another 5,500 USDC. The price condition is now met and the hook rebalances | Unchanged until the burn |
| 5 | Hook burns its GPX/USDC position: +148,868.602189 USDC credit | Net +33,868.603002 |
| 6 | Hook's TAKE_PAIR withdraws the full net credit to itself | 33,868.603002 USDC to the hook. Delta now zero, debt gone |
| 7 | Attacker burns its own WETH/USDC position and takes the credit | 114,999.999186 USDC to the attacker |
| 8 | Sell back the GPX and repay Morpho 175,000 USDC | Unlock closes balanced |
| 9 | Swap the 114,428.509331 USDC profit to USDT and send it to the attacker EOA | Exit begins |
The arithmetic closes: the 148,868.602189 USDC credit minus the 114,999.999187 USDC debt is exactly the 33,868.603002 USDC the hook received, the figures SlowMist and the DeFiHackLabs reconstruction both give. The attacker's own burn then returned 114,999.999186 USDC, one microunit less than the debt it had cancelled, which is consistent with v4 rounding liquidity math against the position holder. The hook's burn proceeds were split between the hook and the attacker, and the PoolManager closed the unlock with every delta at zero.
What Did the Attacker Actually Walk Away With?
The three figures in circulation measure different things. Defimon's $114K and SlowMist's $114.9K both describe the 114,999.999186 USDC that left the PoolManager. GoldPesa's loss is the matching shortfall: its rebalance burned a position worth 148,868.602189 USDC and got back 33,868.603002, a gap of 114,999.999187 USDC. The DeFiHackLabs reconstruction asserts a net of 114,428.509331 USDC once the two GPX buys are unwound, so the round trip cost the attacker about 571 USDC.
What the attacker kept is lower. The last step of the attack transaction sent 114,428.509331 USDC into a single USDC/USDT pool on Base and got 96,388.996061 USDT back, the amount the attacker forwarded to a second wallet minutes later. That one swap cost about $18,040, close to 16% of the haul. We trust the $115K figure as GoldPesa's loss, because it is the exact on-chain transfer out of the PoolManager. Treat about $96.4K as the attacker's realised take on Base.
The setup was quick. The attacker EOA was funded with 0.000957 ETH by a Relay solver at 12:58:39 UTC, deployed its attack contract at 13:02:39, and fired at 13:05:51, about seven minutes after funding, per its transaction list.
Where Did the Funds Go?
At 13:09:19 UTC the attacker moved all 96,388.996061 USDT to that second address. From there it went into the Rango cross-chain router in three transfers: a 1,000 USDT test at 13:28:39, 10,000 at 13:30:29 and the remaining 85,388.996061 at 13:31:33. Defimon reports the USDT was bridged to Solana and then to BNB Chain, and names a BNB Chain address as the laundering destination. We traced the Base side. The Solana and BNB Chain hops are Defimon's account.
The funds sat on Base, in plain externally owned accounts, for about 26 minutes after the drain.
Why Was the Rebalance Waiting for Anyone?
The hourly gate had been open for ten days. The contract's lastRebalance value in the block before the attack points to a rebalance on September 22, 2026 at 03:00:01 UTC, whose logs record 144,603.535756 USDC in the protocol position. The price condition inside checkRebalance had not been met since, so ordinary swaps ran the check and moved on.
The attacker supplied the missing condition, and the hook's own logs show by how little. checkRebalance simulates every circulating GPX being sold into the pool and only rebalances if the resulting tick lands strictly above the position's lower tick. During the first buy, the hook's simulation in the attack transaction landed on -275983, exactly on the lower tick, so it failed. After that 5,500 USDC buy, the second check landed on -275982. One tick above the floor, the smallest move that passes. Two small buys turned a dormant maintenance routine into a call the attacker could schedule right where its unpaid debt was waiting.
The rebalance in the attack transaction re-minted the protocol position with the 33,868.603002 USDC the hook had received, against 144,603.535756 on September 22. By our arithmetic that is a 77% cut to the hook's USDC liquidity, recorded in the hook's own Rebalanced event.
Is This a Uniswap v4 Bug?
No. The PoolManager and PositionManager did what they are documented to do, and every delta closed at zero. GPXHooks called a "join the open unlock" entry point from a context where someone else owned the unlock, and treated the full shared credit as its own.
The closest named precedent is the Bunni incident, which cost about $8.4 million in September 2025. BlockSec's analysis describes Bunni as an AMM "built on Uniswap V4" whose hook redistributes liquidity across price ranges, and the drain came through a rounding error in its withdraw() accounting. That is a different bug. What the two share is narrower: both hooks moved protocol liquidity as a side effect of other people's trades, so the attacker got to choose the state that accounting ran in.
Would an Audit Have Caught It?
Yes, if the review covered the hook's calls into the PositionManager. The flaw is visible in two functions of the verified source, beforeSwap and reBalance: a privileged liquidity operation reachable from beforeSwap, a call into the shared PositionManager without an unlock of its own, and no check on what TAKE_PAIR returned. A reviewer who knows v4's delta model asks one question at that call site, "whose unlock is this?", and the answer exposes the bug.
We have not found a public audit report for GPXHooks, and GoldPesa has not said whether it had one.
V4 hook bugs live where custom code meets the PoolManager's accounting, which is why our smart contract audits review hooks against the unlock model specifically: who owns each unlock a hook runs in, which entry points join it, and what the hook assumes about every delta it reads.
Could Monitoring Have Changed the Outcome?
No. Everything from the Morpho flash loan to the final burn landed in one transaction, block 52078502, so there was no on-chain moment between setup and loss for an alert to act on. The GPXHooks NatSpec also says the contract "cannot be paused, modified, or censored", so there was nothing for an alert to trigger. The only standing condition visible beforehand was the hook's simulated tick sitting exactly on its floor, one buy from a rebalance, and that is the hook's normal waiting state.
The signals came after the fact:
- The hook's rebalance redeployed 33,868.603002 USDC, against 144,603.535756 ten days earlier.
- One transaction both set off the GPX rebalance and burned a WETH/USDC position for 114,999.999186 USDC from the PoolManager.
- 96,388.996061 USDT moved to a fresh address and into a cross-chain router within 26 minutes of the drain.
For responders, the funds sat in plain wallets on Base for those 26 minutes, which was the window to warn the router and bridges in their path.
What Should Uniswap v4 Hook Developers Do Now?
- Never call
modifyLiquiditiesWithoutUnlockfrom a swap callback. A rebalance any swap can trigger lets the swapper choose the surrounding state. If the hook must move liquidity, do it in a separate keeper transaction that opens its ownunlockthroughmodifyLiquidities, where the PositionManager's deltas start at zero. - If you must operate inside a foreign
unlock, assert a clean ledger first. ReadcurrencyDeltafor the PositionManager in both pool currencies before the burn and revert unless both are zero, which is the check SlowMist's alert names. - Check what arrived, not just what you burned. Compare the hook's token balance before and after
TAKE_PAIRagainst the expected burn proceeds, and revert on a shortfall. GPXHooks only checked for a surplus. - Alert on your own events. If your hook emits the liquidity it redeploys, compare each value with the last one.
- Keep a brake on protocol-owned liquidity. GPXHooks advertised that it could not be paused. For a contract that holds the protocol's own liquidity, an owner-gated pause is cheap insurance against the bug nobody has found yet.
What We Still Do Not Know
GoldPesa has not published a postmortem, so we do not know whether it plans to move the remaining position. The hook cannot be patched in place: its NatSpec rules out modification, and Blockscout lists it as a plain contract with no proxy. We have seen no announcement of a replacement. If the code is unchanged, the same path applies to the next rebalance, against a position now holding about a quarter of its former USDC. We also do not know who controls the BNB Chain destination address Defimon names.
Frequently Asked Questions
How much was stolen in the GoldPesa exploit? 114,999.999186 USDC left the Uniswap v4 PoolManager on October 2, 2026, which Defimon reports as $114K and SlowMist as about $114.9K. That is GoldPesa's loss. The attacker kept less: its final swap turned 114,428.509331 USDC into 96,388.996061 USDT.
Was Uniswap v4 itself hacked?
No. The PoolManager and PositionManager behaved as documented and every delta settled to zero. The bug was in GoldPesa's GPXHooks contract, which used the PositionManager's shared balance inside an unlock the attacker controlled.
What is a shared-delta or shared-unlock bug in Uniswap v4?
It is when two parties' PositionManager actions run inside the same PoolManager unlock and one of them withdraws the PositionManager's full net credit as if it were its own. The credit is netted across everyone in the unlock, so one party's unpaid debt is silently paid by the other party's proceeds.
Are other Uniswap v4 hooks vulnerable?
Yes, if they follow the same pattern. A hook that calls modifyLiquiditiesWithoutUnlock with TAKE_PAIR from inside a swap callback, without checking that the PositionManager's deltas are zero first, is exposed in proportion to the liquidity it manages.
Sources / References
- Defimon alert: GoldPesa.com Loss $114K (2026-10-02)
- SlowMist TI Alert, October 3, 2026
- Add GoldPesa (GPX) exploit PoC (DeFiHackLabs PR #1288)
- GoldPesa's GPXHooks Allegedly Drained for $114K in Base Exploit (The Crypto Times)
- Goldpesatoken Hit by $114.9K Exploit Loss as Flaws Revealed (Coinfomania)
- Uniswap v4 Flash Accounting (Uniswap Developers)
- EIP-1153: Transient storage opcodes
- PositionManager.sol (Uniswap v4-periphery)
- DeltaResolver.sol (Uniswap v4-periphery)
- IPositionManager.sol (Uniswap v4-periphery)
- GPXHooks contract record (Base Blockscout API)
- GPXHooks verified source (Base Blockscout API)
- Attack transaction (Base Blockscout API)
- Attack transaction event logs (Base Blockscout API)
- Attack contract deployment (Base Blockscout API)
- Rango transfers: 1,000, 10,000 and 85,388.996061 USDT (Base Blockscout API)
- Attack transaction token transfers (Base Blockscout API)
- Previous rebalance, September 22, 2026, event logs (Base Blockscout API)
- Attacker EOA transactions (Base Blockscout API)
- Attacker funding transaction from a Relay solver (Base Blockscout API)
- Attacker second wallet token transfers (Base Blockscout API)
- USDC/USDT exit pool (Base Blockscout API)
- Decentralized exchange Bunni loses an estimated $8.4 million in smart contract exploit (The Block)
- #8 Bunni Incident: Repeated Small Withdrawals Compound a Rounding Error into an $8.4M Drain (BlockSec)



