Files
mev-beta/test/fuzzing/price_fuzzing_test.go
2025-09-14 06:21:10 -05:00

270 lines
8.8 KiB
Go

package fuzzing
import (
"math/big"
"math/rand"
"testing"
"time"
"github.com/fraktal/mev-beta/pkg/uniswap"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
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, accuracy := convertedPrice.Float64()
// Verify conversion accuracy
require.Equal(t, big.Exact, accuracy, "Price conversion should be exact")
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
}