Files
mev-beta/test/property/pricing_property_test.go
Krypto Kajun 850223a953 fix(multicall): resolve critical multicall parsing corruption issues
- Added comprehensive bounds checking to prevent buffer overruns in multicall parsing
- Implemented graduated validation system (Strict/Moderate/Permissive) to reduce false positives
- Added LRU caching system for address validation with 10-minute TTL
- Enhanced ABI decoder with missing Universal Router and Arbitrum-specific DEX signatures
- Fixed duplicate function declarations and import conflicts across multiple files
- Added error recovery mechanisms with multiple fallback strategies
- Updated tests to handle new validation behavior for suspicious addresses
- Fixed parser test expectations for improved validation system
- Applied gofmt formatting fixes to ensure code style compliance
- Fixed mutex copying issues in monitoring package by introducing MetricsSnapshot
- Resolved critical security vulnerabilities in heuristic address extraction
- Progress: Updated TODO audit from 10% to 35% complete

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-17 00:12:55 -05:00

263 lines
7.9 KiB
Go

//go:build math_property
// +build math_property
package property
import (
"math"
"math/big"
"math/rand"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/fraktal/mev-beta/pkg/uniswap"
)
// Property-based testing for Uniswap V3 pricing functions
// These tests verify mathematical properties that should hold for all valid inputs
func init() {
rand.Seed(time.Now().UnixNano())
}
// generateRandomPrice generates a random price within realistic bounds
func generateRandomPrice() float64 {
// Generate prices between 0.000001 and 1000000 (6 orders of magnitude)
exponent := rand.Float64()*12 - 6 // -6 to 6
return math.Pow(10, exponent)
}
// generateRandomTick generates a random tick within valid bounds
func generateRandomTick() int {
// Uniswap V3 tick range is approximately -887272 to 887272
return rand.Intn(1774544) - 887272
}
// TestPriceConversionRoundTrip verifies that price->sqrt->price conversions are consistent
func TestPriceConversionRoundTrip(t *testing.T) {
const numTests = 1000
const tolerance = 0.001 // 0.1% tolerance for floating point precision
for i := 0; i < numTests; i++ {
originalPrice := generateRandomPrice()
if originalPrice <= 0 || originalPrice > 1e10 { // Skip extreme values
continue
}
// Convert price to sqrtPriceX96 and back
priceBigFloat := new(big.Float).SetFloat64(originalPrice)
sqrtPriceX96 := uniswap.PriceToSqrtPriceX96(priceBigFloat)
convertedPrice := uniswap.SqrtPriceX96ToPrice(sqrtPriceX96)
convertedPriceFloat, _ := convertedPrice.Float64()
// Verify round-trip consistency within tolerance
relativeError := abs(originalPrice-convertedPriceFloat) / originalPrice
assert.True(t, relativeError < tolerance,
"Round-trip conversion failed: original=%.6f, converted=%.6f, error=%.6f",
originalPrice, convertedPriceFloat, relativeError)
}
}
// TestTickConversionRoundTrip verifies that tick->sqrt->tick conversions are consistent
func TestTickConversionRoundTrip(t *testing.T) {
const numTests = 1000
for i := 0; i < numTests; i++ {
originalTick := generateRandomTick()
// Convert tick to sqrtPriceX96 and back
sqrtPriceX96 := uniswap.TickToSqrtPriceX96(originalTick)
convertedTick := uniswap.SqrtPriceX96ToTick(sqrtPriceX96)
// For tick conversions, we expect exact equality or at most 1 tick difference
// due to rounding in the logarithmic calculations
tickDifference := abs64(int64(originalTick) - int64(convertedTick))
assert.True(t, tickDifference <= 1,
"Tick round-trip failed: original=%d, converted=%d, difference=%d",
originalTick, convertedTick, tickDifference)
}
}
// TestPriceMonotonicity verifies that price increases monotonically with tick
func TestPriceMonotonicity(t *testing.T) {
const numTests = 100
const tickStep = 1000
for i := 0; i < numTests; i++ {
baseTick := generateRandomTick()
if baseTick > 800000 { // Ensure we don't overflow
baseTick = 800000
}
tick1 := baseTick
tick2 := baseTick + tickStep
sqrtPrice1 := uniswap.TickToSqrtPriceX96(tick1)
sqrtPrice2 := uniswap.TickToSqrtPriceX96(tick2)
price1 := uniswap.SqrtPriceX96ToPrice(sqrtPrice1)
price2 := uniswap.SqrtPriceX96ToPrice(sqrtPrice2)
price1Float, _ := price1.Float64()
price2Float, _ := price2.Float64()
// Higher tick should result in higher price
assert.True(t, price2Float > price1Float,
"Price monotonicity violated: tick1=%d, price1=%.6f, tick2=%d, price2=%.6f",
tick1, price1Float, tick2, price2Float)
}
}
// TestSqrtPriceX96Bounds verifies that sqrtPriceX96 values are within expected bounds
func TestSqrtPriceX96Bounds(t *testing.T) {
const numTests = 1000
// Define reasonable bounds for sqrtPriceX96
minBound := new(big.Int)
minBound.SetString("4295128739", 10) // Very small price
maxBound := new(big.Int)
maxBound.SetString("1461446703485210103287273052203988822378723970341", 10) // Very large price
for i := 0; i < numTests; i++ {
tick := generateRandomTick()
sqrtPriceX96 := uniswap.TickToSqrtPriceX96(tick)
// Verify bounds
assert.True(t, sqrtPriceX96.Cmp(minBound) >= 0,
"sqrtPriceX96 below minimum bound: tick=%d, sqrtPriceX96=%s",
tick, sqrtPriceX96.String())
assert.True(t, sqrtPriceX96.Cmp(maxBound) <= 0,
"sqrtPriceX96 above maximum bound: tick=%d, sqrtPriceX96=%s",
tick, sqrtPriceX96.String())
}
}
// TestPriceSymmetry verifies that inverse prices work correctly
func TestPriceSymmetry(t *testing.T) {
const numTests = 100
const tolerance = 0.001
for i := 0; i < numTests; i++ {
originalPrice := generateRandomPrice()
if originalPrice <= 0 || originalPrice > 1e6 {
continue
}
// Calculate inverse price
inversePrice := 1.0 / originalPrice
// Convert both to sqrtPriceX96
priceBigFloat := new(big.Float).SetFloat64(originalPrice)
inverseBigFloat := new(big.Float).SetFloat64(inversePrice)
sqrtPrice := uniswap.PriceToSqrtPriceX96(priceBigFloat)
sqrtInverse := uniswap.PriceToSqrtPriceX96(inverseBigFloat)
// Convert back to prices
convertedPrice := uniswap.SqrtPriceX96ToPrice(sqrtPrice)
convertedInverse := uniswap.SqrtPriceX96ToPrice(sqrtInverse)
convertedPriceFloat, _ := convertedPrice.Float64()
convertedInverseFloat, _ := convertedInverse.Float64()
// Verify that price * inverse ≈ 1
product := convertedPriceFloat * convertedInverseFloat
assert.InDelta(t, 1.0, product, tolerance,
"Price symmetry failed: price=%.6f, inverse=%.6f, product=%.6f",
convertedPriceFloat, convertedInverseFloat, product)
}
}
// TestEdgeCases tests edge cases and boundary conditions
func TestEdgeCases(t *testing.T) {
testCases := []struct {
name string
tick int
shouldPass bool
description string
}{
{"MinTick", -887272, true, "Minimum valid tick"},
{"MaxTick", 887272, true, "Maximum valid tick"},
{"ZeroTick", 0, true, "Zero tick (price = 1)"},
{"NegativeTick", -100000, true, "Negative tick"},
{"PositiveTick", 100000, true, "Positive tick"},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if tc.shouldPass {
// Test tick to sqrtPrice conversion
sqrtPriceX96 := uniswap.TickToSqrtPriceX96(tc.tick)
require.NotNil(t, sqrtPriceX96, "sqrtPriceX96 should not be nil for %s", tc.description)
// Test sqrtPrice to price conversion
price := uniswap.SqrtPriceX96ToPrice(sqrtPriceX96)
require.NotNil(t, price, "price should not be nil for %s", tc.description)
// Test round-trip: tick -> sqrt -> tick
convertedTick := uniswap.SqrtPriceX96ToTick(sqrtPriceX96)
tickDiff := abs64(int64(tc.tick) - int64(convertedTick))
assert.True(t, tickDiff <= 1, "Round-trip tick conversion failed for %s: original=%d, converted=%d",
tc.description, tc.tick, convertedTick)
}
})
}
}
// TestPricePrecision verifies precision of price calculations
func TestPricePrecision(t *testing.T) {
knownCases := []struct {
name string
sqrtPriceX96 string
expectedPrice float64
tolerance float64
}{
{
name: "Price_1_ETH_USDC",
sqrtPriceX96: "79228162514264337593543950336", // 2^96, price = 1
expectedPrice: 1.0,
tolerance: 0.0001,
},
{
name: "Price_4_ETH_USDC",
sqrtPriceX96: "158456325028528675187087900672", // 2 * 2^96, price = 4
expectedPrice: 4.0,
tolerance: 0.01,
},
}
for _, tc := range knownCases {
t.Run(tc.name, func(t *testing.T) {
sqrtPriceX96 := new(big.Int)
_, ok := sqrtPriceX96.SetString(tc.sqrtPriceX96, 10)
require.True(t, ok, "Failed to parse sqrtPriceX96")
price := uniswap.SqrtPriceX96ToPrice(sqrtPriceX96)
priceFloat, accuracy := price.Float64()
require.Equal(t, big.Exact, accuracy, "Price conversion should be exact")
assert.InDelta(t, tc.expectedPrice, priceFloat, tc.tolerance,
"Price precision test failed for %s", tc.name)
})
}
}
// Helper functions
func abs(x float64) float64 {
if x < 0 {
return -x
}
return x
}
func abs64(x int64) int64 {
if x < 0 {
return -x
}
return x
}