Files
mev-beta/pkg/arbitrage/live_execution_framework.go
Krypto Kajun 850223a953 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>
2025-10-17 00:12:55 -05:00

1006 lines
32 KiB
Go

package arbitrage
import (
"context"
"fmt"
stdmath "math"
"math/big"
"sync"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/fraktal/mev-beta/internal/logger"
"github.com/fraktal/mev-beta/pkg/arbitrum"
"github.com/fraktal/mev-beta/pkg/exchanges"
"github.com/fraktal/mev-beta/pkg/math"
"github.com/fraktal/mev-beta/pkg/mev"
"github.com/fraktal/mev-beta/pkg/security"
pkgtypes "github.com/fraktal/mev-beta/pkg/types"
)
// safeConvertUint64ToInt64 safely converts a uint64 to int64, capping at MaxInt64 if overflow would occur
func safeConvertUint64ToInt64(v uint64) int64 {
if v > stdmath.MaxInt64 {
return stdmath.MaxInt64
}
return int64(v)
}
// LiveExecutionFramework orchestrates the complete MEV bot pipeline
type LiveExecutionFramework struct {
// Core components
client *ethclient.Client
logger *logger.Logger
keyManager *security.KeyManager
gasEstimator *arbitrum.L2GasEstimator
decimalConverter *math.DecimalConverter
// Exchange and market components
exchangeRegistry *exchanges.ExchangeRegistry
pricingEngine *math.ExchangePricingEngine
calculator *math.ArbitrageCalculator
// Detection and execution
detectionEngine *ArbitrageDetectionEngine
flashExecutor *FlashSwapExecutor
competitionAnalyzer *mev.CompetitionAnalyzer
// Configuration
config FrameworkConfig
// State management
isRunning bool
runningMutex sync.RWMutex
stopChan chan struct{}
// Performance tracking
stats *FrameworkStats
statsMutex sync.RWMutex
// Opportunity processing
opportunityQueue chan *pkgtypes.ArbitrageOpportunity
executionQueue chan *ExecutionTask
workerPool *ExecutionWorkerPool
}
// FrameworkConfig configures the live execution framework
type FrameworkConfig struct {
// Detection settings
DetectionConfig DetectionConfig
// Execution settings
ExecutionConfig ExecutionConfig
// Risk management
MaxConcurrentExecutions int
DailyProfitTarget *math.UniversalDecimal
DailyLossLimit *math.UniversalDecimal
MaxPositionSize *math.UniversalDecimal
// Performance settings
WorkerPoolSize int
OpportunityQueueSize int
ExecutionQueueSize int
// Emergency controls
EmergencyStopEnabled bool
CircuitBreakerEnabled bool
MaxFailureRate float64 // Stop if failure rate exceeds this
HealthCheckInterval time.Duration
}
// ExecutionTask is defined in executor.go to avoid duplication
// LiveExecutionMetrics contains metrics from the live execution framework
type LiveExecutionMetrics struct {
OpportunitiesDetected int64
OpportunitiesExecuted int64
SuccessfulExecutions int64
FailedExecutions int64
TotalProfit *math.UniversalDecimal
TotalGasCost *math.UniversalDecimal
AverageExecutionTime time.Duration
AverageGasUsed uint64
CurrentWorkers int
QueueLength int
HealthStatus string
LastExecutionTime time.Time
LastOpportunityTime time.Time
}
// TaskPriority represents execution priority
type TaskPriority int
const (
PriorityLow TaskPriority = 1
PriorityMedium TaskPriority = 2
PriorityHigh TaskPriority = 3
PriorityCritical TaskPriority = 4
)
// FrameworkStats tracks framework performance
type FrameworkStats struct {
StartTime time.Time
TotalOpportunitiesDetected uint64
TotalOpportunitiesQueued uint64
TotalExecutionsAttempted uint64
TotalExecutionsSuccessful uint64
TotalProfitRealized *math.UniversalDecimal
TotalGasCostPaid *math.UniversalDecimal
AverageExecutionTime time.Duration
CurrentSuccessRate float64
DailyStats map[string]*DailyStats
}
// DailyStats tracks daily performance
type DailyStats struct {
Date string
OpportunitiesDetected uint64
ExecutionsAttempted uint64
ExecutionsSuccessful uint64
ProfitRealized *math.UniversalDecimal
GasCostPaid *math.UniversalDecimal
NetProfit *math.UniversalDecimal
}
// ExecutionWorkerPool manages concurrent execution workers
type ExecutionWorkerPool struct {
workers int
taskChan chan *ExecutionTask
wg sync.WaitGroup
ctx context.Context
cancel context.CancelFunc
framework *LiveExecutionFramework
}
// NewLiveExecutionFramework creates a new live execution framework
func NewLiveExecutionFramework(
client *ethclient.Client,
logger *logger.Logger,
keyManager *security.KeyManager,
gasEstimator *arbitrum.L2GasEstimator,
flashSwapContract,
arbitrageContract common.Address,
config FrameworkConfig,
) (*LiveExecutionFramework, error) {
// Initialize exchange registry
exchangeRegistry := exchanges.NewExchangeRegistry(client, logger)
// Initialize pricing engine
pricingEngine := math.NewExchangePricingEngine()
// Create gas estimator wrapper for calculator
gasEstWrapper := &GasEstimatorWrapper{gasEstimator: gasEstimator}
// Initialize arbitrage calculator
calculator := math.NewArbitrageCalculator(gasEstWrapper)
// Initialize competition analyzer
competitionAnalyzer := mev.NewCompetitionAnalyzer(client, logger)
// Initialize detection engine
detectionEngine := NewArbitrageDetectionEngine(
exchangeRegistry,
gasEstWrapper,
logger,
config.DetectionConfig,
)
// Initialize flash executor
flashExecutor := NewFlashSwapExecutor(
client,
logger,
keyManager,
gasEstimator,
flashSwapContract,
arbitrageContract,
config.ExecutionConfig,
)
// Initialize statistics
stats := &FrameworkStats{
StartTime: time.Now(),
DailyStats: make(map[string]*DailyStats),
}
dc := math.NewDecimalConverter()
stats.TotalProfitRealized, _ = dc.FromString("0", 18, "ETH")
stats.TotalGasCostPaid, _ = dc.FromString("0", 18, "ETH")
framework := &LiveExecutionFramework{
client: client,
logger: logger,
keyManager: keyManager,
gasEstimator: gasEstimator,
decimalConverter: dc,
exchangeRegistry: exchangeRegistry,
pricingEngine: pricingEngine,
calculator: calculator,
detectionEngine: detectionEngine,
flashExecutor: flashExecutor,
competitionAnalyzer: competitionAnalyzer,
config: config,
stats: stats,
stopChan: make(chan struct{}),
opportunityQueue: make(chan *pkgtypes.ArbitrageOpportunity, config.OpportunityQueueSize),
executionQueue: make(chan *ExecutionTask, config.ExecutionQueueSize),
}
// Set default configuration
framework.setDefaultConfig()
return framework, nil
}
// setDefaultConfig sets default configuration values
func (framework *LiveExecutionFramework) setDefaultConfig() {
if framework.config.MaxConcurrentExecutions == 0 {
framework.config.MaxConcurrentExecutions = 5
}
if framework.config.WorkerPoolSize == 0 {
framework.config.WorkerPoolSize = 10
}
if framework.config.OpportunityQueueSize == 0 {
framework.config.OpportunityQueueSize = 1000
}
if framework.config.ExecutionQueueSize == 0 {
framework.config.ExecutionQueueSize = 100
}
if framework.config.MaxFailureRate == 0 {
framework.config.MaxFailureRate = 0.5 // 50% failure rate threshold
}
if framework.config.HealthCheckInterval == 0 {
framework.config.HealthCheckInterval = 30 * time.Second
}
if framework.config.DailyProfitTarget == nil {
framework.config.DailyProfitTarget, _ = framework.decimalConverter.FromString("1", 18, "ETH")
}
if framework.config.DailyLossLimit == nil {
framework.config.DailyLossLimit, _ = framework.decimalConverter.FromString("0.1", 18, "ETH")
}
if framework.config.MaxPositionSize == nil {
framework.config.MaxPositionSize, _ = framework.decimalConverter.FromString("10", 18, "ETH")
}
}
// Start begins the live execution framework
func (framework *LiveExecutionFramework) Start(ctx context.Context) error {
framework.runningMutex.Lock()
defer framework.runningMutex.Unlock()
if framework.isRunning {
return fmt.Errorf("framework is already running")
}
framework.logger.Info("🚀 Starting Live MEV Execution Framework...")
framework.logger.Info("================================================")
framework.logger.Info(fmt.Sprintf("⚙️ Max Concurrent Executions: %d", framework.config.MaxConcurrentExecutions))
framework.logger.Info(fmt.Sprintf("💰 Daily Profit Target: %s ETH", framework.decimalConverter.ToHumanReadable(framework.config.DailyProfitTarget)))
framework.logger.Info(fmt.Sprintf("🛡️ Daily Loss Limit: %s ETH", framework.decimalConverter.ToHumanReadable(framework.config.DailyLossLimit)))
framework.logger.Info(fmt.Sprintf("📊 Worker Pool Size: %d", framework.config.WorkerPoolSize))
// Initialize worker pool
framework.initializeWorkerPool(ctx)
// Start detection engine
if err := framework.detectionEngine.Start(ctx); err != nil {
return fmt.Errorf("failed to start detection engine: %w", err)
}
framework.isRunning = true
// Start main processing loops
go framework.opportunityProcessor(ctx)
go framework.executionCoordinator(ctx)
go framework.healthMonitor(ctx)
go framework.performanceTracker(ctx)
framework.logger.Info("✅ Live Execution Framework started successfully!")
framework.logger.Info("🔍 Monitoring for arbitrage opportunities...")
return nil
}
// Stop halts the live execution framework
func (framework *LiveExecutionFramework) Stop() error {
framework.runningMutex.Lock()
defer framework.runningMutex.Unlock()
if !framework.isRunning {
return fmt.Errorf("framework is not running")
}
framework.logger.Info("🛑 Stopping Live Execution Framework...")
// Signal stop
close(framework.stopChan)
// Stop detection engine
if err := framework.detectionEngine.Stop(); err != nil {
framework.logger.Warn(fmt.Sprintf("Error stopping detection engine: %v", err))
}
// Stop worker pool
if framework.workerPool != nil {
framework.workerPool.Stop()
}
framework.isRunning = false
// Print final statistics
framework.printFinalStats()
framework.logger.Info("✅ Live Execution Framework stopped successfully")
return nil
}
// initializeWorkerPool sets up the execution worker pool
func (framework *LiveExecutionFramework) initializeWorkerPool(ctx context.Context) {
framework.workerPool = &ExecutionWorkerPool{
workers: framework.config.WorkerPoolSize,
taskChan: framework.executionQueue,
framework: framework,
}
workerCtx, cancel := context.WithCancel(ctx)
framework.workerPool.ctx = workerCtx
framework.workerPool.cancel = cancel
framework.workerPool.Start()
}
// opportunityProcessor processes detected opportunities
func (framework *LiveExecutionFramework) opportunityProcessor(ctx context.Context) {
framework.logger.Debug("Starting opportunity processor...")
for {
select {
case <-ctx.Done():
return
case <-framework.stopChan:
return
case opportunity := <-framework.detectionEngine.GetOpportunityChannel():
framework.processOpportunity(ctx, opportunity)
}
}
}
// convertArbitrageOpportunityToMEVOpportunity converts types.ArbitrageOpportunity to mev.MEVOpportunity
func (framework *LiveExecutionFramework) convertArbitrageOpportunityToMEVOpportunity(opportunity *pkgtypes.ArbitrageOpportunity) *mev.MEVOpportunity {
// Convert the arbitrage opportunity to MEV opportunity format for competition analysis
estimatedProfit := big.NewInt(0)
if opportunity.NetProfit != nil {
estimatedProfit = new(big.Int).Set(opportunity.NetProfit)
}
// Calculate required gas estimate (placeholder - would be more precise in production)
requiredGas := uint64(800000) // typical for flash swaps
if len(opportunity.Path) > 0 {
requiredGas += uint64(len(opportunity.Path)-1) * 100000 // additional for each hop
}
return &mev.MEVOpportunity{
TxHash: "", // Will be populated later
Block: 0, // Will be determined at execution time
OpportunityType: "arbitrage", // or "sandwich", "liquidation", etc.
EstimatedProfit: estimatedProfit,
RequiredGas: requiredGas,
Competition: 0, // This will be determined by the analyzer
Confidence: opportunity.Confidence,
}
}
// processOpportunity processes a single arbitrage opportunity
func (framework *LiveExecutionFramework) processOpportunity(ctx context.Context, opportunity *pkgtypes.ArbitrageOpportunity) {
framework.statsMutex.Lock()
framework.stats.TotalOpportunitiesDetected++
framework.statsMutex.Unlock()
framework.logger.Debug(fmt.Sprintf("Processing opportunity: %s profit",
framework.decimalConverter.ToHumanReadable(universalFromWei(framework.decimalConverter, opportunity.NetProfit, "ETH"))))
// Perform risk checks
if !framework.performRiskChecks(opportunity) {
framework.logger.Debug("Opportunity failed risk checks, skipping")
return
}
// Convert opportunity for competition analysis
mevOpportunity := framework.convertArbitrageOpportunityToMEVOpportunity(opportunity)
// Analyze competition
competitionAnalysis, err := framework.competitionAnalyzer.AnalyzeCompetition(ctx, mevOpportunity)
if err != nil {
framework.logger.Warn(fmt.Sprintf("Competition analysis failed: %v", err))
return
}
// Check if we should proceed based on competition
if !framework.shouldExecuteBasedOnCompetition(competitionAnalysis) {
framework.logger.Debug("Skipping opportunity due to competition analysis")
return
}
// Determine priority
priority := framework.calculatePriority(opportunity, competitionAnalysis)
// Create execution task
task := &ExecutionTask{
Opportunity: opportunity,
CompetitionAnalysis: competitionAnalysis,
Priority: int(priority), // Convert TaskPriority to int
SubmissionTime: time.Now(),
ResultChan: make(chan *ExecutionResult, 1),
}
// Queue for execution
select {
case framework.executionQueue <- task:
framework.statsMutex.Lock()
framework.stats.TotalOpportunitiesQueued++
framework.statsMutex.Unlock()
framework.logger.Info(fmt.Sprintf("🎯 Queued opportunity for execution: %s profit, Priority: %d",
framework.decimalConverter.ToHumanReadable(universalFromWei(framework.decimalConverter, opportunity.NetProfit, "ETH")), priority))
default:
framework.logger.Warn("Execution queue full, dropping opportunity")
}
}
// executionCoordinator coordinates the execution of queued opportunities
func (framework *LiveExecutionFramework) executionCoordinator(ctx context.Context) {
framework.logger.Debug("Starting execution coordinator...")
activExecutions := 0
maxConcurrent := framework.config.MaxConcurrentExecutions
for {
select {
case <-ctx.Done():
return
case <-framework.stopChan:
return
case task := <-framework.executionQueue:
// Check if we can start another execution
if activExecutions >= maxConcurrent {
framework.logger.Debug("Max concurrent executions reached, queuing task")
// Put the task back in queue (simplified - production would use priority queue)
go func() {
time.Sleep(100 * time.Millisecond)
select {
case framework.executionQueue <- task:
default:
framework.logger.Warn("Failed to requeue task")
}
}()
continue
}
activExecutions++
// Execute asynchronously
go func(t *ExecutionTask) {
defer func() { activExecutions-- }()
framework.executeOpportunity(ctx, t)
}(task)
}
}
}
// ExecuteOpportunity executes a single arbitrage opportunity
func (framework *LiveExecutionFramework) ExecuteOpportunity(ctx context.Context, task *ExecutionTask) (*ExecutionResult, error) {
// Submit the task
framework.SubmitExecutionTask(ctx, task)
// Wait for completion with timeout
select {
case result := <-task.ResultChan:
if result == nil {
return nil, fmt.Errorf("execution returned nil result")
}
return result, nil
case <-time.After(45 * time.Second): // 45s timeout
return nil, fmt.Errorf("execution timeout")
}
}
// executeOpportunity executes a single arbitrage opportunity (internal worker method)
func (framework *LiveExecutionFramework) executeOpportunity(ctx context.Context, task *ExecutionTask) {
framework.statsMutex.Lock()
framework.stats.TotalExecutionsAttempted++
framework.statsMutex.Unlock()
framework.logger.Info(fmt.Sprintf("🚀 Executing arbitrage: %s expected profit",
opportunityAmountString(framework.decimalConverter, task.Opportunity)))
startTime := time.Now()
// Execute the arbitrage
result, err := framework.flashExecutor.ExecuteArbitrage(ctx, task.Opportunity)
if err != nil {
framework.logger.Error(fmt.Sprintf("Execution failed: %v", err))
result = &ExecutionResult{
Success: false,
Error: err,
ErrorMessage: err.Error(),
}
}
executionTime := time.Since(startTime)
// Update statistics
framework.updateExecutionStats(result, executionTime)
// Log result
if result.Success {
realized := executionProfitToString(framework.decimalConverter, result)
framework.logger.Info(fmt.Sprintf("✅ Execution successful: %s profit realized in %v",
realized,
executionTime))
} else {
framework.logger.Warn(fmt.Sprintf("❌ Execution failed: %s", result.ErrorMessage))
}
// Send result back if someone is waiting
select {
case task.ResultChan <- result:
default:
}
}
// Helper methods for risk management and decision making
func (framework *LiveExecutionFramework) performRiskChecks(opportunity *pkgtypes.ArbitrageOpportunity) bool {
// Check position size
if !framework.checkPositionSize(opportunity) {
return false
}
// Check daily loss limit
if !framework.checkDailyLossLimit() {
return false
}
// Check daily profit target
if !framework.checkDailyProfitTarget(opportunity) {
return false
}
return true
}
// checkPositionSize validates that the position size is within limits
func (framework *LiveExecutionFramework) checkPositionSize(opportunity *pkgtypes.ArbitrageOpportunity) bool {
amountWei := opportunity.AmountIn
if amountWei == nil {
amountWei = opportunity.RequiredAmount
}
if framework.config.MaxPositionSize != nil {
positionSize := universalFromWei(framework.decimalConverter, amountWei, "ETH")
if comp, _ := framework.decimalConverter.Compare(positionSize, framework.config.MaxPositionSize); comp > 0 {
return false
}
}
return true
}
// checkDailyLossLimit validates that we haven't exceeded daily loss limits
func (framework *LiveExecutionFramework) checkDailyLossLimit() bool {
today := time.Now().Format("2006-01-02")
if framework.config.DailyLossLimit != nil {
if dailyStats, exists := framework.stats.DailyStats[today]; exists && dailyStats.NetProfit != nil {
if dailyStats.NetProfit.IsNegative() {
loss := dailyStats.NetProfit.Copy()
loss.Value.Abs(loss.Value)
if comp, err := framework.decimalConverter.Compare(loss, framework.config.DailyLossLimit); err == nil {
if comp > 0 {
framework.logger.Warn("Daily loss limit reached, skipping opportunity")
return false
}
} else {
framework.logger.Warn(fmt.Sprintf("Failed to compare daily loss to limit: %v", err))
}
}
}
}
return true
}
// checkDailyProfitTarget validates that we haven't exceeded daily profit target
func (framework *LiveExecutionFramework) checkDailyProfitTarget(opportunity *pkgtypes.ArbitrageOpportunity) bool {
today := time.Now().Format("2006-01-02")
if dailyStats, exists := framework.stats.DailyStats[today]; exists {
if comp, _ := framework.decimalConverter.Compare(dailyStats.ProfitRealized, framework.config.DailyProfitTarget); comp >= 0 {
framework.logger.Info("Daily profit target reached, being conservative")
// Could still execute high-confidence opportunities
if opportunity.Confidence < 0.9 {
return false
}
}
}
return true
}
func (framework *LiveExecutionFramework) shouldExecuteBasedOnCompetition(analysis *mev.CompetitionData) bool {
// Skip if competition is too intense
// CompetitionLevel for CompetitionData is a float64 representing intensity (0.0-1.0)
if analysis.CompetitionLevel > 0.8 { // Considered extreme competition
return false
}
// Skip if profit after gas is negative
if analysis.NetProfit.Sign() < 0 {
return false
}
return true
}
func (framework *LiveExecutionFramework) calculatePriority(
opportunity *pkgtypes.ArbitrageOpportunity,
competition *mev.CompetitionData,
) TaskPriority {
// Base priority on profit size
largeProfit, _ := framework.decimalConverter.FromString("0.1", 18, "ETH")
mediumProfit, _ := framework.decimalConverter.FromString("0.05", 18, "ETH")
var basePriority TaskPriority
netProfitDecimal := opportunityNetProfitDecimal(framework.decimalConverter, opportunity)
if comp, _ := framework.decimalConverter.Compare(netProfitDecimal, largeProfit); comp > 0 {
basePriority = PriorityHigh
} else if comp, _ := framework.decimalConverter.Compare(netProfitDecimal, mediumProfit); comp > 0 {
basePriority = PriorityMedium
} else {
basePriority = PriorityLow
}
// Adjust for confidence
if opportunity.Confidence > 0.9 && basePriority == PriorityHigh {
basePriority = PriorityCritical
}
// Adjust for competition - CompetitionData uses CompetitionLevel (0.0-1.0) instead of CompetitionLevel string
if competition.CompetitionLevel > 0.6 { // high competition
if basePriority > PriorityLow {
basePriority--
}
}
return basePriority
}
func (framework *LiveExecutionFramework) updateExecutionStats(result *ExecutionResult, executionTime time.Duration) {
framework.statsMutex.Lock()
defer framework.statsMutex.Unlock()
if result.Success {
framework.stats.TotalExecutionsSuccessful++
if result.ProfitRealized != nil {
profitDecimal, _ := math.NewUniversalDecimal(result.ProfitRealized, 18, "ETH")
framework.stats.TotalProfitRealized, _ = framework.decimalConverter.Add(
framework.stats.TotalProfitRealized,
profitDecimal,
)
}
}
if result.GasCost != nil {
framework.stats.TotalGasCostPaid, _ = framework.decimalConverter.Add(
framework.stats.TotalGasCostPaid,
result.GasCost,
)
}
// Update success rate
if framework.stats.TotalExecutionsAttempted > 0 {
framework.stats.CurrentSuccessRate = float64(framework.stats.TotalExecutionsSuccessful) / float64(framework.stats.TotalExecutionsAttempted)
}
// Update average execution time
framework.stats.AverageExecutionTime = (framework.stats.AverageExecutionTime + executionTime) / 2
// Update daily stats
framework.updateDailyStats(result)
}
func (framework *LiveExecutionFramework) updateDailyStats(result *ExecutionResult) {
today := time.Now().Format("2006-01-02")
if _, exists := framework.stats.DailyStats[today]; !exists {
framework.stats.DailyStats[today] = &DailyStats{
Date: today,
}
framework.stats.DailyStats[today].ProfitRealized, _ = framework.decimalConverter.FromString("0", 18, "ETH")
framework.stats.DailyStats[today].GasCostPaid, _ = framework.decimalConverter.FromString("0", 18, "ETH")
framework.stats.DailyStats[today].NetProfit, _ = framework.decimalConverter.FromString("0", 18, "ETH")
}
dailyStats := framework.stats.DailyStats[today]
dailyStats.ExecutionsAttempted++
if result.Success {
dailyStats.ExecutionsSuccessful++
if result.ProfitRealized != nil {
profitDecimal, _ := math.NewUniversalDecimal(result.ProfitRealized, 18, "ETH")
dailyStats.ProfitRealized, _ = framework.decimalConverter.Add(dailyStats.ProfitRealized, profitDecimal)
}
}
if result.GasCost != nil {
dailyStats.GasCostPaid, _ = framework.decimalConverter.Add(dailyStats.GasCostPaid, result.GasCost)
}
// Calculate net profit
dailyStats.NetProfit, _ = framework.decimalConverter.Subtract(dailyStats.ProfitRealized, dailyStats.GasCostPaid)
}
// healthMonitor monitors the health of the framework
func (framework *LiveExecutionFramework) healthMonitor(ctx context.Context) {
ticker := time.NewTicker(framework.config.HealthCheckInterval)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return
case <-framework.stopChan:
return
case <-ticker.C:
framework.performHealthCheck()
}
}
}
func (framework *LiveExecutionFramework) performHealthCheck() {
framework.statsMutex.RLock()
successRate := framework.stats.CurrentSuccessRate
framework.statsMutex.RUnlock()
// Check if failure rate exceeds threshold
if successRate < (1.0 - framework.config.MaxFailureRate) {
framework.logger.Warn(fmt.Sprintf("⚠️ Success rate below threshold: %.1f%%", successRate*100))
if framework.config.CircuitBreakerEnabled {
framework.logger.Warn("🔥 Circuit breaker triggered - stopping framework")
framework.Stop()
}
}
// Log health status
framework.logger.Debug(fmt.Sprintf("Health check - Success rate: %.1f%%, Active: %t",
successRate*100, framework.isRunning))
}
// performanceTracker tracks and logs performance metrics
func (framework *LiveExecutionFramework) performanceTracker(ctx context.Context) {
ticker := time.NewTicker(5 * time.Minute)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return
case <-framework.stopChan:
return
case <-ticker.C:
framework.logPerformanceMetrics()
}
}
}
func (framework *LiveExecutionFramework) logPerformanceMetrics() {
framework.statsMutex.RLock()
stats := framework.stats
framework.statsMutex.RUnlock()
framework.logger.Info("📊 Performance Metrics:")
framework.logger.Info(fmt.Sprintf(" Opportunities Detected: %d", stats.TotalOpportunitiesDetected))
framework.logger.Info(fmt.Sprintf(" Executions Attempted: %d", stats.TotalExecutionsAttempted))
framework.logger.Info(fmt.Sprintf(" Success Rate: %.1f%%", stats.CurrentSuccessRate*100))
framework.logger.Info(fmt.Sprintf(" Total Profit: %s ETH", framework.decimalConverter.ToHumanReadable(stats.TotalProfitRealized)))
framework.logger.Info(fmt.Sprintf(" Total Gas Cost: %s ETH", framework.decimalConverter.ToHumanReadable(stats.TotalGasCostPaid)))
// Calculate net profit
netProfit, _ := framework.decimalConverter.Subtract(stats.TotalProfitRealized, stats.TotalGasCostPaid)
framework.logger.Info(fmt.Sprintf(" Net Profit: %s ETH", framework.decimalConverter.ToHumanReadable(netProfit)))
framework.logger.Info(fmt.Sprintf(" Average Execution Time: %v", stats.AverageExecutionTime))
}
func (framework *LiveExecutionFramework) printFinalStats() {
framework.statsMutex.RLock()
stats := framework.stats
framework.statsMutex.RUnlock()
framework.logger.Info("📈 Final Statistics:")
framework.logger.Info("==================")
framework.logger.Info(fmt.Sprintf("Runtime: %v", time.Since(stats.StartTime)))
framework.logger.Info(fmt.Sprintf("Opportunities Detected: %d", stats.TotalOpportunitiesDetected))
framework.logger.Info(fmt.Sprintf("Opportunities Queued: %d", stats.TotalOpportunitiesQueued))
framework.logger.Info(fmt.Sprintf("Executions Attempted: %d", stats.TotalExecutionsAttempted))
framework.logger.Info(fmt.Sprintf("Executions Successful: %d", stats.TotalExecutionsSuccessful))
framework.logger.Info(fmt.Sprintf("Final Success Rate: %.1f%%", stats.CurrentSuccessRate*100))
framework.logger.Info(fmt.Sprintf("Total Profit Realized: %s ETH", framework.decimalConverter.ToHumanReadable(stats.TotalProfitRealized)))
framework.logger.Info(fmt.Sprintf("Total Gas Cost Paid: %s ETH", framework.decimalConverter.ToHumanReadable(stats.TotalGasCostPaid)))
netProfit, _ := framework.decimalConverter.Subtract(stats.TotalProfitRealized, stats.TotalGasCostPaid)
framework.logger.Info(fmt.Sprintf("Final Net Profit: %s ETH", framework.decimalConverter.ToHumanReadable(netProfit)))
}
// GetStats returns current framework statistics
func (framework *LiveExecutionFramework) GetStats() *FrameworkStats {
framework.statsMutex.RLock()
defer framework.statsMutex.RUnlock()
// Return a copy to avoid race conditions
statsCopy := *framework.stats
return &statsCopy
}
// GetMetrics returns live execution metrics
func (framework *LiveExecutionFramework) GetMetrics() *LiveExecutionMetrics {
stats := framework.GetStats()
return &LiveExecutionMetrics{
OpportunitiesDetected: safeConvertUint64ToInt64(stats.TotalOpportunitiesDetected),
SuccessfulExecutions: safeConvertUint64ToInt64(stats.TotalExecutionsSuccessful),
FailedExecutions: safeConvertUint64ToInt64(stats.TotalExecutionsAttempted - stats.TotalExecutionsSuccessful), // Failed = Attempted - Successful
TotalProfit: stats.TotalProfitRealized,
AverageExecutionTime: stats.AverageExecutionTime,
CurrentWorkers: int(framework.config.WorkerPoolSize), // Using configured worker pool size as a proxy for active workers
QueueLength: int(len(framework.executionQueue)), // Current queue length
}
}
// SubmitExecutionTask submits an execution task to the framework queue
func (framework *LiveExecutionFramework) SubmitExecutionTask(ctx context.Context, task *ExecutionTask) {
// Check if the framework is running
framework.runningMutex.RLock()
isRunning := framework.isRunning
framework.runningMutex.RUnlock()
if !isRunning {
framework.logger.Error("Cannot submit task: framework is not running")
return
}
// Add the task to the execution queue
select {
case framework.executionQueue <- task:
framework.logger.Info(fmt.Sprintf("🎯 Queued execution task for opportunity with priority: %d", task.Priority))
framework.statsMutex.Lock()
framework.stats.TotalOpportunitiesQueued++
framework.statsMutex.Unlock()
case <-ctx.Done():
framework.logger.Warn("Context cancelled while trying to submit execution task")
case <-time.After(5 * time.Second): // Timeout to avoid blocking indefinitely
framework.logger.Error("Failed to submit execution task: queue is full")
}
}
// SetMonitoringMode sets the framework to monitoring-only mode
func (framework *LiveExecutionFramework) SetMonitoringMode(enabled bool) {
framework.runningMutex.Lock()
defer framework.runningMutex.Unlock()
// In a real implementation, this would control whether execution is enabled
// For now, we'll just log the change
if enabled {
framework.logger.Info("✅ Live execution framework set to monitoring mode")
} else {
framework.logger.Info("✅ Live execution framework set to active mode")
}
}
// Worker pool implementation
func (pool *ExecutionWorkerPool) Start() {
for i := 0; i < pool.workers; i++ {
pool.wg.Add(1)
go pool.worker()
}
}
func (pool *ExecutionWorkerPool) Stop() {
pool.cancel()
pool.wg.Wait()
}
func (pool *ExecutionWorkerPool) worker() {
defer pool.wg.Done()
for {
select {
case <-pool.ctx.Done():
return
case task := <-pool.taskChan:
pool.framework.executeOpportunity(pool.ctx, task)
}
}
}
func opportunityNetProfitDecimal(dc *math.DecimalConverter, opportunity *pkgtypes.ArbitrageOpportunity) *math.UniversalDecimal {
if opportunity == nil {
zero, _ := math.NewUniversalDecimal(big.NewInt(0), 18, "ETH")
return zero
}
if opportunity.Quantities != nil {
if val, ok := new(big.Int).SetString(opportunity.Quantities.NetProfit.Value, 10); ok {
if ud, err := math.NewUniversalDecimal(val, opportunity.Quantities.NetProfit.Decimals, opportunity.Quantities.NetProfit.Symbol); err == nil {
return ud
}
}
}
return universalFromWei(dc, opportunity.NetProfit, "ETH")
}
func opportunityAmountString(dc *math.DecimalConverter, opportunity *pkgtypes.ArbitrageOpportunity) string {
if opportunity == nil {
return "n/a"
}
ud := opportunityNetProfitDecimal(dc, opportunity)
return floatStringFromDecimal(ud, 6)
}
func executionProfitToString(dc *math.DecimalConverter, result *ExecutionResult) string {
if result != nil && result.ProfitRealized != nil {
ud := universalOrFromWei(dc, nil, result.ProfitRealized, 18, "ETH")
return floatStringFromDecimal(ud, 6)
}
return "0.000000"
}
// GasEstimatorWrapper wraps the Arbitrum gas estimator to implement the math.GasEstimator interface
type GasEstimatorWrapper struct {
gasEstimator *arbitrum.L2GasEstimator
}
func (w *GasEstimatorWrapper) EstimateSwapGas(exchangeType math.ExchangeType, poolData *math.PoolData) (uint64, error) {
// Return estimates based on exchange type
switch exchangeType {
case math.ExchangeUniswapV3, math.ExchangeCamelot:
return 200000, nil // Concentrated liquidity swaps
case math.ExchangeUniswapV2, math.ExchangeSushiSwap:
return 150000, nil // Simple AMM swaps
case math.ExchangeBalancer:
return 250000, nil // Weighted pool swaps
case math.ExchangeCurve:
return 180000, nil // Stable swaps
default:
return 200000, nil // Default estimate
}
}
func (w *GasEstimatorWrapper) EstimateFlashSwapGas(route []*math.PoolData) (uint64, error) {
baseGas := uint64(300000) // Base flash swap overhead
gasPerHop := uint64(150000) // Additional gas per hop
return baseGas + gasPerHop*uint64(len(route)), nil
}
func (w *GasEstimatorWrapper) GetCurrentGasPrice() (*math.UniversalDecimal, error) {
// Return a mock gas price - production would get from the gas estimator
dc := math.NewDecimalConverter()
gasPrice, _ := dc.FromString("0.1", 9, "GWEI") // 0.1 gwei
return gasPrice, nil
}