- 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>
482 lines
16 KiB
Go
482 lines
16 KiB
Go
package risk
|
|
|
|
import (
|
|
"fmt"
|
|
"math/big"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/ethereum/go-ethereum/common"
|
|
|
|
"github.com/fraktal/mev-beta/internal/logger"
|
|
)
|
|
|
|
// ProfitValidator validates profitability of MEV opportunities
|
|
type ProfitValidator struct {
|
|
logger *logger.Logger
|
|
mu sync.RWMutex
|
|
|
|
// Profit thresholds
|
|
minProfitUSD float64 // Minimum profit in USD
|
|
minProfitETH *big.Int // Minimum profit in ETH
|
|
minProfitMargin float64 // Minimum profit margin percentage
|
|
maxSlippage float64 // Maximum acceptable slippage
|
|
maxGasPrice *big.Int // Maximum gas price willing to pay
|
|
|
|
// Historical performance tracking
|
|
totalOpportunities uint64
|
|
profitableOps uint64
|
|
unprofitableOps uint64
|
|
totalProfitETH *big.Int
|
|
totalGasCostETH *big.Int
|
|
averageGasCost *big.Int
|
|
|
|
// Performance metrics
|
|
validationSuccessRate float64
|
|
averageProfitMargin float64
|
|
averageSlippage float64
|
|
|
|
// Token price tracking
|
|
tokenPrices map[common.Address]*TokenPrice
|
|
priceMu sync.RWMutex
|
|
|
|
// Validation configuration
|
|
strictValidation bool // Whether to use strict validation rules
|
|
}
|
|
|
|
// TokenPrice represents real-time token pricing data
|
|
type TokenPrice struct {
|
|
Address common.Address
|
|
PriceUSD float64
|
|
LastUpdated time.Time
|
|
Confidence float64 // Price confidence 0-1
|
|
Volume24h float64
|
|
Volatility float64
|
|
}
|
|
|
|
// ProfitValidationResult represents the result of a profit validation
|
|
type ProfitValidationResult struct {
|
|
OpportunityID string
|
|
Valid bool
|
|
Reason string
|
|
ExpectedProfitETH *big.Int
|
|
ExpectedProfitUSD float64
|
|
GasCostETH *big.Int
|
|
GasCostUSD float64
|
|
NetProfitETH *big.Int
|
|
NetProfitUSD float64
|
|
ProfitMargin float64
|
|
Slippage float64
|
|
GasPrice *big.Int
|
|
Acceptable bool
|
|
Recommendation string
|
|
Confidence float64
|
|
RiskScore float64
|
|
ValidationTime time.Duration
|
|
}
|
|
|
|
// NewProfitValidator creates a new profit validator
|
|
func NewProfitValidator(logger *logger.Logger) *ProfitValidator {
|
|
return &ProfitValidator{
|
|
logger: logger,
|
|
minProfitUSD: 5.0, // $5 minimum profit
|
|
minProfitETH: big.NewInt(10000000000000000), // 0.01 ETH minimum profit
|
|
minProfitMargin: 0.005, // 0.5% minimum margin
|
|
maxSlippage: 0.01, // 1% maximum slippage
|
|
maxGasPrice: big.NewInt(20000000000), // 20 gwei max gas price
|
|
totalOpportunities: 0,
|
|
profitableOps: 0,
|
|
unprofitableOps: 0,
|
|
totalProfitETH: big.NewInt(0),
|
|
totalGasCostETH: big.NewInt(0),
|
|
averageGasCost: big.NewInt(0),
|
|
validationSuccessRate: 0.0,
|
|
averageProfitMargin: 0.0,
|
|
averageSlippage: 0.0,
|
|
tokenPrices: make(map[common.Address]*TokenPrice),
|
|
strictValidation: true, // Default to strict validation
|
|
}
|
|
}
|
|
|
|
// ValidateProfit validates the profitability of an MEV opportunity
|
|
func (pv *ProfitValidator) ValidateProfit(opportunityID string, expectedProfitETH *big.Int, gasCostETH *big.Int, slippage float64, gasPrice *big.Int) *ProfitValidationResult {
|
|
startTime := time.Now()
|
|
|
|
pv.mu.Lock()
|
|
pv.totalOpportunities++
|
|
pv.mu.Unlock()
|
|
|
|
result := &ProfitValidationResult{
|
|
OpportunityID: opportunityID,
|
|
Valid: false,
|
|
Reason: "",
|
|
ExpectedProfitETH: expectedProfitETH,
|
|
ExpectedProfitUSD: 0.0,
|
|
GasCostETH: gasCostETH,
|
|
GasCostUSD: 0.0,
|
|
NetProfitETH: big.NewInt(0),
|
|
NetProfitUSD: 0.0,
|
|
ProfitMargin: 0.0,
|
|
Slippage: slippage,
|
|
GasPrice: gasPrice,
|
|
Acceptable: false,
|
|
Recommendation: "",
|
|
Confidence: 0.0,
|
|
RiskScore: 0.0,
|
|
ValidationTime: 0,
|
|
}
|
|
|
|
// Calculate USD values using token prices
|
|
expectedProfitUSD := pv.convertETHToUSD(expectedProfitETH)
|
|
gasCostUSD := pv.convertETHToUSD(gasCostETH)
|
|
|
|
result.ExpectedProfitUSD = expectedProfitUSD
|
|
result.GasCostUSD = gasCostUSD
|
|
|
|
// Calculate net profit
|
|
netProfitETH := new(big.Int).Sub(expectedProfitETH, gasCostETH)
|
|
if netProfitETH.Sign() < 0 {
|
|
netProfitETH = big.NewInt(0)
|
|
}
|
|
|
|
result.NetProfitETH = netProfitETH
|
|
result.NetProfitUSD = pv.convertETHToUSD(netProfitETH)
|
|
|
|
// Calculate profit margin
|
|
if expectedProfitETH.Sign() > 0 {
|
|
margin := new(big.Float).Quo(new(big.Float).SetInt(netProfitETH), new(big.Float).SetInt(expectedProfitETH))
|
|
marginFloat, _ := margin.Float64()
|
|
result.ProfitMargin = marginFloat * 100 // Convert to percentage
|
|
}
|
|
|
|
// Perform validation checks
|
|
valid, reason := pv.performValidationChecks(expectedProfitETH, netProfitETH, gasCostETH, slippage, gasPrice)
|
|
result.Valid = valid
|
|
result.Reason = reason
|
|
|
|
// Determine if opportunity is acceptable
|
|
acceptable, recommendation := pv.isAcceptable(expectedProfitETH, netProfitETH, gasCostETH, slippage, gasPrice)
|
|
result.Acceptable = acceptable
|
|
result.Recommendation = recommendation
|
|
|
|
// Calculate confidence based on validation results
|
|
result.Confidence = pv.calculateConfidence(valid, acceptable, result.ProfitMargin, slippage)
|
|
result.RiskScore = pv.calculateRiskScore(slippage, gasPrice)
|
|
|
|
// Update statistics
|
|
pv.mu.Lock()
|
|
if valid && acceptable {
|
|
pv.profitableOps++
|
|
pv.totalProfitETH.Add(pv.totalProfitETH, netProfitETH)
|
|
pv.totalGasCostETH.Add(pv.totalGasCostETH, gasCostETH)
|
|
} else {
|
|
pv.unprofitableOps++
|
|
}
|
|
pv.updateValidationMetrics()
|
|
pv.mu.Unlock()
|
|
|
|
result.ValidationTime = time.Since(startTime)
|
|
|
|
// Log validation result
|
|
if result.Valid && result.Acceptable {
|
|
pv.logger.Info(fmt.Sprintf("✅ Profit validation PASSED for %s: Net profit %s ETH ($%.2f), Margin %.2f%%",
|
|
opportunityID, formatEther(result.NetProfitETH), result.NetProfitUSD, result.ProfitMargin))
|
|
} else {
|
|
pv.logger.Debug(fmt.Sprintf("❌ Profit validation FAILED for %s: %s", opportunityID, result.Reason))
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
// performValidationChecks performs all validation checks
|
|
func (pv *ProfitValidator) performValidationChecks(expectedProfitETH, netProfitETH, gasCostETH *big.Int, slippage float64, gasPrice *big.Int) (bool, string) {
|
|
// Check minimum profit in ETH
|
|
if expectedProfitETH.Cmp(pv.minProfitETH) < 0 {
|
|
return false, fmt.Sprintf("Expected profit %s ETH below minimum %s ETH",
|
|
formatEther(expectedProfitETH), formatEther(pv.minProfitETH))
|
|
}
|
|
|
|
// Check minimum profit in USD
|
|
expectedProfitUSD := pv.convertETHToUSD(expectedProfitETH)
|
|
if expectedProfitUSD < pv.minProfitUSD {
|
|
return false, fmt.Sprintf("Expected profit $%.2f below minimum $%.2f",
|
|
expectedProfitUSD, pv.minProfitUSD)
|
|
}
|
|
|
|
// Check net profit is positive
|
|
if netProfitETH.Sign() <= 0 {
|
|
return false, fmt.Sprintf("Net profit %s ETH is not positive",
|
|
formatEther(netProfitETH))
|
|
}
|
|
|
|
// Check slippage tolerance
|
|
if slippage > pv.maxSlippage {
|
|
return false, fmt.Sprintf("Slippage %.2f%% exceeds maximum %.2f%%",
|
|
slippage*100, pv.maxSlippage*100)
|
|
}
|
|
|
|
// Check gas price limits
|
|
if gasPrice.Cmp(pv.maxGasPrice) > 0 {
|
|
return false, fmt.Sprintf("Gas price %s gwei exceeds maximum %s gwei",
|
|
formatGwei(gasPrice), formatGwei(pv.maxGasPrice))
|
|
}
|
|
|
|
// Check gas cost vs profit ratio
|
|
if gasCostETH.Sign() > 0 && expectedProfitETH.Sign() > 0 {
|
|
gasRatio := new(big.Float).Quo(new(big.Float).SetInt(gasCostETH), new(big.Float).SetInt(expectedProfitETH))
|
|
gasRatioFloat, _ := gasRatio.Float64()
|
|
if gasRatioFloat > 0.5 { // Gas cost > 50% of expected profit
|
|
return false, fmt.Sprintf("Gas cost %s ETH (%.1f%%) too high relative to profit %s ETH",
|
|
formatEther(gasCostETH), gasRatioFloat*100, formatEther(expectedProfitETH))
|
|
}
|
|
}
|
|
|
|
return true, "All validation checks passed"
|
|
}
|
|
|
|
// isAcceptable determines if an opportunity is acceptable
|
|
func (pv *ProfitValidator) isAcceptable(expectedProfitETH, netProfitETH, gasCostETH *big.Int, slippage float64, gasPrice *big.Int) (bool, string) {
|
|
// Calculate profit margin
|
|
var profitMargin float64
|
|
if expectedProfitETH.Sign() > 0 {
|
|
margin := new(big.Float).Quo(new(big.Float).SetInt(netProfitETH), new(big.Float).SetInt(expectedProfitETH))
|
|
marginFloat, _ := margin.Float64()
|
|
profitMargin = marginFloat * 100 // Convert to percentage
|
|
}
|
|
|
|
// Check minimum profit margin
|
|
if profitMargin < pv.minProfitMargin*100 {
|
|
return false, fmt.Sprintf("Profit margin %.2f%% below minimum %.2f%%",
|
|
profitMargin, pv.minProfitMargin*100)
|
|
}
|
|
|
|
// For strict validation, apply additional checks
|
|
if pv.strictValidation {
|
|
// Check if net profit is at least 2x gas cost
|
|
doubleGasCost := new(big.Int).Mul(gasCostETH, big.NewInt(2))
|
|
if netProfitETH.Cmp(doubleGasCost) < 0 {
|
|
return false, fmt.Sprintf("Net profit %s ETH not at least 2x gas cost %s ETH",
|
|
formatEther(netProfitETH), formatEther(gasCostETH))
|
|
}
|
|
|
|
// Check if profit margin is at least 2x minimum
|
|
if profitMargin < pv.minProfitMargin*200 {
|
|
return false, fmt.Sprintf("Profit margin %.2f%% not at least 2x minimum %.2f%%",
|
|
profitMargin, pv.minProfitMargin*200)
|
|
}
|
|
}
|
|
|
|
return true, "Opportunity is acceptable"
|
|
}
|
|
|
|
// calculateConfidence calculates confidence in the validation result
|
|
func (pv *ProfitValidator) calculateConfidence(valid, acceptable bool, profitMargin, slippage float64) float64 {
|
|
if !valid || !acceptable {
|
|
return 0.1 // Low confidence for invalid opportunities
|
|
}
|
|
|
|
// Start with base confidence
|
|
confidence := 0.5
|
|
|
|
// Increase confidence based on profit margin
|
|
if profitMargin > pv.minProfitMargin*300 { // 3x minimum margin
|
|
confidence += 0.3
|
|
} else if profitMargin > pv.minProfitMargin*200 { // 2x minimum margin
|
|
confidence += 0.2
|
|
} else if profitMargin > pv.minProfitMargin*150 { // 1.5x minimum margin
|
|
confidence += 0.1
|
|
}
|
|
|
|
// Increase confidence for low slippage
|
|
if slippage < pv.maxSlippage*0.3 { // 30% of max slippage
|
|
confidence += 0.1
|
|
} else if slippage < pv.maxSlippage*0.5 { // 50% of max slippage
|
|
confidence += 0.05
|
|
}
|
|
|
|
// Cap at maximum confidence
|
|
if confidence > 0.95 {
|
|
confidence = 0.95
|
|
}
|
|
|
|
return confidence
|
|
}
|
|
|
|
// calculateRiskScore calculates a risk score for the opportunity
|
|
func (pv *ProfitValidator) calculateRiskScore(slippage float64, gasPrice *big.Int) float64 {
|
|
// Base risk (0-0.2)
|
|
baseRisk := 0.1
|
|
|
|
// Slippage risk (0-0.3)
|
|
slippageRisk := slippage / pv.maxSlippage * 0.3
|
|
if slippageRisk > 0.3 {
|
|
slippageRisk = 0.3
|
|
}
|
|
|
|
// Gas price risk (0-0.3)
|
|
gasRisk := 0.0
|
|
if gasPrice != nil && pv.maxGasPrice != nil && pv.maxGasPrice.Sign() > 0 {
|
|
gasRatio := new(big.Float).Quo(new(big.Float).SetInt(gasPrice), new(big.Float).SetInt(pv.maxGasPrice))
|
|
gasRatioFloat, _ := gasRatio.Float64()
|
|
gasRisk = gasRatioFloat * 0.3
|
|
if gasRisk > 0.3 {
|
|
gasRisk = 0.3
|
|
}
|
|
}
|
|
|
|
// Calculate total risk score
|
|
totalRisk := baseRisk + slippageRisk + gasRisk
|
|
if totalRisk > 1.0 {
|
|
totalRisk = 1.0
|
|
}
|
|
|
|
return totalRisk
|
|
}
|
|
|
|
// updateValidationMetrics updates validation metrics
|
|
func (pv *ProfitValidator) updateValidationMetrics() {
|
|
if pv.totalOpportunities > 0 {
|
|
pv.validationSuccessRate = float64(pv.profitableOps) / float64(pv.totalOpportunities)
|
|
}
|
|
|
|
// Update average metrics (simplified)
|
|
if pv.profitableOps > 0 {
|
|
avgProfitMargin := new(big.Float).Quo(new(big.Float).SetInt(pv.totalProfitETH), big.NewFloat(float64(pv.profitableOps)))
|
|
avgMarginFloat, _ := avgProfitMargin.Float64()
|
|
pv.averageProfitMargin = avgMarginFloat * 100 // Convert to percentage
|
|
}
|
|
}
|
|
|
|
// convertETHToUSD converts ETH amount to USD using current token prices
|
|
func (pv *ProfitValidator) convertETHToUSD(ethAmount *big.Int) float64 {
|
|
if ethAmount == nil {
|
|
return 0.0
|
|
}
|
|
|
|
// Get current ETH price in USD
|
|
ethPrice := pv.getTokenPriceUSD(common.HexToAddress("0x82af49447d8a07e3bd95bd0d56f35241523fbab1")) // WETH
|
|
if ethPrice == 0.0 {
|
|
ethPrice = 2000.0 // Default to $2000 ETH
|
|
}
|
|
|
|
// Convert ETH to USD
|
|
ethFloat := new(big.Float).SetInt(ethAmount)
|
|
ethDivisor := new(big.Float).SetFloat64(1e18) // Convert wei to ETH
|
|
ethETH := new(big.Float).Quo(ethFloat, ethDivisor)
|
|
|
|
usdFloat := new(big.Float).Mul(ethETH, big.NewFloat(ethPrice))
|
|
usdValue, _ := usdFloat.Float64()
|
|
|
|
return usdValue
|
|
}
|
|
|
|
// getTokenPriceUSD gets the USD price of a token
|
|
func (pv *ProfitValidator) getTokenPriceUSD(tokenAddr common.Address) float64 {
|
|
pv.priceMu.RLock()
|
|
defer pv.priceMu.RUnlock()
|
|
|
|
if price, exists := pv.tokenPrices[tokenAddr]; exists {
|
|
// Check if price is recent (within 5 minutes)
|
|
if time.Since(price.LastUpdated) < 5*time.Minute {
|
|
return price.PriceUSD
|
|
}
|
|
}
|
|
|
|
// Return known prices for common tokens
|
|
knownPrices := map[common.Address]float64{
|
|
common.HexToAddress("0x82af49447d8a07e3bd95bd0d56f35241523fbab1"): 2000.0, // WETH
|
|
common.HexToAddress("0xaf88d065e77c8cc2239327c5edb3a432268e5831"): 1.0, // USDC
|
|
common.HexToAddress("0xff970a61a04b1ca14834a43f5de4533ebddb5cc8"): 1.0, // USDC.e
|
|
common.HexToAddress("0xfd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9"): 1.0, // USDT
|
|
common.HexToAddress("0x2f2a2543b76a4166549f7aab2e75bef0aefc5b0f"): 43000.0, // WBTC
|
|
common.HexToAddress("0x912ce59144191c1204e64559fe8253a0e49e6548"): 0.75, // ARB
|
|
common.HexToAddress("0xfc5a1a6eb076a2c7ad06ed22c90d7e710e35ad0a"): 45.0, // GMX
|
|
common.HexToAddress("0xf97f4df75117a78c1a5a0dbb814af92458539fb4"): 12.0, // LINK
|
|
common.HexToAddress("0xfa7f8980b0f1e64a2062791cc3b0871572f1f7f0"): 8.0, // UNI
|
|
common.HexToAddress("0xba5ddd1f9d7f570dc94a51479a000e3bce967196"): 85.0, // AAVE
|
|
}
|
|
|
|
if price, exists := knownPrices[tokenAddr]; exists {
|
|
return price
|
|
}
|
|
|
|
// Default to $0 for unknown tokens
|
|
return 0.0
|
|
}
|
|
|
|
// UpdateTokenPrice updates the price of a token
|
|
func (pv *ProfitValidator) UpdateTokenPrice(tokenAddr common.Address, priceUSD float64, confidence float64) {
|
|
pv.priceMu.Lock()
|
|
defer pv.priceMu.Unlock()
|
|
|
|
pv.tokenPrices[tokenAddr] = &TokenPrice{
|
|
Address: tokenAddr,
|
|
PriceUSD: priceUSD,
|
|
LastUpdated: time.Now(),
|
|
Confidence: confidence,
|
|
Volume24h: 0.0, // Would be populated from real data
|
|
Volatility: 0.0, // Would be populated from real data
|
|
}
|
|
}
|
|
|
|
// GetStatistics returns validation statistics
|
|
func (pv *ProfitValidator) GetStatistics() map[string]interface{} {
|
|
pv.mu.RLock()
|
|
defer pv.mu.RUnlock()
|
|
|
|
return map[string]interface{}{
|
|
"total_opportunities": pv.totalOpportunities,
|
|
"profitable_opportunities": pv.profitableOps,
|
|
"unprofitable_opportunities": pv.unprofitableOps,
|
|
"validation_success_rate": pv.validationSuccessRate * 100, // Convert to percentage
|
|
"average_profit_margin": pv.averageProfitMargin,
|
|
"average_slippage": pv.averageSlippage * 100, // Convert to percentage
|
|
"total_profit_eth": formatEther(pv.totalProfitETH),
|
|
"total_gas_cost_eth": formatEther(pv.totalGasCostETH),
|
|
"tracked_tokens": len(pv.tokenPrices),
|
|
"strict_validation": pv.strictValidation,
|
|
}
|
|
}
|
|
|
|
// SetStrictValidation enables or disables strict validation
|
|
func (pv *ProfitValidator) SetStrictValidation(strict bool) {
|
|
pv.mu.Lock()
|
|
defer pv.mu.Unlock()
|
|
pv.strictValidation = strict
|
|
}
|
|
|
|
// SetMinProfitUSD sets the minimum profit threshold in USD
|
|
func (pv *ProfitValidator) SetMinProfitUSD(minProfit float64) {
|
|
pv.mu.Lock()
|
|
defer pv.mu.Unlock()
|
|
pv.minProfitUSD = minProfit
|
|
}
|
|
|
|
// SetMinProfitETH sets the minimum profit threshold in ETH
|
|
func (pv *ProfitValidator) SetMinProfitETH(minProfit *big.Int) {
|
|
pv.mu.Lock()
|
|
defer pv.mu.Unlock()
|
|
pv.minProfitETH = minProfit
|
|
}
|
|
|
|
// SetMinProfitMargin sets the minimum profit margin percentage
|
|
func (pv *ProfitValidator) SetMinProfitMargin(minMargin float64) {
|
|
pv.mu.Lock()
|
|
defer pv.mu.Unlock()
|
|
pv.minProfitMargin = minMargin
|
|
}
|
|
|
|
// SetMaxSlippage sets the maximum acceptable slippage
|
|
func (pv *ProfitValidator) SetMaxSlippage(maxSlippage float64) {
|
|
pv.mu.Lock()
|
|
defer pv.mu.Unlock()
|
|
pv.maxSlippage = maxSlippage
|
|
}
|
|
|
|
// SetMaxGasPrice sets the maximum gas price willing to pay
|
|
func (pv *ProfitValidator) SetMaxGasPrice(maxGasPrice *big.Int) {
|
|
pv.mu.Lock()
|
|
defer pv.mu.Unlock()
|
|
pv.maxGasPrice = maxGasPrice
|
|
}
|