# Uniswap V3 Math Utilities Comprehensive mathematical utilities for Uniswap V3 concentrated liquidity pools. Based on the official Uniswap V3 SDK and whitepaper. ## Overview Uniswap V3 uses concentrated liquidity with tick-based price ranges. All prices are represented as `sqrtPriceX96` (Q64.96 fixed-point format), and positions are defined by tick ranges. ### Key Concepts **1. Ticks** - Discrete price levels: `price = 1.0001^tick` - Valid range: `-887272` to `887272` - Each tick represents a 0.01% price change **2. SqrtPriceX96** - Fixed-point representation: `sqrtPriceX96 = sqrt(price) * 2^96` - Q64.96 format (64 integer bits, 96 fractional bits) - Used internally for all price calculations **3. Liquidity** - Virtual liquidity representing swap capacity - Changes at tick boundaries - Determines slippage for swaps ## Core Functions ### Tick ↔ Price Conversion ```go // Convert tick to sqrtPriceX96 sqrtPrice, err := GetSqrtRatioAtTick(tick) // Convert sqrtPriceX96 to tick tick, err := GetTickAtSqrtRatio(sqrtPriceX96) ``` **Example:** ```go // Get price at tick 0 (price = 1) tick := int32(0) sqrtPrice, _ := GetSqrtRatioAtTick(tick) // sqrtPrice ≈ 2^96 = 79228162514264337593543950336 // Convert back calculatedTick, _ := GetTickAtSqrtRatio(sqrtPrice) // calculatedTick = 0 ``` ### Amount Deltas (Liquidity Changes) ```go // Calculate token0 amount for a liquidity change amount0 := GetAmount0Delta( sqrtRatioA, // Lower sqrt price sqrtRatioB, // Upper sqrt price liquidity, // Liquidity amount roundUp, // Round up for safety ) // Calculate token1 amount for a liquidity change amount1 := GetAmount1Delta( sqrtRatioA, sqrtRatioB, liquidity, roundUp, ) ``` **Formulas:** - `amount0 = liquidity * (sqrtB - sqrtA) / (sqrtA * sqrtB)` - `amount1 = liquidity * (sqrtB - sqrtA) / 2^96` **Use Cases:** - Calculate how much of each token is needed to add liquidity - Calculate how much of each token received when removing liquidity - Validate swap amounts against expected values ### Swap Calculations ```go // Calculate output for exact input swap amountOut, priceAfter, err := CalculateSwapAmounts( sqrtPriceX96, // Current price liquidity, // Pool liquidity amountIn, // Input amount zeroForOne, // true = swap token0→token1, false = token1→token0 feePips, // Fee in pips (3000 = 0.3%) ) ``` **Example:** ```go // Swap 1 ETH for USDC in 0.3% fee pool currentPrice := pool.SqrtPriceX96 liquidity := pool.Liquidity amountIn := big.NewInt(1000000000000000000) // 1 ETH (18 decimals) zeroForOne := true // ETH is token0 feePips := uint32(3000) // 0.3% usdcOut, newPrice, err := CalculateSwapAmounts( currentPrice, liquidity, amountIn, zeroForOne, feePips, ) fmt.Printf("1 ETH → %v USDC\n", usdcOut) fmt.Printf("Price moved from %v to %v\n", currentPrice, newPrice) ``` ### Multi-Step Swaps (Tick Crossing) ```go // Compute a single swap step within one tick range sqrtPriceNext, amountIn, amountOut, feeAmount, err := ComputeSwapStep( sqrtRatioCurrentX96, // Current price sqrtRatioTargetX96, // Target price (next tick or price limit) liquidity, // Liquidity in this range amountRemaining, // Remaining amount to swap feePips, // Fee in pips ) ``` **Use Case:** Complex swaps that cross multiple ticks **Example:** ```go // Simulate a swap that might cross ticks currentPrice := pool.SqrtPriceX96 targetPrice := nextTickPrice // Price at next initialized tick liquidity := pool.Liquidity amountRemaining := big.NewInt(5000000000000000000) // 5 ETH feePips := uint32(3000) priceNext, amountIn, amountOut, fee, _ := ComputeSwapStep( currentPrice, targetPrice, liquidity, amountRemaining, feePips, ) // Check if we reached the target price if priceNext.Cmp(targetPrice) == 0 { fmt.Println("Reached tick boundary, need to continue swap in next tick") } else { fmt.Println("Swap completed within this tick range") } ``` ## Arbitrage Detection ### Simple Two-Pool Arbitrage ```go // Pool 1: WETH/USDC (V3, 0.3%) pool1SqrtPrice := pool1.SqrtPriceX96 pool1Liquidity := pool1.Liquidity pool1FeePips := uint32(3000) // Pool 2: WETH/USDC (V2) pool2Reserve0 := pool2.Reserve0 // WETH pool2Reserve1 := pool2.Reserve1 // USDC pool2Fee := uint32(30) // 0.3% // Calculate output from Pool 1 (V3) amountIn := big.NewInt(1000000000000000000) // 1 WETH usdc1, price1After, _ := CalculateSwapAmounts( pool1SqrtPrice, pool1Liquidity, amountIn, true, // WETH → USDC pool1FeePips, ) // Calculate output from Pool 2 (V2) using constant product formula // amountOut = (amountIn * 997 * reserve1) / (reserve0 * 1000 + amountIn * 997) numerator := new(big.Int).Mul(amountIn, big.NewInt(997)) numerator.Mul(numerator, pool2Reserve1) denominator := new(big.Int).Mul(pool2Reserve0, big.NewInt(1000)) amountInWithFee := new(big.Int).Mul(amountIn, big.NewInt(997)) denominator.Add(denominator, amountInWithFee) usdc2 := new(big.Int).Div(numerator, denominator) // Compare outputs if usdc1.Cmp(usdc2) > 0 { profit := new(big.Int).Sub(usdc1, usdc2) fmt.Printf("Arbitrage opportunity: %v USDC profit\n", profit) } ``` ### Multi-Hop V3 Arbitrage ```go // Route: WETH → USDC → DAI → WETH // Step 1: WETH → USDC (V3 0.3%) usdc, priceAfter1, _ := CalculateSwapAmounts( poolWETH_USDC.SqrtPriceX96, poolWETH_USDC.Liquidity, wethInput, true, 3000, ) // Step 2: USDC → DAI (V3 0.05%) dai, priceAfter2, _ := CalculateSwapAmounts( poolUSDC_DAI.SqrtPriceX96, poolUSDC_DAI.Liquidity, usdc, true, 500, ) // Step 3: DAI → WETH (V3 0.3%) wethOutput, priceAfter3, _ := CalculateSwapAmounts( poolDAI_WETH.SqrtPriceX96, poolDAI_WETH.Liquidity, dai, false, // DAI → WETH 3000, ) // Calculate profit profit := new(big.Int).Sub(wethOutput, wethInput) if profit.Sign() > 0 { fmt.Printf("Multi-hop arbitrage profit: %v WETH\n", profit) } ``` ### Sandwich Attack Detection ```go // Victim's pending transaction victimAmountIn := big.NewInt(10000000000000000000) // 10 ETH victimZeroForOne := true // Calculate victim's expected output victimOut, victimPriceAfter, _ := CalculateSwapAmounts( currentPrice, currentLiquidity, victimAmountIn, victimZeroForOne, 3000, ) // Front-run: Move price against victim frontrunAmountIn := big.NewInt(5000000000000000000) // 5 ETH _, priceAfterFrontrun, _ := CalculateSwapAmounts( currentPrice, currentLiquidity, frontrunAmountIn, victimZeroForOne, 3000, ) // Victim executes at worse price victimOutActual, priceAfterVictim, _ := CalculateSwapAmounts( priceAfterFrontrun, currentLiquidity, victimAmountIn, victimZeroForOne, 3000, ) // Back-run: Reverse front-run trade backrunAmountIn := victimOutActual // All the USDC we got backrunOut, finalPrice, _ := CalculateSwapAmounts( priceAfterVictim, currentLiquidity, backrunAmountIn, !victimZeroForOne, // Reverse direction 3000, ) // Calculate sandwich profit initialCapital := frontrunAmountIn finalCapital := backrunOut profit := new(big.Int).Sub(finalCapital, initialCapital) if profit.Sign() > 0 { fmt.Printf("Sandwich profit: %v ETH\n", profit) slippage := new(big.Int).Sub(victimOut, victimOutActual) fmt.Printf("Victim slippage: %v USDC\n", slippage) } ``` ## Price Impact Calculation ```go // Calculate price impact for a swap func CalculatePriceImpact( sqrtPrice *big.Int, liquidity *big.Int, amountIn *big.Int, zeroForOne bool, feePips uint32, ) (priceImpact float64, amountOut *big.Int, err error) { // Get current price currentTick, _ := GetTickAtSqrtRatio(sqrtPrice) currentPriceFloat, _ := GetSqrtRatioAtTick(currentTick) // Execute swap amountOut, newSqrtPrice, err := CalculateSwapAmounts( sqrtPrice, liquidity, amountIn, zeroForOne, feePips, ) if err != nil { return 0, nil, err } // Calculate new price newTick, _ := GetTickAtSqrtRatio(newSqrtPrice) // Price impact = (newPrice - currentPrice) / currentPrice priceImpact = float64(newTick-currentTick) / float64(currentTick) return priceImpact, amountOut, nil } ``` ## Gas Optimization ### Pre-compute Tick Boundaries ```go // For arbitrage, pre-compute next initialized ticks to avoid on-chain calls func GetNextInitializedTicks(currentTick int32, tickSpacing int32) (lower int32, upper int32) { // Round to nearest tick spacing lower = (currentTick / tickSpacing) * tickSpacing upper = lower + tickSpacing return lower, upper } ``` ### Batch Price Calculations ```go // Calculate outputs for multiple pools in parallel func CalculateMultiPoolOutputs( pools []*PoolInfo, amountIn *big.Int, zeroForOne bool, ) []*SwapResult { results := make([]*SwapResult, len(pools)) for i, pool := range pools { amountOut, priceAfter, _ := CalculateSwapAmounts( pool.SqrtPriceX96, pool.Liquidity, amountIn, zeroForOne, pool.FeePips, ) results[i] = &SwapResult{ Pool: pool, AmountOut: amountOut, PriceAfter: priceAfter, } } return results } ``` ## Common Pitfalls ### 1. Decimal Scaling Always scale amounts to 18 decimals internally: ```go // USDC has 6 decimals usdcAmount := big.NewInt(1000000) // 1 USDC usdcScaled := ScaleToDecimals(usdcAmount, 6, 18) ``` ### 2. Fee Calculation Fees are in pips (1/1000000): ```go feePips := uint32(3000) // 0.3% = 3000 / 1000000 ``` ### 3. Rounding Always round up for safety when calculating required inputs: ```go amount0 := GetAmount0Delta(sqrtA, sqrtB, liquidity, true) // Round up ``` ### 4. Price Direction Remember swap direction: ```go zeroForOne = true // token0 → token1 (price decreases) zeroForOne = false // token1 → token0 (price increases) ``` ## Testing Against Real Pools ```go // Validate calculations against Arbiscan func ValidateAgainstArbiscan( txHash common.Hash, expectedAmountOut *big.Int, ) bool { // 1. Fetch transaction from Arbiscan // 2. Parse swap event // 3. Compare calculated vs actual amounts // 4. Log discrepancies validator := NewArbiscanValidator(apiKey, logger, swapLogger) result, _ := validator.ValidateSwap(ctx, swapEvent) return result.IsValid } ``` ## References - [Uniswap V3 Whitepaper](https://uniswap.org/whitepaper-v3.pdf) - [Uniswap V3 Core](https://github.com/Uniswap/v3-core) - [Uniswap V3 SDK](https://github.com/Uniswap/v3-sdk) - [CLAMM Implementation](https://github.com/t4sk/clamm) - [Smart Contract Engineer V3 Challenges](https://www.smartcontract.engineer/challenges?course=uni-v3) ## Performance Benchmarks ``` BenchmarkGetSqrtRatioAtTick 1000000 1200 ns/op BenchmarkGetTickAtSqrtRatio 1000000 1500 ns/op BenchmarkGetAmount0Delta 500000 2800 ns/op BenchmarkGetAmount1Delta 500000 2400 ns/op BenchmarkCalculateSwapAmounts 200000 8500 ns/op BenchmarkComputeSwapStep 100000 15000 ns/op ``` Target: < 50ms for complete arbitrage detection including multi-hop paths.