KolanHook drained for 1.279453 ETH net on Ethereum after a 6.7 WETH flash-borrow moved the ETH/KLN spot enough to misposition two expired escrowed windows through permissionless `accept()`.
Security ·
Logged as “2026-10-15: Kolanhook Settles Its Escrowed Eth Buy Windows At The Same Uniswap V4 Pools Instantaneous Spot Price With No Twap Or Bound And Accept Is Ungated Once A Window Has Expired So The Attacker Flash Borrows 67 Weth From Balancer Swaps It To Move The Kln Spot Then Calls Accept On Two Expired Windows It Does Not Own So _Closewindow Mispositions Their Escrowed Eth As Single Sided Liquidity Around The Manipulated Price And Runs An Unbounded Buyback And Reverses The Swap Back Through That Freshly Placed Eth Liquidity Before Repaying The Loan For A 1279453 Eth Net Gain on KolanHook”.
KolanHook escrows ETH-funded "buy windows" against a single Uniswap v4 pool it owns, the ETH/KLN pair. A buyer opens a window by locking some ETH into the hook, with a promised strike price and an expiry. After expiry, the buyer (or anyone, per the code) can call accept(id) to settle the window at the pool's spot price. The escrowed ETH sits in the hook until settlement, and the hook holds the whole pool's LP range and rebalances around the spot it reads. Two bugs in that settlement line up.
First, accept(id) is permissionless once the window has expired. The only check on entry is if (msg.sender != w.owner && block.timestamp < w.expiry) revert WindowStillOpen();. After w.expiry, any address may settle a window it does not own. There is no keeper, owner, role, or signature gate.
Second, the settlement price comes from the same pool's instantaneous spot, with no time-weighted average price (TWAP), no observation, and no slippage bound. _closeWindow() calls _sqrtPrice(), which returns the live slot0() of the Uniswap v4 pool, then runs an unbounded buy-back through _buyBack(spendable) with sqrtPriceLimitX96 = TickMath.MIN_SQRT_PRICE + 1. A single swap against the pool can move the spot enough to push a window out of its "back in place" range; the hook then mispositions that escrowed ETH as single-sided liquidity around the manipulated price and runs the buy-back at the inflated quote.
The attacker flash-borrows 6.7 WETH from the Balancer Vault (zero fee), unwraps to ETH, and under one PoolManager.unlock swaps the full 6.7 ETH into KLN, pushing the spot. From that invariant call, the attacker calls accept(10) and accept(42) on two real pre-existing expired windows belonging to other addresses. Both settle at the moved spot; window 42 is no longer "back in place", so its escrowed ETH is re-injected as single-sided liquidity around the manipulated price. Under a second unlock the attacker reverses the swap, taking the whole KLN balance back to ETH through the freshly placed ETH liquidity, and unwinds 7.979914 ETH out of the pool. The flash loan is rewrapped to 6.7 WETH and repaid in full.
DeFiHackLabs records a loss of 1.279453 ETH net to the attacker EOA on Ethereum, draining KolanHook's full 2.309861 ETH of escrowed window funds. The on-chain attack transaction is 0x7b131926e6f06491ae1aead3e419b208c8aa70ed7898e8c5a33d9f52e40915a9, mined on 2026-10-15.
Two local changes would have stopped this. Settlement should read a TWAP over the window's duration rather than the instantaneous spot, and accept() should be permissioned to the window owner at every call, not just before expiry.
This sits against the other spot-price-oracle incidents in the corpus. PancakeV3SwapHelper signed a delta against an unsigned counterpart without re-checking the signs after a swap, GoldPesa rebalanced its hook against an open PoolManager unlock whose delta was not settled, and the MALT SLP checked its constant-product invariant against the final balances of a swap into which it had injected treasury reserves. KolanHook is the spot-price version of the same assumption: an unauthenticated price read treated as a binding oracle in a step that moves funds. The mechanisms are unrelated; the assumption is the same.
Query every incident yourself — free, no signup
Updated 11 Nov 2026. Figures are as reported by the sources listed above and are not independently verified.