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>
This commit is contained in:
@@ -2,20 +2,23 @@ package swap
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/holiman/uint256"
|
||||
|
||||
"github.com/fraktal/mev-beta/internal/logger"
|
||||
"github.com/fraktal/mev-beta/pkg/events"
|
||||
"github.com/fraktal/mev-beta/pkg/marketdata"
|
||||
"github.com/fraktal/mev-beta/pkg/profitcalc"
|
||||
scannercommon "github.com/fraktal/mev-beta/pkg/scanner/common"
|
||||
"github.com/fraktal/mev-beta/pkg/scanner/market"
|
||||
stypes "github.com/fraktal/mev-beta/pkg/types"
|
||||
"github.com/fraktal/mev-beta/pkg/uniswap"
|
||||
"github.com/holiman/uint256"
|
||||
)
|
||||
|
||||
// SwapAnalyzer handles analysis of swap events for price movement opportunities
|
||||
@@ -26,6 +29,36 @@ type SwapAnalyzer struct {
|
||||
opportunityRanker *profitcalc.OpportunityRanker
|
||||
}
|
||||
|
||||
var (
|
||||
factoryProtocolMap = map[common.Address]string{
|
||||
common.HexToAddress("0x1F98431c8aD98523631AE4a59f267346ea31F984"): "UniswapV3",
|
||||
common.HexToAddress("0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f"): "UniswapV2",
|
||||
common.HexToAddress("0xf1D7CC64Fb4452F05c498126312eBE29f30Fbcf9"): "UniswapV2",
|
||||
common.HexToAddress("0xc35DADB65012eC5796536bD9864eD8773aBc74C4"): "SushiSwap",
|
||||
common.HexToAddress("0xBA12222222228d8Ba445958a75a0704d566BF2C8"): "Balancer",
|
||||
common.HexToAddress("0xF18056Bbd320E96A48e3Fbf8bC061322531aac99"): "Curve",
|
||||
common.HexToAddress("0x5ffe7FB82894076ECB99A30D6A32e969e6e35E98"): "Curve",
|
||||
common.HexToAddress("0x5F1dddbf348aC2fbe22a163e30F99F9ECE3DD50a"): "KyberElastic",
|
||||
common.HexToAddress("0x1a3c9B1d2F0529D97f2afC5136Cc23e58f1FD35B"): "Camelot",
|
||||
}
|
||||
|
||||
protocolDefaultFactory = map[string]common.Address{
|
||||
"UniswapV3": common.HexToAddress("0x1F98431c8aD98523631AE4a59f267346ea31F984"),
|
||||
"UniswapV2": common.HexToAddress("0xf1D7CC64Fb4452F05c498126312eBE29f30Fbcf9"),
|
||||
"SushiSwap": common.HexToAddress("0xc35DADB65012eC5796536bD9864eD8773aBc74C4"),
|
||||
"Balancer": common.HexToAddress("0xBA12222222228d8Ba445958a75a0704d566BF2C8"),
|
||||
"Curve": common.HexToAddress("0xF18056Bbd320E96A48e3Fbf8bC061322531aac99"),
|
||||
"KyberElastic": common.HexToAddress("0x5F1dddbf348aC2fbe22a163e30F99F9ECE3DD50a"),
|
||||
"Camelot": common.HexToAddress("0x1a3c9B1d2F0529D97f2afC5136Cc23e58f1FD35B"),
|
||||
}
|
||||
|
||||
protocolSpecialByAddress = map[common.Address]string{
|
||||
common.HexToAddress("0xBA12222222228d8Ba445958a75a0704d566BF2C8"): "Balancer",
|
||||
common.HexToAddress("0xF18056Bbd320E96A48e3Fbf8bC061322531aac99"): "Curve",
|
||||
common.HexToAddress("0x5F1dddbf348aC2fbe22a163e30F99F9ECE3DD50a"): "KyberElastic",
|
||||
}
|
||||
)
|
||||
|
||||
// NewSwapAnalyzer creates a new swap analyzer
|
||||
func NewSwapAnalyzer(
|
||||
logger *logger.Logger,
|
||||
@@ -45,6 +78,22 @@ func NewSwapAnalyzer(
|
||||
func (s *SwapAnalyzer) AnalyzeSwapEvent(event events.Event, marketScanner *market.MarketScanner) {
|
||||
s.logger.Debug(fmt.Sprintf("Analyzing swap event in pool %s", event.PoolAddress))
|
||||
|
||||
// Validate pool address before attempting expensive lookups
|
||||
if event.PoolAddress == (common.Address{}) {
|
||||
s.logger.Warn(fmt.Sprintf("Skipping swap analysis: empty pool address (tx: %s)", event.TransactionHash.Hex()))
|
||||
return
|
||||
}
|
||||
|
||||
if event.PoolAddress == event.Token0 || event.PoolAddress == event.Token1 {
|
||||
s.logger.Warn(fmt.Sprintf("Skipping swap analysis: pool address %s matches token address (tx: %s)", event.PoolAddress.Hex(), event.TransactionHash.Hex()))
|
||||
return
|
||||
}
|
||||
|
||||
if strings.HasPrefix(strings.ToLower(event.PoolAddress.Hex()), "0x000000") {
|
||||
s.logger.Warn(fmt.Sprintf("Skipping swap analysis: suspicious pool address %s (tx: %s)", event.PoolAddress.Hex(), event.TransactionHash.Hex()))
|
||||
return
|
||||
}
|
||||
|
||||
// Get comprehensive pool data to determine factory and fee
|
||||
poolInfo, poolExists := s.marketDataLogger.GetPoolInfo(event.PoolAddress)
|
||||
factory := common.Address{}
|
||||
@@ -108,7 +157,42 @@ func (s *SwapAnalyzer) AnalyzeSwapEvent(event events.Event, marketScanner *marke
|
||||
// Calculate price impact based on pool liquidity and swap amounts
|
||||
swapData.PriceImpact = s.calculateSwapPriceImpact(event, swapData)
|
||||
|
||||
// Log comprehensive swap event to market data logger
|
||||
// Get pool data with caching
|
||||
poolData, err := marketScanner.GetPoolData(event.PoolAddress.Hex())
|
||||
if err != nil {
|
||||
if errors.Is(err, market.ErrInvalidPoolCandidate) {
|
||||
s.logger.Debug("Skipping pool data fetch for invalid candidate",
|
||||
"pool", event.PoolAddress,
|
||||
"tx", event.TransactionHash,
|
||||
"error", err)
|
||||
} else {
|
||||
// Enhanced error logging with context - check if this is an ERC-20 token misclassified as a pool
|
||||
errorMsg := fmt.Sprintf("Error getting pool data for %s: %v", event.PoolAddress, err)
|
||||
contextMsg := fmt.Sprintf("event_type:%s protocol:%s block:%d tx:%s",
|
||||
event.Type.String(), event.Protocol, event.BlockNumber, event.TransactionHash.Hex())
|
||||
s.logger.Error(fmt.Sprintf("%s [context: %s]", errorMsg, contextMsg))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
finalProtocol := s.detectSwapProtocol(event, poolInfo, poolData, factory)
|
||||
if finalProtocol == "" || strings.EqualFold(finalProtocol, "unknown") {
|
||||
if fallback := canonicalProtocolName(event.Protocol); fallback != "" {
|
||||
finalProtocol = fallback
|
||||
} else {
|
||||
finalProtocol = "Unknown"
|
||||
}
|
||||
}
|
||||
|
||||
event.Protocol = finalProtocol
|
||||
swapData.Protocol = finalProtocol
|
||||
if poolData != nil {
|
||||
poolData.Protocol = finalProtocol
|
||||
}
|
||||
|
||||
factory = s.resolveFactory(factory, finalProtocol, marketScanner)
|
||||
swapData.Factory = factory
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
if err := s.marketDataLogger.LogSwapEvent(ctx, event, swapData); err != nil {
|
||||
@@ -118,11 +202,12 @@ func (s *SwapAnalyzer) AnalyzeSwapEvent(event events.Event, marketScanner *marke
|
||||
// Log the swap event to database (legacy)
|
||||
marketScanner.LogSwapEvent(event)
|
||||
|
||||
// Get pool data with caching
|
||||
poolData, err := marketScanner.GetPoolData(event.PoolAddress.Hex())
|
||||
if err != nil {
|
||||
s.logger.Error(fmt.Sprintf("Error getting pool data for %s: %v", event.PoolAddress, err))
|
||||
return
|
||||
s.marketDataLogger.UpdatePoolMetadata(event.PoolAddress, finalProtocol, factory)
|
||||
|
||||
// DEBUG: Log zero address events to trace their source
|
||||
if event.PoolAddress == (common.Address{}) {
|
||||
s.logger.Error(fmt.Sprintf("ZERO ADDRESS DEBUG [ANALYZER]: Received Event with zero PoolAddress - TxHash: %s, Protocol: %s, Token0: %s, Token1: %s, Type: %v",
|
||||
event.TransactionHash.Hex(), event.Protocol, event.Token0.Hex(), event.Token1.Hex(), event.Type))
|
||||
}
|
||||
|
||||
// Calculate price impact
|
||||
@@ -373,7 +458,11 @@ func (s *SwapAnalyzer) findArbitrageOpportunities(event events.Event, movement *
|
||||
// Get pool data
|
||||
poolData, err := marketScanner.GetPoolData(pool.Address.Hex())
|
||||
if err != nil {
|
||||
s.logger.Error(fmt.Sprintf("Error getting pool data for related pool %s: %v", pool.Address.Hex(), err))
|
||||
// Enhanced error logging with context for related pool analysis
|
||||
errorMsg := fmt.Sprintf("Error getting pool data for related pool %s: %v", pool.Address.Hex(), err)
|
||||
contextMsg := fmt.Sprintf("original_pool:%s related_pool:%s token_pair:%s-%s",
|
||||
event.PoolAddress.Hex(), pool.Address.Hex(), pool.Token0.Hex(), pool.Token1.Hex())
|
||||
s.logger.Error(fmt.Sprintf("%s [context: %s]", errorMsg, contextMsg))
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -509,48 +598,18 @@ func (s *SwapAnalyzer) calculateSwapPriceImpact(event events.Event, swapData *ma
|
||||
|
||||
// getTokenPriceUSD gets the USD price of a token using various price sources
|
||||
func (s *SwapAnalyzer) getTokenPriceUSD(tokenAddr common.Address) float64 {
|
||||
// Known token prices (in a production system, this would query price oracles)
|
||||
knownPrices := map[common.Address]float64{
|
||||
common.HexToAddress("0x82af49447d8a07e3bd95bd0d56f35241523fbab1"): 2000.0, // WETH
|
||||
common.HexToAddress("0xaf88d065e77c8cc2239327c5edb3a432268e5831"): 1.0, // USDC
|
||||
common.HexToAddress("0xff970a61a04b1ca14834a43f5de4533ebddb5cc8"): 1.0, // USDC.e
|
||||
common.HexToAddress("0xfd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9"): 1.0, // USDT
|
||||
common.HexToAddress("0x2f2a2543b76a4166549f7aab2e75bef0aefc5b0f"): 43000.0, // WBTC
|
||||
common.HexToAddress("0x912ce59144191c1204e64559fe8253a0e49e6548"): 0.75, // ARB
|
||||
common.HexToAddress("0xfc5a1a6eb076a2c7ad06ed22c90d7e710e35ad0a"): 45.0, // GMX
|
||||
common.HexToAddress("0xf97f4df75117a78c1a5a0dbb814af92458539fb4"): 12.0, // LINK
|
||||
common.HexToAddress("0xfa7f8980b0f1e64a2062791cc3b0871572f1f7f0"): 8.0, // UNI
|
||||
common.HexToAddress("0xba5ddd1f9d7f570dc94a51479a000e3bce967196"): 85.0, // AAVE
|
||||
}
|
||||
|
||||
if price, exists := knownPrices[tokenAddr]; exists {
|
||||
if price, exists := scannercommon.GetTokenPriceUSD(tokenAddr); exists {
|
||||
return price
|
||||
}
|
||||
|
||||
// For unknown tokens, return 0 (in production, would query price oracle or DEX)
|
||||
return 0.0
|
||||
}
|
||||
|
||||
// getTokenDecimals returns the decimal places for a token
|
||||
func (s *SwapAnalyzer) getTokenDecimals(tokenAddr common.Address) uint8 {
|
||||
// Known token decimals
|
||||
knownDecimals := map[common.Address]uint8{
|
||||
common.HexToAddress("0x82af49447d8a07e3bd95bd0d56f35241523fbab1"): 18, // WETH
|
||||
common.HexToAddress("0xaf88d065e77c8cc2239327c5edb3a432268e5831"): 6, // USDC
|
||||
common.HexToAddress("0xff970a61a04b1ca14834a43f5de4533ebddb5cc8"): 6, // USDC.e
|
||||
common.HexToAddress("0xfd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9"): 6, // USDT
|
||||
common.HexToAddress("0x2f2a2543b76a4166549f7aab2e75bef0aefc5b0f"): 8, // WBTC
|
||||
common.HexToAddress("0x912ce59144191c1204e64559fe8253a0e49e6548"): 18, // ARB
|
||||
common.HexToAddress("0xfc5a1a6eb076a2c7ad06ed22c90d7e710e35ad0a"): 18, // GMX
|
||||
common.HexToAddress("0xf97f4df75117a78c1a5a0dbb814af92458539fb4"): 18, // LINK
|
||||
common.HexToAddress("0xfa7f8980b0f1e64a2062791cc3b0871572f1f7f0"): 18, // UNI
|
||||
common.HexToAddress("0xba5ddd1f9d7f570dc94a51479a000e3bce967196"): 18, // AAVE
|
||||
}
|
||||
|
||||
if decimals, exists := knownDecimals[tokenAddr]; exists {
|
||||
if decimals, exists := scannercommon.GetTokenDecimals(tokenAddr); exists {
|
||||
return decimals
|
||||
}
|
||||
|
||||
// Default to 18 for unknown tokens
|
||||
return 18
|
||||
}
|
||||
@@ -583,3 +642,117 @@ func (s *SwapAnalyzer) sqrtPriceX96ToPrice(sqrtPriceX96 *uint256.Int) float64 {
|
||||
priceFloat, _ := price.Float64()
|
||||
return priceFloat
|
||||
}
|
||||
|
||||
func canonicalProtocolName(raw string) string {
|
||||
raw = strings.TrimSpace(raw)
|
||||
if raw == "" {
|
||||
return ""
|
||||
}
|
||||
lower := strings.ToLower(raw)
|
||||
if idx := strings.Index(lower, "_fee"); idx != -1 {
|
||||
lower = lower[:idx]
|
||||
}
|
||||
replacer := strings.NewReplacer(" ", "", "-", "", ".", "", ":", "")
|
||||
lower = replacer.Replace(lower)
|
||||
lower = strings.ReplaceAll(lower, "_", "")
|
||||
|
||||
switch {
|
||||
case strings.Contains(lower, "kyberelastic"):
|
||||
return "KyberElastic"
|
||||
case strings.Contains(lower, "kyberclassic"):
|
||||
return "KyberClassic"
|
||||
case strings.Contains(lower, "kyberswap"):
|
||||
return "Kyber"
|
||||
case strings.Contains(lower, "kyber"):
|
||||
return "Kyber"
|
||||
case strings.Contains(lower, "balancer"):
|
||||
return "Balancer"
|
||||
case strings.Contains(lower, "curve"):
|
||||
return "Curve"
|
||||
case strings.Contains(lower, "camelot"):
|
||||
return "Camelot"
|
||||
case strings.Contains(lower, "traderjoe"):
|
||||
return "TraderJoe"
|
||||
case strings.Contains(lower, "sushiswap") || strings.Contains(lower, "sushi"):
|
||||
return "SushiSwap"
|
||||
case strings.Contains(lower, "pancake"):
|
||||
return "PancakeSwap"
|
||||
case strings.Contains(lower, "ramses"):
|
||||
return "Ramses"
|
||||
case strings.Contains(lower, "uniswap") && strings.Contains(lower, "v3"):
|
||||
return "UniswapV3"
|
||||
case strings.Contains(lower, "uniswap") && strings.Contains(lower, "v2"):
|
||||
return "UniswapV2"
|
||||
case strings.Contains(lower, "uniswap"):
|
||||
return "Uniswap"
|
||||
}
|
||||
|
||||
return strings.Title(strings.ReplaceAll(raw, "_", ""))
|
||||
}
|
||||
|
||||
func protocolFromFactory(factory common.Address) string {
|
||||
if factory == (common.Address{}) {
|
||||
return ""
|
||||
}
|
||||
if protocol, ok := factoryProtocolMap[factory]; ok {
|
||||
return protocol
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func factoryForProtocol(protocol string) common.Address {
|
||||
canonical := canonicalProtocolName(protocol)
|
||||
if canonical == "" {
|
||||
return common.Address{}
|
||||
}
|
||||
if addr, ok := protocolDefaultFactory[canonical]; ok {
|
||||
return addr
|
||||
}
|
||||
return common.Address{}
|
||||
}
|
||||
|
||||
func (s *SwapAnalyzer) resolveFactory(existing common.Address, protocol string, marketScanner *market.MarketScanner) common.Address {
|
||||
if existing != (common.Address{}) {
|
||||
return existing
|
||||
}
|
||||
if addr := factoryForProtocol(protocol); addr != (common.Address{}) {
|
||||
return addr
|
||||
}
|
||||
if marketScanner != nil {
|
||||
if addr := marketScanner.GetFactoryForProtocol(protocol); addr != (common.Address{}) {
|
||||
return addr
|
||||
}
|
||||
}
|
||||
return existing
|
||||
}
|
||||
|
||||
func (s *SwapAnalyzer) detectSwapProtocol(event events.Event, poolInfo *marketdata.PoolInfo, poolData *market.CachedData, factory common.Address) string {
|
||||
candidates := []string{event.Protocol}
|
||||
|
||||
if poolInfo != nil {
|
||||
candidates = append(candidates, poolInfo.Protocol)
|
||||
if factory == (common.Address{}) && poolInfo.Factory != (common.Address{}) {
|
||||
factory = poolInfo.Factory
|
||||
}
|
||||
}
|
||||
|
||||
if poolData != nil {
|
||||
candidates = append(candidates, poolData.Protocol)
|
||||
}
|
||||
|
||||
if proto := protocolFromFactory(factory); proto != "" {
|
||||
candidates = append(candidates, proto)
|
||||
}
|
||||
|
||||
for _, candidate := range candidates {
|
||||
if canonical := canonicalProtocolName(candidate); canonical != "" && !strings.EqualFold(canonical, "unknown") {
|
||||
return canonical
|
||||
}
|
||||
}
|
||||
|
||||
if proto, ok := protocolSpecialByAddress[event.PoolAddress]; ok {
|
||||
return proto
|
||||
}
|
||||
|
||||
return "Unknown"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user