Files
mev-beta/pkg/validation/input_validator.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

957 lines
29 KiB
Go

package validation
import (
"fmt"
"math"
"math/big"
"regexp"
"strconv"
"strings"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/fraktal/mev-beta/internal/logger"
"github.com/fraktal/mev-beta/internal/utils"
"github.com/fraktal/mev-beta/pkg/security"
)
// safeConvertInt64ToUint64 safely converts an int64 to uint64, ensuring no negative values
func safeConvertInt64ToUint64(v int64) uint64 {
if v < 0 {
return 0
}
return uint64(v)
}
// InputValidator provides comprehensive validation for transaction parameters and user inputs
type InputValidator struct {
logger *logger.Logger
maxGasLimit uint64
maxGasPrice *big.Int
maxValue *big.Int
allowedMethods map[string]bool // method signatures that are allowed
// Regex patterns for validation
addressPattern *regexp.Regexp
txHashPattern *regexp.Regexp
blockHashPattern *regexp.Regexp
hexDataPattern *regexp.Regexp
}
// ValidationConfig contains configuration for input validation
type ValidationConfig struct {
MaxGasLimit uint64 `json:"max_gas_limit"`
MaxGasPriceGwei int64 `json:"max_gas_price_gwei"`
MaxValueEther int64 `json:"max_value_ether"`
AllowedMethods []string `json:"allowed_methods"`
RequireDeadline bool `json:"require_deadline"`
MaxDeadlineHours int `json:"max_deadline_hours"`
}
// TransactionValidationResult contains the result of transaction validation
type TransactionValidationResult struct {
IsValid bool `json:"is_valid"`
Errors []string `json:"errors"`
Warnings []string `json:"warnings"`
RiskLevel string `json:"risk_level"` // "low", "medium", "high", "critical"
EstimatedCost *big.Int `json:"estimated_cost,omitempty"`
}
// SwapParams represents swap transaction parameters
type SwapParams struct {
TokenIn common.Address `json:"token_in"`
TokenOut common.Address `json:"token_out"`
AmountIn *big.Int `json:"amount_in"`
AmountOutMinimum *big.Int `json:"amount_out_minimum"`
Fee uint32 `json:"fee"`
Recipient common.Address `json:"recipient"`
Deadline uint64 `json:"deadline"`
SlippageTolerance *big.Int `json:"slippage_tolerance"` // in basis points
}
// ArbitrageParams represents arbitrage transaction parameters
type ArbitrageParams struct {
Path []common.Address `json:"path"`
AmountIn *big.Int `json:"amount_in"`
MinAmountOut *big.Int `json:"min_amount_out"`
Deadline uint64 `json:"deadline"`
MaxGasPrice *big.Int `json:"max_gas_price"`
ProfitThreshold *big.Int `json:"profit_threshold"`
MaxSlippageBps *big.Int `json:"max_slippage_bps"`
}
// LiquidityParams represents liquidity provision parameters
type LiquidityParams struct {
Token0 common.Address `json:"token0"`
Token1 common.Address `json:"token1"`
Fee uint32 `json:"fee"`
TickLower int32 `json:"tick_lower"`
TickUpper int32 `json:"tick_upper"`
Amount0Desired *big.Int `json:"amount0_desired"`
Amount1Desired *big.Int `json:"amount1_desired"`
Amount0Min *big.Int `json:"amount0_min"`
Amount1Min *big.Int `json:"amount1_min"`
Recipient common.Address `json:"recipient"`
Deadline uint64 `json:"deadline"`
}
// NewInputValidator creates a new input validator
func NewInputValidator(config *ValidationConfig, logger *logger.Logger) *InputValidator {
if config == nil {
config = getDefaultValidationConfig()
}
validator := &InputValidator{
logger: logger,
maxGasLimit: config.MaxGasLimit,
maxGasPrice: big.NewInt(config.MaxGasPriceGwei * 1e9), // Convert Gwei to Wei
maxValue: big.NewInt(config.MaxValueEther * 1e18), // Convert Ether to Wei
allowedMethods: make(map[string]bool),
addressPattern: regexp.MustCompile(`^0x[a-fA-F0-9]{40}$`),
txHashPattern: regexp.MustCompile(`^0x[a-fA-F0-9]{64}$`),
blockHashPattern: regexp.MustCompile(`^0x[a-fA-F0-9]{64}$`),
hexDataPattern: regexp.MustCompile(`^0x[a-fA-F0-9]*$`),
}
// Initialize allowed methods
for _, method := range config.AllowedMethods {
validator.allowedMethods[method] = true
}
return validator
}
// ValidateTransaction performs comprehensive validation of a transaction
func (iv *InputValidator) ValidateTransaction(tx *types.Transaction) (*TransactionValidationResult, error) {
result := &TransactionValidationResult{
IsValid: true,
Errors: make([]string, 0),
Warnings: make([]string, 0),
RiskLevel: "low",
}
// 0. Early check for nil or malformed transactions
if tx == nil {
result.IsValid = false
result.Errors = append(result.Errors, "transaction is nil")
return result, nil
}
// Skip validation for known problematic transactions to reduce log spam
txHash := tx.Hash().Hex()
if iv.isKnownProblematicTransaction(txHash) {
result.IsValid = false
// Don't add to errors to avoid logging spam
return result, nil
}
// 1. Basic transaction validation
iv.validateBasicTransaction(tx, result)
// 2. Gas validation
iv.validateGas(tx, result)
// 3. Value validation
iv.validateValue(tx, result)
// 4. Recipient validation
iv.validateRecipient(tx, result)
// 5. Data validation (for contract calls)
if len(tx.Data()) > 0 {
iv.validateData(tx.Data(), result)
}
// 6. Calculate estimated cost
result.EstimatedCost = iv.calculateEstimatedCost(tx)
// 7. Determine final validity and risk level
iv.finalizeValidation(result)
if len(result.Errors) > 0 {
iv.logger.Warn(fmt.Sprintf("Transaction validation failed: %v", result.Errors))
}
return result, nil
}
// ValidateSwapParams validates swap transaction parameters
func (iv *InputValidator) ValidateSwapParams(params *SwapParams) (*TransactionValidationResult, error) {
result := &TransactionValidationResult{
IsValid: true,
Errors: make([]string, 0),
Warnings: make([]string, 0),
RiskLevel: "low",
}
// 1. Validate token addresses
if err := iv.ValidateAddress(params.TokenIn); err != nil {
result.Errors = append(result.Errors, fmt.Sprintf("Invalid token_in address: %v", err))
}
if err := iv.ValidateAddress(params.TokenOut); err != nil {
result.Errors = append(result.Errors, fmt.Sprintf("Invalid token_out address: %v", err))
}
// 2. Check tokens are different
if params.TokenIn == params.TokenOut {
result.Errors = append(result.Errors, "token_in and token_out must be different")
}
// 3. Validate amounts
if err := iv.ValidateAmount(params.AmountIn); err != nil {
result.Errors = append(result.Errors, fmt.Sprintf("Invalid amount_in: %v", err))
}
if err := iv.ValidateAmount(params.AmountOutMinimum); err != nil {
result.Errors = append(result.Errors, fmt.Sprintf("Invalid amount_out_minimum: %v", err))
}
// 4. Validate slippage tolerance
if err := iv.ValidateSlippage(params.SlippageTolerance); err != nil {
result.Errors = append(result.Errors, fmt.Sprintf("Invalid slippage tolerance: %v", err))
}
// 5. Validate fee tier
if err := iv.validateFeeTier(params.Fee); err != nil {
result.Errors = append(result.Errors, fmt.Sprintf("Invalid fee tier: %v", err))
}
// 6. Validate recipient
if err := iv.ValidateAddress(params.Recipient); err != nil {
result.Errors = append(result.Errors, fmt.Sprintf("Invalid recipient address: %v", err))
}
// 7. Validate deadline
if err := iv.validateDeadline(params.Deadline); err != nil {
result.Errors = append(result.Errors, fmt.Sprintf("Invalid deadline: %v", err))
}
// 8. Additional security checks
iv.performSwapSecurityChecks(params, result)
iv.finalizeValidation(result)
return result, nil
}
// ValidateArbitrageParams validates arbitrage transaction parameters
func (iv *InputValidator) ValidateArbitrageParams(params *ArbitrageParams) (*TransactionValidationResult, error) {
result := &TransactionValidationResult{
IsValid: true,
Errors: make([]string, 0),
Warnings: make([]string, 0),
RiskLevel: "medium", // Arbitrage is inherently riskier
}
// 1. Validate path
if len(params.Path) < 2 {
result.Errors = append(result.Errors, "arbitrage path must have at least 2 tokens")
}
if len(params.Path) > 5 {
result.Warnings = append(result.Warnings, "long arbitrage paths increase gas costs and slippage")
}
for i, addr := range params.Path {
if err := iv.ValidateAddress(addr); err != nil {
result.Errors = append(result.Errors, fmt.Sprintf("Invalid address at path[%d]: %v", i, err))
}
}
// 2. Check for duplicate tokens in path
seen := make(map[common.Address]bool)
for _, addr := range params.Path {
if seen[addr] {
result.Errors = append(result.Errors, fmt.Sprintf("Duplicate token in path: %s", addr.Hex()))
}
seen[addr] = true
}
// 3. Validate amounts
if err := iv.ValidateAmount(params.AmountIn); err != nil {
result.Errors = append(result.Errors, fmt.Sprintf("Invalid amount_in: %v", err))
}
if err := iv.ValidateAmount(params.MinAmountOut); err != nil {
result.Errors = append(result.Errors, fmt.Sprintf("Invalid min_amount_out: %v", err))
}
if err := iv.ValidateAmount(params.ProfitThreshold); err != nil {
result.Errors = append(result.Errors, fmt.Sprintf("Invalid profit_threshold: %v", err))
}
// 4. Validate profit expectation
if params.MinAmountOut.Cmp(params.AmountIn) <= 0 {
result.Errors = append(result.Errors, "min_amount_out must be greater than amount_in for profitable arbitrage")
}
// 5. Validate slippage
if err := iv.ValidateSlippage(params.MaxSlippageBps); err != nil {
result.Errors = append(result.Errors, fmt.Sprintf("Invalid max_slippage: %v", err))
}
// 6. Validate gas price
if params.MaxGasPrice != nil && params.MaxGasPrice.Cmp(iv.maxGasPrice) > 0 {
result.Warnings = append(result.Warnings, "very high gas price may eat into profits")
}
// 7. Validate deadline
if err := iv.validateDeadline(params.Deadline); err != nil {
result.Errors = append(result.Errors, fmt.Sprintf("Invalid deadline: %v", err))
}
iv.finalizeValidation(result)
return result, nil
}
// ValidateAddress validates an Ethereum address
func (iv *InputValidator) ValidateAddress(addr common.Address) error {
if addr == (common.Address{}) {
return fmt.Errorf("address cannot be zero")
}
// Check format using regex
if !iv.addressPattern.MatchString(addr.Hex()) {
return fmt.Errorf("invalid address format")
}
// Check for common invalid addresses
if iv.isKnownInvalidAddress(addr) {
return fmt.Errorf("address is known to be invalid or malicious")
}
return nil
}
// ValidateAmount validates a big.Int amount
func (iv *InputValidator) ValidateAmount(amount *big.Int) error {
if amount == nil {
return fmt.Errorf("amount cannot be nil")
}
if amount.Sign() < 0 {
return fmt.Errorf("amount cannot be negative")
}
if amount.Sign() == 0 {
return fmt.Errorf("amount cannot be zero")
}
// Check for unreasonably large amounts (prevent overflow attacks)
maxAmount := new(big.Int).Exp(big.NewInt(10), big.NewInt(30), nil) // 10^30 wei
if amount.Cmp(maxAmount) > 0 {
return fmt.Errorf("amount exceeds maximum allowed value")
}
return nil
}
// ValidateSlippage validates slippage tolerance in basis points
func (iv *InputValidator) ValidateSlippage(slippageBps *big.Int) error {
if slippageBps == nil {
return fmt.Errorf("slippage cannot be nil")
}
if slippageBps.Sign() < 0 {
return fmt.Errorf("slippage cannot be negative")
}
// Maximum 50% slippage (5000 basis points)
maxSlippage := big.NewInt(5000)
if slippageBps.Cmp(maxSlippage) > 0 {
return fmt.Errorf("slippage tolerance cannot exceed 50%%")
}
return nil
}
// validateBasicTransaction validates basic transaction properties
func (iv *InputValidator) validateBasicTransaction(tx *types.Transaction, result *TransactionValidationResult) {
// Check nonce
if tx.Nonce() > 1000000 {
result.Warnings = append(result.Warnings, "unusually high nonce")
}
// Check transaction size
txSize := len(tx.Data()) + 200 // approximate overhead
if txSize > 128*1024 { // 128KB limit
result.Errors = append(result.Errors, "transaction size exceeds limit")
}
}
// validateGas validates gas-related parameters
func (iv *InputValidator) validateGas(tx *types.Transaction, result *TransactionValidationResult) {
// Validate gas limit
if tx.Gas() == 0 {
result.Errors = append(result.Errors, "gas limit cannot be zero")
}
if tx.Gas() > iv.maxGasLimit {
result.Errors = append(result.Errors, fmt.Sprintf("gas limit %d exceeds maximum %d", tx.Gas(), iv.maxGasLimit))
}
// Validate gas price
if tx.GasPrice() != nil {
if tx.GasPrice().Sign() == 0 {
result.Errors = append(result.Errors, "gas price cannot be zero")
}
if tx.GasPrice().Cmp(iv.maxGasPrice) > 0 {
result.Errors = append(result.Errors, fmt.Sprintf("gas price exceeds maximum"))
}
// Warn about very high gas prices
highGasPrice := new(big.Int).Mul(big.NewInt(100), big.NewInt(1e9)) // 100 Gwei
if tx.GasPrice().Cmp(highGasPrice) > 0 {
result.Warnings = append(result.Warnings, "very high gas price")
result.RiskLevel = "medium"
}
}
// Validate gas fee cap and tip for EIP-1559 transactions
if tx.GasFeeCap() != nil {
if tx.GasFeeCap().Sign() == 0 {
result.Errors = append(result.Errors, "gas fee cap cannot be zero")
}
if tx.GasFeeCap().Cmp(iv.maxGasPrice) > 0 {
result.Errors = append(result.Errors, "gas fee cap exceeds maximum")
}
}
if tx.GasTipCap() != nil {
if tx.GasTipCap().Sign() < 0 {
result.Errors = append(result.Errors, "gas tip cap cannot be negative")
}
if tx.GasFeeCap() != nil && tx.GasTipCap().Cmp(tx.GasFeeCap()) > 0 {
result.Errors = append(result.Errors, "gas tip cap cannot exceed gas fee cap")
}
}
}
// validateValue validates the transaction value
func (iv *InputValidator) validateValue(tx *types.Transaction, result *TransactionValidationResult) {
if tx.Value() == nil {
return
}
if tx.Value().Sign() < 0 {
result.Errors = append(result.Errors, "transaction value cannot be negative")
}
if tx.Value().Cmp(iv.maxValue) > 0 {
result.Errors = append(result.Errors, "transaction value exceeds maximum allowed")
}
// Warn about large value transfers
largeValue := new(big.Int).Mul(big.NewInt(10), big.NewInt(1e18)) // 10 ETH
if tx.Value().Cmp(largeValue) > 0 {
result.Warnings = append(result.Warnings, "large value transfer")
result.RiskLevel = "high"
}
}
// validateRecipient validates the transaction recipient
func (iv *InputValidator) validateRecipient(tx *types.Transaction, result *TransactionValidationResult) {
if tx.To() == nil {
// Contract creation transaction
result.Warnings = append(result.Warnings, "contract creation transaction")
result.RiskLevel = "high"
return
}
// Check for zero address
if *tx.To() == (common.Address{}) {
result.Errors = append(result.Errors, "recipient cannot be zero address")
}
// Check for known malicious addresses
if iv.isKnownInvalidAddress(*tx.To()) {
result.Errors = append(result.Errors, "recipient is known malicious address")
}
}
// validateData validates transaction data for contract calls
func (iv *InputValidator) validateData(data []byte, result *TransactionValidationResult) {
if len(data) == 0 {
return
}
if len(data) < 4 {
result.Errors = append(result.Errors, "invalid function call data")
return
}
// Extract function selector
selector := data[:4]
methodSig := fmt.Sprintf("0x%x", selector)
// Check if method is allowed
if len(iv.allowedMethods) > 0 && !iv.allowedMethods[methodSig] {
result.Errors = append(result.Errors, fmt.Sprintf("method %s not allowed", methodSig))
}
// Check for suspicious patterns
if iv.hasSuspiciousPatterns(data) {
result.Warnings = append(result.Warnings, "transaction data contains suspicious patterns")
result.RiskLevel = "high"
}
}
// validateFeeTier validates Uniswap V3 fee tiers
func (iv *InputValidator) validateFeeTier(fee uint32) error {
validFees := []uint32{100, 500, 3000, 10000} // 0.01%, 0.05%, 0.3%, 1%
for _, validFee := range validFees {
if fee == validFee {
return nil
}
}
return fmt.Errorf("invalid fee tier: %d (must be one of: 100, 500, 3000, 10000)", fee)
}
// validateDeadline validates transaction deadline
func (iv *InputValidator) validateDeadline(deadline uint64) error {
if deadline == 0 {
return fmt.Errorf("deadline cannot be zero")
}
now := safeConvertInt64ToUint64(time.Now().Unix())
if deadline <= now {
return fmt.Errorf("deadline must be in the future")
}
// Warn about very long deadlines
maxDeadline := now + 24*60*60 // 24 hours from now
if deadline > maxDeadline {
return fmt.Errorf("deadline too far in future (max 24 hours)")
}
return nil
}
// performSwapSecurityChecks performs additional security checks for swap parameters
func (iv *InputValidator) performSwapSecurityChecks(params *SwapParams, result *TransactionValidationResult) {
// Check for sandwich attack vulnerability
if params.SlippageTolerance != nil && params.SlippageTolerance.Cmp(big.NewInt(500)) > 0 { // >5%
result.Warnings = append(result.Warnings, "high slippage tolerance increases sandwich attack risk")
result.RiskLevel = "medium"
}
// Check for MEV vulnerability
if params.AmountIn != nil {
// Large trades are more susceptible to MEV
largeTradeThreshold := new(big.Int).Mul(big.NewInt(100), big.NewInt(1e18)) // 100 tokens
if params.AmountIn.Cmp(largeTradeThreshold) > 0 {
result.Warnings = append(result.Warnings, "large trade may be subject to MEV attacks")
}
}
// Check deadline proximity
now := safeConvertInt64ToUint64(time.Now().Unix())
if params.Deadline-now < 60 { // Less than 1 minute
result.Warnings = append(result.Warnings, "very short deadline may cause transaction failures")
}
}
// calculateEstimatedCost estimates the total cost of a transaction
func (iv *InputValidator) calculateEstimatedCost(tx *types.Transaction) *big.Int {
cost := new(big.Int)
// Gas cost
if tx.GasPrice() != nil {
gasInt64, err := security.SafeUint64ToInt64(tx.Gas())
if err != nil {
// Log the error but use a safe fallback
iv.logger.Error("Gas value exceeds int64 maximum", "gas", tx.Gas(), "error", err)
gasInt64 = math.MaxInt64 // Use maximum safe value as fallback
}
gasCost := new(big.Int).Mul(big.NewInt(gasInt64), tx.GasPrice())
cost.Add(cost, gasCost)
} else if tx.GasFeeCap() != nil {
// For EIP-1559 transactions, use fee cap as estimate
gasInt64, err := security.SafeUint64ToInt64(tx.Gas())
if err != nil {
// Log the error but use a safe fallback
iv.logger.Error("Gas value exceeds int64 maximum", "gas", tx.Gas(), "error", err)
gasInt64 = math.MaxInt64 // Use maximum safe value as fallback
}
gasCost := new(big.Int).Mul(big.NewInt(gasInt64), tx.GasFeeCap())
cost.Add(cost, gasCost)
}
// Value transfer
if tx.Value() != nil {
cost.Add(cost, tx.Value())
}
return cost
}
// finalizeValidation determines final validation result
func (iv *InputValidator) finalizeValidation(result *TransactionValidationResult) {
if len(result.Errors) > 0 {
result.IsValid = false
result.RiskLevel = "critical"
return
}
// Adjust risk level based on warnings
if len(result.Warnings) > 2 {
if result.RiskLevel == "low" {
result.RiskLevel = "medium"
} else if result.RiskLevel == "medium" {
result.RiskLevel = "high"
}
}
}
// Helper functions
func (iv *InputValidator) isKnownInvalidAddress(addr common.Address) bool {
// Check against known malicious addresses
// This would be populated from a real blacklist in production
maliciousAddresses := map[common.Address]bool{
// Add known malicious addresses here
}
return maliciousAddresses[addr]
}
func (iv *InputValidator) isKnownProblematicTransaction(txHash string) bool {
// List of known problematic transaction hashes that should be skipped
problematicTxs := map[string]bool{
"0xe79e4719c6770b41405f691c18be3346b691e220d730d6b61abb5dd3ac9d71f0": true,
// Add other problematic transaction hashes here
}
return problematicTxs[txHash]
}
func (iv *InputValidator) hasSuspiciousPatterns(data []byte) bool {
// Check for suspicious patterns in transaction data
// This is a simplified implementation
// Check for self-destruct calls
if len(data) >= 4 {
// selfdestruct selector: 0xff
if data[0] == 0xff {
return true
}
}
// Check for delegate calls to unknown addresses
// This would require more sophisticated analysis in production
return false
}
func getDefaultValidationConfig() *ValidationConfig {
return &ValidationConfig{
MaxGasLimit: 10000000, // 10M gas
MaxGasPriceGwei: 500, // 500 Gwei
MaxValueEther: 1000, // 1000 ETH
AllowedMethods: []string{}, // Empty means all methods allowed
RequireDeadline: true,
MaxDeadlineHours: 24,
}
}
// Legacy validation functions (keeping for backward compatibility)
// ValidateEthereumAddress validates an Ethereum address string
func (iv *InputValidator) ValidateEthereumAddress(address string) error {
if !iv.addressPattern.MatchString(address) {
return fmt.Errorf("invalid Ethereum address format")
}
return nil
}
// ValidateTransactionHash validates a transaction hash string
func (iv *InputValidator) ValidateTransactionHash(hash string) error {
if !iv.txHashPattern.MatchString(hash) {
return fmt.Errorf("invalid transaction hash format")
}
return nil
}
// ValidateBlockHash validates a block hash string
func (iv *InputValidator) ValidateBlockHash(hash string) error {
if !iv.blockHashPattern.MatchString(hash) {
return fmt.Errorf("invalid block hash format")
}
return nil
}
// ValidateEvent validates an event structure with comprehensive checks
func (iv *InputValidator) ValidateEvent(event interface{}) error {
if event == nil {
return fmt.Errorf("event cannot be nil")
}
// Use reflection to validate event structure based on type
eventType := fmt.Sprintf("%T", event)
iv.logger.Debug(fmt.Sprintf("Validating event of type: %s", eventType))
// Type-specific validation based on event structure
switch e := event.(type) {
case map[string]interface{}:
return iv.validateEventMap(e)
default:
// For other types, perform basic structural validation
return iv.validateEventStructure(event)
}
}
// validateEventMap validates map-based event structures
func (iv *InputValidator) validateEventMap(eventMap map[string]interface{}) error {
// Check for required common fields
requiredFields := []string{"type", "timestamp"}
for _, field := range requiredFields {
if _, exists := eventMap[field]; !exists {
return fmt.Errorf("missing required field: %s", field)
}
}
// Validate timestamp if present
if timestamp, ok := eventMap["timestamp"]; ok {
if err := iv.validateTimestamp(timestamp); err != nil {
return fmt.Errorf("invalid timestamp: %w", err)
}
}
// Validate addresses if present
addressFields := []string{"address", "token0", "token1", "pool", "sender", "recipient"}
for _, field := range addressFields {
if addr, exists := eventMap[field]; exists {
if addrStr, ok := addr.(string); ok {
// PHASE 2 FIX: Use safe address conversion
conversionResult := utils.SafeHexToAddress(addrStr)
if !conversionResult.IsValid {
return fmt.Errorf("invalid address in field %s: %v", field, conversionResult.Error)
}
if err := iv.ValidateCommonAddress(conversionResult.Address); err != nil {
return fmt.Errorf("invalid address in field %s: %w", field, err)
}
}
}
}
// Validate amounts if present
amountFields := []string{"amount", "amount0", "amount1", "amountIn", "amountOut", "value"}
for _, field := range amountFields {
if amount, exists := eventMap[field]; exists {
if err := iv.validateAmount(amount); err != nil {
return fmt.Errorf("invalid amount in field %s: %w", field, err)
}
}
}
iv.logger.Debug("Event map validation completed successfully")
return nil
}
// validateEventStructure validates arbitrary event structures using reflection
func (iv *InputValidator) validateEventStructure(event interface{}) error {
// Basic structural validation
eventStr := fmt.Sprintf("%+v", event)
// Check if event structure is not empty
if len(eventStr) < 10 {
return fmt.Errorf("event structure appears to be empty or malformed")
}
// Check for common patterns that indicate valid events
validPatterns := []string{
"BlockNumber", "TxHash", "Address", "Token", "Amount", "Pool",
"block", "transaction", "address", "token", "amount", "pool",
}
hasValidPattern := false
for _, pattern := range validPatterns {
if strings.Contains(eventStr, pattern) {
hasValidPattern = true
break
}
}
if !hasValidPattern {
iv.logger.Warn(fmt.Sprintf("Event structure may not contain expected fields: %s", eventStr[:min(100, len(eventStr))]))
}
iv.logger.Debug("Event structure validation completed")
return nil
}
// validateTimestamp validates timestamp values in various formats
func (iv *InputValidator) validateTimestamp(timestamp interface{}) error {
switch ts := timestamp.(type) {
case int64:
if ts < 0 || ts > time.Now().Unix()+86400 { // Not more than 1 day in future
return fmt.Errorf("timestamp out of valid range")
}
case uint64:
if ts > safeConvertInt64ToUint64(time.Now().Unix()+86400) { // Not more than 1 day in future
return fmt.Errorf("timestamp out of valid range")
}
case time.Time:
if ts.Before(time.Unix(0, 0)) || ts.After(time.Now().Add(24*time.Hour)) {
return fmt.Errorf("timestamp out of valid range")
}
case string:
// Try to parse as RFC3339 or Unix timestamp
if _, err := time.Parse(time.RFC3339, ts); err != nil {
if _, err := strconv.ParseInt(ts, 10, 64); err != nil {
return fmt.Errorf("invalid timestamp format")
}
}
default:
return fmt.Errorf("unsupported timestamp type: %T", timestamp)
}
return nil
}
// validateAmount validates amount values in various formats
func (iv *InputValidator) validateAmount(amount interface{}) error {
switch a := amount.(type) {
case *big.Int:
if a == nil {
return fmt.Errorf("amount cannot be nil")
}
if a.Sign() < 0 {
return fmt.Errorf("amount cannot be negative")
}
// Check for unreasonably large amounts (> 1e30)
maxAmount := new(big.Int).Exp(big.NewInt(10), big.NewInt(30), nil)
if a.Cmp(maxAmount) > 0 {
return fmt.Errorf("amount exceeds maximum allowed value")
}
case int64:
if a < 0 {
return fmt.Errorf("amount cannot be negative")
}
case uint64:
// Always valid for uint64
case string:
if _, ok := new(big.Int).SetString(a, 10); !ok {
return fmt.Errorf("invalid amount format")
}
case float64:
if a < 0 {
return fmt.Errorf("amount cannot be negative")
}
if a > 1e30 {
return fmt.Errorf("amount exceeds maximum allowed value")
}
default:
return fmt.Errorf("unsupported amount type: %T", amount)
}
return nil
}
// min returns the minimum of two integers
func min(a, b int) int {
if a < b {
return a
}
return b
}
// ValidateHexData validates hex data string
func (iv *InputValidator) ValidateHexData(data string) error {
if !iv.hexDataPattern.MatchString(data) {
return fmt.Errorf("invalid hex data format")
}
return nil
}
// SanitizeInput sanitizes string inputs to prevent injection attacks
func SanitizeInput(input string) string {
// Remove potentially dangerous characters
reg := regexp.MustCompile(`[^\w\s\-\.]`)
sanitized := reg.ReplaceAllString(input, "")
// Limit length
if len(sanitized) > 1000 {
sanitized = sanitized[:1000]
}
return strings.TrimSpace(sanitized)
}
// ValidateHexString validates a hex string
func ValidateHexString(hexStr string) error {
if !strings.HasPrefix(hexStr, "0x") {
return fmt.Errorf("hex string must start with 0x")
}
hexStr = hexStr[2:] // Remove 0x prefix
if len(hexStr)%2 != 0 {
return fmt.Errorf("hex string must have even length")
}
matched, err := regexp.MatchString("^[0-9a-fA-F]*$", hexStr)
if err != nil {
return err
}
if !matched {
return fmt.Errorf("invalid hex characters")
}
return nil
}
// ValidateCommonAddress validates an Ethereum address (common.Address type)
func (iv *InputValidator) ValidateCommonAddress(addr common.Address) error {
return iv.ValidateAddress(addr)
}
// ValidateBigInt validates a big.Int value with context
func (iv *InputValidator) ValidateBigInt(value *big.Int, fieldName string) error {
if value == nil {
return fmt.Errorf("%s cannot be nil", fieldName)
}
if value.Sign() < 0 {
return fmt.Errorf("%s cannot be negative", fieldName)
}
if value.Sign() == 0 {
return fmt.Errorf("%s cannot be zero", fieldName)
}
// Check for unreasonably large values
maxValue := new(big.Int).Exp(big.NewInt(10), big.NewInt(30), nil)
if value.Cmp(maxValue) > 0 {
return fmt.Errorf("%s exceeds maximum allowed value", fieldName)
}
return nil
}
// ValidateSlippageTolerance validates slippage tolerance (same as ValidateSlippage)
func (iv *InputValidator) ValidateSlippageTolerance(slippage interface{}) error {
switch v := slippage.(type) {
case *big.Int:
return iv.ValidateSlippage(v)
case float64:
if v < 0 {
return fmt.Errorf("slippage cannot be negative")
}
if v > 50.0 { // 50% maximum
return fmt.Errorf("slippage tolerance cannot exceed 50%%")
}
return nil
default:
return fmt.Errorf("unsupported slippage type: must be *big.Int or float64")
}
}
// ValidateDeadline validates a deadline timestamp (public wrapper for validateDeadline)
func (iv *InputValidator) ValidateDeadline(deadline uint64) error {
return iv.validateDeadline(deadline)
}