//go:build math_fuzz // +build math_fuzz package fuzzing import ( "math/big" "math/rand" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/fraktal/mev-beta/pkg/uniswap" ) func init() { rand.Seed(time.Now().UnixNano()) } // FuzzPricingConversions performs fuzz testing on pricing conversion functions func FuzzPricingConversions(f *testing.F) { // Add seed values for fuzzing f.Add(float64(1.0)) f.Add(float64(0.001)) f.Add(float64(1000.0)) f.Add(float64(0.000001)) f.Add(float64(1000000.0)) f.Fuzz(func(t *testing.T, price float64) { // Skip invalid inputs if price <= 0 || price != price { // NaN check t.Skip("Invalid price input") } // Skip extreme values that would cause overflow if price > 1e15 || price < 1e-15 { t.Skip("Price too extreme") } // Convert price to sqrtPriceX96 and back priceBigFloat := new(big.Float).SetFloat64(price) sqrtPriceX96 := uniswap.PriceToSqrtPriceX96(priceBigFloat) // Verify sqrtPriceX96 is positive require.True(t, sqrtPriceX96.Sign() > 0, "sqrtPriceX96 must be positive") convertedPrice := uniswap.SqrtPriceX96ToPrice(sqrtPriceX96) convertedPriceFloat, _ := convertedPrice.Float64() // Verify conversion accuracy require.True(t, convertedPriceFloat > 0, "Converted price must be positive") // Check round-trip consistency (allow some tolerance for floating point precision) tolerance := 0.01 // 1% tolerance if price > 0.01 && price < 100000 { // For reasonable price ranges relativeError := abs(price-convertedPriceFloat) / price assert.True(t, relativeError < tolerance, "Round-trip conversion failed: original=%.6f, converted=%.6f, error=%.6f", price, convertedPriceFloat, relativeError) } }) } // FuzzTickConversions performs fuzz testing on tick conversion functions func FuzzTickConversions(f *testing.F) { // Add seed values for fuzzing f.Add(int(0)) f.Add(int(100000)) f.Add(int(-100000)) f.Add(int(500000)) f.Add(int(-500000)) f.Fuzz(func(t *testing.T, tick int) { // Skip ticks outside valid range if tick < -887272 || tick > 887272 { t.Skip("Tick outside valid range") } // Convert tick to sqrtPriceX96 and back sqrtPriceX96 := uniswap.TickToSqrtPriceX96(tick) // Verify sqrtPriceX96 is positive require.True(t, sqrtPriceX96.Sign() > 0, "sqrtPriceX96 must be positive") convertedTick := uniswap.SqrtPriceX96ToTick(sqrtPriceX96) // Check round-trip consistency (should be exact for ticks) tickDifference := abs64(int64(tick) - int64(convertedTick)) assert.True(t, tickDifference <= 1, "Tick round-trip failed: original=%d, converted=%d, difference=%d", tick, convertedTick, tickDifference) }) } // FuzzSqrtPriceX96Operations performs fuzz testing on sqrtPriceX96 operations func FuzzSqrtPriceX96Operations(f *testing.F) { // Add seed values for fuzzing f.Add("79228162514264337593543950336") // 2^96 (price = 1) f.Add("158456325028528675187087900672") // 2 * 2^96 (price = 4) f.Add("39614081257132168796771975168") // 2^95 (price = 0.25) f.Add("1122334455667788990011223344") // Random value f.Add("999888777666555444333222111") // Another random value f.Fuzz(func(t *testing.T, sqrtPriceX96Str string) { sqrtPriceX96 := new(big.Int) _, ok := sqrtPriceX96.SetString(sqrtPriceX96Str, 10) if !ok { t.Skip("Invalid sqrtPriceX96 string") } // Skip if sqrtPriceX96 is zero or negative if sqrtPriceX96.Sign() <= 0 { t.Skip("sqrtPriceX96 must be positive") } // Skip extremely large values to prevent overflow maxValue := new(big.Int) maxValue.SetString("1461446703485210103287273052203988822378723970341", 10) if sqrtPriceX96.Cmp(maxValue) > 0 { t.Skip("sqrtPriceX96 too large") } // Skip extremely small values minValue := new(big.Int) minValue.SetString("4295128739", 10) if sqrtPriceX96.Cmp(minValue) < 0 { t.Skip("sqrtPriceX96 too small") } // Convert sqrtPriceX96 to price price := uniswap.SqrtPriceX96ToPrice(sqrtPriceX96) require.NotNil(t, price, "Price should not be nil") priceFloat, accuracy := price.Float64() if accuracy != big.Exact { t.Skip("Price conversion not exact, value too large") } require.True(t, priceFloat > 0, "Price must be positive") // Convert sqrtPriceX96 to tick tick := uniswap.SqrtPriceX96ToTick(sqrtPriceX96) // Verify tick is in valid range assert.True(t, tick >= -887272 && tick <= 887272, "Tick %d outside valid range", tick) // Convert back to sqrtPriceX96 backToSqrtPrice := uniswap.TickToSqrtPriceX96(tick) // Check consistency (allow small difference due to rounding) diff := new(big.Int).Sub(sqrtPriceX96, backToSqrtPrice) diff.Abs(diff) // Allow difference of up to 0.01% of original value tolerance := new(big.Int).Div(sqrtPriceX96, big.NewInt(10000)) assert.True(t, diff.Cmp(tolerance) <= 0, "sqrtPriceX96 round-trip failed: original=%s, converted=%s, diff=%s", sqrtPriceX96.String(), backToSqrtPrice.String(), diff.String()) }) } // FuzzPriceImpactCalculations performs fuzz testing on price impact calculations func FuzzPriceImpactCalculations(f *testing.F) { // Add seed values for fuzzing f.Add(int64(1000000), int64(1000000000000000000)) // Small swap, large liquidity f.Add(int64(1000000000), int64(1000000000000000000)) // Large swap, large liquidity f.Add(int64(1000000), int64(1000000000)) // Small swap, small liquidity f.Add(int64(100000000), int64(1000000000)) // Large swap, small liquidity f.Fuzz(func(t *testing.T, swapAmount int64, liquidity int64) { // Skip invalid inputs if swapAmount <= 0 || liquidity <= 0 { t.Skip("Invalid swap amount or liquidity") } // Skip extreme values if swapAmount > 1e18 || liquidity > 1e18 { t.Skip("Values too extreme") } // Calculate price impact as percentage // Simple approximation: impact ≈ (swapAmount / liquidity) * 100 impactFloat := float64(swapAmount) / float64(liquidity) * 100 // Verify price impact is reasonable assert.True(t, impactFloat >= 0, "Price impact must be non-negative") assert.True(t, impactFloat <= 100, "Price impact should not exceed 100%") // For very large swaps relative to liquidity, impact should be significant if float64(swapAmount) > float64(liquidity)*0.1 { assert.True(t, impactFloat > 1, "Large swaps should have significant price impact") } // For very small swaps relative to liquidity, impact should be minimal if float64(swapAmount) < float64(liquidity)*0.001 { assert.True(t, impactFloat < 1, "Small swaps should have minimal price impact") } }) } // FuzzMathematicalProperties performs fuzz testing on mathematical properties func FuzzMathematicalProperties(f *testing.F) { // Add seed values for fuzzing f.Add(float64(1.0), float64(2.0)) f.Add(float64(0.5), float64(0.25)) f.Add(float64(100.0), float64(200.0)) f.Add(float64(0.001), float64(0.002)) f.Fuzz(func(t *testing.T, price1 float64, price2 float64) { // Skip invalid inputs if price1 <= 0 || price2 <= 0 || price1 != price1 || price2 != price2 { t.Skip("Invalid price inputs") } // Skip extreme values if price1 > 1e10 || price2 > 1e10 || price1 < 1e-10 || price2 < 1e-10 { t.Skip("Prices too extreme") } // Convert prices to sqrtPriceX96 price1BigFloat := new(big.Float).SetFloat64(price1) price2BigFloat := new(big.Float).SetFloat64(price2) sqrtPrice1 := uniswap.PriceToSqrtPriceX96(price1BigFloat) sqrtPrice2 := uniswap.PriceToSqrtPriceX96(price2BigFloat) // Test monotonicity: if price1 < price2, then sqrtPrice1 < sqrtPrice2 if price1 < price2 { assert.True(t, sqrtPrice1.Cmp(sqrtPrice2) < 0, "Monotonicity violated: price1=%.6f < price2=%.6f but sqrtPrice1=%s >= sqrtPrice2=%s", price1, price2, sqrtPrice1.String(), sqrtPrice2.String()) } else if price1 > price2 { assert.True(t, sqrtPrice1.Cmp(sqrtPrice2) > 0, "Monotonicity violated: price1=%.6f > price2=%.6f but sqrtPrice1=%s <= sqrtPrice2=%s", price1, price2, sqrtPrice1.String(), sqrtPrice2.String()) } // Test that sqrt(price1 * price2) ≈ geometric mean of sqrtPrices geometricMean := price1 * price2 if geometricMean > 0 { geometricMeanSqrt := new(big.Float).SetFloat64(geometricMean) geometricMeanSqrt.Sqrt(geometricMeanSqrt) geometricMeanSqrtX96 := uniswap.PriceToSqrtPriceX96(geometricMeanSqrt) // Calculate geometric mean of sqrtPrices // This is complex with big integers, so we'll skip this test for extreme values if sqrtPrice1.BitLen() < 200 && sqrtPrice2.BitLen() < 200 { // Test passes if we can perform the calculation without overflow assert.NotNil(t, geometricMeanSqrtX96) } } }) } // Helper function for absolute value of float64 func abs(x float64) float64 { if x < 0 { return -x } return x } // Helper function for absolute value of int64 func abs64(x int64) int64 { if x < 0 { return -x } return x }