- 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>
323 lines
12 KiB
Go
323 lines
12 KiB
Go
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)
|
|
}
|
|
}
|
|
}
|