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 ์ง€์›ํ•˜๊ธฐ

์ด ํŽ˜์ด์ง€๋Š” 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์—์„œ ํ”ํ•œ ์ทจ์•ฝ ํŒจํ„ด:

  1. ํ›…์ด perโ€‘swap ์œ ๋™์„ฑ ๋˜๋Š” ์ž”์•ก ๋ธํƒ€๋ฅผ integer division, mulDiv, ๋˜๋Š” fixedโ€‘point ๋ณ€ํ™˜(์˜ˆ: token โ†” liquidity ๋ณ€ํ™˜์— sqrtPrice์™€ tick ranges ์‚ฌ์šฉ)์œผ๋กœ ๊ณ„์‚ฐํ•œ๋‹ค.
  2. ์ž„๊ณ„๊ฐ’ ๋กœ์ง(์˜ˆ: ๋ฆฌ๋ฐธ๋Ÿฐ์‹ฑ, ๋‹จ๊ณ„๋ณ„ ์žฌ๋ถ„๋ฐฐ, ๋˜๋Š” ๋ฒ”์œ„๋ณ„ ํ™œ์„ฑํ™”)์ด ์Šค์™‘ ํฌ๊ธฐ๋‚˜ ๊ฐ€๊ฒฉ ์ด๋™์ด ๋‚ด๋ถ€ ๊ฒฝ๊ณ„๋ฅผ ๋„˜์„ ๋•Œ ํŠธ๋ฆฌ๊ฑฐ๋œ๋‹ค.
  3. ์ „์ง„ ๊ณ„์‚ฐ๊ณผ ์ •์‚ฐ ๊ฒฝ๋กœ ์‚ฌ์ด์— ๋ฐ˜์˜ฌ๋ฆผ์ด ์ผ๊ด€๋˜๊ฒŒ ์ ์šฉ๋˜์ง€ ์•Š๋Š”๋‹ค(์˜ˆ: 0์œผ๋กœ ์ ˆ๋‹จ(truncation toward zero), floor ๋Œ€ ceil). ์ž‘์€ ๋ถˆ์ผ์น˜๊ฐ€ ์ƒ์‡„๋˜์ง€ ์•Š๊ณ  ๋Œ€์‹  ํ˜ธ์ถœ์ž์—๊ฒŒ ํฌ๋ ˆ๋”ง์œผ๋กœ ๊ท€์†๋œ๋‹ค.
  4. ์ž„๊ณ„๊ฐ’์„ ๊ฐ€๋กœ์ง€๋ฅด๋„๋ก ์ •๋ฐ€ํ•˜๊ฒŒ ์กฐ์ •๋œ exactโ€‘input ์Šค์™‘์€ ์–‘(+)์˜ ๋ฐ˜์˜ฌ๋ฆผ ์ž”์—ฌ๋ถ„์„ ๋ฐ˜๋ณต์ ์œผ๋กœ ์ˆ˜ํ™•ํ•œ๋‹ค. ๊ณต๊ฒฉ์ž๋Š” ์ดํ›„ ์ถ•์ ๋œ ํฌ๋ ˆ๋”ง์„ ์ธ์ถœํ•œ๋‹ค.

Attack preconditions

  • ๊ฐ ์Šค์™‘์—์„œ ์ถ”๊ฐ€ ์—ฐ์‚ฐ์„ ์ˆ˜ํ–‰ํ•˜๋Š” custom v4 hook์„ ์‚ฌ์šฉํ•˜๋Š” ํ’€(์˜ˆ: LDF/rebalancer).
  • ์ž„๊ณ„๊ฐ’ ๊ต์ฐจ์—์„œ ์Šค์™‘ ์‹œ์ž‘์ž์—๊ฒŒ ๋ฐ˜์˜ฌ๋ฆผ ์ด์ต์„ ์ฃผ๋Š” ์ ์–ด๋„ ํ•˜๋‚˜์˜ ์‹คํ–‰ ๊ฒฝ๋กœ.
  • ๋งŽ์€ ์ˆ˜์˜ ์Šค์™‘์„ ์›์ž์ ์œผ๋กœ ๋ฐ˜๋ณตํ•  ์ˆ˜ ์žˆ๋Š” ๋Šฅ๋ ฅ(flash loans๋Š” ์ž„์‹œ ์œ ๋™์„ฑ ์ œ๊ณต๊ณผ ๊ฐ€์Šค ๋ถ„์‚ฐ์— ์ด์ƒ์ ).

Practical attack methodology

  1. Identify candidate pools with hooks
  • v4 pools๋ฅผ ์—ด๊ฑฐํ•˜๊ณ  PoolKey.hooks != address(0)์„ ํ™•์ธํ•œ๋‹ค.
  • beforeSwap/afterSwap ๊ฐ™์€ ์ฝœ๋ฐฑ๊ณผ custom rebalancing ๋ฉ”์„œ๋“œ๋ฅผ ์œ„ํ•ด hook bytecode/ABI๋ฅผ ๊ฒ€์‚ฌํ•œ๋‹ค.
  • ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ˆ˜ํ•™์„ ์ฐพ๋Š”๋‹ค: liquidity๋กœ ๋‚˜๋ˆ„๊ธฐ, token๊ณผ liquidity ์‚ฌ์ด ๋ณ€ํ™˜, ๋˜๋Š” ๋ฐ˜์˜ฌ๋ฆผ์„ ํฌํ•จํ•œ BalanceDelta ์ง‘๊ณ„ ๋“ฑ.
  1. Model the hookโ€™s math and thresholds
  • ํ›…์˜ liquidity/redistribution ๊ณต์‹์„ ์žฌํ˜„ํ•œ๋‹ค: ์ž…๋ ฅ์—๋Š” ์ผ๋ฐ˜์ ์œผ๋กœ sqrtPriceX96, tickLower/Upper, currentTick, fee tier, net liquidity ๋“ฑ์ด ํฌํ•จ๋œ๋‹ค.
  • ์ž„๊ณ„๊ฐ’/์Šคํ… ํ•จ์ˆ˜๋“ค์„ ๋งคํ•‘ํ•œ๋‹ค: ticks, ๋ฒ„ํ‚ท ๊ฒฝ๊ณ„, ๋˜๋Š” LDF ๋ถ„๊ธฐ์ . ๊ฐ ๊ฒฝ๊ณ„์˜ ์–ด๋А ์ชฝ์—์„œ ๋ธํƒ€๊ฐ€ ๋ฐ˜์˜ฌ๋ฆผ๋˜๋Š”์ง€ ๊ฒฐ์ •ํ•œ๋‹ค.
  • ์–ด๋””์—์„œ uint256/int256 ๊ฐ„ ์บ์ŠคํŠธ๊ฐ€ ์ผ์–ด๋‚˜๋Š”์ง€, SafeCast๋ฅผ ์“ฐ๋Š”์ง€, ๋˜๋Š” ์•”๋ฌต์  floor๋ฅผ ๊ฐ€์ง„ mulDiv๋ฅผ ์‚ฌ์šฉํ•˜๋Š”์ง€ ์‹๋ณ„ํ•œ๋‹ค.
  1. 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์ด ๋‹น์‹ ์—๊ฒŒ ์œ ๋ฆฌํ•˜๊ฒŒ ๋ฐ˜์˜ฌ๋ฆผํ•˜๋Š” ๋ถ„๊ธฐ๋ฅผ ์ฐพ์œผ์„ธ์š”.
  1. 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;
}
  1. 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

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 ์ง€์›ํ•˜๊ธฐ