Skip to content

Commit c246b86

Browse files
authored
Merge pull request #2025 from HackTricks-wiki/research_update_src_blockchain_blockchain-and-crypto-currencies_defi-amm-hook-precision_20260319_024558
Research Update Enhanced src/blockchain/blockchain-and-crypt...
2 parents 944d38d + c0061ac commit c246b86

1 file changed

Lines changed: 26 additions & 5 deletions

File tree

src/blockchain/blockchain-and-crypto-currencies/defi-amm-hook-precision.md

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,17 @@
22

33
{{#include ../../banners/hacktricks-training.md}}
44

5+
6+
57
This page documents a class of DeFi/AMM exploitation techniques against Uniswap v4–style DEXes that extend core math with custom hooks. A recent incident in Bunni V2 leveraged a rounding/precision flaw in a Liquidity Distribution Function (LDF) executed on each swap, enabling the attacker to accrue positive credits and drain liquidity.
68

79
Key idea: if a hook implements additional accounting that depends on fixed‑point math, tick rounding, and threshold logic, an attacker can craft exact‑input swaps that cross specific thresholds so that rounding discrepancies accumulate in their favor. Repeating the pattern and then withdrawing the inflated balance realizes profit, often financed with a flash loan.
810

911
## Background: Uniswap v4 hooks and swap flow
1012

11-
- Hooks are contracts that the PoolManager calls at specific lifecycle points (e.g., beforeSwap/afterSwap, beforeAddLiquidity/afterAddLiquidity, beforeRemoveLiquidity/afterRemoveLiquidity).
13+
- Hooks are contracts that the PoolManager calls at specific lifecycle points (e.g., beforeSwap/afterSwap, beforeAddLiquidity/afterAddLiquidity, beforeRemoveLiquidity/afterRemoveLiquidity, beforeInitialize/afterInitialize, beforeDonate/afterDonate).
1214
- Pools are initialized with a PoolKey including hooks address. If non‑zero, PoolManager performs callbacks on every relevant operation.
15+
- Hooks can return **custom deltas** that modify the final balance changes of a swap or liquidity action (custom accounting). Those deltas are settled as net balances at the end of the call, so any rounding error inside hook math accumulates before settlement.
1316
- Core math uses fixed‑point formats such as Q64.96 for sqrtPriceX96 and tick arithmetic with 1.0001^tick. Any custom math layered on top must carefully match rounding semantics to avoid invariant drift.
1417
- Swaps can be exactInput or exactOutput. In v3/v4, price moves along ticks; crossing a tick boundary may activate/deactivate range liquidity. Hooks may implement extra logic on threshold/tick crossings.
1518

@@ -122,6 +125,13 @@ function executeOperation(
122125
- Precision loss in Q64.96 conversions (sqrtPriceX96) not mirrored in reverse mapping.
123126
- Accumulation pathways: per‑swap remainders tracked as credits that are withdrawable by the caller instead of being burned/zero‑sum.
124127

128+
129+
## Custom accounting & delta amplification
130+
131+
- Uniswap v4 custom accounting lets hooks return deltas that directly adjust what the caller owes/receives. If the hook tracks credits internally, rounding residue can accumulate across many small operations **before** the final settlement happens.
132+
- This makes boundary/threshold abuse stronger: the attacker can alternate `swap → withdraw → swap` in the same tx, forcing the hook to recompute deltas on slightly different state while all balances are still pending.
133+
- When reviewing hooks, always trace how BalanceDelta/HookDelta is produced and settled. A single biased rounding in one branch can become a compounding credit when deltas are repeatedly re‑computed.
134+
125135
## Defensive guidance
126136

127137
- Differential testing: mirror the hook’s math vs a reference implementation using high‑precision rational arithmetic and assert equality or bounded error that is always adversarial (never favorable to caller).
@@ -137,10 +147,19 @@ function executeOperation(
137147
## Case study: Bunni V2 (2025‑09‑02)
138148

139149
- Protocol: Bunni V2 (Uniswap v4 hook) with an LDF applied per swap to rebalance.
140-
- Root cause: rounding/precision error in LDF liquidity accounting during threshold‑crossing swaps; per‑swap discrepancies accrued as positive credits for the caller.
141-
- Ethereum leg: attacker took a ~3M USDT flash loan, performed calibrated exact‑input swaps on USDC/USDT to build credits, withdrew inflated balances, repaid, and routed funds via Aave.
142-
- UniChain leg: repeated the exploit with a 2000 WETH flash loan, siphoning ~1366 WETH and bridging to Ethereum.
143-
- Impact: ~USD 8.3M drained across chains. No user interaction required; entirely on‑chain.
150+
- Affected pools: USDC/USDT on Ethereum and weETH/ETH on Unichain, totaling about $8.4M.
151+
- Step 1 (price push): the attacker flash‑borrowed ~3M USDT and swapped to push the tick to ~5000, shrinking the **active** USDC balance down to ~28 wei.
152+
- Step 2 (rounding drain): 44 tiny withdrawals exploited floor rounding in `BunniHubLogic::withdraw()` to reduce the active USDC balance from 28 wei to 4 wei (‑85.7%) while only a tiny fraction of LP shares was burned. Total liquidity was underestimated by ~84.4%.
153+
- Step 3 (liquidity rebound sandwich): a large swap moved the tick to ~839,189 (1 USDC ≈ 2.77e36 USDT). Liquidity estimates flipped and increased by ~16.8%, enabling a sandwich where the attacker swapped back at the inflated price and exited with profit.
154+
- Fix identified in the post‑mortem: change the idle‑balance update to round **up** so repeated micro‑withdrawals can’t ratchet the pool’s active balance downward.
155+
156+
Simplified vulnerable line (and post‑mortem fix)
157+
```solidity
158+
// BunniHubLogic::withdraw() idle balance update (simplified)
159+
uint256 newBalance = balance - balance.mulDiv(shares, currentTotalSupply);
160+
// Fix: round up to avoid cumulative underestimation
161+
uint256 newBalance = balance - balance.mulDivUp(shares, currentTotalSupply);
162+
```
144163

145164
## Hunting checklist
146165

@@ -158,5 +177,7 @@ function executeOperation(
158177
- [Liquidity mechanics in Uniswap v4 core](https://www.quillaudits.com/research/uniswap-development/uniswap-v4/liquidity-mechanics-in-uniswap-v4-core)
159178
- [Swap mechanics in Uniswap v4 core](https://www.quillaudits.com/research/uniswap-development/uniswap-v4/swap-mechanics-in-uniswap-v4-core)
160179
- [Uniswap v4 Hooks and Security Considerations](https://www.quillaudits.com/research/uniswap-development/uniswap-v4/uniswap-v4-hooks-and-security)
180+
- [Bunni Exploit Post Mortem (Sep 2025)](https://blog.bunni.xyz/posts/exploit-post-mortem/)
181+
- [Uniswap v4 Core Whitepaper](https://app.uniswap.org/whitepaper-v4.pdf)
161182

162183
{{#include ../../banners/hacktricks-training.md}}

0 commit comments

Comments
 (0)