Files
mev-beta/pkg/profitcalc/profit_calc.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

410 lines
14 KiB
Go

package profitcalc
import (
"context"
"fmt"
"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/security"
)
// ProfitCalculator provides sophisticated arbitrage profit estimation with slippage protection and multi-DEX price feeds
type ProfitCalculator struct {
logger *logger.Logger
minProfitThreshold *big.Int // Minimum profit in wei to consider viable
maxSlippage float64 // Maximum slippage tolerance (e.g., 0.03 for 3%)
gasPrice *big.Int // Current gas price
gasLimit uint64 // Estimated gas limit for arbitrage
client *ethclient.Client // Ethereum client for gas price updates
gasPriceMutex sync.RWMutex // Protects gas price updates
lastGasPriceUpdate time.Time // Last time gas price was updated
gasPriceUpdateInterval time.Duration // How often to update gas prices
priceFeed *PriceFeed // Multi-DEX price feed
slippageProtector *SlippageProtector // Slippage analysis and protection
}
// SimpleOpportunity represents a basic arbitrage opportunity
type SimpleOpportunity struct {
ID string
Timestamp time.Time
TokenA common.Address
TokenB common.Address
AmountIn *big.Float
AmountOut *big.Float
PriceDifference *big.Float
EstimatedProfit *big.Float
GasCost *big.Float
NetProfit *big.Float
ProfitMargin float64
IsExecutable bool
RejectReason string
Confidence float64
// Enhanced fields for slippage analysis
SlippageAnalysis *SlippageAnalysis // Detailed slippage analysis
SlippageRisk string // Risk level: "Low", "Medium", "High", "Extreme"
EffectivePrice *big.Float // Price after slippage
MinAmountOut *big.Float // Minimum amount out with slippage protection
}
// NewProfitCalculator creates a new simplified profit calculator
func NewProfitCalculator(logger *logger.Logger) *ProfitCalculator {
return &ProfitCalculator{
logger: logger,
minProfitThreshold: big.NewInt(10000000000000000), // 0.01 ETH minimum (more realistic)
maxSlippage: 0.03, // 3% max slippage
gasPrice: big.NewInt(1000000000), // 1 gwei default
gasLimit: 200000, // 200k gas for simple arbitrage
gasPriceUpdateInterval: 30 * time.Second, // Update gas price every 30 seconds
slippageProtector: NewSlippageProtector(logger), // Initialize slippage protection
}
}
// NewProfitCalculatorWithClient creates a profit calculator with Ethereum client for gas price updates
func NewProfitCalculatorWithClient(logger *logger.Logger, client *ethclient.Client) *ProfitCalculator {
calc := NewProfitCalculator(logger)
calc.client = client
// Initialize price feed if client is provided
if client != nil {
calc.priceFeed = NewPriceFeed(logger, client)
calc.priceFeed.Start()
// Start gas price updater
go calc.startGasPriceUpdater()
}
return calc
}
// AnalyzeSwapOpportunity analyzes a swap event for potential arbitrage profit
func (spc *ProfitCalculator) AnalyzeSwapOpportunity(
ctx context.Context,
tokenA, tokenB common.Address,
amountIn, amountOut *big.Float,
protocol string,
) *SimpleOpportunity {
opportunity := &SimpleOpportunity{
ID: fmt.Sprintf("arb_%d_%s", time.Now().Unix(), tokenA.Hex()[:8]),
Timestamp: time.Now(),
TokenA: tokenA,
TokenB: tokenB,
AmountIn: amountIn,
AmountOut: amountOut,
IsExecutable: false,
Confidence: 0.0,
}
// Calculate profit using multi-DEX price comparison if available
if amountIn.Sign() > 0 && amountOut.Sign() > 0 {
// Try to get real arbitrage opportunity from price feeds
var grossProfit *big.Float
var priceDiff *big.Float
if spc.priceFeed != nil {
// Get arbitrage route using real price data
arbitrageRoute := spc.priceFeed.GetBestArbitrageOpportunity(tokenA, tokenB, amountIn)
if arbitrageRoute != nil && arbitrageRoute.SpreadBps > 50 { // Minimum 50 bps spread
grossProfit = arbitrageRoute.GrossProfit
priceDiff = new(big.Float).Sub(arbitrageRoute.SellPrice, arbitrageRoute.BuyPrice)
opportunity.PriceDifference = priceDiff
spc.logger.Debug(fmt.Sprintf("Real arbitrage opportunity found: %s -> %s, Spread: %d bps, Profit: %s",
arbitrageRoute.BuyDEX, arbitrageRoute.SellDEX, arbitrageRoute.SpreadBps, grossProfit.String()))
} else {
// No profitable arbitrage found with real prices
grossProfit = big.NewFloat(0)
priceDiff = big.NewFloat(0)
}
} else {
// Fallback to simplified calculation
price := new(big.Float).Quo(amountOut, amountIn)
priceDiff = new(big.Float).Mul(price, big.NewFloat(0.01))
grossProfit = new(big.Float).Mul(amountOut, big.NewFloat(0.005))
}
opportunity.PriceDifference = priceDiff
opportunity.EstimatedProfit = grossProfit
// Perform slippage analysis if we have sufficient data
var slippageAnalysis *SlippageAnalysis
var adjustedProfit *big.Float = grossProfit
if spc.priceFeed != nil {
// Get price data for slippage calculation
multiPrice := spc.priceFeed.GetMultiDEXPrice(tokenA, tokenB)
if multiPrice != nil && len(multiPrice.Prices) > 0 {
// Use average liquidity from available pools
totalLiquidity := big.NewFloat(0)
for _, price := range multiPrice.Prices {
totalLiquidity.Add(totalLiquidity, price.Liquidity)
}
avgLiquidity := new(big.Float).Quo(totalLiquidity, big.NewFloat(float64(len(multiPrice.Prices))))
// Calculate current price
currentPrice := new(big.Float).Quo(amountOut, amountIn)
// Perform slippage analysis
slippageAnalysis = spc.slippageProtector.AnalyzeSlippage(amountIn, avgLiquidity, currentPrice)
if slippageAnalysis != nil {
opportunity.SlippageAnalysis = slippageAnalysis
opportunity.SlippageRisk = slippageAnalysis.RiskLevel
opportunity.EffectivePrice = slippageAnalysis.EffectivePrice
opportunity.MinAmountOut = slippageAnalysis.MinAmountOut
// Adjust profit for slippage
adjustedProfit = spc.slippageProtector.CalculateSlippageAdjustedProfit(grossProfit, slippageAnalysis)
spc.logger.Debug(fmt.Sprintf("Slippage analysis for %s: Risk=%s, Slippage=%.2f%%, Adjusted Profit=%s",
opportunity.ID, slippageAnalysis.RiskLevel, slippageAnalysis.EstimatedSlippage*100, adjustedProfit.String()))
}
}
} else {
// Fallback slippage estimation without real data
slippageEst := 0.005 // Assume 0.5% slippage
slippageReduction := new(big.Float).Mul(grossProfit, big.NewFloat(slippageEst))
adjustedProfit = new(big.Float).Sub(grossProfit, slippageReduction)
opportunity.SlippageRisk = "Medium" // Default to medium risk
}
// Calculate gas cost (potentially adjusted for slippage complexity)
gasCost := spc.calculateGasCost()
if slippageAnalysis != nil {
additionalGas := spc.slippageProtector.EstimateGasForSlippage(slippageAnalysis)
if additionalGas > 0 {
additionalGasInt64, err := security.SafeUint64ToInt64(additionalGas)
if err != nil {
spc.logger.Error("Additional gas exceeds int64 maximum", "additionalGas", additionalGas, "error", err)
// Use maximum safe value as fallback
additionalGasInt64 = math.MaxInt64
}
extraGasCost := new(big.Int).Mul(spc.GetCurrentGasPrice(), big.NewInt(additionalGasInt64))
extraGasCostFloat := new(big.Float).Quo(new(big.Float).SetInt(extraGasCost), big.NewFloat(1e18))
gasCost.Add(gasCost, extraGasCostFloat)
}
}
opportunity.GasCost = gasCost
// Net profit = Adjusted profit - Gas cost
netProfit := new(big.Float).Sub(adjustedProfit, gasCost)
opportunity.NetProfit = netProfit
// Calculate profit margin
if amountOut.Sign() > 0 && amountOut.Cmp(big.NewFloat(0)) != 0 {
profitMargin := new(big.Float).Quo(netProfit, amountOut)
profitMarginFloat, _ := profitMargin.Float64()
// Ensure the profit margin is reasonable and not extremely high
if profitMarginFloat > 1.0 { // 100% profit margin is unrealistic
opportunity.ProfitMargin = 0.0 // Set to 0 if profit margin is unrealistic
opportunity.IsExecutable = false
opportunity.RejectReason = "unrealistic profit margin"
opportunity.Confidence = 0.0
} else {
opportunity.ProfitMargin = profitMarginFloat
}
}
// Determine if executable (considering both profit and slippage risk)
if netProfit.Sign() > 0 {
netProfitWei, _ := netProfit.Int(nil)
if netProfitWei.Cmp(spc.minProfitThreshold) >= 0 {
// Check slippage risk
if opportunity.SlippageRisk == "Extreme" {
opportunity.IsExecutable = false
opportunity.RejectReason = "extreme slippage risk"
opportunity.Confidence = 0.1
} else if slippageAnalysis != nil && !slippageAnalysis.IsAcceptable {
opportunity.IsExecutable = false
opportunity.RejectReason = fmt.Sprintf("slippage too high: %s", slippageAnalysis.Recommendation)
opportunity.Confidence = 0.2
} else {
opportunity.IsExecutable = true
opportunity.Confidence = spc.calculateConfidence(opportunity)
opportunity.RejectReason = ""
}
} else {
opportunity.IsExecutable = false
opportunity.RejectReason = "profit below minimum threshold"
opportunity.Confidence = 0.3
}
} else {
opportunity.IsExecutable = false
opportunity.RejectReason = "negative profit after gas and slippage costs"
opportunity.Confidence = 0.1
}
} else {
opportunity.IsExecutable = false
opportunity.RejectReason = "invalid swap amounts"
opportunity.Confidence = 0.0
}
spc.logger.Debug(fmt.Sprintf("Analyzed arbitrage opportunity: ID=%s, NetProfit=%s ETH, Executable=%t, Reason=%s",
opportunity.ID,
spc.FormatEther(opportunity.NetProfit),
opportunity.IsExecutable,
opportunity.RejectReason,
))
return opportunity
}
// calculateGasCost estimates the gas cost for an arbitrage transaction
func (spc *ProfitCalculator) calculateGasCost() *big.Float {
// Gas cost = Gas price * Gas limit
// Gas cost = Gas price * Gas limit
gasLimitInt64, err := security.SafeUint64ToInt64(spc.gasLimit)
if err != nil {
spc.logger.Error("Gas limit exceeds int64 maximum", "gasLimit", spc.gasLimit, "error", err)
// Use maximum safe value as fallback
gasLimitInt64 = math.MaxInt64
}
gasLimit := big.NewInt(gasLimitInt64)
currentGasPrice := spc.GetCurrentGasPrice()
gasCostWei := new(big.Int).Mul(currentGasPrice, gasLimit)
// Add 20% buffer for MEV competition
buffer := new(big.Int).Div(gasCostWei, big.NewInt(5)) // 20%
gasCostWei.Add(gasCostWei, buffer)
// Convert to big.Float for easier calculation
gasCostFloat := new(big.Float).SetInt(gasCostWei)
// Convert from wei to ether
etherDenominator := new(big.Float).SetInt(big.NewInt(1e18))
return new(big.Float).Quo(gasCostFloat, etherDenominator)
}
// calculateConfidence calculates a confidence score for the opportunity
func (spc *ProfitCalculator) calculateConfidence(opp *SimpleOpportunity) float64 {
confidence := 0.0
// Base confidence for positive profit
if opp.NetProfit != nil && opp.NetProfit.Sign() > 0 {
confidence += 0.4
}
// Confidence based on profit margin
if opp.ProfitMargin > 0.02 { // > 2% margin
confidence += 0.3
} else if opp.ProfitMargin > 0.01 { // > 1% margin
confidence += 0.2
} else if opp.ProfitMargin > 0.005 { // > 0.5% margin
confidence += 0.1
}
// Confidence based on trade size (larger trades = more confidence)
if opp.AmountOut != nil {
amountOutFloat, _ := opp.AmountOut.Float64()
if amountOutFloat > 1000 { // > $1000 equivalent
confidence += 0.2
} else if amountOutFloat > 100 { // > $100 equivalent
confidence += 0.1
}
}
// Cap at 1.0
if confidence > 1.0 {
confidence = 1.0
}
return confidence
}
// FormatEther formats a big.Float ether amount to string (public method)
func (spc *ProfitCalculator) FormatEther(ether *big.Float) string {
if ether == nil {
return "0.000000"
}
return fmt.Sprintf("%.6f", ether)
}
// UpdateGasPrice updates the current gas price for calculations
func (spc *ProfitCalculator) UpdateGasPrice(gasPrice *big.Int) {
spc.gasPriceMutex.Lock()
defer spc.gasPriceMutex.Unlock()
spc.gasPrice = gasPrice
spc.lastGasPriceUpdate = time.Now()
spc.logger.Debug(fmt.Sprintf("Updated gas price to %s gwei",
new(big.Float).Quo(new(big.Float).SetInt(gasPrice), big.NewFloat(1e9))))
}
// GetCurrentGasPrice gets the current gas price (thread-safe)
func (spc *ProfitCalculator) GetCurrentGasPrice() *big.Int {
spc.gasPriceMutex.RLock()
defer spc.gasPriceMutex.RUnlock()
return new(big.Int).Set(spc.gasPrice)
}
// startGasPriceUpdater starts a background goroutine to update gas prices
func (spc *ProfitCalculator) startGasPriceUpdater() {
ticker := time.NewTicker(spc.gasPriceUpdateInterval)
defer ticker.Stop()
spc.logger.Info(fmt.Sprintf("Starting gas price updater with %s interval", spc.gasPriceUpdateInterval))
// Update gas price immediately on start
spc.updateGasPriceFromNetwork()
for range ticker.C {
spc.updateGasPriceFromNetwork()
}
}
// updateGasPriceFromNetwork fetches current gas price from the network
func (spc *ProfitCalculator) updateGasPriceFromNetwork() {
if spc.client == nil {
return
}
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
gasPrice, err := spc.client.SuggestGasPrice(ctx)
if err != nil {
spc.logger.Debug(fmt.Sprintf("Failed to fetch gas price from network: %v", err))
return
}
// Add MEV priority fee (50% boost for competitive transactions)
mevGasPrice := new(big.Int).Mul(gasPrice, big.NewInt(150))
mevGasPrice.Div(mevGasPrice, big.NewInt(100))
spc.UpdateGasPrice(mevGasPrice)
}
// SetMinProfitThreshold sets the minimum profit threshold
func (spc *ProfitCalculator) SetMinProfitThreshold(threshold *big.Int) {
spc.minProfitThreshold = threshold
spc.logger.Info(fmt.Sprintf("Updated minimum profit threshold to %s ETH",
new(big.Float).Quo(new(big.Float).SetInt(threshold), big.NewFloat(1e18))))
}
// GetPriceFeedStats returns statistics about the price feed
func (spc *ProfitCalculator) GetPriceFeedStats() map[string]interface{} {
if spc.priceFeed != nil {
return spc.priceFeed.GetPriceStats()
}
return map[string]interface{}{
"status": "price feed not available",
}
}
// HasPriceFeed returns true if the calculator has an active price feed
func (spc *ProfitCalculator) HasPriceFeed() bool {
return spc.priceFeed != nil
}
// Stop gracefully shuts down the profit calculator
func (spc *ProfitCalculator) Stop() {
if spc.priceFeed != nil {
spc.priceFeed.Stop()
spc.logger.Info("Price feed stopped")
}
}