- 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>
957 lines
29 KiB
Go
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)
|
|
}
|