package slippage import ( "context" "fmt" "math/big" "time" "github.com/ethereum/go-ethereum/common" "github.com/fraktal/mev-beta/internal/logger" "github.com/fraktal/mev-beta/pkg/oracle" ) // SlippageProtection provides comprehensive slippage calculation and protection type SlippageProtection struct { logger *logger.Logger oracle *oracle.PriceOracle maxSlippageBps *big.Int // Maximum allowed slippage in basis points impactThresholds map[string]*big.Int // Pool size -> impact threshold } // SlippageConfig represents slippage protection configuration type SlippageConfig struct { MaxSlippageBps *big.Int // Maximum slippage (basis points) ImpactThresholds map[string]*big.Int // Pool size thresholds EmergencySlippageBps *big.Int // Emergency max slippage TimeoutSeconds int // Price check timeout RevertOnHighSlippage bool // Whether to revert on high slippage } // SlippageResult contains the result of slippage analysis type SlippageResult struct { EstimatedSlippageBps *big.Int // Estimated slippage in basis points MaxAllowedAmountOut *big.Int // Maximum amount out considering slippage MinRequiredAmountOut *big.Int // Minimum amount out required PriceImpactBps *big.Int // Price impact in basis points RecommendedGasPrice *big.Int // Recommended gas price for execution SafeToExecute bool // Whether trade is safe to execute WarningMessages []string // Any warnings about the trade EmergencyStop bool // Whether to emergency stop Timestamp time.Time // When analysis was performed } // TradeParams represents the parameters for a trade type TradeParams struct { TokenIn common.Address TokenOut common.Address AmountIn *big.Int PoolAddress common.Address Fee *big.Int // Pool fee tier Deadline uint64 // Transaction deadline Recipient common.Address } // NewSlippageProtection creates a new slippage protection instance func NewSlippageProtection(logger *logger.Logger, oracle *oracle.PriceOracle, config *SlippageConfig) *SlippageProtection { if config == nil { config = &SlippageConfig{ MaxSlippageBps: big.NewInt(500), // 5% default EmergencySlippageBps: big.NewInt(1000), // 10% emergency TimeoutSeconds: 10, RevertOnHighSlippage: true, ImpactThresholds: getDefaultImpactThresholds(), } } return &SlippageProtection{ logger: logger, oracle: oracle, maxSlippageBps: config.MaxSlippageBps, impactThresholds: config.ImpactThresholds, } } // AnalyzeSlippage performs comprehensive slippage analysis for a trade func (sp *SlippageProtection) AnalyzeSlippage(ctx context.Context, params *TradeParams) (*SlippageResult, error) { result := &SlippageResult{ Timestamp: time.Now(), WarningMessages: make([]string, 0), } // 1. Get current price from oracle priceReq := &oracle.PriceRequest{ TokenIn: params.TokenIn, TokenOut: params.TokenOut, AmountIn: params.AmountIn, Timestamp: time.Now(), } priceResp, err := sp.oracle.GetPrice(ctx, priceReq) if err != nil { return nil, fmt.Errorf("failed to get price from oracle: %w", err) } if !priceResp.Valid { return nil, fmt.Errorf("oracle returned invalid price") } // 2. Calculate estimated slippage result.EstimatedSlippageBps = priceResp.SlippageBps // 3. Calculate price impact priceImpact, err := sp.calculatePriceImpact(params, priceResp) if err != nil { sp.logger.Warn(fmt.Sprintf("Failed to calculate price impact: %v", err)) priceImpact = big.NewInt(0) } result.PriceImpactBps = priceImpact // 4. Calculate minimum amount out with slippage protection result.MinRequiredAmountOut = sp.calculateMinAmountOut(priceResp.AmountOut, result.EstimatedSlippageBps) result.MaxAllowedAmountOut = priceResp.AmountOut // 5. Check if trade is safe to execute result.SafeToExecute, result.WarningMessages = sp.evaluateTradeSafety(result) // 6. Set emergency stop if needed result.EmergencyStop = sp.shouldEmergencyStop(result) // 7. Recommend gas price based on urgency and slippage result.RecommendedGasPrice = sp.recommendGasPrice(result) sp.logger.Debug(fmt.Sprintf("Slippage analysis: slippage=%s bps, impact=%s bps, safe=%v, emergency=%v", result.EstimatedSlippageBps.String(), result.PriceImpactBps.String(), result.SafeToExecute, result.EmergencyStop)) return result, nil } // calculatePriceImpact calculates the price impact of a trade on the pool func (sp *SlippageProtection) calculatePriceImpact(params *TradeParams, priceResp *oracle.PriceResponse) (*big.Int, error) { // Price impact calculation for Uniswap V3: // Impact = (amount_in / pool_liquidity) * price_sensitivity_factor // For simplified calculation, we use the amount as percentage of typical pool size // In practice, you'd need to: // 1. Get actual pool liquidity from the pool contract // 2. Calculate exact price impact using the constant product formula // 3. Account for concentrated liquidity in V3 // Simplified calculation: impact proportional to trade size // Assume typical pool has 1M USD liquidity typicalPoolSizeUSD := big.NewInt(1000000) // $1M // Convert amount to USD (simplified - assumes $1 per token unit) amountFloat := new(big.Float).Quo(new(big.Float).SetInt(params.AmountIn), big.NewFloat(1e18)) amountUSD, _ := amountFloat.Float64() amountUSDBig := big.NewInt(int64(amountUSD)) // Price impact = (amountUSD / poolSizeUSD) * 10000 (to get basis points) if typicalPoolSizeUSD.Sign() == 0 { return big.NewInt(0), nil } impact := new(big.Int).Mul(amountUSDBig, big.NewInt(10000)) impact.Div(impact, typicalPoolSizeUSD) // Cap impact at reasonable maximum (50% = 5000 bps) maxImpact := big.NewInt(5000) if impact.Cmp(maxImpact) > 0 { impact = maxImpact } return impact, nil } // calculateMinAmountOut calculates minimum amount out considering slippage func (sp *SlippageProtection) calculateMinAmountOut(expectedOut, slippageBps *big.Int) *big.Int { if expectedOut.Sign() == 0 || slippageBps.Sign() == 0 { return expectedOut } // minAmountOut = expectedOut * (10000 - slippageBps) / 10000 slippageMultiplier := new(big.Int).Sub(big.NewInt(10000), slippageBps) minAmount := new(big.Int).Mul(expectedOut, slippageMultiplier) minAmount.Div(minAmount, big.NewInt(10000)) return minAmount } // evaluateTradeSafety evaluates whether a trade is safe to execute func (sp *SlippageProtection) evaluateTradeSafety(result *SlippageResult) (bool, []string) { warnings := make([]string, 0) safe := true // Check slippage against maximum allowed if result.EstimatedSlippageBps.Cmp(sp.maxSlippageBps) > 0 { safe = false warnings = append(warnings, fmt.Sprintf("Slippage %s bps exceeds maximum %s bps", result.EstimatedSlippageBps.String(), sp.maxSlippageBps.String())) } // Check price impact highImpactThreshold := big.NewInt(1000) // 10% if result.PriceImpactBps.Cmp(highImpactThreshold) > 0 { warnings = append(warnings, fmt.Sprintf("High price impact detected: %s bps", result.PriceImpactBps.String())) // Don't fail the trade for high impact, but warn if result.PriceImpactBps.Cmp(big.NewInt(2000)) > 0 { // 20% safe = false } } // Check for extremely high slippage (possible market manipulation) extremeSlippageThreshold := big.NewInt(2000) // 20% if result.EstimatedSlippageBps.Cmp(extremeSlippageThreshold) > 0 { safe = false warnings = append(warnings, "EXTREME slippage detected - possible market manipulation") } // Check if amounts are reasonable if result.MinRequiredAmountOut.Sign() == 0 { safe = false warnings = append(warnings, "Minimum amount out is zero - trade would result in total loss") } return safe, warnings } // shouldEmergencyStop determines if an emergency stop should be triggered func (sp *SlippageProtection) shouldEmergencyStop(result *SlippageResult) bool { // Emergency stop conditions: // 1. Extreme slippage (>30%) if result.EstimatedSlippageBps.Cmp(big.NewInt(3000)) > 0 { return true } // 2. Extreme price impact (>50%) if result.PriceImpactBps.Cmp(big.NewInt(5000)) > 0 { return true } // 3. Total loss scenario if result.MinRequiredAmountOut.Sign() == 0 { return true } return false } // recommendGasPrice recommends gas price based on trade urgency and slippage func (sp *SlippageProtection) recommendGasPrice(result *SlippageResult) *big.Int { baseGasPrice := big.NewInt(20e9) // 20 gwei base // Increase gas price for high slippage trades (need faster execution) if result.EstimatedSlippageBps.Cmp(big.NewInt(300)) > 0 { // >3% slippage // Increase by 50% multiplier := big.NewInt(150) baseGasPrice.Mul(baseGasPrice, multiplier) baseGasPrice.Div(baseGasPrice, big.NewInt(100)) } // Increase for high price impact (competitive trades) if result.PriceImpactBps.Cmp(big.NewInt(500)) > 0 { // >5% impact // Additional 25% increase multiplier := big.NewInt(125) baseGasPrice.Mul(baseGasPrice, multiplier) baseGasPrice.Div(baseGasPrice, big.NewInt(100)) } // Cap at reasonable maximum (200 gwei) maxGasPrice := big.NewInt(200e9) if baseGasPrice.Cmp(maxGasPrice) > 0 { baseGasPrice = maxGasPrice } return baseGasPrice } // ValidateSlippageTolerance validates if the provided slippage tolerance is reasonable func (sp *SlippageProtection) ValidateSlippageTolerance(slippageBps *big.Int) error { if slippageBps.Sign() < 0 { return fmt.Errorf("slippage tolerance cannot be negative") } if slippageBps.Cmp(big.NewInt(10000)) >= 0 { return fmt.Errorf("slippage tolerance cannot be 100%% or more") } // Warn about very high slippage tolerance if slippageBps.Cmp(big.NewInt(1000)) > 0 { // >10% sp.logger.Warn(fmt.Sprintf("Very high slippage tolerance: %s bps", slippageBps.String())) } return nil } // CalculateOptimalSlippage calculates optimal slippage based on market conditions func (sp *SlippageProtection) CalculateOptimalSlippage(ctx context.Context, params *TradeParams) (*big.Int, error) { // Factors affecting optimal slippage: // 1. Pool size and liquidity // 2. Recent volatility // 3. Trade size // 4. Time of day / market hours // 5. Network congestion baseSlippage := big.NewInt(50) // 0.5% base // Adjust for trade size (larger trades need more slippage protection) tradeValue := new(big.Float).Quo(new(big.Float).SetInt(params.AmountIn), big.NewFloat(1e18)) tradeValueFloat, _ := tradeValue.Float64() if tradeValueFloat > 100000 { // >$100k trade baseSlippage = big.NewInt(200) // 2% } else if tradeValueFloat > 10000 { // >$10k trade baseSlippage = big.NewInt(100) // 1% } // TODO: Add more sophisticated calculation based on: // - Historical volatility analysis // - Pool liquidity depth // - Network congestion metrics // - Time-based volatility patterns return baseSlippage, nil } // getDefaultImpactThresholds returns default price impact thresholds func getDefaultImpactThresholds() map[string]*big.Int { return map[string]*big.Int{ "small": big.NewInt(100), // 1% for small pools "medium": big.NewInt(500), // 5% for medium pools "large": big.NewInt(1000), // 10% for large pools } } // MonitorSlippage continuously monitors slippage for active trades func (sp *SlippageProtection) MonitorSlippage(ctx context.Context, params *TradeParams, interval time.Duration) (<-chan *SlippageResult, error) { results := make(chan *SlippageResult, 10) go func() { defer close(results) ticker := time.NewTicker(interval) defer ticker.Stop() for { select { case <-ctx.Done(): return case <-ticker.C: result, err := sp.AnalyzeSlippage(ctx, params) if err != nil { sp.logger.Error(fmt.Sprintf("Slippage monitoring error: %v", err)) continue } select { case results <- result: case <-ctx.Done(): return default: // Channel full, skip this update } } } }() return results, nil }