DeFi/AMM Exploitation: Uniswap v4 Hook Precision/Rounding Abuse
Tip
AWS ํดํน ๋ฐฐ์ฐ๊ธฐ ๋ฐ ์ฐ์ตํ๊ธฐ:
HackTricks Training AWS Red Team Expert (ARTE)
GCP ํดํน ๋ฐฐ์ฐ๊ธฐ ๋ฐ ์ฐ์ตํ๊ธฐ:HackTricks Training GCP Red Team Expert (GRTE)
Azure ํดํน ๋ฐฐ์ฐ๊ธฐ ๋ฐ ์ฐ์ตํ๊ธฐ:
HackTricks Training Azure Red Team Expert (AzRTE)
HackTricks ์ง์ํ๊ธฐ
- ๊ตฌ๋ ๊ณํ ํ์ธํ๊ธฐ!
- **๐ฌ ๋์ค์ฝ๋ ๊ทธ๋ฃน ๋๋ ํ ๋ ๊ทธ๋จ ๊ทธ๋ฃน์ ์ฐธ์ฌํ๊ฑฐ๋ ํธ์ํฐ ๐ฆ @hacktricks_live๋ฅผ ํ๋ก์ฐํ์ธ์.
- HackTricks ๋ฐ HackTricks Cloud ๊นํ๋ธ ๋ฆฌํฌ์งํ ๋ฆฌ์ PR์ ์ ์ถํ์ฌ ํดํน ํธ๋ฆญ์ ๊ณต์ ํ์ธ์.
์ด ํ์ด์ง๋ custom hooks๋ก ์ฝ์ด ์ํ์ ํ์ฅํ๋ Uniswap v4 ์คํ์ผ DEX๋ค์ ๋ํ ์ผ๋ จ์ DeFi/AMM ๊ณต๊ฒฉ ๊ธฐ๋ฒ์ ์ค๋ช ํ๋ค. ์ต๊ทผ Bunni V2 ์ฌ๊ณ ์์๋ Liquidity Distribution Function (LDF)์ ๋ฐ์ฌ๋ฆผ/์ ๋ฐ๋ ๊ฒฐํจ์ ์ด์ฉํด ๊ฐ ์ค์์์ ๊ณต๊ฒฉ์๊ฐ ์(+)์ ํฌ๋ ๋ง์ ์ถ์ ํ๊ณ ์ ๋์ฑ์ ํ์ทจํ ์ ์์๋ค.
ํต์ฌ ์์ด๋์ด: ํ ์ด ๊ณ ์ ์์์ ์ฐ์ฐ, tick ๋ฐ์ฌ๋ฆผ, ์๊ณ๊ฐ ๋ก์ง์ ์์กดํ๋ ์ถ๊ฐ ํ๊ณ(accounting)๋ฅผ ๊ตฌํํ๋ฉด, ๊ณต๊ฒฉ์๋ ํน์ ์๊ณ๊ฐ์ ๋๋๋ก ๊ฐ๊ฒฉ์ ์ ํํ ์ด๋์ํค๋ exactโinput ์ค์์ ์ค๊ณํด ๋ฐ์ฌ๋ฆผ ์ฐจ์ด๊ฐ ์์ ์๊ฒ ์ ๋ฆฌํ๊ฒ ๋์ ๋๋๋ก ํ ์ ์๋ค. ์ด ํจํด์ ๋ฐ๋ณตํ๊ณ ์ฆ์๋ ์์ก์ ์ธ์ถํ๋ฉด ์ด์ต์ ์คํํ๋ฉฐ, ์ข ์ข flash loan์ผ๋ก ์๊ธ์ ์กฐ๋ฌํ๋ค.
Background: Uniswap v4 hooks and swap flow
- Hooks๋ PoolManager๊ฐ ํน์ ์๋ช ์ฃผ๊ธฐ ์ง์ ์์ ํธ์ถํ๋ contracts์ด๋ค(์: beforeSwap/afterSwap, beforeAddLiquidity/afterAddLiquidity, beforeRemoveLiquidity/afterRemoveLiquidity).
- Pools๋ PoolKey์ hooks ์ฃผ์๋ก ์ด๊ธฐํ๋๋ค. nonโzero์ธ ๊ฒฝ์ฐ PoolManager๋ ๊ด๋ จ๋ ๋ชจ๋ ์์ ์์ ์ฝ๋ฐฑ์ ํธ์ถํ๋ค.
- ์ฝ์ด ์ํ์ sqrtPriceX96์ ๋ํด Q64.96 ๊ฐ์ fixedโpoint ํฌ๋งท๊ณผ 1.0001^tick์ ์ฌ์ฉํ๋ tick ์ฐ์ ์ ์ฌ์ฉํ๋ค. ๊ทธ ์์ ์ถ๊ฐ๋ ๋ชจ๋ custom math๋ ๋ถ๋ณ์ฑ ๋๋ฆฌํํธ๋ฅผ ํผํ๊ธฐ ์ํด ๋ฐ์ฌ๋ฆผ ์๋ฏธ๋ก ์ ์ ํํ ๋ง์ถฐ์ผ ํ๋ค.
- Swaps๋ exactInput ๋๋ exactOutput์ผ ์ ์๋ค. v3/v4์์ ๊ฐ๊ฒฉ์ ticks๋ฅผ ๋ฐ๋ผ ์์ง์ด๊ณ , tick ๊ฒฝ๊ณ๋ฅผ ๋์ผ๋ฉด ๋ฒ์ ์ ๋์ฑ์ด ํ์ฑํ/๋นํ์ฑํ๋ ์ ์๋ค. Hooks๋ ์๊ณ๊ฐ/ํฑ ๊ต์ฐจ์์ ์ถ๊ฐ ๋ก์ง์ ๊ตฌํํ ์ ์๋ค.
Vulnerability archetype: thresholdโcrossing precision/rounding drift
custom hooks์์ ํํ ์ทจ์ฝ ํจํด:
- ํ ์ด perโswap ์ ๋์ฑ ๋๋ ์์ก ๋ธํ๋ฅผ integer division, mulDiv, ๋๋ fixedโpoint ๋ณํ(์: token โ liquidity ๋ณํ์ sqrtPrice์ tick ranges ์ฌ์ฉ)์ผ๋ก ๊ณ์ฐํ๋ค.
- ์๊ณ๊ฐ ๋ก์ง(์: ๋ฆฌ๋ฐธ๋ฐ์ฑ, ๋จ๊ณ๋ณ ์ฌ๋ถ๋ฐฐ, ๋๋ ๋ฒ์๋ณ ํ์ฑํ)์ด ์ค์ ํฌ๊ธฐ๋ ๊ฐ๊ฒฉ ์ด๋์ด ๋ด๋ถ ๊ฒฝ๊ณ๋ฅผ ๋์ ๋ ํธ๋ฆฌ๊ฑฐ๋๋ค.
- ์ ์ง ๊ณ์ฐ๊ณผ ์ ์ฐ ๊ฒฝ๋ก ์ฌ์ด์ ๋ฐ์ฌ๋ฆผ์ด ์ผ๊ด๋๊ฒ ์ ์ฉ๋์ง ์๋๋ค(์: 0์ผ๋ก ์ ๋จ(truncation toward zero), floor ๋ ceil). ์์ ๋ถ์ผ์น๊ฐ ์์๋์ง ์๊ณ ๋์ ํธ์ถ์์๊ฒ ํฌ๋ ๋ง์ผ๋ก ๊ท์๋๋ค.
- ์๊ณ๊ฐ์ ๊ฐ๋ก์ง๋ฅด๋๋ก ์ ๋ฐํ๊ฒ ์กฐ์ ๋ exactโinput ์ค์์ ์(+)์ ๋ฐ์ฌ๋ฆผ ์์ฌ๋ถ์ ๋ฐ๋ณต์ ์ผ๋ก ์ํํ๋ค. ๊ณต๊ฒฉ์๋ ์ดํ ์ถ์ ๋ ํฌ๋ ๋ง์ ์ธ์ถํ๋ค.
Attack preconditions
- ๊ฐ ์ค์์์ ์ถ๊ฐ ์ฐ์ฐ์ ์ํํ๋ custom v4 hook์ ์ฌ์ฉํ๋ ํ(์: LDF/rebalancer).
- ์๊ณ๊ฐ ๊ต์ฐจ์์ ์ค์ ์์์์๊ฒ ๋ฐ์ฌ๋ฆผ ์ด์ต์ ์ฃผ๋ ์ ์ด๋ ํ๋์ ์คํ ๊ฒฝ๋ก.
- ๋ง์ ์์ ์ค์์ ์์์ ์ผ๋ก ๋ฐ๋ณตํ ์ ์๋ ๋ฅ๋ ฅ(flash loans๋ ์์ ์ ๋์ฑ ์ ๊ณต๊ณผ ๊ฐ์ค ๋ถ์ฐ์ ์ด์์ ).
Practical attack methodology
- Identify candidate pools with hooks
- v4 pools๋ฅผ ์ด๊ฑฐํ๊ณ PoolKey.hooks != address(0)์ ํ์ธํ๋ค.
- beforeSwap/afterSwap ๊ฐ์ ์ฝ๋ฐฑ๊ณผ custom rebalancing ๋ฉ์๋๋ฅผ ์ํด hook bytecode/ABI๋ฅผ ๊ฒ์ฌํ๋ค.
- ๋ค์๊ณผ ๊ฐ์ ์ํ์ ์ฐพ๋๋ค: liquidity๋ก ๋๋๊ธฐ, token๊ณผ liquidity ์ฌ์ด ๋ณํ, ๋๋ ๋ฐ์ฌ๋ฆผ์ ํฌํจํ BalanceDelta ์ง๊ณ ๋ฑ.
- Model the hookโs math and thresholds
- ํ ์ liquidity/redistribution ๊ณต์์ ์ฌํํ๋ค: ์ ๋ ฅ์๋ ์ผ๋ฐ์ ์ผ๋ก sqrtPriceX96, tickLower/Upper, currentTick, fee tier, net liquidity ๋ฑ์ด ํฌํจ๋๋ค.
- ์๊ณ๊ฐ/์คํ ํจ์๋ค์ ๋งคํํ๋ค: ticks, ๋ฒํท ๊ฒฝ๊ณ, ๋๋ LDF ๋ถ๊ธฐ์ . ๊ฐ ๊ฒฝ๊ณ์ ์ด๋ ์ชฝ์์ ๋ธํ๊ฐ ๋ฐ์ฌ๋ฆผ๋๋์ง ๊ฒฐ์ ํ๋ค.
- ์ด๋์์ uint256/int256 ๊ฐ ์บ์คํธ๊ฐ ์ผ์ด๋๋์ง, SafeCast๋ฅผ ์ฐ๋์ง, ๋๋ ์๋ฌต์ floor๋ฅผ ๊ฐ์ง mulDiv๋ฅผ ์ฌ์ฉํ๋์ง ์๋ณํ๋ค.
- Calibrate exactโinput swaps to cross boundaries
- Foundry/Hardhat ์๋ฎฌ๋ ์ด์ ์ ์ฌ์ฉํด ๊ฐ๊ฒฉ์ ๊ฒฝ๊ณ ๋ฐ๋ก ๋์ด๊ฐ๊ฒ ํ๊ณ ํ ์ ๋ถ๊ธฐ๋ฅผ ํธ๋ฆฌ๊ฑฐํ๊ธฐ ์ํ ์ต์ ฮin์ ๊ณ์ฐํ๋ค.
- afterSwap ์ ์ฐ์ด ๋น์ฉ๋ณด๋ค ํธ์ถ์์๊ฒ ๋ ๋ง์ ํฌ๋ ๋ง์ ๋ถ์ฌํด positive BalanceDelta ๋๋ ํ ํ๊ณ์ ํฌ๋ ๋ง์ ๋จ๊ธฐ๋์ง ๊ฒ์ฆํ๋ค.
- ํฌ๋ ๋ง์ ์ถ์ ํ๊ธฐ ์ํด ์ค์์ ๋ฐ๋ณตํ ๋ค ํ ์ withdrawal/settlement ๊ฒฝ๋ก๋ฅผ ํธ์ถํ๋ค.
Example Foundryโstyle test harness (pseudocode)
function test_precision_rounding_abuse() public {
// 1) Arrange: set up pool with hook
PoolKey memory key = PoolKey({
currency0: USDC,
currency1: USDT,
fee: 500, // 0.05%
tickSpacing: 10,
hooks: address(bunniHook)
});
pm.initialize(key, initialSqrtPriceX96);
// 2) Determine a boundaryโcrossing exactInput
uint256 exactIn = calibrateToCrossThreshold(key, targetTickBoundary);
// 3) Loop swaps to accrue rounding credit
for (uint i; i < N; ++i) {
pm.swap(
key,
IPoolManager.SwapParams({
zeroForOne: true,
amountSpecified: int256(exactIn), // exactInput
sqrtPriceLimitX96: 0 // allow tick crossing
}),
""
);
}
// 4) Realize inflated credit via hookโexposed withdrawal
bunniHook.withdrawCredits(msg.sender);
}
exactInput ๋ณด์
- ํฑ ์คํ ์ ๋ํ ฮsqrtP ๊ณ์ฐ: sqrtP_next = sqrtP_current ร 1.0001^(ฮtick).
- v3/v4 ๊ณต์์ ์ฌ์ฉํด ฮin์ ๊ทผ์ฌ: ฮx โ L ร (ฮsqrtP / (sqrtP_next ร sqrtP_current)). ๋ฐ์ฌ๋ฆผ ๋ฐฉํฅ์ด ํต์ฌ ์ํ๊ณผ ์ผ์นํ๋์ง ํ์ธํ์ธ์.
- ๊ฒฝ๊ณ ์ฃผ๋ณ์์ ฮin์ ยฑ1 wei๋งํผ ์กฐ์ ํด hook์ด ๋น์ ์๊ฒ ์ ๋ฆฌํ๊ฒ ๋ฐ์ฌ๋ฆผํ๋ ๋ถ๊ธฐ๋ฅผ ์ฐพ์ผ์ธ์.
- flash loans๋ก ์ฆํญํ๊ธฐ
- ์์์ ์ผ๋ก ์ฌ๋ฌ ๋ฐ๋ณต์ ์คํํ๊ธฐ ์ํด ํฐ ๋ช ๋ชฉ์ก(์: 3M USDT ๋๋ 2000 WETH)์ ๋น๋ฆฌ์ธ์.
- ๋ณด์ ๋ swap ๋ฃจํ๋ฅผ ์คํํ ๋ค์, flash loan callback ๋ด์์ ์ถ๊ธํ๊ณ ์ํํ์ธ์.
Aave V3 flash loan skeleton
function executeOperation(
address[] calldata assets,
uint256[] calldata amounts,
uint256[] calldata premiums,
address initiator,
bytes calldata params
) external returns (bool) {
// run thresholdโcrossing swap loop here
for (uint i; i < N; ++i) {
_exactInBoundaryCrossingSwap();
}
// realize credits / withdraw inflated balances
bunniHook.withdrawCredits(address(this));
// repay
for (uint j; j < assets.length; ++j) {
IERC20(assets[j]).approve(address(POOL), amounts[j] + premiums[j]);
}
return true;
}
- Exit and crossโchain replication
- If hooks are deployed on multiple chains, repeat the same calibration per chain.
- Bridge proceeds back to the target chain and optionally cycle via lending protocols to obfuscate flows.
Common root causes in hook math
- Mixed rounding semantics: mulDiv floors while later paths effectively round up; or conversions between token/liquidity apply different rounding.
- Tick alignment errors: using unrounded ticks in one path and tickโspaced rounding in another.
- BalanceDelta sign/overflow issues when converting between int256 and uint256 during settlement.
- Precision loss in Q64.96 conversions (sqrtPriceX96) not mirrored in reverse mapping.
- Accumulation pathways: perโswap remainders tracked as credits that are withdrawable by the caller instead of being burned/zeroโsum.
Defensive guidance
- 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).
- Invariant/property tests:
- Sum of deltas (tokens, liquidity) across swap paths and hook adjustments must conserve value modulo fees.
- No path should create positive net credit for the swap initiator over repeated exactInput iterations.
- Threshold/tick boundary tests around ยฑ1 wei inputs for both exactInput/exactOutput.
- Rounding policy: centralize rounding helpers that always round against the user; eliminate inconsistent casts and implicit floors.
- Settlement sinks: accumulate unavoidable rounding residue to protocol treasury or burn it; never attribute to msg.sender.
- Rateโlimits/guardrails: minimum swap sizes for rebalancing triggers; disable rebalances if deltas are subโwei; sanityโcheck deltas against expected ranges.
- Review hook callbacks holistically: beforeSwap/afterSwap and before/after liquidity changes should agree on tick alignment and delta rounding.
Case study: Bunni V2 (2025โ09โ02)
- Protocol: Bunni V2 (Uniswap v4 hook) with an LDF applied per swap to rebalance.
- Root cause: rounding/precision error in LDF liquidity accounting during thresholdโcrossing swaps; perโswap discrepancies accrued as positive credits for the caller.
- 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.
- UniChain leg: repeated the exploit with a 2000 WETH flash loan, siphoning ~1366 WETH and bridging to Ethereum.
- Impact: ~USD 8.3M drained across chains. No user interaction required; entirely onโchain.
Hunting checklist
- Does the pool use a nonโzero hooks address? Which callbacks are enabled?
- Are there perโswap redistributions/rebalances using custom math? Any tick/threshold logic?
- Where are divisions/mulDiv, Q64.96 conversions, or SafeCast used? Are rounding semantics globally consistent?
- Can you construct ฮin that barely crosses a boundary and yields a favorable rounding branch? Test both directions and both exactInput and exactOutput.
- Does the hook track perโcaller credits or deltas that can be withdrawn later? Ensure residue is neutralized.
References
- Bunni V2 Exploit: $8.3M Drained via Liquidity Flaw (summary)
- Bunni V2 Exploit: Full Hack Analysis
- Uniswap v4 background (QuillAudits research)
- Liquidity mechanics in Uniswap v4 core
- Swap mechanics in Uniswap v4 core
- Uniswap v4 Hooks and Security Considerations
Tip
AWS ํดํน ๋ฐฐ์ฐ๊ธฐ ๋ฐ ์ฐ์ตํ๊ธฐ:
HackTricks Training AWS Red Team Expert (ARTE)
GCP ํดํน ๋ฐฐ์ฐ๊ธฐ ๋ฐ ์ฐ์ตํ๊ธฐ:HackTricks Training GCP Red Team Expert (GRTE)
Azure ํดํน ๋ฐฐ์ฐ๊ธฐ ๋ฐ ์ฐ์ตํ๊ธฐ:
HackTricks Training Azure Red Team Expert (AzRTE)
HackTricks ์ง์ํ๊ธฐ
- ๊ตฌ๋ ๊ณํ ํ์ธํ๊ธฐ!
- **๐ฌ ๋์ค์ฝ๋ ๊ทธ๋ฃน ๋๋ ํ ๋ ๊ทธ๋จ ๊ทธ๋ฃน์ ์ฐธ์ฌํ๊ฑฐ๋ ํธ์ํฐ ๐ฆ @hacktricks_live๋ฅผ ํ๋ก์ฐํ์ธ์.
- HackTricks ๋ฐ HackTricks Cloud ๊นํ๋ธ ๋ฆฌํฌ์งํ ๋ฆฌ์ PR์ ์ ์ถํ์ฌ ํดํน ํธ๋ฆญ์ ๊ณต์ ํ์ธ์.


