// Package scanner provides tests for the market scanner functionality package scanner import ( "math/big" "testing" "time" "github.com/ethereum/go-ethereum/common" "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" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" ) // 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, TxHash: 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, TxHash: 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, TxHash: 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) }