Files
mev-beta/pkg/exchanges/dex_aggregator.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

380 lines
14 KiB
Go

package exchanges
import (
"fmt"
"math/big"
"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/math"
)
// DexAggregatorPoolDetector implements PoolDetector for DEX aggregators
type DexAggregatorPoolDetector struct {
client *ethclient.Client
logger *logger.Logger
config *ExchangeConfig
registry *ExchangeRegistry
}
// NewDexAggregatorPoolDetector creates a new DEX aggregator pool detector
func NewDexAggregatorPoolDetector(client *ethclient.Client, logger *logger.Logger, config *ExchangeConfig, registry *ExchangeRegistry) *DexAggregatorPoolDetector {
return &DexAggregatorPoolDetector{
client: client,
logger: logger,
config: config,
registry: registry,
}
}
// GetAllPools returns all pools containing the specified tokens across all exchanges
func (d *DexAggregatorPoolDetector) GetAllPools(token0, token1 common.Address) ([]common.Address, error) {
// This would aggregate pools from all registered exchanges
var allPools []common.Address
// Get all supported exchanges for this token pair
exchanges := d.registry.GetExchangesForPair(token0, token1)
for _, exchangeConfig := range exchanges {
// Get the pool detector for this exchange
poolDetector := d.registry.GetPoolDetector(exchangeConfig.Type)
if poolDetector != nil {
pools, err := poolDetector.GetAllPools(token0, token1)
if err != nil {
d.logger.Warn(fmt.Sprintf("Error getting pools from exchange %s: %v", exchangeConfig.Name, err))
continue
}
allPools = append(allPools, pools...)
}
}
return allPools, nil
}
// GetPoolForPair returns the best pool address for a specific token pair across exchanges
func (d *DexAggregatorPoolDetector) GetPoolForPair(token0, token1 common.Address) (common.Address, error) {
// In a real implementation, this would find the pool with best pricing across exchanges
// For now, return a placeholder address
return common.HexToAddress("0x0"), nil
}
// GetSupportedFeeTiers returns supported fee tiers for the aggregated exchanges
func (d *DexAggregatorPoolDetector) GetSupportedFeeTiers() []int64 {
// Return a range that covers all possible fee tiers across exchanges
return []int64{100, 250, 300, 500, 1000, 2500, 3000, 5000, 10000} // Various fee tiers
}
// GetPoolType returns the pool type
func (d *DexAggregatorPoolDetector) GetPoolType() string {
return "dex_aggregator"
}
// DexAggregatorLiquidityFetcher implements LiquidityFetcher for DEX aggregators
type DexAggregatorLiquidityFetcher struct {
client *ethclient.Client
logger *logger.Logger
config *ExchangeConfig
registry *ExchangeRegistry
engine *math.ExchangePricingEngine
}
// NewDexAggregatorLiquidityFetcher creates a new DEX aggregator liquidity fetcher
func NewDexAggregatorLiquidityFetcher(client *ethclient.Client, logger *logger.Logger, config *ExchangeConfig, registry *ExchangeRegistry, engine *math.ExchangePricingEngine) *DexAggregatorLiquidityFetcher {
return &DexAggregatorLiquidityFetcher{
client: client,
logger: logger,
config: config,
registry: registry,
engine: engine,
}
}
// GetPoolData fetches pool information from the best available exchange for the token pair
func (f *DexAggregatorLiquidityFetcher) GetPoolData(poolAddress common.Address) (*math.PoolData, error) {
// For aggregator, this would depend on which exchange's pool is being represented
// For this implementation, we'll return placeholder data
return nil, fmt.Errorf("GetPoolData not directly supported for aggregator, use GetBestPoolData instead")
}
// GetTokenReserves fetches reserves from the best available exchange for a token pair
func (f *DexAggregatorLiquidityFetcher) GetTokenReserves(poolAddress, token0, token1 common.Address) (*big.Int, *big.Int, error) {
// This would aggregate reserves across exchanges
// For now, return placeholder values
return big.NewInt(0), big.NewInt(0), fmt.Errorf("not implemented for aggregator directly")
}
// GetBestPoolData fetches the best pool data across all exchanges for a token pair
func (f *DexAggregatorLiquidityFetcher) GetBestPoolData(token0, token1 common.Address) (*math.PoolData, math.ExchangeType, error) {
// Get all supported exchanges for this token pair
exchanges := f.registry.GetExchangesForPair(token0, token1)
bestAmountOut := big.NewInt(0)
var bestPoolData *math.PoolData
var bestExchangeType math.ExchangeType
for _, exchangeConfig := range exchanges {
// Get the liquidity fetcher for this exchange
liquidityFetcher := f.registry.GetLiquidityFetcher(exchangeConfig.Type)
if liquidityFetcher != nil {
// Get pool data for this exchange
poolData, err := liquidityFetcher.GetPoolData(common.HexToAddress(exchangeConfig.FactoryAddress.Hex())) // This is a simplification
if err != nil {
f.logger.Warn(fmt.Sprintf("Error getting pool data from exchange %s: %v", exchangeConfig.Name, err))
continue
}
// Calculate amount out for a standard amount to compare
testAmount := big.NewInt(1000000000000000000) // 1 ETH equivalent
decimalAmountIn, err := math.NewUniversalDecimal(testAmount, 18, "AMOUNT_IN")
if err != nil {
f.logger.Warn(fmt.Sprintf("Error creating decimal amount: %v", err))
continue
}
pricer, err := f.engine.GetExchangePricer(poolData.ExchangeType)
if err != nil {
continue
}
amountOut, err := pricer.CalculateAmountOut(decimalAmountIn, poolData)
if err != nil {
continue
}
// Check if this is the best rate
if amountOut.Value.Cmp(bestAmountOut) > 0 {
bestAmountOut = amountOut.Value
bestPoolData = poolData
bestExchangeType = poolData.ExchangeType
}
}
}
if bestPoolData == nil {
return nil, "", fmt.Errorf("no pools found for token pair")
}
return bestPoolData, bestExchangeType, nil
}
// GetPoolPrice calculates the best price of token1 in terms of token0 across exchanges
func (f *DexAggregatorLiquidityFetcher) GetPoolPrice(poolAddress common.Address) (*big.Float, error) {
// For an aggregator, we'd need to look up which exchange the pool belongs to
// For this simplified version, return an error
return nil, fmt.Errorf("GetPoolPrice not directly supported for aggregator, use GetBestPoolPrice instead")
}
// GetBestPoolPrice calculates the best price across all exchanges for a token pair
func (f *DexAggregatorLiquidityFetcher) GetBestPoolPrice(token0, token1 common.Address) (*big.Float, math.ExchangeType, error) {
poolData, exchangeType, err := f.GetBestPoolData(token0, token1)
if err != nil {
return nil, "", err
}
pricer, err := f.engine.GetExchangePricer(poolData.ExchangeType)
if err != nil {
return nil, "", err
}
spotPrice, err := pricer.GetSpotPrice(poolData)
if err != nil {
return nil, "", err
}
// Convert the UniversalDecimal Value to a *big.Float
result := new(big.Float).SetInt(spotPrice.Value)
return result, exchangeType, nil
}
// GetLiquidityDepth calculates the liquidity depth across all exchanges
func (f *DexAggregatorLiquidityFetcher) GetLiquidityDepth(poolAddress, tokenIn common.Address, amount *big.Int) (*big.Int, error) {
// This would aggregate liquidity across exchanges
// For now, return placeholder
return amount, nil
}
// DexAggregatorSwapRouter implements SwapRouter for DEX aggregators
type DexAggregatorSwapRouter struct {
client *ethclient.Client
logger *logger.Logger
config *ExchangeConfig
registry *ExchangeRegistry
engine *math.ExchangePricingEngine
}
// NewDexAggregatorSwapRouter creates a new DEX aggregator swap router
func NewDexAggregatorSwapRouter(client *ethclient.Client, logger *logger.Logger, config *ExchangeConfig, registry *ExchangeRegistry, engine *math.ExchangePricingEngine) *DexAggregatorSwapRouter {
return &DexAggregatorSwapRouter{
client: client,
logger: logger,
config: config,
registry: registry,
engine: engine,
}
}
// CalculateSwap calculates the expected best output amount across all exchanges
func (r *DexAggregatorSwapRouter) CalculateSwap(tokenIn, tokenOut common.Address, amountIn *big.Int) (*big.Int, error) {
// Get the best available rate across all exchanges
exchanges := r.registry.GetExchangesForPair(tokenIn, tokenOut)
bestAmountOut := big.NewInt(0)
for _, exchangeConfig := range exchanges {
// Get the swap router for this exchange
swapRouter := r.registry.GetSwapRouter(exchangeConfig.Type)
if swapRouter != nil {
amountOut, err := swapRouter.CalculateSwap(tokenIn, tokenOut, amountIn)
if err != nil {
r.logger.Warn(fmt.Sprintf("Error calculating swap on exchange %s: %v", exchangeConfig.Name, err))
continue
}
// Check if this is the best rate
if amountOut.Cmp(bestAmountOut) > 0 {
bestAmountOut = amountOut
}
}
}
if bestAmountOut.Sign() <= 0 {
return nil, fmt.Errorf("no favorable swap route found across exchanges")
}
return bestAmountOut, nil
}
// CalculateMultiSwap calculates the output amount considering multiple exchanges and routing
func (r *DexAggregatorSwapRouter) CalculateMultiSwap(tokenIn, tokenOut common.Address, amountIn *big.Int) (*big.Int, []math.ExchangeType, error) {
// Find best path across exchanges
exchanges := r.registry.GetExchangesForPair(tokenIn, tokenOut)
bestAmountOut := big.NewInt(0)
var bestRoutes []math.ExchangeType
for _, exchangeConfig := range exchanges {
// Get the swap router for this exchange
swapRouter := r.registry.GetSwapRouter(exchangeConfig.Type)
if swapRouter != nil {
amountOut, err := swapRouter.CalculateSwap(tokenIn, tokenOut, amountIn)
if err != nil {
r.logger.Warn(fmt.Sprintf("Error calculating swap on exchange %s: %v", exchangeConfig.Name, err))
continue
}
// Check if this is the best rate
if amountOut.Cmp(bestAmountOut) > 0 {
bestAmountOut = amountOut
bestRoutes = []math.ExchangeType{exchangeConfig.Type}
}
}
}
if bestAmountOut.Sign() <= 0 {
return nil, nil, fmt.Errorf("no favorable swap route found across exchanges")
}
return bestAmountOut, bestRoutes, nil
}
// GenerateSwapData generates the calldata for an aggregated swap transaction
func (r *DexAggregatorSwapRouter) GenerateSwapData(tokenIn, tokenOut common.Address, amountIn, minAmountOut *big.Int, deadline *big.Int) ([]byte, error) {
// In a real implementation, this would generate data for a complex multi-exchange swap
// For now, return placeholder
return []byte{}, nil
}
// GetSwapRoute returns the best route for a swap across all exchanges
func (r *DexAggregatorSwapRouter) GetSwapRoute(tokenIn, tokenOut common.Address) ([]common.Address, error) {
// Find the best exchange for this swap
exchanges := r.registry.GetExchangesForPair(tokenIn, tokenOut)
if len(exchanges) == 0 {
return nil, fmt.Errorf("no exchanges support this token pair")
}
// For this simplified implementation, return a direct route
return []common.Address{tokenIn, tokenOut}, nil
}
// GetBestSwapRoute returns the best route considering multiple exchanges
func (r *DexAggregatorSwapRouter) GetBestSwapRoute(tokenIn, tokenOut common.Address) ([]common.Address, math.ExchangeType, error) {
// Find the best exchange for this swap
exchanges := r.registry.GetExchangesForPair(tokenIn, tokenOut)
bestAmountOut := big.NewInt(0)
var bestExchange math.ExchangeType
for _, exchangeConfig := range exchanges {
// Get the swap router for this exchange
swapRouter := r.registry.GetSwapRouter(exchangeConfig.Type)
if swapRouter != nil {
amountOut, err := swapRouter.CalculateSwap(tokenIn, tokenOut, big.NewInt(1000000000000000000)) // Use 1 ETH equivalent to compare
if err != nil {
r.logger.Warn(fmt.Sprintf("Error calculating swap on exchange %s: %v", exchangeConfig.Name, err))
continue
}
// Check if this is the best rate
if amountOut.Cmp(bestAmountOut) > 0 {
bestAmountOut = amountOut
bestExchange = exchangeConfig.Type
}
}
}
if bestAmountOut.Sign() <= 0 {
return nil, "", fmt.Errorf("no favorable swap route found across exchanges")
}
// Return the best route for the exchange type
return []common.Address{tokenIn, tokenOut}, bestExchange, nil
}
// ValidateSwap validates a swap across exchanges before execution
func (r *DexAggregatorSwapRouter) ValidateSwap(tokenIn, tokenOut common.Address, amountIn *big.Int) error {
if amountIn.Sign() <= 0 {
return fmt.Errorf("amountIn must be positive")
}
if tokenIn == tokenOut {
return fmt.Errorf("tokenIn and tokenOut cannot be the same")
}
if tokenIn == common.HexToAddress("0x0") || tokenOut == common.HexToAddress("0x0") {
return fmt.Errorf("invalid token addresses")
}
// Check if any exchange supports this pair
if !r.registry.IsPairSupported(tokenIn, tokenOut) {
return fmt.Errorf("no exchange supports this token pair")
}
return nil
}
// RegisterDexAggregatorWithRegistry registers DEX aggregator implementation with the exchange registry
func RegisterDexAggregatorWithRegistry(registry *ExchangeRegistry) error {
// Note: For a complete implementation, we would need to define a specific type for aggregators
// For now, we're not registering it as it needs a dedicated ExchangeType
// config := &ExchangeConfig{
// Type: math.ExchangeUniswapV2, // Using a placeholder type for the aggregator
// Name: "DEX Aggregator",
// FactoryAddress: common.HexToAddress("0x0"), // Aggregators don't have a factory address
// RouterAddress: common.HexToAddress("0x0"), // Would actually be aggregator contract address
// PoolInitCodeHash: "",
// SwapSelector: []byte{0x00, 0x00, 0x00, 0x00}, // Placeholder
// StableSwapSelector: []byte{0x00, 0x00, 0x00, 0x00}, // Placeholder
// ChainID: 1, // Ethereum mainnet
// SupportsFlashSwaps: true,
// RequiresApproval: true,
// MaxHops: 5, // Can route through multiple exchanges
// DefaultSlippagePercent: 0.5,
// Url: "https://dex.ag",
// ApiUrl: "https://api.dex.ag",
// }
// Note: For a complete implementation, we would need to define a specific type for aggregators
// registry.exchanges[math.ExchangeDexAggregator] = config
return nil
}