- 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>
385 lines
12 KiB
Go
385 lines
12 KiB
Go
package recovery
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/ethereum/go-ethereum/common"
|
|
|
|
"github.com/fraktal/mev-beta/internal/logger"
|
|
"github.com/fraktal/mev-beta/internal/registry"
|
|
)
|
|
|
|
// DefaultFallbackProvider implements FallbackDataProvider with multiple data sources
|
|
type DefaultFallbackProvider struct {
|
|
mu sync.RWMutex
|
|
logger *logger.Logger
|
|
contractRegistry *registry.ContractRegistry
|
|
staticTokenData map[common.Address]*FallbackTokenInfo
|
|
staticPoolData map[common.Address]*FallbackPoolInfo
|
|
cacheTimeout time.Duration
|
|
enabled bool
|
|
}
|
|
|
|
// NewDefaultFallbackProvider creates a new fallback data provider
|
|
func NewDefaultFallbackProvider(logger *logger.Logger, contractRegistry *registry.ContractRegistry) *DefaultFallbackProvider {
|
|
provider := &DefaultFallbackProvider{
|
|
logger: logger,
|
|
contractRegistry: contractRegistry,
|
|
staticTokenData: make(map[common.Address]*FallbackTokenInfo),
|
|
staticPoolData: make(map[common.Address]*FallbackPoolInfo),
|
|
cacheTimeout: 5 * time.Minute,
|
|
enabled: true,
|
|
}
|
|
|
|
// Initialize with known safe data
|
|
provider.initializeStaticData()
|
|
|
|
return provider
|
|
}
|
|
|
|
// initializeStaticData populates the provider with known good data for critical Arbitrum contracts
|
|
func (fp *DefaultFallbackProvider) initializeStaticData() {
|
|
fp.mu.Lock()
|
|
defer fp.mu.Unlock()
|
|
|
|
// Major Arbitrum tokens with verified addresses
|
|
fp.staticTokenData[common.HexToAddress("0x82aF49447D8a07e3bd95BD0d56f35241523fBab1")] = &FallbackTokenInfo{
|
|
Address: common.HexToAddress("0x82aF49447D8a07e3bd95BD0d56f35241523fBab1"),
|
|
Symbol: "WETH",
|
|
Name: "Wrapped Ether",
|
|
Decimals: 18,
|
|
IsVerified: true,
|
|
Source: "static_fallback",
|
|
Confidence: 1.0,
|
|
}
|
|
|
|
fp.staticTokenData[common.HexToAddress("0xaf88d065e77c8cC2239327C5EDb3A432268e5831")] = &FallbackTokenInfo{
|
|
Address: common.HexToAddress("0xaf88d065e77c8cC2239327C5EDb3A432268e5831"),
|
|
Symbol: "USDC",
|
|
Name: "USD Coin",
|
|
Decimals: 6,
|
|
IsVerified: true,
|
|
Source: "static_fallback",
|
|
Confidence: 1.0,
|
|
}
|
|
|
|
fp.staticTokenData[common.HexToAddress("0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9")] = &FallbackTokenInfo{
|
|
Address: common.HexToAddress("0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9"),
|
|
Symbol: "USDT",
|
|
Name: "Tether USD",
|
|
Decimals: 6,
|
|
IsVerified: true,
|
|
Source: "static_fallback",
|
|
Confidence: 1.0,
|
|
}
|
|
|
|
fp.staticTokenData[common.HexToAddress("0x2f2a2543B76A4166549F7aaB2e75Bef0aefC5B0f")] = &FallbackTokenInfo{
|
|
Address: common.HexToAddress("0x2f2a2543B76A4166549F7aaB2e75Bef0aefC5B0f"),
|
|
Symbol: "WBTC",
|
|
Name: "Wrapped BTC",
|
|
Decimals: 8,
|
|
IsVerified: true,
|
|
Source: "static_fallback",
|
|
Confidence: 1.0,
|
|
}
|
|
|
|
fp.staticTokenData[common.HexToAddress("0x912CE59144191C1204E64559FE8253a0e49E6548")] = &FallbackTokenInfo{
|
|
Address: common.HexToAddress("0x912CE59144191C1204E64559FE8253a0e49E6548"),
|
|
Symbol: "ARB",
|
|
Name: "Arbitrum",
|
|
Decimals: 18,
|
|
IsVerified: true,
|
|
Source: "static_fallback",
|
|
Confidence: 1.0,
|
|
}
|
|
|
|
// High-volume Uniswap V3 pools with verified addresses and token pairs
|
|
fp.staticPoolData[common.HexToAddress("0xC6962004f452bE9203591991D15f6b388e09E8D0")] = &FallbackPoolInfo{
|
|
Address: common.HexToAddress("0xC6962004f452bE9203591991D15f6b388e09E8D0"),
|
|
Token0: common.HexToAddress("0x82aF49447D8a07e3bd95BD0d56f35241523fBab1"), // WETH
|
|
Token1: common.HexToAddress("0xaf88d065e77c8cC2239327C5EDb3A432268e5831"), // USDC
|
|
Protocol: "UniswapV3",
|
|
Fee: 500, // 0.05%
|
|
IsVerified: true,
|
|
Source: "static_fallback",
|
|
Confidence: 1.0,
|
|
}
|
|
|
|
fp.staticPoolData[common.HexToAddress("0x641C00A822e8b671738d32a431a4Fb6074E5c79d")] = &FallbackPoolInfo{
|
|
Address: common.HexToAddress("0x641C00A822e8b671738d32a431a4Fb6074E5c79d"),
|
|
Token0: common.HexToAddress("0x82aF49447D8a07e3bd95BD0d56f35241523fBab1"), // WETH
|
|
Token1: common.HexToAddress("0xaf88d065e77c8cC2239327C5EDb3A432268e5831"), // USDC
|
|
Protocol: "UniswapV3",
|
|
Fee: 3000, // 0.3%
|
|
IsVerified: true,
|
|
Source: "static_fallback",
|
|
Confidence: 1.0,
|
|
}
|
|
|
|
fp.staticPoolData[common.HexToAddress("0x17c14D2c404D167802b16C450d3c99F88F2c4F4d")] = &FallbackPoolInfo{
|
|
Address: common.HexToAddress("0x17c14D2c404D167802b16C450d3c99F88F2c4F4d"),
|
|
Token0: common.HexToAddress("0xaf88d065e77c8cC2239327C5EDb3A432268e5831"), // USDC
|
|
Token1: common.HexToAddress("0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9"), // USDT
|
|
Protocol: "UniswapV3",
|
|
Fee: 100, // 0.01%
|
|
IsVerified: true,
|
|
Source: "static_fallback",
|
|
Confidence: 1.0,
|
|
}
|
|
|
|
fp.staticPoolData[common.HexToAddress("0x2f5e87C032bc4F8526F320c012A4e678F1fa6cAB")] = &FallbackPoolInfo{
|
|
Address: common.HexToAddress("0x2f5e87C032bc4F8526F320c012A4e678F1fa6cAB"),
|
|
Token0: common.HexToAddress("0x2f2a2543B76A4166549F7aaB2e75Bef0aefC5B0f"), // WBTC
|
|
Token1: common.HexToAddress("0x82aF49447D8a07e3bd95BD0d56f35241523fBab1"), // WETH
|
|
Protocol: "UniswapV3",
|
|
Fee: 500, // 0.05%
|
|
IsVerified: true,
|
|
Source: "static_fallback",
|
|
Confidence: 1.0,
|
|
}
|
|
|
|
fp.logger.Info("Initialized fallback provider with static data",
|
|
"tokens", len(fp.staticTokenData),
|
|
"pools", len(fp.staticPoolData))
|
|
}
|
|
|
|
// GetFallbackTokenInfo provides fallback token information
|
|
func (fp *DefaultFallbackProvider) GetFallbackTokenInfo(ctx context.Context, address common.Address) (*FallbackTokenInfo, error) {
|
|
if !fp.enabled {
|
|
return nil, fmt.Errorf("fallback provider disabled")
|
|
}
|
|
|
|
fp.mu.RLock()
|
|
defer fp.mu.RUnlock()
|
|
|
|
// First, try static data
|
|
if tokenInfo, exists := fp.staticTokenData[address]; exists {
|
|
fp.logger.Debug("Fallback token info from static data",
|
|
"address", address.Hex(),
|
|
"symbol", tokenInfo.Symbol,
|
|
"source", tokenInfo.Source)
|
|
return tokenInfo, nil
|
|
}
|
|
|
|
// Second, try contract registry if available
|
|
if fp.contractRegistry != nil {
|
|
if contractInfo, err := fp.contractRegistry.GetContractInfo(ctx, address); err == nil && contractInfo != nil {
|
|
tokenInfo := &FallbackTokenInfo{
|
|
Address: address,
|
|
Symbol: contractInfo.Symbol,
|
|
Name: contractInfo.Name,
|
|
Decimals: contractInfo.Decimals,
|
|
IsVerified: contractInfo.IsVerified,
|
|
Source: "contract_registry",
|
|
Confidence: contractInfo.Confidence,
|
|
}
|
|
|
|
fp.logger.Debug("Fallback token info from registry",
|
|
"address", address.Hex(),
|
|
"symbol", tokenInfo.Symbol,
|
|
"confidence", tokenInfo.Confidence)
|
|
|
|
return tokenInfo, nil
|
|
}
|
|
}
|
|
|
|
// Third, provide minimal safe fallback for unknown tokens
|
|
tokenInfo := &FallbackTokenInfo{
|
|
Address: address,
|
|
Symbol: fmt.Sprintf("UNK_%s", address.Hex()[:8]),
|
|
Name: "Unknown Token",
|
|
Decimals: 18, // Safe default
|
|
IsVerified: false,
|
|
Source: "generated_fallback",
|
|
Confidence: 0.1,
|
|
}
|
|
|
|
fp.logger.Warn("Using generated fallback token info",
|
|
"address", address.Hex(),
|
|
"symbol", tokenInfo.Symbol)
|
|
|
|
return tokenInfo, nil
|
|
}
|
|
|
|
// GetFallbackPoolInfo provides fallback pool information
|
|
func (fp *DefaultFallbackProvider) GetFallbackPoolInfo(ctx context.Context, address common.Address) (*FallbackPoolInfo, error) {
|
|
if !fp.enabled {
|
|
return nil, fmt.Errorf("fallback provider disabled")
|
|
}
|
|
|
|
fp.mu.RLock()
|
|
defer fp.mu.RUnlock()
|
|
|
|
// First, try static data
|
|
if poolInfo, exists := fp.staticPoolData[address]; exists {
|
|
fp.logger.Debug("Fallback pool info from static data",
|
|
"address", address.Hex(),
|
|
"protocol", poolInfo.Protocol,
|
|
"token0", poolInfo.Token0.Hex(),
|
|
"token1", poolInfo.Token1.Hex())
|
|
return poolInfo, nil
|
|
}
|
|
|
|
// Second, try contract registry if available
|
|
if fp.contractRegistry != nil {
|
|
if poolInfo := fp.contractRegistry.GetPoolInfo(address); poolInfo != nil {
|
|
fallbackInfo := &FallbackPoolInfo{
|
|
Address: address,
|
|
Token0: poolInfo.Token0,
|
|
Token1: poolInfo.Token1,
|
|
Protocol: poolInfo.Protocol,
|
|
Fee: poolInfo.Fee,
|
|
IsVerified: poolInfo.IsVerified,
|
|
Source: "contract_registry",
|
|
Confidence: poolInfo.Confidence,
|
|
}
|
|
|
|
fp.logger.Debug("Fallback pool info from registry",
|
|
"address", address.Hex(),
|
|
"protocol", fallbackInfo.Protocol,
|
|
"confidence", fallbackInfo.Confidence)
|
|
|
|
return fallbackInfo, nil
|
|
}
|
|
}
|
|
|
|
// No fallback available for unknown pools - return error
|
|
return nil, fmt.Errorf("no fallback data available for pool %s", address.Hex())
|
|
}
|
|
|
|
// GetFallbackContractType provides fallback contract type information
|
|
func (fp *DefaultFallbackProvider) GetFallbackContractType(ctx context.Context, address common.Address) (string, error) {
|
|
if !fp.enabled {
|
|
return "", fmt.Errorf("fallback provider disabled")
|
|
}
|
|
|
|
fp.mu.RLock()
|
|
defer fp.mu.RUnlock()
|
|
|
|
// Check if it's a known token
|
|
if _, exists := fp.staticTokenData[address]; exists {
|
|
return "ERC20", nil
|
|
}
|
|
|
|
// Check if it's a known pool
|
|
if _, exists := fp.staticPoolData[address]; exists {
|
|
return "Pool", nil
|
|
}
|
|
|
|
// Try contract registry
|
|
if fp.contractRegistry != nil {
|
|
if contractInfo, err := fp.contractRegistry.GetContractInfo(ctx, address); err == nil && contractInfo != nil {
|
|
return contractInfo.Type.String(), nil
|
|
}
|
|
}
|
|
|
|
// Default to unknown
|
|
return "Unknown", nil
|
|
}
|
|
|
|
// AddStaticTokenData adds static token data for fallback use
|
|
func (fp *DefaultFallbackProvider) AddStaticTokenData(address common.Address, info *FallbackTokenInfo) {
|
|
fp.mu.Lock()
|
|
defer fp.mu.Unlock()
|
|
|
|
fp.staticTokenData[address] = info
|
|
fp.logger.Debug("Added static token data",
|
|
"address", address.Hex(),
|
|
"symbol", info.Symbol)
|
|
}
|
|
|
|
// AddStaticPoolData adds static pool data for fallback use
|
|
func (fp *DefaultFallbackProvider) AddStaticPoolData(address common.Address, info *FallbackPoolInfo) {
|
|
fp.mu.Lock()
|
|
defer fp.mu.Unlock()
|
|
|
|
fp.staticPoolData[address] = info
|
|
fp.logger.Debug("Added static pool data",
|
|
"address", address.Hex(),
|
|
"protocol", info.Protocol)
|
|
}
|
|
|
|
// IsAddressKnown checks if an address is in the static fallback data
|
|
func (fp *DefaultFallbackProvider) IsAddressKnown(address common.Address) bool {
|
|
fp.mu.RLock()
|
|
defer fp.mu.RUnlock()
|
|
|
|
_, isToken := fp.staticTokenData[address]
|
|
_, isPool := fp.staticPoolData[address]
|
|
|
|
return isToken || isPool
|
|
}
|
|
|
|
// GetKnownAddresses returns all known addresses in the fallback provider
|
|
func (fp *DefaultFallbackProvider) GetKnownAddresses() (tokens []common.Address, pools []common.Address) {
|
|
fp.mu.RLock()
|
|
defer fp.mu.RUnlock()
|
|
|
|
for addr := range fp.staticTokenData {
|
|
tokens = append(tokens, addr)
|
|
}
|
|
|
|
for addr := range fp.staticPoolData {
|
|
pools = append(pools, addr)
|
|
}
|
|
|
|
return tokens, pools
|
|
}
|
|
|
|
// ValidateAddressWithFallback performs validation using fallback data
|
|
func (fp *DefaultFallbackProvider) ValidateAddressWithFallback(ctx context.Context, address common.Address, expectedType string) (bool, float64, error) {
|
|
if !fp.enabled {
|
|
return false, 0.0, fmt.Errorf("fallback provider disabled")
|
|
}
|
|
|
|
// Check if address is known in our static data
|
|
if fp.IsAddressKnown(address) {
|
|
actualType, err := fp.GetFallbackContractType(ctx, address)
|
|
if err != nil {
|
|
return false, 0.0, err
|
|
}
|
|
|
|
if actualType == expectedType {
|
|
return true, 1.0, nil // High confidence for known addresses
|
|
}
|
|
|
|
return false, 0.0, fmt.Errorf("type mismatch: expected %s, got %s", expectedType, actualType)
|
|
}
|
|
|
|
// For unknown addresses, provide low confidence validation
|
|
return true, 0.3, nil // Allow with low confidence
|
|
}
|
|
|
|
// GetStats returns statistics about the fallback provider
|
|
func (fp *DefaultFallbackProvider) GetStats() map[string]interface{} {
|
|
fp.mu.RLock()
|
|
defer fp.mu.RUnlock()
|
|
|
|
return map[string]interface{}{
|
|
"enabled": fp.enabled,
|
|
"static_tokens_count": len(fp.staticTokenData),
|
|
"static_pools_count": len(fp.staticPoolData),
|
|
"cache_timeout": fp.cacheTimeout.String(),
|
|
"has_registry": fp.contractRegistry != nil,
|
|
}
|
|
}
|
|
|
|
// Enable enables the fallback provider
|
|
func (fp *DefaultFallbackProvider) Enable() {
|
|
fp.mu.Lock()
|
|
defer fp.mu.Unlock()
|
|
fp.enabled = true
|
|
fp.logger.Info("Fallback provider enabled")
|
|
}
|
|
|
|
// Disable disables the fallback provider
|
|
func (fp *DefaultFallbackProvider) Disable() {
|
|
fp.mu.Lock()
|
|
defer fp.mu.Unlock()
|
|
fp.enabled = false
|
|
fp.logger.Info("Fallback provider disabled")
|
|
}
|