- 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>
382 lines
12 KiB
Go
382 lines
12 KiB
Go
//go:build legacy_scanner
|
|
// +build legacy_scanner
|
|
|
|
// Package scanner provides tests for the market scanner functionality
|
|
package scanner
|
|
|
|
import (
|
|
"math/big"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/ethereum/go-ethereum/common"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/mock"
|
|
|
|
"github.com/fraktal/mev-beta/internal/config"
|
|
"github.com/fraktal/mev-beta/internal/logger"
|
|
"github.com/fraktal/mev-beta/pkg/database"
|
|
"github.com/fraktal/mev-beta/pkg/events"
|
|
"github.com/fraktal/mev-beta/pkg/pools"
|
|
)
|
|
|
|
// MockContractExecutor is a mock implementation of the contract executor
|
|
type MockContractExecutor struct {
|
|
mock.Mock
|
|
}
|
|
|
|
func (m *MockContractExecutor) ExecuteArbitrage(opportunity ArbitrageOpportunity) error {
|
|
args := m.Called(opportunity)
|
|
return args.Error(0)
|
|
}
|
|
|
|
func (m *MockContractExecutor) ExecuteTriangularArbitrage(opportunity ArbitrageOpportunity) error {
|
|
args := m.Called(opportunity)
|
|
return args.Error(0)
|
|
}
|
|
|
|
func (m *MockContractExecutor) Close() error {
|
|
args := m.Called()
|
|
return args.Error(0)
|
|
}
|
|
|
|
// MockDatabase is a mock implementation of the database
|
|
type MockDatabase struct {
|
|
mock.Mock
|
|
}
|
|
|
|
func (m *MockDatabase) InsertSwapEvent(event *database.SwapEvent) error {
|
|
args := m.Called(event)
|
|
return args.Error(0)
|
|
}
|
|
|
|
func (m *MockDatabase) InsertLiquidityEvent(event *database.LiquidityEvent) error {
|
|
args := m.Called(event)
|
|
return args.Error(0)
|
|
}
|
|
|
|
func (m *MockDatabase) InsertPoolData(pool *database.PoolData) error {
|
|
args := m.Called(pool)
|
|
return args.Error(0)
|
|
}
|
|
|
|
func (m *MockDatabase) GetRecentSwapEvents(limit int) ([]*database.SwapEvent, error) {
|
|
args := m.Called(limit)
|
|
return args.Get(0).([]*database.SwapEvent), args.Error(1)
|
|
}
|
|
|
|
func (m *MockDatabase) GetRecentLiquidityEvents(limit int) ([]*database.LiquidityEvent, error) {
|
|
args := m.Called(limit)
|
|
return args.Get(0).([]*database.LiquidityEvent), args.Error(1)
|
|
}
|
|
|
|
func (m *MockDatabase) GetPoolData(poolAddress common.Address) (*database.PoolData, error) {
|
|
args := m.Called(poolAddress)
|
|
return args.Get(0).(*database.PoolData), args.Error(1)
|
|
}
|
|
|
|
func (m *MockDatabase) Close() error {
|
|
args := m.Called()
|
|
return args.Error(0)
|
|
}
|
|
|
|
// TestMarketScannerInitialization tests that the scanner can be initialized properly
|
|
func TestMarketScannerInitialization(t *testing.T) {
|
|
// Create test logger
|
|
log := logger.New("debug", "text", "")
|
|
|
|
// Create test config
|
|
cfg := &config.BotConfig{
|
|
Enabled: true,
|
|
PollingInterval: 1,
|
|
MinProfitThreshold: 0.01,
|
|
GasPriceMultiplier: 1.2,
|
|
MaxWorkers: 2,
|
|
ChannelBufferSize: 10,
|
|
RPCTimeout: 30,
|
|
}
|
|
|
|
// Create mock contract executor
|
|
mockExecutor := new(MockContractExecutor)
|
|
mockExecutor.On("Close").Return(nil)
|
|
|
|
// Create mock database
|
|
mockDB := new(MockDatabase)
|
|
mockDB.On("Close").Return(nil)
|
|
|
|
// Create mock CREATE2 calculator
|
|
mockCalculator := new(pools.CREATE2Calculator)
|
|
|
|
// Create market scanner
|
|
scanner := NewMarketScanner(cfg, log, mockExecutor, mockDB, mockCalculator)
|
|
|
|
// Verify scanner was created
|
|
assert.NotNil(t, scanner)
|
|
assert.Equal(t, cfg, scanner.config)
|
|
assert.Equal(t, log, scanner.logger)
|
|
assert.Equal(t, mockExecutor, scanner.contractExecutor)
|
|
assert.Equal(t, mockDB, scanner.database)
|
|
assert.Equal(t, mockCalculator, scanner.create2Calculator)
|
|
|
|
// Test stopping the scanner
|
|
scanner.Stop()
|
|
|
|
// Verify mocks were called
|
|
mockExecutor.AssertExpectations(t)
|
|
mockDB.AssertExpectations(t)
|
|
}
|
|
|
|
// TestMarketScannerEventProcessing tests that the scanner can process events
|
|
func TestMarketScannerEventProcessing(t *testing.T) {
|
|
// Create test logger
|
|
log := logger.New("debug", "text", "")
|
|
|
|
// Create test config
|
|
cfg := &config.BotConfig{
|
|
Enabled: true,
|
|
PollingInterval: 1,
|
|
MinProfitThreshold: 0.01,
|
|
GasPriceMultiplier: 1.2,
|
|
MaxWorkers: 2,
|
|
ChannelBufferSize: 10,
|
|
RPCTimeout: 30,
|
|
}
|
|
|
|
// Create mock contract executor
|
|
mockExecutor := new(MockContractExecutor)
|
|
mockExecutor.On("Close").Return(nil)
|
|
|
|
// Create mock database
|
|
mockDB := new(MockDatabase)
|
|
mockDB.On("InsertSwapEvent", mock.Anything).Return(nil)
|
|
mockDB.On("InsertLiquidityEvent", mock.Anything).Return(nil)
|
|
mockDB.On("InsertPoolData", mock.Anything).Return(nil)
|
|
mockDB.On("Close").Return(nil)
|
|
|
|
// Create mock CREATE2 calculator
|
|
mockCalculator := new(pools.CREATE2Calculator)
|
|
|
|
// Create market scanner
|
|
scanner := NewMarketScanner(cfg, log, mockExecutor, mockDB, mockCalculator)
|
|
|
|
// Create test swap event
|
|
swapEvent := events.Event{
|
|
Type: events.Swap,
|
|
Protocol: "UniswapV3",
|
|
PoolAddress: common.HexToAddress("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640"),
|
|
Token0: common.HexToAddress("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"), // USDC
|
|
Token1: common.HexToAddress("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"), // WETH
|
|
Amount0: big.NewInt(1000000000), // 1000 USDC
|
|
Amount1: big.NewInt(500000000000000000), // 0.5 WETH
|
|
BlockNumber: 12345678,
|
|
TransactionHash: common.HexToHash("0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"),
|
|
Tick: 200000,
|
|
Fee: 3000,
|
|
Liquidity: big.NewInt(1000000000000000000),
|
|
SqrtPriceX96: func() *big.Int {
|
|
val, _ := big.NewInt(0).SetString("2505414483750470000", 10)
|
|
return val
|
|
}(),
|
|
Timestamp: 1234567890,
|
|
}
|
|
|
|
// Test submitting the event for processing
|
|
scanner.SubmitEvent(swapEvent)
|
|
|
|
// Give some time for processing
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
// Test stopping the scanner
|
|
scanner.Stop()
|
|
|
|
// Verify mocks were called
|
|
mockDB.AssertExpectations(t)
|
|
mockExecutor.AssertExpectations(t)
|
|
}
|
|
|
|
// TestMarketScannerLiquidityEventProcessing tests that the scanner can process liquidity events
|
|
func TestMarketScannerLiquidityEventProcessing(t *testing.T) {
|
|
// Create test logger
|
|
log := logger.New("debug", "text", "")
|
|
|
|
// Create test config
|
|
cfg := &config.BotConfig{
|
|
Enabled: true,
|
|
PollingInterval: 1,
|
|
MinProfitThreshold: 0.01,
|
|
GasPriceMultiplier: 1.2,
|
|
MaxWorkers: 2,
|
|
ChannelBufferSize: 10,
|
|
RPCTimeout: 30,
|
|
}
|
|
|
|
// Create mock contract executor
|
|
mockExecutor := new(MockContractExecutor)
|
|
mockExecutor.On("Close").Return(nil)
|
|
|
|
// Create mock database
|
|
mockDB := new(MockDatabase)
|
|
mockDB.On("InsertSwapEvent", mock.Anything).Return(nil)
|
|
mockDB.On("InsertLiquidityEvent", mock.Anything).Return(nil)
|
|
mockDB.On("InsertPoolData", mock.Anything).Return(nil)
|
|
mockDB.On("Close").Return(nil)
|
|
|
|
// Create mock CREATE2 calculator
|
|
mockCalculator := new(pools.CREATE2Calculator)
|
|
|
|
// Create market scanner
|
|
scanner := NewMarketScanner(cfg, log, mockExecutor, mockDB, mockCalculator)
|
|
|
|
// Create test liquidity add event
|
|
liquidityEvent := events.Event{
|
|
Type: events.AddLiquidity,
|
|
Protocol: "UniswapV3",
|
|
PoolAddress: common.HexToAddress("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640"),
|
|
Token0: common.HexToAddress("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"), // USDC
|
|
Token1: common.HexToAddress("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"), // WETH
|
|
Amount0: big.NewInt(2000000000), // 2000 USDC
|
|
Amount1: big.NewInt(1000000000000000000), // 1 WETH
|
|
BlockNumber: 12345679,
|
|
TransactionHash: common.HexToHash("0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890"),
|
|
Fee: 3000,
|
|
Liquidity: big.NewInt(1000000000000000000),
|
|
SqrtPriceX96: func() *big.Int {
|
|
val, _ := big.NewInt(0).SetString("2505414483750470000", 10)
|
|
return val
|
|
}(),
|
|
Timestamp: 1234567891,
|
|
}
|
|
|
|
// Test submitting the event for processing
|
|
scanner.SubmitEvent(liquidityEvent)
|
|
|
|
// Give some time for processing
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
// Test stopping the scanner
|
|
scanner.Stop()
|
|
|
|
// Verify mocks were called
|
|
mockDB.AssertExpectations(t)
|
|
mockExecutor.AssertExpectations(t)
|
|
}
|
|
|
|
// TestMarketScannerNewPoolEventProcessing tests that the scanner can process new pool events
|
|
func TestMarketScannerNewPoolEventProcessing(t *testing.T) {
|
|
// Create test logger
|
|
log := logger.New("debug", "text", "")
|
|
|
|
// Create test config
|
|
cfg := &config.BotConfig{
|
|
Enabled: true,
|
|
PollingInterval: 1,
|
|
MinProfitThreshold: 0.01,
|
|
GasPriceMultiplier: 1.2,
|
|
MaxWorkers: 2,
|
|
ChannelBufferSize: 10,
|
|
RPCTimeout: 30,
|
|
}
|
|
|
|
// Create mock contract executor
|
|
mockExecutor := new(MockContractExecutor)
|
|
mockExecutor.On("Close").Return(nil)
|
|
|
|
// Create mock database
|
|
mockDB := new(MockDatabase)
|
|
mockDB.On("InsertSwapEvent", mock.Anything).Return(nil)
|
|
mockDB.On("InsertLiquidityEvent", mock.Anything).Return(nil)
|
|
mockDB.On("InsertPoolData", mock.Anything).Return(nil)
|
|
mockDB.On("Close").Return(nil)
|
|
|
|
// Create mock CREATE2 calculator
|
|
mockCalculator := new(pools.CREATE2Calculator)
|
|
|
|
// Create market scanner
|
|
scanner := NewMarketScanner(cfg, log, mockExecutor, mockDB, mockCalculator)
|
|
|
|
// Create test new pool event
|
|
newPoolEvent := events.Event{
|
|
Type: events.NewPool,
|
|
Protocol: "UniswapV3",
|
|
PoolAddress: common.HexToAddress("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640"),
|
|
Token0: common.HexToAddress("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"), // USDC
|
|
Token1: common.HexToAddress("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"), // WETH
|
|
Fee: 3000,
|
|
BlockNumber: 12345680,
|
|
TransactionHash: common.HexToHash("0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"),
|
|
Timestamp: 1234567892,
|
|
}
|
|
|
|
// Test submitting the event for processing
|
|
scanner.SubmitEvent(newPoolEvent)
|
|
|
|
// Give some time for processing
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
// Test stopping the scanner
|
|
scanner.Stop()
|
|
|
|
// Verify mocks were called
|
|
mockDB.AssertExpectations(t)
|
|
mockExecutor.AssertExpectations(t)
|
|
}
|
|
|
|
// TestMarketScannerArbitrageExecution tests that the scanner can execute arbitrage opportunities
|
|
func TestMarketScannerArbitrageExecution(t *testing.T) {
|
|
// Create test logger
|
|
log := logger.New("debug", "text", "")
|
|
|
|
// Create test config
|
|
cfg := &config.BotConfig{
|
|
Enabled: true,
|
|
PollingInterval: 1,
|
|
MinProfitThreshold: 0.01,
|
|
GasPriceMultiplier: 1.2,
|
|
MaxWorkers: 2,
|
|
ChannelBufferSize: 10,
|
|
RPCTimeout: 30,
|
|
}
|
|
|
|
// Create mock contract executor
|
|
mockExecutor := new(MockContractExecutor)
|
|
mockExecutor.On("ExecuteArbitrage", mock.Anything).Return(nil)
|
|
mockExecutor.On("Close").Return(nil)
|
|
|
|
// Create mock database
|
|
mockDB := new(MockDatabase)
|
|
mockDB.On("InsertSwapEvent", mock.Anything).Return(nil)
|
|
mockDB.On("InsertLiquidityEvent", mock.Anything).Return(nil)
|
|
mockDB.On("InsertPoolData", mock.Anything).Return(nil)
|
|
mockDB.On("Close").Return(nil)
|
|
|
|
// Create mock CREATE2 calculator
|
|
mockCalculator := new(pools.CREATE2Calculator)
|
|
|
|
// Create market scanner
|
|
scanner := NewMarketScanner(cfg, log, mockExecutor, mockDB, mockCalculator)
|
|
|
|
// Create test arbitrage opportunity with high profit
|
|
opportunity := ArbitrageOpportunity{
|
|
Path: []string{"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"},
|
|
Pools: []string{"0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640"},
|
|
Profit: big.NewInt(1000000000000000000), // 1 ETH profit
|
|
GasEstimate: big.NewInt(300000),
|
|
ROI: 5.0, // 5% ROI
|
|
Protocol: "UniswapV3",
|
|
}
|
|
|
|
// Test executing the arbitrage opportunity
|
|
scanner.executeArbitrageOpportunity(opportunity)
|
|
|
|
// Give some time for execution
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
// Test stopping the scanner
|
|
scanner.Stop()
|
|
|
|
// Verify that the contract executor was called
|
|
mockExecutor.AssertCalled(t, "ExecuteArbitrage", mock.Anything)
|
|
mockDB.AssertExpectations(t)
|
|
mockExecutor.AssertExpectations(t)
|
|
}
|