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>
This commit is contained in:
Krypto Kajun
2025-10-17 00:12:55 -05:00
parent f358f49aa9
commit 850223a953
8621 changed files with 79808 additions and 7340 deletions

View File

@@ -7,17 +7,23 @@ import (
"time"
"github.com/fraktal/mev-beta/internal/logger"
"github.com/fraktal/mev-beta/pkg/math"
)
// RiskManager manages risk for MEV operations
type RiskManager struct {
logger *logger.Logger
mu sync.RWMutex
logger *logger.Logger
mu sync.RWMutex
decimalConverter *math.DecimalConverter
// Position limits
maxPositionSize *big.Int // Maximum position size in wei
dailyLossLimit *big.Int // Maximum daily loss in wei
maxConcurrent int // Maximum concurrent positions
maxPositionSize *big.Int // Maximum position size in wei
dailyLossLimit *big.Int // Maximum daily loss in wei
maxConcurrent int // Maximum concurrent positions
maxPositionSizeDecimal *math.UniversalDecimal
dailyLossLimitDecimal *math.UniversalDecimal
minProfitThresholdDecimal *math.UniversalDecimal
maxGasPriceDecimal *math.UniversalDecimal
// Current state
currentPositions int
@@ -25,11 +31,13 @@ type RiskManager struct {
lastReset time.Time
// Risk metrics
totalTrades uint64
successfulTrades uint64
failedTrades uint64
totalProfit *big.Int
totalLoss *big.Int
totalTrades uint64
successfulTrades uint64
failedTrades uint64
totalProfit *big.Int
totalLoss *big.Int
totalProfitDecimal *math.UniversalDecimal
totalLossDecimal *math.UniversalDecimal
// Configuration
minProfitThreshold *big.Int // Minimum profit threshold in wei
@@ -40,17 +48,31 @@ type RiskManager struct {
circuitBreaker *CircuitBreaker
}
func (rm *RiskManager) fromWei(value *big.Int, symbol string) *math.UniversalDecimal {
if value == nil {
zero, _ := math.NewUniversalDecimal(big.NewInt(0), 18, symbol)
return zero
}
decimals := uint8(18)
if symbol == "GWEI" {
decimals = 9
}
return rm.decimalConverter.FromWei(value, decimals, symbol)
}
// RiskAssessment represents a risk assessment for an MEV opportunity
type RiskAssessment struct {
OpportunityID string
RiskScore float64 // 0-1, higher = riskier
Confidence float64 // 0-1, higher = more confident
MaxPositionSize *big.Int // Maximum position size for this opportunity
RecommendedGas *big.Int // Recommended gas price
SlippageLimit float64 // Maximum slippage for this opportunity
Profitability float64 // Expected ROI percentage
Acceptable bool // Whether the opportunity passes risk checks
Reason string // Reason for acceptance/rejection
OpportunityID string
RiskScore float64 // 0-1, higher = riskier
Confidence float64 // 0-1, higher = more confident
MaxPositionSize *big.Int // Maximum position size for this opportunity
RecommendedGas *big.Int // Recommended gas price
SlippageLimit float64 // Maximum slippage for this opportunity
Profitability float64 // Expected ROI percentage
Acceptable bool // Whether the opportunity passes risk checks
Reason string // Reason for acceptance/rejection
MaxPositionSizeDecimal *math.UniversalDecimal
RecommendedGasDecimal *math.UniversalDecimal
}
// CircuitBreaker manages circuit breaking for risk control
@@ -65,8 +87,11 @@ type CircuitBreaker struct {
// NewRiskManager creates a new risk manager
func NewRiskManager(logger *logger.Logger) *RiskManager {
dc := math.NewDecimalConverter()
rm := &RiskManager{
logger: logger,
decimalConverter: dc,
maxPositionSize: big.NewInt(1000000000000000000), // 1 ETH
dailyLossLimit: big.NewInt(100000000000000000), // 0.1 ETH
maxConcurrent: 5,
@@ -87,6 +112,13 @@ func NewRiskManager(logger *logger.Logger) *RiskManager {
},
}
rm.maxPositionSizeDecimal, _ = math.NewUniversalDecimal(new(big.Int).Set(rm.maxPositionSize), 18, "ETH")
rm.dailyLossLimitDecimal, _ = math.NewUniversalDecimal(new(big.Int).Set(rm.dailyLossLimit), 18, "ETH")
rm.minProfitThresholdDecimal, _ = math.NewUniversalDecimal(new(big.Int).Set(rm.minProfitThreshold), 18, "ETH")
rm.maxGasPriceDecimal, _ = math.NewUniversalDecimal(new(big.Int).Set(rm.maxGasPrice), 9, "GWEI")
rm.totalProfitDecimal, _ = math.NewUniversalDecimal(big.NewInt(0), 18, "ETH")
rm.totalLossDecimal, _ = math.NewUniversalDecimal(big.NewInt(0), 18, "ETH")
// Start daily reset timer
go rm.dailyReset()
@@ -99,15 +131,17 @@ func (rm *RiskManager) AssessOpportunity(opportunityID string, expectedProfit, g
defer rm.mu.Unlock()
assessment := &RiskAssessment{
OpportunityID: opportunityID,
RiskScore: 0.0,
Confidence: 0.0,
MaxPositionSize: big.NewInt(0),
RecommendedGas: big.NewInt(0),
SlippageLimit: 0.0,
Profitability: 0.0,
Acceptable: false,
Reason: "",
OpportunityID: opportunityID,
RiskScore: 0.0,
Confidence: 0.0,
MaxPositionSize: big.NewInt(0),
RecommendedGas: big.NewInt(0),
SlippageLimit: 0.0,
Profitability: 0.0,
Acceptable: false,
Reason: "",
MaxPositionSizeDecimal: rm.maxPositionSizeDecimal,
RecommendedGasDecimal: rm.maxGasPriceDecimal,
}
// Check circuit breaker
@@ -116,6 +150,10 @@ func (rm *RiskManager) AssessOpportunity(opportunityID string, expectedProfit, g
return assessment
}
expectedProfitDec := rm.fromWei(expectedProfit, "ETH")
gasPriceDec := rm.fromWei(gasPrice, "GWEI")
dailyLossDec := rm.fromWei(rm.dailyLoss, "ETH")
// Check if we've exceeded concurrent position limits
if rm.currentPositions >= rm.maxConcurrent {
assessment.Reason = fmt.Sprintf("Maximum concurrent positions reached: %d", rm.maxConcurrent)
@@ -123,16 +161,18 @@ func (rm *RiskManager) AssessOpportunity(opportunityID string, expectedProfit, g
}
// Check if we've exceeded daily loss limits
if rm.dailyLoss.Cmp(rm.dailyLossLimit) > 0 {
if cmp, err := rm.decimalConverter.Compare(dailyLossDec, rm.dailyLossLimitDecimal); err == nil && cmp > 0 {
assessment.Reason = fmt.Sprintf("Daily loss limit exceeded: %s > %s",
formatEther(rm.dailyLoss), formatEther(rm.dailyLossLimit))
rm.decimalConverter.ToHumanReadable(dailyLossDec),
rm.decimalConverter.ToHumanReadable(rm.dailyLossLimitDecimal))
return assessment
}
// Check minimum profit threshold
if expectedProfit.Cmp(rm.minProfitThreshold) < 0 {
if cmp, err := rm.decimalConverter.Compare(expectedProfitDec, rm.minProfitThresholdDecimal); err == nil && cmp < 0 {
assessment.Reason = fmt.Sprintf("Profit below minimum threshold: %s < %s",
formatEther(expectedProfit), formatEther(rm.minProfitThreshold))
rm.decimalConverter.ToHumanReadable(expectedProfitDec),
rm.decimalConverter.ToHumanReadable(rm.minProfitThresholdDecimal))
return assessment
}
@@ -144,9 +184,10 @@ func (rm *RiskManager) AssessOpportunity(opportunityID string, expectedProfit, g
}
// Check gas price limits
if gasPrice.Cmp(rm.maxGasPrice) > 0 {
if cmp, err := rm.decimalConverter.Compare(gasPriceDec, rm.maxGasPriceDecimal); err == nil && cmp > 0 {
assessment.Reason = fmt.Sprintf("Gas price exceeds limit: %s > %s",
formatGwei(gasPrice), formatGwei(rm.maxGasPrice))
rm.decimalConverter.ToHumanReadable(gasPriceDec),
rm.decimalConverter.ToHumanReadable(rm.maxGasPriceDecimal))
return assessment
}
@@ -165,10 +206,12 @@ func (rm *RiskManager) AssessOpportunity(opportunityID string, expectedProfit, g
// Determine maximum position size based on risk
maxPosition := rm.calculateMaxPositionSize(riskScore, expectedProfit, gasCost)
assessment.MaxPositionSize = maxPosition
assessment.MaxPositionSizeDecimal = rm.fromWei(maxPosition, "ETH")
// Recommend gas price based on network conditions and risk
recommendedGas := rm.calculateRecommendedGas(gasPrice, riskScore)
assessment.RecommendedGas = recommendedGas
assessment.RecommendedGasDecimal = rm.fromWei(recommendedGas, "GWEI")
// Set slippage limit based on risk tolerance
slippageLimit := rm.calculateSlippageLimit(riskScore)
@@ -200,12 +243,16 @@ func (rm *RiskManager) RecordTrade(success bool, profit, gasCost *big.Int) {
rm.successfulTrades++
if profit != nil {
rm.totalProfit.Add(rm.totalProfit, profit)
profitDec := rm.fromWei(profit, "ETH")
rm.totalProfitDecimal, _ = rm.decimalConverter.Add(rm.totalProfitDecimal, profitDec)
}
} else {
rm.failedTrades++
if gasCost != nil {
rm.totalLoss.Add(rm.totalLoss, gasCost)
rm.dailyLoss.Add(rm.dailyLoss, gasCost)
lossDec := rm.fromWei(gasCost, "ETH")
rm.totalLossDecimal, _ = rm.decimalConverter.Add(rm.totalLossDecimal, lossDec)
}
}
@@ -217,7 +264,10 @@ func (rm *RiskManager) RecordTrade(success bool, profit, gasCost *big.Int) {
}
rm.logger.Debug(fmt.Sprintf("Trade recorded: Success=%t, Profit=%s, Gas=%s, DailyLoss=%s",
success, formatEther(profit), formatEther(gasCost), formatEther(rm.dailyLoss)))
success,
rm.decimalConverter.ToHumanReadable(rm.fromWei(profit, "ETH")),
rm.decimalConverter.ToHumanReadable(rm.fromWei(gasCost, "ETH")),
rm.decimalConverter.ToHumanReadable(rm.fromWei(rm.dailyLoss, "ETH"))))
}
// UpdatePositionCount updates the current position count
@@ -243,16 +293,16 @@ func (rm *RiskManager) GetStatistics() map[string]interface{} {
"successful_trades": rm.successfulTrades,
"failed_trades": rm.failedTrades,
"success_rate": float64(rm.successfulTrades) / float64(max(1, rm.totalTrades)),
"total_profit": formatEther(rm.totalProfit),
"total_loss": formatEther(rm.totalLoss),
"daily_loss": formatEther(rm.dailyLoss),
"daily_loss_limit": formatEther(rm.dailyLossLimit),
"total_profit": rm.decimalConverter.ToHumanReadable(rm.totalProfitDecimal),
"total_loss": rm.decimalConverter.ToHumanReadable(rm.totalLossDecimal),
"daily_loss": rm.decimalConverter.ToHumanReadable(rm.fromWei(rm.dailyLoss, "ETH")),
"daily_loss_limit": rm.decimalConverter.ToHumanReadable(rm.dailyLossLimitDecimal),
"current_positions": rm.currentPositions,
"max_concurrent": rm.maxConcurrent,
"circuit_breaker_open": rm.circuitBreaker.IsOpen(),
"min_profit_threshold": formatEther(rm.minProfitThreshold),
"min_profit_threshold": rm.decimalConverter.ToHumanReadable(rm.minProfitThresholdDecimal),
"max_slippage": rm.maxSlippage * 100, // Convert to percentage
"max_gas_price": formatGwei(rm.maxGasPrice),
"max_gas_price": rm.decimalConverter.ToHumanReadable(rm.maxGasPriceDecimal),
}
}

83
pkg/risk/manager_test.go Normal file
View File

@@ -0,0 +1,83 @@
package risk
import (
"math/big"
"strings"
"testing"
"github.com/fraktal/mev-beta/internal/logger"
"github.com/fraktal/mev-beta/pkg/math"
)
func TestAssessOpportunityProvidesDecimalSnapshots(t *testing.T) {
log := logger.New("debug", "text", "")
rm := NewRiskManager(log)
expectedProfit := big.NewInt(20000000000000000) // 0.02 ETH
gasCost := big.NewInt(500000000000000) // 0.0005 ETH
gasPrice := big.NewInt(3000000000) // 3 gwei
assessment := rm.AssessOpportunity("op-1", expectedProfit, gasCost, 0.005, gasPrice)
if assessment.MaxPositionSizeDecimal == nil {
t.Fatalf("expected max position size decimal snapshot to be populated")
}
if assessment.RecommendedGasDecimal == nil {
t.Fatalf("expected gas decimal snapshot to be populated")
}
expectedMax := rm.fromWei(assessment.MaxPositionSize, "ETH")
if cmp, err := rm.decimalConverter.Compare(assessment.MaxPositionSizeDecimal, expectedMax); err != nil || cmp != 0 {
t.Fatalf("expected position decimal %s to match %s", rm.decimalConverter.ToHumanReadable(assessment.MaxPositionSizeDecimal), rm.decimalConverter.ToHumanReadable(expectedMax))
}
expectedGas := rm.fromWei(assessment.RecommendedGas, "GWEI")
if cmp, err := rm.decimalConverter.Compare(assessment.RecommendedGasDecimal, expectedGas); err != nil || cmp != 0 {
t.Fatalf("expected gas decimal %s to match %s", rm.decimalConverter.ToHumanReadable(assessment.RecommendedGasDecimal), rm.decimalConverter.ToHumanReadable(expectedGas))
}
}
func TestAssessOpportunityRejectsBelowMinimumProfit(t *testing.T) {
log := logger.New("debug", "text", "")
rm := NewRiskManager(log)
belowThreshold := big.NewInt(5000000000000000) // 0.005 ETH < 0.01 ETH
gasCost := big.NewInt(100000000000000) // 0.0001 ETH
gasPrice := big.NewInt(1500000000) // 1.5 gwei
assessment := rm.AssessOpportunity("op-2", belowThreshold, gasCost, 0.001, gasPrice)
if assessment.Acceptable {
t.Fatalf("expected opportunity below profit threshold to be rejected")
}
if !strings.Contains(assessment.Reason, "Profit below minimum threshold") {
t.Fatalf("unexpected rejection reason: %s", assessment.Reason)
}
thresholdDecimal := rm.minProfitThresholdDecimal
if cmp, err := rm.decimalConverter.Compare(assessment.MaxPositionSizeDecimal, rm.maxPositionSizeDecimal); err != nil || cmp != 0 {
t.Fatalf("expected max position decimal to remain default when rejected")
}
if cmp, err := rm.decimalConverter.Compare(rm.minProfitThresholdDecimal, thresholdDecimal); err != nil || cmp != 0 {
t.Fatalf("expected threshold decimal to remain unchanged")
}
}
func TestRecordTradeTracksDecimalTotals(t *testing.T) {
log := logger.New("debug", "text", "")
rm := NewRiskManager(log)
profit := big.NewInt(20000000000000000) // 0.02 ETH
gasCost := big.NewInt(5000000000000000) // 0.005 ETH
rm.RecordTrade(true, profit, gasCost)
expectedProfit, _ := math.NewUniversalDecimal(profit, 18, "ETH")
if cmp, err := rm.decimalConverter.Compare(rm.totalProfitDecimal, expectedProfit); err != nil || cmp != 0 {
t.Fatalf("expected total profit decimal %s to equal %s", rm.decimalConverter.ToHumanReadable(rm.totalProfitDecimal), rm.decimalConverter.ToHumanReadable(expectedProfit))
}
rm.RecordTrade(false, nil, gasCost)
expectedLoss, _ := math.NewUniversalDecimal(gasCost, 18, "ETH")
if cmp, err := rm.decimalConverter.Compare(rm.totalLossDecimal, expectedLoss); err != nil || cmp != 0 {
t.Fatalf("expected total loss decimal %s to equal %s", rm.decimalConverter.ToHumanReadable(rm.totalLossDecimal), rm.decimalConverter.ToHumanReadable(expectedLoss))
}
}

View File

@@ -7,6 +7,7 @@ import (
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/fraktal/mev-beta/internal/logger"
)