Completed clean root directory structure: - Root now contains only: .git, .env, docs/, orig/ - Moved all remaining files and directories to orig/: - Config files (.claude, .dockerignore, .drone.yml, etc.) - All .env variants (except active .env) - Git config (.gitconfig, .github, .gitignore, etc.) - Tool configs (.golangci.yml, .revive.toml, etc.) - Documentation (*.md files, @prompts) - Build files (Dockerfiles, Makefile, go.mod, go.sum) - Docker compose files - All source directories (scripts, tests, tools, etc.) - Runtime directories (logs, monitoring, reports) - Dependency files (node_modules, lib, cache) - Special files (--delete) - Removed empty runtime directories (bin/, data/) V2 structure is now clean: - docs/planning/ - V2 planning documents - orig/ - Complete V1 codebase preserved - .env - Active environment config (not in git) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
274 lines
8.8 KiB
Go
274 lines
8.8 KiB
Go
//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
|
|
}
|