removed the fucking vendor files
This commit is contained in:
360
pkg/slippage/protection.go
Normal file
360
pkg/slippage/protection.go
Normal file
@@ -0,0 +1,360 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user