- 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>
436 lines
16 KiB
Go
436 lines
16 KiB
Go
package security
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"math/big"
|
|
"time"
|
|
|
|
"github.com/ethereum/go-ethereum/common"
|
|
"github.com/ethereum/go-ethereum/core/types"
|
|
"github.com/ethereum/go-ethereum/ethclient"
|
|
|
|
"github.com/fraktal/mev-beta/internal/logger"
|
|
)
|
|
|
|
// TransactionSecurity provides comprehensive transaction security checks
|
|
type TransactionSecurity struct {
|
|
logger *logger.Logger
|
|
inputValidator *InputValidator
|
|
safeMath *SafeMath
|
|
client *ethclient.Client
|
|
chainID uint64
|
|
|
|
// Security thresholds
|
|
maxTransactionValue *big.Int
|
|
maxGasPrice *big.Int
|
|
maxSlippageBps uint64
|
|
|
|
// Blacklisted addresses
|
|
blacklistedAddresses map[common.Address]bool
|
|
|
|
// Rate limiting per address
|
|
transactionCounts map[common.Address]int
|
|
lastReset time.Time
|
|
maxTxPerAddress int
|
|
}
|
|
|
|
// TransactionSecurityResult contains the security analysis result
|
|
type TransactionSecurityResult struct {
|
|
Approved bool `json:"approved"`
|
|
RiskLevel string `json:"risk_level"` // LOW, MEDIUM, HIGH, CRITICAL
|
|
SecurityChecks map[string]bool `json:"security_checks"`
|
|
Warnings []string `json:"warnings"`
|
|
Errors []string `json:"errors"`
|
|
RecommendedGas *big.Int `json:"recommended_gas,omitempty"`
|
|
MaxSlippage uint64 `json:"max_slippage_bps,omitempty"`
|
|
EstimatedProfit *big.Int `json:"estimated_profit,omitempty"`
|
|
Metadata map[string]interface{} `json:"metadata,omitempty"`
|
|
}
|
|
|
|
// MEVTransactionRequest represents an MEV transaction request
|
|
type MEVTransactionRequest struct {
|
|
Transaction *types.Transaction `json:"transaction"`
|
|
ExpectedProfit *big.Int `json:"expected_profit"`
|
|
MaxSlippage uint64 `json:"max_slippage_bps"`
|
|
Deadline time.Time `json:"deadline"`
|
|
Priority string `json:"priority"` // LOW, MEDIUM, HIGH
|
|
Source string `json:"source"` // Origin of the transaction
|
|
}
|
|
|
|
// NewTransactionSecurity creates a new transaction security checker
|
|
func NewTransactionSecurity(client *ethclient.Client, logger *logger.Logger, chainID uint64) *TransactionSecurity {
|
|
return &TransactionSecurity{
|
|
logger: logger,
|
|
inputValidator: NewInputValidator(chainID),
|
|
safeMath: NewSafeMath(),
|
|
client: client,
|
|
chainID: chainID,
|
|
maxTransactionValue: new(big.Int).Mul(big.NewInt(1000), big.NewInt(1e18)), // 1000 ETH
|
|
maxGasPrice: new(big.Int).Mul(big.NewInt(10000), big.NewInt(1e9)), // 10000 Gwei
|
|
maxSlippageBps: 1000, // 10%
|
|
blacklistedAddresses: make(map[common.Address]bool),
|
|
transactionCounts: make(map[common.Address]int),
|
|
lastReset: time.Now(),
|
|
maxTxPerAddress: 100, // Max 100 transactions per address per hour
|
|
}
|
|
}
|
|
|
|
// AnalyzeMEVTransaction performs comprehensive security analysis on an MEV transaction
|
|
func (ts *TransactionSecurity) AnalyzeMEVTransaction(ctx context.Context, req *MEVTransactionRequest) (*TransactionSecurityResult, error) {
|
|
result := &TransactionSecurityResult{
|
|
Approved: true,
|
|
RiskLevel: "LOW",
|
|
SecurityChecks: make(map[string]bool),
|
|
Warnings: []string{},
|
|
Errors: []string{},
|
|
Metadata: make(map[string]interface{}),
|
|
}
|
|
|
|
// Basic transaction validation
|
|
if err := ts.basicTransactionChecks(req.Transaction, result); err != nil {
|
|
return result, fmt.Errorf("basic transaction checks failed: %w", err)
|
|
}
|
|
|
|
// MEV-specific checks
|
|
if err := ts.mevSpecificChecks(ctx, req, result); err != nil {
|
|
return result, fmt.Errorf("MEV specific checks failed: %w", err)
|
|
}
|
|
|
|
// Gas price and limit validation
|
|
if err := ts.gasValidation(req.Transaction, result); err != nil {
|
|
return result, fmt.Errorf("gas validation failed: %w", err)
|
|
}
|
|
|
|
// Profit validation
|
|
if err := ts.profitValidation(req, result); err != nil {
|
|
return result, fmt.Errorf("profit validation failed: %w", err)
|
|
}
|
|
|
|
// Front-running protection checks
|
|
if err := ts.frontRunningProtection(ctx, req, result); err != nil {
|
|
return result, fmt.Errorf("front-running protection failed: %w", err)
|
|
}
|
|
|
|
// Rate limiting checks
|
|
if err := ts.rateLimitingChecks(req.Transaction, result); err != nil {
|
|
return result, fmt.Errorf("rate limiting checks failed: %w", err)
|
|
}
|
|
|
|
// Calculate final risk level
|
|
ts.calculateRiskLevel(result)
|
|
|
|
return result, nil
|
|
}
|
|
|
|
// basicTransactionChecks performs basic transaction security checks
|
|
func (ts *TransactionSecurity) basicTransactionChecks(tx *types.Transaction, result *TransactionSecurityResult) error {
|
|
// Validate transaction using input validator
|
|
validationResult := ts.inputValidator.ValidateTransaction(tx)
|
|
if !validationResult.Valid {
|
|
result.Approved = false
|
|
result.Errors = append(result.Errors, validationResult.Errors...)
|
|
result.SecurityChecks["basic_validation"] = false
|
|
return fmt.Errorf("transaction failed basic validation")
|
|
}
|
|
result.SecurityChecks["basic_validation"] = true
|
|
result.Warnings = append(result.Warnings, validationResult.Warnings...)
|
|
|
|
// Check against blacklisted addresses
|
|
if tx.To() != nil {
|
|
if ts.blacklistedAddresses[*tx.To()] {
|
|
result.Approved = false
|
|
result.Errors = append(result.Errors, "transaction recipient is blacklisted")
|
|
result.SecurityChecks["blacklist_check"] = false
|
|
return fmt.Errorf("blacklisted recipient address")
|
|
}
|
|
}
|
|
result.SecurityChecks["blacklist_check"] = true
|
|
|
|
// Check transaction size
|
|
if tx.Size() > 128*1024 { // 128KB limit
|
|
result.Approved = false
|
|
result.Errors = append(result.Errors, "transaction size exceeds limit")
|
|
result.SecurityChecks["size_check"] = false
|
|
return fmt.Errorf("transaction too large")
|
|
}
|
|
result.SecurityChecks["size_check"] = true
|
|
|
|
return nil
|
|
}
|
|
|
|
// mevSpecificChecks performs MEV-specific security validations
|
|
func (ts *TransactionSecurity) mevSpecificChecks(ctx context.Context, req *MEVTransactionRequest, result *TransactionSecurityResult) error {
|
|
// Check deadline
|
|
if req.Deadline.Before(time.Now()) {
|
|
result.Approved = false
|
|
result.Errors = append(result.Errors, "transaction deadline has passed")
|
|
result.SecurityChecks["deadline_check"] = false
|
|
return fmt.Errorf("deadline expired")
|
|
}
|
|
|
|
// Warn if deadline is too far in the future
|
|
if req.Deadline.After(time.Now().Add(1 * time.Hour)) {
|
|
result.Warnings = append(result.Warnings, "deadline is more than 1 hour in the future")
|
|
}
|
|
result.SecurityChecks["deadline_check"] = true
|
|
|
|
// Validate slippage
|
|
if req.MaxSlippage > ts.maxSlippageBps {
|
|
result.Approved = false
|
|
result.Errors = append(result.Errors, fmt.Sprintf("slippage %d bps exceeds maximum %d bps", req.MaxSlippage, ts.maxSlippageBps))
|
|
result.SecurityChecks["slippage_check"] = false
|
|
return fmt.Errorf("excessive slippage")
|
|
}
|
|
|
|
if req.MaxSlippage > 500 { // Warn if > 5%
|
|
result.Warnings = append(result.Warnings, fmt.Sprintf("high slippage detected: %d bps", req.MaxSlippage))
|
|
}
|
|
result.SecurityChecks["slippage_check"] = true
|
|
|
|
// Check transaction priority vs gas price
|
|
if err := ts.validatePriorityVsGasPrice(req, result); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// gasValidation performs gas-related security checks
|
|
func (ts *TransactionSecurity) gasValidation(tx *types.Transaction, result *TransactionSecurityResult) error {
|
|
// Calculate minimum required gas
|
|
minGas := uint64(21000) // Base transaction gas
|
|
if len(tx.Data()) > 0 {
|
|
// Add gas for contract call
|
|
minGas += uint64(len(tx.Data())) * 16 // 16 gas per non-zero byte
|
|
}
|
|
|
|
if tx.Gas() < minGas {
|
|
result.Approved = false
|
|
result.Errors = append(result.Errors, fmt.Sprintf("gas limit %d below minimum required %d", tx.Gas(), minGas))
|
|
result.SecurityChecks["gas_limit_check"] = false
|
|
return fmt.Errorf("insufficient gas limit")
|
|
}
|
|
|
|
// Recommend optimal gas limit (add 20% buffer)
|
|
recommendedGas := new(big.Int).SetUint64(minGas * 120 / 100)
|
|
result.RecommendedGas = recommendedGas
|
|
result.SecurityChecks["gas_limit_check"] = true
|
|
|
|
// Validate gas price
|
|
if tx.GasPrice() != nil {
|
|
if err := ts.safeMath.ValidateGasPrice(tx.GasPrice()); err != nil {
|
|
result.Approved = false
|
|
result.Errors = append(result.Errors, fmt.Sprintf("invalid gas price: %v", err))
|
|
result.SecurityChecks["gas_price_check"] = false
|
|
return fmt.Errorf("invalid gas price")
|
|
}
|
|
|
|
// Check if gas price is suspiciously high
|
|
highGasThreshold := new(big.Int).Mul(big.NewInt(1000), big.NewInt(1e9)) // 1000 Gwei
|
|
if tx.GasPrice().Cmp(highGasThreshold) > 0 {
|
|
result.Warnings = append(result.Warnings, fmt.Sprintf("high gas price detected: %s Gwei",
|
|
new(big.Int).Div(tx.GasPrice(), big.NewInt(1e9)).String()))
|
|
}
|
|
}
|
|
result.SecurityChecks["gas_price_check"] = true
|
|
|
|
return nil
|
|
}
|
|
|
|
// profitValidation validates expected profit and ensures it covers costs
|
|
func (ts *TransactionSecurity) profitValidation(req *MEVTransactionRequest, result *TransactionSecurityResult) error {
|
|
if req.ExpectedProfit == nil || req.ExpectedProfit.Sign() <= 0 {
|
|
result.Approved = false
|
|
result.Errors = append(result.Errors, "expected profit must be positive")
|
|
result.SecurityChecks["profit_check"] = false
|
|
return fmt.Errorf("invalid expected profit")
|
|
}
|
|
|
|
// Calculate transaction cost
|
|
if req.Transaction.GasPrice() != nil {
|
|
gasInt64, err := SafeUint64ToInt64(req.Transaction.Gas())
|
|
if err != nil {
|
|
ts.logger.Error("Transaction gas exceeds int64 maximum", "gas", req.Transaction.Gas(), "error", err)
|
|
result.Approved = false
|
|
result.Errors = append(result.Errors, fmt.Sprintf("gas value exceeds maximum allowed: %v", err))
|
|
result.SecurityChecks["profit_check"] = false
|
|
return fmt.Errorf("gas value exceeds maximum allowed: %w", err)
|
|
}
|
|
gasCost := new(big.Int).Mul(req.Transaction.GasPrice(), big.NewInt(gasInt64))
|
|
|
|
// Ensure profit exceeds gas cost by at least 50%
|
|
minProfit := new(big.Int).Mul(gasCost, big.NewInt(150))
|
|
minProfit.Div(minProfit, big.NewInt(100))
|
|
|
|
if req.ExpectedProfit.Cmp(minProfit) < 0 {
|
|
result.Approved = false
|
|
result.Errors = append(result.Errors, "expected profit does not cover transaction costs with adequate margin")
|
|
result.SecurityChecks["profit_check"] = false
|
|
return fmt.Errorf("insufficient profit margin")
|
|
}
|
|
|
|
result.EstimatedProfit = req.ExpectedProfit
|
|
result.Metadata["gas_cost"] = gasCost.String()
|
|
result.Metadata["profit_margin"] = new(big.Int).Div(
|
|
new(big.Int).Mul(req.ExpectedProfit, big.NewInt(100)),
|
|
gasCost,
|
|
).String() + "%"
|
|
}
|
|
|
|
result.SecurityChecks["profit_check"] = true
|
|
return nil
|
|
}
|
|
|
|
// frontRunningProtection implements front-running protection measures
|
|
func (ts *TransactionSecurity) frontRunningProtection(ctx context.Context, req *MEVTransactionRequest, result *TransactionSecurityResult) error {
|
|
// Check if transaction might be front-runnable
|
|
if req.Transaction.GasPrice() != nil {
|
|
// Get current network gas price
|
|
networkGasPrice, err := ts.client.SuggestGasPrice(ctx)
|
|
if err != nil {
|
|
result.Warnings = append(result.Warnings, "could not fetch network gas price for front-running analysis")
|
|
} else {
|
|
// If our gas price is significantly higher, we might be front-runnable
|
|
threshold := new(big.Int).Mul(networkGasPrice, big.NewInt(150)) // 50% above network
|
|
threshold.Div(threshold, big.NewInt(100))
|
|
|
|
if req.Transaction.GasPrice().Cmp(threshold) > 0 {
|
|
result.Warnings = append(result.Warnings, "transaction gas price significantly above network average - vulnerable to front-running")
|
|
result.Metadata["front_running_risk"] = "HIGH"
|
|
} else {
|
|
result.Metadata["front_running_risk"] = "LOW"
|
|
}
|
|
}
|
|
}
|
|
|
|
// Recommend using private mempool for high-value transactions
|
|
if req.Transaction.Value() != nil {
|
|
highValueThreshold := new(big.Int).Mul(big.NewInt(10), big.NewInt(1e18)) // 10 ETH
|
|
if req.Transaction.Value().Cmp(highValueThreshold) > 0 {
|
|
result.Warnings = append(result.Warnings, "high-value transaction should consider private mempool")
|
|
}
|
|
}
|
|
|
|
result.SecurityChecks["front_running_protection"] = true
|
|
return nil
|
|
}
|
|
|
|
// rateLimitingChecks implements per-address rate limiting
|
|
func (ts *TransactionSecurity) rateLimitingChecks(tx *types.Transaction, result *TransactionSecurityResult) error {
|
|
// Reset counters if more than an hour has passed
|
|
if time.Since(ts.lastReset) > time.Hour {
|
|
ts.transactionCounts = make(map[common.Address]int)
|
|
ts.lastReset = time.Now()
|
|
}
|
|
|
|
// Get sender address via signature recovery
|
|
signer := types.LatestSignerForChainID(tx.ChainId())
|
|
addr, err := types.Sender(signer, tx)
|
|
if err != nil {
|
|
// If signature recovery fails, use zero address
|
|
// Note: In production, this should be logged to a centralized logging system
|
|
addr = common.Address{}
|
|
}
|
|
|
|
// Increment counter
|
|
ts.transactionCounts[addr]++
|
|
|
|
// Check if limit exceeded
|
|
if ts.transactionCounts[addr] > ts.maxTxPerAddress {
|
|
result.Approved = false
|
|
result.Errors = append(result.Errors, fmt.Sprintf("rate limit exceeded for address %s", addr.Hex()))
|
|
result.SecurityChecks["rate_limiting"] = false
|
|
return fmt.Errorf("rate limit exceeded")
|
|
}
|
|
|
|
// Warn if approaching limit
|
|
if ts.transactionCounts[addr] > ts.maxTxPerAddress*8/10 {
|
|
result.Warnings = append(result.Warnings, "approaching rate limit for this address")
|
|
}
|
|
|
|
result.SecurityChecks["rate_limiting"] = true
|
|
result.Metadata["transaction_count"] = ts.transactionCounts[addr]
|
|
result.Metadata["rate_limit"] = ts.maxTxPerAddress
|
|
|
|
return nil
|
|
}
|
|
|
|
// validatePriorityVsGasPrice ensures gas price matches declared priority
|
|
func (ts *TransactionSecurity) validatePriorityVsGasPrice(req *MEVTransactionRequest, result *TransactionSecurityResult) error {
|
|
if req.Transaction.GasPrice() == nil {
|
|
return nil
|
|
}
|
|
|
|
gasPrice := req.Transaction.GasPrice()
|
|
gasPriceGwei := new(big.Int).Div(gasPrice, big.NewInt(1e9))
|
|
|
|
switch req.Priority {
|
|
case "LOW":
|
|
if gasPriceGwei.Cmp(big.NewInt(100)) > 0 { // > 100 Gwei
|
|
result.Warnings = append(result.Warnings, "gas price seems high for LOW priority transaction")
|
|
}
|
|
case "MEDIUM":
|
|
if gasPriceGwei.Cmp(big.NewInt(500)) > 0 { // > 500 Gwei
|
|
result.Warnings = append(result.Warnings, "gas price seems high for MEDIUM priority transaction")
|
|
}
|
|
case "HIGH":
|
|
if gasPriceGwei.Cmp(big.NewInt(50)) < 0 { // < 50 Gwei
|
|
result.Warnings = append(result.Warnings, "gas price seems low for HIGH priority transaction")
|
|
}
|
|
}
|
|
|
|
result.SecurityChecks["priority_gas_alignment"] = true
|
|
return nil
|
|
}
|
|
|
|
// calculateRiskLevel calculates the overall risk level based on checks and warnings
|
|
func (ts *TransactionSecurity) calculateRiskLevel(result *TransactionSecurityResult) {
|
|
if !result.Approved {
|
|
result.RiskLevel = "CRITICAL"
|
|
return
|
|
}
|
|
|
|
// Count failed checks
|
|
failedChecks := 0
|
|
for _, passed := range result.SecurityChecks {
|
|
if !passed {
|
|
failedChecks++
|
|
}
|
|
}
|
|
|
|
// Determine risk level
|
|
if failedChecks > 0 {
|
|
result.RiskLevel = "HIGH"
|
|
} else if len(result.Warnings) > 3 {
|
|
result.RiskLevel = "MEDIUM"
|
|
} else if len(result.Warnings) > 0 {
|
|
result.RiskLevel = "LOW"
|
|
} else {
|
|
result.RiskLevel = "MINIMAL"
|
|
}
|
|
}
|
|
|
|
// AddBlacklistedAddress adds an address to the blacklist
|
|
func (ts *TransactionSecurity) AddBlacklistedAddress(addr common.Address) {
|
|
ts.blacklistedAddresses[addr] = true
|
|
}
|
|
|
|
// RemoveBlacklistedAddress removes an address from the blacklist
|
|
func (ts *TransactionSecurity) RemoveBlacklistedAddress(addr common.Address) {
|
|
delete(ts.blacklistedAddresses, addr)
|
|
}
|
|
|
|
// GetSecurityMetrics returns current security metrics
|
|
func (ts *TransactionSecurity) GetSecurityMetrics() map[string]interface{} {
|
|
return map[string]interface{}{
|
|
"blacklisted_addresses_count": len(ts.blacklistedAddresses),
|
|
"active_address_count": len(ts.transactionCounts),
|
|
"max_transactions_per_address": ts.maxTxPerAddress,
|
|
"max_transaction_value": ts.maxTransactionValue.String(),
|
|
"max_gas_price": ts.maxGasPrice.String(),
|
|
"max_slippage_bps": ts.maxSlippageBps,
|
|
"last_reset": ts.lastReset.Format(time.RFC3339),
|
|
}
|
|
}
|