Sequencer is working (minimal parsing)

This commit is contained in:
Krypto Kajun
2025-09-14 06:21:10 -05:00
parent 7dd5b5b692
commit 518758790a
59 changed files with 10539 additions and 471 deletions

View File

@@ -0,0 +1,157 @@
package benchmarks
import (
"math/big"
"testing"
"github.com/fraktal/mev-beta/pkg/uniswap"
"github.com/stretchr/testify/require"
)
// BenchmarkSqrtPriceX96ToPrice benchmarks the SqrtPriceX96ToPrice function
func BenchmarkSqrtPriceX96ToPrice(b *testing.B) {
sqrtPriceX96 := new(big.Int)
sqrtPriceX96.SetString("79228162514264337593543950336", 10) // 2^96
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = uniswap.SqrtPriceX96ToPrice(sqrtPriceX96)
}
}
// BenchmarkPriceToSqrtPriceX96 benchmarks the PriceToSqrtPriceX96 function
func BenchmarkPriceToSqrtPriceX96(b *testing.B) {
price := new(big.Float).SetFloat64(1.0)
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = uniswap.PriceToSqrtPriceX96(price)
}
}
// BenchmarkTickToSqrtPriceX96 benchmarks the TickToSqrtPriceX96 function
func BenchmarkTickToSqrtPriceX96(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = uniswap.TickToSqrtPriceX96(0)
}
}
// BenchmarkSqrtPriceX96ToTick benchmarks the SqrtPriceX96ToTick function
func BenchmarkSqrtPriceX96ToTick(b *testing.B) {
sqrtPriceX96 := new(big.Int)
sqrtPriceX96.SetString("79228162514264337593543950336", 10) // 2^96
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = uniswap.SqrtPriceX96ToTick(sqrtPriceX96)
}
}
// BenchmarkPricingConversionsSequential benchmarks sequential pricing conversions
func BenchmarkPricingConversionsSequential(b *testing.B) {
price := new(big.Float).SetFloat64(1.0)
b.ResetTimer()
for i := 0; i < b.N; i++ {
sqrtPriceX96 := uniswap.PriceToSqrtPriceX96(price)
tick := uniswap.SqrtPriceX96ToTick(sqrtPriceX96)
backToSqrt := uniswap.TickToSqrtPriceX96(tick)
_ = uniswap.SqrtPriceX96ToPrice(backToSqrt)
}
}
// BenchmarkPricingCalculationRealistic benchmarks realistic pricing calculations
func BenchmarkPricingCalculationRealistic(b *testing.B) {
testCases := []struct {
name string
sqrtPriceX96 string
}{
{"ETH_USDC_1800", "2231455953840924584200896000"}, // ~1800 USDC per ETH
{"ETH_USDC_3000", "2890903041336652768307200000"}, // ~3000 USDC per ETH
{"WBTC_ETH_15", "977228162514264337593543950"}, // ~15 ETH per WBTC
{"DAI_USDC_1", "79228162514264337593543950336"}, // ~1 DAI per USDC
}
for _, tc := range testCases {
b.Run(tc.name, func(b *testing.B) {
sqrtPriceX96 := new(big.Int)
sqrtPriceX96.SetString(tc.sqrtPriceX96, 10)
b.ResetTimer()
for i := 0; i < b.N; i++ {
price := uniswap.SqrtPriceX96ToPrice(sqrtPriceX96)
tick := uniswap.SqrtPriceX96ToTick(sqrtPriceX96)
backToSqrt := uniswap.TickToSqrtPriceX96(tick)
_ = uniswap.SqrtPriceX96ToPrice(backToSqrt)
// Verify we get similar price back (within reasonable precision)
require.NotNil(b, price)
}
})
}
}
// BenchmarkExtremePriceValues benchmarks extreme price value conversions
func BenchmarkExtremePriceValues(b *testing.B) {
extremeCases := []struct {
name string
price float64
}{
{"VeryLow_0.000001", 0.000001},
{"Low_0.01", 0.01},
{"Normal_1.0", 1.0},
{"High_100.0", 100.0},
{"VeryHigh_1000000.0", 1000000.0},
}
for _, tc := range extremeCases {
b.Run(tc.name, func(b *testing.B) {
price := new(big.Float).SetFloat64(tc.price)
b.ResetTimer()
for i := 0; i < b.N; i++ {
sqrtPriceX96 := uniswap.PriceToSqrtPriceX96(price)
tick := uniswap.SqrtPriceX96ToTick(sqrtPriceX96)
backToSqrt := uniswap.TickToSqrtPriceX96(tick)
_ = uniswap.SqrtPriceX96ToPrice(backToSqrt)
}
})
}
}
// BenchmarkBigIntOperations benchmarks the underlying big.Int operations
func BenchmarkBigIntOperations(b *testing.B) {
b.Run("BigInt_Multiplication", func(b *testing.B) {
x := big.NewInt(1000000)
y := big.NewInt(2000000)
result := new(big.Int)
b.ResetTimer()
for i := 0; i < b.N; i++ {
result.Mul(x, y)
}
})
b.Run("BigInt_Division", func(b *testing.B) {
x := big.NewInt(1000000000000)
y := big.NewInt(1000000)
result := new(big.Int)
b.ResetTimer()
for i := 0; i < b.N; i++ {
result.Div(x, y)
}
})
b.Run("BigFloat_Operations", func(b *testing.B) {
x := big.NewFloat(1000000.5)
y := big.NewFloat(2000000.3)
result := new(big.Float)
b.ResetTimer()
for i := 0; i < b.N; i++ {
result.Mul(x, y)
}
})
}

View File

@@ -0,0 +1,270 @@
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
}

View File

@@ -0,0 +1,195 @@
package integration
import (
"math/big"
"testing"
"time"
"github.com/fraktal/mev-beta/internal/logger"
"github.com/fraktal/mev-beta/pkg/arbitrum"
"github.com/fraktal/mev-beta/test/mocks"
"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// TestL2MessageParsingAccuracy tests the accuracy of L2 message parsing
func TestL2MessageParsingAccuracy(t *testing.T) {
log := logger.New("info", "text", "")
parser := arbitrum.NewL2MessageParser(log)
testCases := []struct {
name string
protocol string
expectedTokens []common.Address
expectedFee uint32
}{
{
name: "UniswapV3_USDC_WETH",
protocol: "UniswapV3",
expectedTokens: []common.Address{
common.HexToAddress("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"), // USDC
common.HexToAddress("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"), // WETH
},
expectedFee: 3000,
},
{
name: "SushiSwap_USDC_WETH",
protocol: "SushiSwap",
expectedTokens: []common.Address{
common.HexToAddress("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"), // USDC
common.HexToAddress("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"), // WETH
},
expectedFee: 3000,
},
{
name: "Camelot_ARB_WETH",
protocol: "Camelot",
expectedTokens: []common.Address{
common.HexToAddress("0x912CE59144191C1204E64559FE8253a0e49E6548"), // ARB
common.HexToAddress("0x82aF49447D8a07e3bd95BD0d56f35241523fBab1"), // WETH on Arbitrum
},
expectedFee: 3000,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
// Create mock transaction with DEX interaction
poolAddress := common.HexToAddress("0xE592427A0AEce92De3Edee1F18E0157C05861564") // Uniswap V3 Router
// Create mock transaction data for swap
swapData := createMockSwapData(tc.expectedTokens[0], tc.expectedTokens[1], tc.expectedFee)
tx := mocks.CreateMockTransaction(poolAddress, swapData)
// Parse DEX interaction
interaction, err := parser.ParseDEXInteraction(tx)
if tc.protocol == "UniswapV3" {
// UniswapV3 should be successfully parsed
require.NoError(t, err)
require.NotNil(t, interaction)
assert.Equal(t, tc.protocol, interaction.Protocol)
// Note: Fee field not available in current DEXInteraction struct
assert.Equal(t, tc.protocol, interaction.Protocol)
} else {
// Other protocols might not be implemented yet, so we allow nil results
if interaction != nil {
assert.Equal(t, tc.protocol, interaction.Protocol)
}
}
})
}
}
// TestL2MessageLatency tests the latency of L2 message processing
func TestL2MessageLatency(t *testing.T) {
log := logger.New("info", "text", "")
parser := arbitrum.NewL2MessageParser(log)
const numMessages = 100
const maxLatencyMs = 10 // Maximum acceptable latency in milliseconds
for i := 0; i < numMessages; i++ {
// Create L2 message
l2Message := mocks.CreateMockL2Message()
// Measure parsing time
startTime := time.Now()
if l2Message.ParsedTx != nil {
_, err := parser.ParseDEXInteraction(l2Message.ParsedTx)
// Error is expected for mock data, just measure timing
_ = err
}
latency := time.Since(startTime)
latencyMs := latency.Nanoseconds() / 1000000
// Verify latency is acceptable
assert.LessOrEqual(t, latencyMs, int64(maxLatencyMs),
"L2 message processing latency too high: %dms", latencyMs)
}
}
// TestMultiProtocolDetection tests detection of multiple DEX protocols
func TestMultiProtocolDetection(t *testing.T) {
log := logger.New("info", "text", "")
parser := arbitrum.NewL2MessageParser(log)
protocols := []string{"UniswapV3", "SushiSwap", "Camelot", "Balancer", "Curve"}
for _, protocol := range protocols {
t.Run(protocol, func(t *testing.T) {
// Create mock transaction for each protocol
poolAddress := getProtocolPoolAddress(protocol)
swapData := createMockSwapDataForProtocol(protocol)
tx := mocks.CreateMockTransaction(poolAddress, swapData)
// Parse DEX interaction
interaction, err := parser.ParseDEXInteraction(tx)
// For UniswapV3, we expect successful parsing
// For others, we may not have full implementation yet
if protocol == "UniswapV3" {
require.NoError(t, err)
require.NotNil(t, interaction)
assert.Equal(t, protocol, interaction.Protocol)
} else {
// Log the results for other protocols
if err != nil {
t.Logf("Protocol %s not fully implemented yet: %v", protocol, err)
} else if interaction != nil {
t.Logf("Protocol %s detected: %+v", protocol, interaction)
} else {
t.Logf("Protocol %s: no interaction detected (expected for mock data)", protocol)
}
}
})
}
}
// Helper functions for test data creation
func createMockSwapData(token0, token1 common.Address, fee uint32) []byte {
// exactInputSingle selector: 0x414bf389
selector := []byte{0x41, 0x4b, 0xf3, 0x89}
// Create a mock payload for exactInputSingle
payload := make([]byte, 256)
// tokenIn (address)
copy(payload[12:32], token0.Bytes())
// tokenOut (address)
copy(payload[44:64], token1.Bytes())
// amountIn (uint256)
amountIn := new(big.Int).SetInt64(1000000000000000000) // 1 ETH
amountInBytes := amountIn.Bytes()
copy(payload[192-len(amountInBytes):192], amountInBytes)
return append(selector, payload...)
}
func createMockSwapDataForProtocol(protocol string) []byte {
// For testing, we'll just use the same mock data for all protocols.
// In a real scenario, this would generate protocol-specific data.
token0 := common.HexToAddress("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48") // USDC
token1 := common.HexToAddress("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2") // WETH
return createMockSwapData(token0, token1, 3000)
}
func getProtocolPoolAddress(protocol string) common.Address {
// Return known pool addresses for different protocols on Arbitrum
protocolPools := map[string]string{
"UniswapV3": "0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640",
"SushiSwap": "0x905dfCD5649217c42684f23958568e533C711Aa3",
"Camelot": "0x84652bb2539513BAf36e225c930Fdd8eaa63CE27",
"Balancer": "0x32dF62dc3aEd2cD6224193052Ce665DC18165841",
"Curve": "0x7f90122BF0700F9E7e1F688fe926940E8839F353",
}
if addr, exists := protocolPools[protocol]; exists {
return common.HexToAddress(addr)
}
return common.HexToAddress("0x0000000000000000000000000000000000000000")
}

View File

@@ -0,0 +1,32 @@
package mocks
import (
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/assert"
)
// TestMockDEXInteraction tests mock DEX interaction creation
func TestMockDEXInteraction(t *testing.T) {
dexInteraction := CreateMockDEXInteraction()
assert.Equal(t, "UniswapV3", dexInteraction.Protocol)
assert.Equal(t, common.HexToAddress("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640"), dexInteraction.Pool)
assert.Equal(t, common.HexToAddress("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"), dexInteraction.TokenIn)
assert.Equal(t, common.HexToAddress("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"), dexInteraction.TokenOut)
assert.NotNil(t, dexInteraction.AmountIn)
assert.NotNil(t, dexInteraction.AmountOut)
}
// TestMockTransaction tests mock transaction creation
func TestMockTransaction(t *testing.T) {
to := common.HexToAddress("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640")
data := []byte("test_data")
tx := CreateMockTransaction(to, data)
assert.Equal(t, to, *tx.To())
assert.Equal(t, data, tx.Data())
assert.Equal(t, uint64(1), tx.Nonce())
}

56
test/mocks/mock_types.go Normal file
View File

@@ -0,0 +1,56 @@
package mocks
import (
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/fraktal/mev-beta/pkg/arbitrum"
)
// CreateMockL2Message creates a realistic L2 message for testing
func CreateMockL2Message() *arbitrum.L2Message {
// Create a mock transaction
to := common.HexToAddress("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640") // Uniswap V3 pool
tx := types.NewTransaction(
1, // nonce
to, // to
big.NewInt(0), // value
21000, // gas limit
big.NewInt(20000000000), // gas price (20 gwei)
[]byte{}, // data
)
return &arbitrum.L2Message{
Type: arbitrum.L2Transaction,
MessageNumber: big.NewInt(12345),
ParsedTx: tx,
InnerTxs: []*types.Transaction{tx},
}
}
// CreateMockDEXInteraction creates a realistic DEX interaction for testing
func CreateMockDEXInteraction() *arbitrum.DEXInteraction {
return &arbitrum.DEXInteraction{
Protocol: "UniswapV3",
Pool: common.HexToAddress("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640"),
TokenIn: common.HexToAddress("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"), // USDC
TokenOut: common.HexToAddress("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"), // WETH
AmountIn: big.NewInt(1000000000), // 1000 USDC
AmountOut: big.NewInt(500000000000000000), // 0.5 ETH
MessageNumber: big.NewInt(12345),
Timestamp: uint64(1234567890),
}
}
// CreateMockTransaction creates a realistic transaction for testing
func CreateMockTransaction(to common.Address, data []byte) *types.Transaction {
return types.NewTransaction(
1, // nonce
to, // to
big.NewInt(0), // value
21000, // gas limit
big.NewInt(20000000000), // gas price (20 gwei)
data, // data
)
}

View File

@@ -0,0 +1,257 @@
package property
import (
"math/big"
"math/rand"
"testing"
"time"
"github.com/fraktal/mev-beta/pkg/uniswap"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// 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 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
}