package main import ( "fmt" "math/big" "testing" pkgmath "github.com/fraktal/mev-beta/pkg/math" ) // ProfitThresholdTestCase defines a test case for profit threshold validation type ProfitThresholdTestCase struct { Name string Exchange string Reserve0 string Reserve1 string AmountIn string ExpectedProfitBP float64 // Expected profit in basis points ExpectedSpreadBP float64 // Expected spread in basis points MinProfitThresholdBP float64 // Minimum profit threshold in basis points Tolerance float64 // Error tolerance in basis points } // TestProfitThresholdRegression ensures profit calculations remain stable func TestProfitThresholdRegression(t *testing.T) { // Initialize decimal converter converter := pkgmath.NewDecimalConverter() testCases := []ProfitThresholdTestCase{ { Name: "ETH_USDC_Small_Profit", Exchange: "uniswap_v2", Reserve0: "1000000000000000000000", // 1000 ETH Reserve1: "2000000000000", // 2M USDC (6 decimals) AmountIn: "1000000000000000000", // 1 ETH ExpectedProfitBP: 25.0, // 0.25% profit expected ExpectedSpreadBP: 50.0, // 0.5% spread expected MinProfitThresholdBP: 10.0, // 0.1% minimum profit Tolerance: 2.0, // 2bp tolerance }, { Name: "WBTC_ETH_Medium_Profit", Exchange: "uniswap_v2", Reserve0: "50000000000", // 500 WBTC (8 decimals) Reserve1: "10000000000000000000000", // 10000 ETH AmountIn: "100000000", // 1 WBTC ExpectedProfitBP: 35.0, // 0.35% profit expected ExpectedSpreadBP: 70.0, // 0.7% spread expected MinProfitThresholdBP: 15.0, // 0.15% minimum profit Tolerance: 3.0, // 3bp tolerance }, { Name: "ETH_USDC_V3_Concentrated", Exchange: "uniswap_v3", Reserve0: "1000000000000000000000", // 1000 ETH Reserve1: "2000000000000", // 2M USDC AmountIn: "500000000000000000", // 0.5 ETH ExpectedProfitBP: 15.0, // 0.15% profit expected ExpectedSpreadBP: 25.0, // 0.25% spread expected MinProfitThresholdBP: 5.0, // 0.05% minimum profit Tolerance: 1.5, // 1.5bp tolerance }, { Name: "USDC_USDT_Stable_Spread", Exchange: "curve", Reserve0: "1000000000000", // 1M USDC (6 decimals) Reserve1: "1000000000000", // 1M USDT (6 decimals) AmountIn: "10000000000", // 10k USDC ExpectedProfitBP: 2.0, // 0.02% profit expected ExpectedSpreadBP: 5.0, // 0.05% spread expected MinProfitThresholdBP: 1.0, // 0.01% minimum profit Tolerance: 0.5, // 0.5bp tolerance }, { Name: "ETH_USDC_80_20_Weighted", Exchange: "balancer", Reserve0: "800000000000000000000", // 800 ETH Reserve1: "400000000000", // 400k USDC AmountIn: "2000000000000000000", // 2 ETH ExpectedProfitBP: 40.0, // 0.4% profit expected ExpectedSpreadBP: 80.0, // 0.8% spread expected MinProfitThresholdBP: 20.0, // 0.2% minimum profit Tolerance: 5.0, // 5bp tolerance }, } for _, tc := range testCases { t.Run(tc.Name, func(t *testing.T) { // Calculate actual profit and spread actualProfitBP, actualSpreadBP, err := calculateProfitAndSpread(converter, tc) if err != nil { t.Fatalf("Failed to calculate profit and spread: %v", err) } // Validate profit meets minimum threshold if actualProfitBP < tc.MinProfitThresholdBP { t.Errorf("Profit %.4f bp below minimum threshold %.4f bp", actualProfitBP, tc.MinProfitThresholdBP) } // Validate profit within expected range profitError := abs(actualProfitBP - tc.ExpectedProfitBP) if profitError > tc.Tolerance { t.Errorf("Profit error %.4f bp exceeds tolerance %.4f bp (expected %.4f bp, got %.4f bp)", profitError, tc.Tolerance, tc.ExpectedProfitBP, actualProfitBP) } // Validate spread within expected range spreadError := abs(actualSpreadBP - tc.ExpectedSpreadBP) if spreadError > tc.Tolerance*2 { // Allow 2x tolerance for spread t.Errorf("Spread error %.4f bp exceeds tolerance %.4f bp (expected %.4f bp, got %.4f bp)", spreadError, tc.Tolerance*2, tc.ExpectedSpreadBP, actualSpreadBP) } t.Logf("✓ %s: Profit=%.4f bp, Spread=%.4f bp (within tolerance)", tc.Name, actualProfitBP, actualSpreadBP) }) } } // calculateProfitAndSpread calculates profit and spread for a test case func calculateProfitAndSpread(converter *pkgmath.DecimalConverter, tc ProfitThresholdTestCase) (profitBP, spreadBP float64, err error) { // Determine decimals based on exchange and tokens reserve0Decimals, reserve1Decimals := getTokenDecimals(tc.Exchange, tc.Name) // Convert amounts to UniversalDecimal reserve0, err := converter.FromString(tc.Reserve0, reserve0Decimals, "TOKEN0") if err != nil { return 0, 0, fmt.Errorf("invalid reserve0: %w", err) } reserve1, err := converter.FromString(tc.Reserve1, reserve1Decimals, "TOKEN1") if err != nil { return 0, 0, fmt.Errorf("invalid reserve1: %w", err) } amountIn, err := converter.FromString(tc.AmountIn, reserve0Decimals, "TOKEN0") if err != nil { return 0, 0, fmt.Errorf("invalid amountIn: %w", err) } // Calculate spot price var spotPrice *pkgmath.UniversalDecimal switch tc.Exchange { case "uniswap_v2": spotPrice, err = calculateUniswapV2Price(converter, reserve0, reserve1) case "uniswap_v3": spotPrice, err = calculateUniswapV3EstimatedPrice(converter, reserve0, reserve1) case "curve": spotPrice, err = calculateCurvePrice(converter, reserve0, reserve1) case "balancer": spotPrice, err = calculateBalancerPrice(converter, reserve0, reserve1) default: return 0, 0, fmt.Errorf("unsupported exchange: %s", tc.Exchange) } if err != nil { return 0, 0, fmt.Errorf("failed to calculate spot price: %w", err) } // Calculate amount out using AMM formula amountOut, err := calculateAMMAmountOut(converter, tc.Exchange, reserve0, reserve1, amountIn) if err != nil { return 0, 0, fmt.Errorf("failed to calculate amount out: %w", err) } // Calculate execution price executionPrice := calculateExecutionPrice(converter, amountIn, amountOut, reserve0Decimals, reserve1Decimals) // Calculate profit (difference from spot price) profitBP = calculateRelativeErrorBP(converter, spotPrice, executionPrice) // Calculate spread (bid-ask spread approximation) spreadBP = profitBP * 2.0 // Simple approximation: spread = 2 * price impact return profitBP, spreadBP, nil } // Helper functions for price calculations func calculateUniswapV2Price(converter *pkgmath.DecimalConverter, reserve0, reserve1 *pkgmath.UniversalDecimal) (*pkgmath.UniversalDecimal, error) { // Price = reserve1 / reserve0 (normalized to 18 decimals) priceInt := new(big.Int).Mul(reserve1.Value, new(big.Int).Exp(big.NewInt(10), big.NewInt(18), nil)) priceInt.Div(priceInt, reserve0.Value) return pkgmath.NewUniversalDecimal(priceInt, 18, "PRICE") } func calculateUniswapV3EstimatedPrice(converter *pkgmath.DecimalConverter, reserve0, reserve1 *pkgmath.UniversalDecimal) (*pkgmath.UniversalDecimal, error) { // For simplicity, use the same formula as V2 for testing return calculateUniswapV2Price(converter, reserve0, reserve1) } func calculateCurvePrice(converter *pkgmath.DecimalConverter, reserve0, reserve1 *pkgmath.UniversalDecimal) (*pkgmath.UniversalDecimal, error) { // For stable swaps, price is typically 1:1 with small variations return calculateUniswapV2Price(converter, reserve0, reserve1) } func calculateBalancerPrice(converter *pkgmath.DecimalConverter, reserve0, reserve1 *pkgmath.UniversalDecimal) (*pkgmath.UniversalDecimal, error) { // Weighted pool: price = (reserve1/weight1) / (reserve0/weight0) // Assuming 80/20 weights: weight0=0.8, weight1=0.2 // price = (reserve1/0.2) / (reserve0/0.8) = (reserve1 * 0.8) / (reserve0 * 0.2) = (reserve1 * 4) / reserve0 priceInt := new(big.Int).Mul(reserve1.Value, big.NewInt(4)) priceInt.Mul(priceInt, new(big.Int).Exp(big.NewInt(10), big.NewInt(18), nil)) priceInt.Div(priceInt, reserve0.Value) return pkgmath.NewUniversalDecimal(priceInt, 18, "PRICE") } func calculateAMMAmountOut(converter *pkgmath.DecimalConverter, exchange string, reserve0, reserve1, amountIn *pkgmath.UniversalDecimal) (*pkgmath.UniversalDecimal, error) { // Simplified AMM calculation: k = x * y, amountOut = y - k / (x + amountIn) // k = reserve0 * reserve1 k := new(big.Int).Mul(reserve0.Value, reserve1.Value) // newReserve0 = reserve0 + amountIn newReserve0 := new(big.Int).Add(reserve0.Value, amountIn.Value) // newReserve1 = k / newReserve0 newReserve1 := new(big.Int).Div(k, newReserve0) // amountOut = reserve1 - newReserve1 amountOut := new(big.Int).Sub(reserve1.Value, newReserve1) // Apply 0.3% fee for most exchanges fee := new(big.Int).Div(amountOut, big.NewInt(333)) // ~0.3% amountOut.Sub(amountOut, fee) return pkgmath.NewUniversalDecimal(amountOut, reserve1.Decimals, "TOKEN1") } func calculateExecutionPrice(converter *pkgmath.DecimalConverter, amountIn, amountOut *pkgmath.UniversalDecimal, decimalsIn, decimalsOut uint8) *pkgmath.UniversalDecimal { // Execution price = amountOut / amountIn (normalized to 18 decimals) priceInt := new(big.Int).Mul(amountOut.Value, new(big.Int).Exp(big.NewInt(10), big.NewInt(18), nil)) priceInt.Div(priceInt, amountIn.Value) // Adjust for decimal differences if decimalsOut != decimalsIn { decimalDiff := int64(decimalsIn) - int64(decimalsOut) if decimalDiff > 0 { adjustment := new(big.Int).Exp(big.NewInt(10), big.NewInt(decimalDiff), nil) priceInt.Mul(priceInt, adjustment) } else { adjustment := new(big.Int).Exp(big.NewInt(10), big.NewInt(-decimalDiff), nil) priceInt.Div(priceInt, adjustment) } } result, _ := pkgmath.NewUniversalDecimal(priceInt, 18, "EXECUTION_PRICE") return result } func calculateRelativeErrorBP(converter *pkgmath.DecimalConverter, expected, actual *pkgmath.UniversalDecimal) float64 { if expected.Value.Cmp(big.NewInt(0)) == 0 { return 10000.0 // Max error if expected is zero } // Calculate relative error: |actual - expected| / expected * 10000 diff := new(big.Int).Sub(actual.Value, expected.Value) if diff.Sign() < 0 { diff.Neg(diff) } // Convert to float64 for percentage calculation diffFloat, _ := new(big.Float).SetInt(diff).Float64() expectedFloat, _ := new(big.Float).SetInt(expected.Value).Float64() if expectedFloat == 0 { return 10000.0 } return (diffFloat / expectedFloat) * 10000.0 } func getTokenDecimals(exchange, testName string) (uint8, uint8) { switch { case contains(testName, "ETH") && contains(testName, "USDC"): return 18, 6 // ETH=18, USDC=6 case contains(testName, "WBTC") && contains(testName, "ETH"): return 8, 18 // WBTC=8, ETH=18 case contains(testName, "USDC") && contains(testName, "USDT"): return 6, 6 // USDC=6, USDT=6 default: return 18, 18 // Default to 18 decimals } } func contains(s, substr string) bool { for i := 0; i <= len(s)-len(substr); i++ { if s[i:i+len(substr)] == substr { return true } } return false } func abs(x float64) float64 { if x < 0 { return -x } return x } // BenchmarkProfitCalculation benchmarks profit calculation performance func BenchmarkProfitCalculation(b *testing.B) { converter := pkgmath.NewDecimalConverter() tc := ProfitThresholdTestCase{ Name: "ETH_USDC_Benchmark", Exchange: "uniswap_v2", Reserve0: "1000000000000000000000", Reserve1: "2000000000000", AmountIn: "1000000000000000000", ExpectedProfitBP: 25.0, ExpectedSpreadBP: 50.0, MinProfitThresholdBP: 10.0, Tolerance: 2.0, } b.ResetTimer() for i := 0; i < b.N; i++ { _, _, err := calculateProfitAndSpread(converter, tc) if err != nil { b.Fatalf("Benchmark failed: %v", err) } } }