Files
mev-beta/pkg/arbitrum/swap_parser_fixed.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

313 lines
10 KiB
Go

package arbitrum
import (
"encoding/binary"
"fmt"
"math"
"math/big"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/fraktal/mev-beta/internal/logger"
)
// safeConvertUint32ToInt32 safely converts a uint32 to int32, capping at MaxInt32 if overflow would occur
func safeConvertUint32ToInt32(v uint32) int32 {
if v > math.MaxInt32 {
return math.MaxInt32
}
return int32(v)
}
// FixedSwapParser provides robust swap event parsing with proper error handling
type FixedSwapParser struct {
logger *logger.Logger
}
// NewFixedSwapParser creates a new swap parser with enhanced error handling
func NewFixedSwapParser(logger *logger.Logger) *FixedSwapParser {
return &FixedSwapParser{
logger: logger,
}
}
// ParseSwapEventSafe parses swap events with comprehensive validation and error handling
func (fsp *FixedSwapParser) ParseSwapEventSafe(log *types.Log, tx *types.Transaction, blockNumber uint64) (*SimpleSwapEvent, error) {
// Validate input parameters
if log == nil {
return nil, fmt.Errorf("log cannot be nil")
}
if tx == nil {
return nil, fmt.Errorf("transaction cannot be nil")
}
// Uniswap V3 Pool Swap event signature
swapEventSig := common.HexToHash("0xc42079f94a6350d7e6235f29174924f928cc2ac818eb64fed8004e115fbcca67")
// Uniswap V2 Pool Swap event signature
swapV2EventSig := common.HexToHash("0xd78ad95fa46c994b6551d0da85fc275fe613ce37657fb8d5e3d130840159d822")
if len(log.Topics) == 0 {
return nil, fmt.Errorf("log has no topics")
}
// Determine which version of Uniswap based on event signature
switch log.Topics[0] {
case swapEventSig:
return fsp.parseUniswapV3Swap(log, tx, blockNumber)
case swapV2EventSig:
return fsp.parseUniswapV2Swap(log, tx, blockNumber)
default:
return nil, fmt.Errorf("unknown swap event signature: %s", log.Topics[0].Hex())
}
}
// parseUniswapV3Swap parses Uniswap V3 swap events with proper signed integer handling
func (fsp *FixedSwapParser) parseUniswapV3Swap(log *types.Log, tx *types.Transaction, blockNumber uint64) (*SimpleSwapEvent, error) {
// UniswapV3 Swap event structure:
// event Swap(indexed address sender, indexed address recipient, int256 amount0, int256 amount1, uint160 sqrtPriceX96, uint128 liquidity, int24 tick)
// Validate log structure
if len(log.Topics) < 3 {
return nil, fmt.Errorf("insufficient topics for UniV3 swap: got %d, need 3", len(log.Topics))
}
if len(log.Data) < 160 { // 5 * 32 bytes for amount0, amount1, sqrtPriceX96, liquidity, tick
return nil, fmt.Errorf("insufficient data for UniV3 swap: got %d bytes, need 160", len(log.Data))
}
// Extract indexed parameters
sender := common.BytesToAddress(log.Topics[1].Bytes())
recipient := common.BytesToAddress(log.Topics[2].Bytes())
// Parse signed amounts correctly
amount0, err := fsp.parseSignedInt256(log.Data[0:32])
if err != nil {
return nil, fmt.Errorf("failed to parse amount0: %w", err)
}
amount1, err := fsp.parseSignedInt256(log.Data[32:64])
if err != nil {
return nil, fmt.Errorf("failed to parse amount1: %w", err)
}
// Parse unsigned values
sqrtPriceX96 := new(big.Int).SetBytes(log.Data[64:96])
liquidity := new(big.Int).SetBytes(log.Data[96:128])
// Parse tick as int24 (stored in int256)
tick, err := fsp.parseSignedInt24(log.Data[128:160])
if err != nil {
return nil, fmt.Errorf("failed to parse tick: %w", err)
}
// Validate parsed values
if err := fsp.validateUniV3SwapData(amount0, amount1, sqrtPriceX96, liquidity, tick); err != nil {
return nil, fmt.Errorf("invalid swap data: %w", err)
}
return &SimpleSwapEvent{
TxHash: tx.Hash(),
PoolAddress: log.Address,
Token0: common.Address{}, // Will be filled by caller
Token1: common.Address{}, // Will be filled by caller
Amount0: amount0,
Amount1: amount1,
SqrtPriceX96: sqrtPriceX96,
Liquidity: liquidity,
Tick: int32(tick),
BlockNumber: blockNumber,
LogIndex: log.Index,
Timestamp: time.Now(),
Sender: sender,
Recipient: recipient,
Protocol: "UniswapV3",
}, nil
}
// parseUniswapV2Swap parses Uniswap V2 swap events
func (fsp *FixedSwapParser) parseUniswapV2Swap(log *types.Log, tx *types.Transaction, blockNumber uint64) (*SimpleSwapEvent, error) {
// UniswapV2 Swap event structure:
// event Swap(indexed address sender, uint256 amount0In, uint256 amount1In, uint256 amount0Out, uint256 amount1Out, indexed address to)
if len(log.Topics) < 3 {
return nil, fmt.Errorf("insufficient topics for UniV2 swap: got %d, need 3", len(log.Topics))
}
if len(log.Data) < 128 { // 4 * 32 bytes
return nil, fmt.Errorf("insufficient data for UniV2 swap: got %d bytes, need 128", len(log.Data))
}
// Extract indexed parameters
sender := common.BytesToAddress(log.Topics[1].Bytes())
recipient := common.BytesToAddress(log.Topics[2].Bytes())
// Parse amounts (all unsigned in V2)
amount0In := new(big.Int).SetBytes(log.Data[0:32])
amount1In := new(big.Int).SetBytes(log.Data[32:64])
amount0Out := new(big.Int).SetBytes(log.Data[64:96])
amount1Out := new(big.Int).SetBytes(log.Data[96:128])
// Calculate net amounts (In - Out)
amount0 := new(big.Int).Sub(amount0In, amount0Out)
amount1 := new(big.Int).Sub(amount1In, amount1Out)
// Validate parsed values
if err := fsp.validateUniV2SwapData(amount0In, amount1In, amount0Out, amount1Out); err != nil {
return nil, fmt.Errorf("invalid V2 swap data: %w", err)
}
return &SimpleSwapEvent{
TxHash: tx.Hash(),
PoolAddress: log.Address,
Token0: common.Address{}, // Will be filled by caller
Token1: common.Address{}, // Will be filled by caller
Amount0: amount0,
Amount1: amount1,
BlockNumber: blockNumber,
LogIndex: log.Index,
Timestamp: time.Now(),
Sender: sender,
Recipient: recipient,
Protocol: "UniswapV2",
}, nil
}
// parseSignedInt256 correctly parses a signed 256-bit integer from bytes
func (fsp *FixedSwapParser) parseSignedInt256(data []byte) (*big.Int, error) {
if len(data) != 32 {
return nil, fmt.Errorf("invalid data length for int256: got %d, need 32", len(data))
}
value := new(big.Int).SetBytes(data)
// Check if the value is negative (MSB set)
if len(data) > 0 && data[0]&0x80 != 0 {
// Convert from two's complement
// Subtract 2^256 to get the negative value
maxUint256 := new(big.Int)
maxUint256.Lsh(big.NewInt(1), 256)
value.Sub(value, maxUint256)
}
return value, nil
}
// parseSignedInt24 correctly parses a signed 24-bit integer stored in a 32-byte field
func (fsp *FixedSwapParser) parseSignedInt24(data []byte) (int32, error) {
if len(data) != 32 {
return 0, fmt.Errorf("invalid data length for int24: got %d, need 32", len(data))
}
signByte := data[28]
if signByte != 0x00 && signByte != 0xFF {
return 0, fmt.Errorf("invalid sign extension byte 0x%02x for int24", signByte)
}
if signByte == 0x00 && data[29]&0x80 != 0 {
return 0, fmt.Errorf("value uses more than 23 bits for positive int24")
}
if signByte == 0xFF && data[29]&0x80 == 0 {
return 0, fmt.Errorf("value uses more than 23 bits for negative int24")
}
// Extract the last 4 bytes (since int24 is stored as int256)
value := binary.BigEndian.Uint32(data[28:32])
// Convert to int24 by masking and sign-extending
int24Value := int32(safeConvertUint32ToInt32(value & 0xFFFFFF)) // Mask to 24 bits
// Check if negative (bit 23 set)
if int24Value&0x800000 != 0 {
// Sign extend to int32
int24Value |= ^0xFFFFFF // Set all bits above bit 23 to 1 for negative numbers
}
// Validate range for int24
if int24Value < -8388608 || int24Value > 8388607 {
return 0, fmt.Errorf("value %d out of range for int24", int24Value)
}
return int24Value, nil
}
// validateUniV3SwapData validates parsed UniswapV3 swap data
func (fsp *FixedSwapParser) validateUniV3SwapData(amount0, amount1, sqrtPriceX96, liquidity *big.Int, tick int32) error {
// Check that at least one amount is non-zero
if amount0.Sign() == 0 && amount1.Sign() == 0 {
return fmt.Errorf("both amounts cannot be zero")
}
// Check that amounts have opposite signs (one in, one out)
if amount0.Sign() != 0 && amount1.Sign() != 0 && amount0.Sign() == amount1.Sign() {
fsp.logger.Warn("Unusual swap: both amounts have same sign",
"amount0", amount0.String(),
"amount1", amount1.String())
}
// Validate sqrtPriceX96 is positive
if sqrtPriceX96.Sign() <= 0 {
return fmt.Errorf("sqrtPriceX96 must be positive: %s", sqrtPriceX96.String())
}
// Validate liquidity is non-negative
if liquidity.Sign() < 0 {
return fmt.Errorf("liquidity cannot be negative: %s", liquidity.String())
}
// Validate tick range (typical UniV3 range)
if tick < -887272 || tick > 887272 {
return fmt.Errorf("tick %d out of valid range [-887272, 887272]", tick)
}
return nil
}
// validateUniV2SwapData validates parsed UniswapV2 swap data
func (fsp *FixedSwapParser) validateUniV2SwapData(amount0In, amount1In, amount0Out, amount1Out *big.Int) error {
// At least one input amount must be positive
if amount0In.Sign() <= 0 && amount1In.Sign() <= 0 {
return fmt.Errorf("at least one input amount must be positive")
}
// At least one output amount must be positive
if amount0Out.Sign() <= 0 && amount1Out.Sign() <= 0 {
return fmt.Errorf("at least one output amount must be positive")
}
// Input amounts should not equal output amounts (no zero-value swaps)
if amount0In.Cmp(amount0Out) == 0 && amount1In.Cmp(amount1Out) == 0 {
return fmt.Errorf("input amounts equal output amounts (zero-value swap)")
}
return nil
}
// ExtendedSwapEvent includes additional validation and error information
type ExtendedSwapEvent struct {
*SimpleSwapEvent
ParseErrors []string `json:"parse_errors,omitempty"`
Warnings []string `json:"warnings,omitempty"`
Validated bool `json:"validated"`
}
// SimpleSwapEvent represents a parsed swap event (keeping existing structure for compatibility)
type SimpleSwapEvent struct {
TxHash common.Hash `json:"tx_hash"`
PoolAddress common.Address `json:"pool_address"`
Token0 common.Address `json:"token0"`
Token1 common.Address `json:"token1"`
Amount0 *big.Int `json:"amount0"`
Amount1 *big.Int `json:"amount1"`
SqrtPriceX96 *big.Int `json:"sqrt_price_x96,omitempty"`
Liquidity *big.Int `json:"liquidity,omitempty"`
Tick int32 `json:"tick,omitempty"`
BlockNumber uint64 `json:"block_number"`
LogIndex uint `json:"log_index"`
Timestamp time.Time `json:"timestamp"`
Sender common.Address `json:"sender,omitempty"`
Recipient common.Address `json:"recipient,omitempty"`
Protocol string `json:"protocol"`
}