package integration import ( "context" "fmt" "math/big" "os" "testing" "time" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/ethclient" "github.com/fraktal/mev-beta/internal/config" "github.com/fraktal/mev-beta/internal/logger" "github.com/fraktal/mev-beta/pkg/arbitrage" "github.com/fraktal/mev-beta/pkg/security" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) // Test configuration for forked environment const ( TestRPCEndpoint = "http://localhost:8545" TestChainID = 31337 ) // Arbitrum One token addresses for testing var ( WETH = common.HexToAddress("0x82aF49447D8A07e3bd95BD0d56f35241523fBab1") USDC = common.HexToAddress("0xA0b86a33E6417aB7d461a67E4d3F14F6b49d3e8B") // USDC.e USDT = common.HexToAddress("0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9") ) func TestMain(m *testing.M) { // Check if we're in test mode if os.Getenv("TEST_MODE") != "true" { fmt.Println("Skipping integration tests - set TEST_MODE=true to run") os.Exit(0) } // Run tests code := m.Run() os.Exit(code) } func setupTestEnvironment(t *testing.T) (*arbitrage.SimpleArbitrageService, func()) { // Create test logger log := logger.New("debug", "text", "") // Create test configuration cfg := &config.ArbitrageConfig{ Enabled: true, ArbitrageContractAddress: "0x0000000000000000000000000000000000000001", // Placeholder FlashSwapContractAddress: "0x0000000000000000000000000000000000000002", // Placeholder MinProfitWei: 1000000000000000, // 0.001 ETH MinROIPercent: 1.0, // 1% MinSignificantSwapSize: 1000000000000000000, // 1 ETH SlippageTolerance: 0.005, // 0.5% MinScanAmountWei: 100000000000000000, // 0.1 ETH MaxScanAmountWei: 10000000000000000000, // 10 ETH MaxGasPriceWei: 100000000000, // 100 gwei MaxConcurrentExecutions: 1, // Single execution for testing MaxOpportunitiesPerEvent: 3, OpportunityTTL: 30 * time.Second, MaxPathAge: 60 * time.Second, StatsUpdateInterval: 10 * time.Second, } // Create Ethereum client client, err := ethclient.Dial(TestRPCEndpoint) require.NoError(t, err, "Failed to connect to test RPC") // Create key manager keyManagerConfig := &security.KeyManagerConfig{ KeystorePath: "test_keystore", EncryptionKey: "test-encryption-key", KeyRotationDays: 30, MaxSigningRate: 100, SessionTimeout: time.Hour, } keyManager, err := security.NewKeyManager(keyManagerConfig, log) require.NoError(t, err, "Failed to create key manager") // Create test database database, err := arbitrage.NewSQLiteDatabase(":memory:", log) require.NoError(t, err, "Failed to create test database") // Create arbitrage service service, err := arbitrage.NewSimpleArbitrageService( client, log, cfg, keyManager, database, ) require.NoError(t, err, "Failed to create arbitrage service") // Start the service err = service.Start() require.NoError(t, err, "Failed to start arbitrage service") // Cleanup function cleanup := func() { service.Stop() database.Close() client.Close() } return service, cleanup } func TestArbitrageServiceIntegration(t *testing.T) { service, cleanup := setupTestEnvironment(t) defer cleanup() t.Run("ServiceHealthCheck", func(t *testing.T) { assert.True(t, service.IsRunning(), "Service should be running") stats := service.GetStats() assert.NotNil(t, stats, "Stats should not be nil") assert.Equal(t, int64(0), stats.TotalOpportunitiesDetected, "Initial opportunities should be zero") }) t.Run("TokenValidation", func(t *testing.T) { // Test that we can validate token addresses tokens := []common.Address{WETH, USDC, USDT} for _, token := range tokens { assert.False(t, token.String() == "0x0000000000000000000000000000000000000000", "Token address should not be zero: %s", token.String()) } }) } func TestSwapEventProcessing(t *testing.T) { service, cleanup := setupTestEnvironment(t) defer cleanup() t.Run("ProcessLargeSwapEvent", func(t *testing.T) { // Create a simulated large swap event swapEvent := &arbitrage.SimpleSwapEvent{ TxHash: common.HexToHash("0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"), PoolAddress: common.HexToAddress("0xC6962004f452bE9203591991D15f6b388e09E8D0"), // Example pool Token0: WETH, Token1: USDC, Amount0: big.NewInt(-5000000000000000000), // -5 ETH Amount1: big.NewInt(12500000000), // +12500 USDC SqrtPriceX96: big.NewInt(1000000000000000000000), // Example price Liquidity: big.NewInt(50000000000000000000000), // Example liquidity Tick: int32(-85000), // Example tick BlockNumber: 12345678, LogIndex: 1, Timestamp: time.Now(), } // Process the swap event err := service.ProcessSwapEvent(swapEvent) assert.NoError(t, err, "Should process swap event without error") // Allow time for processing time.Sleep(1 * time.Second) // Check that the event was processed stats := service.GetStats() t.Logf("Opportunities detected: %d", stats.TotalOpportunitiesDetected) }) } func TestPoolDataRetrieval(t *testing.T) { service, cleanup := setupTestEnvironment(t) defer cleanup() t.Run("SaveAndRetrievePoolData", func(t *testing.T) { // Create test pool data poolData := &arbitrage.SimplePoolData{ Address: common.HexToAddress("0xC6962004f452bE9203591991D15f6b388e09E8D0"), Token0: WETH, Token1: USDC, Fee: 500, // 0.05% Liquidity: big.NewInt(50000000000000000000000), SqrtPriceX96: big.NewInt(1000000000000000000000), Tick: -85000, BlockNumber: 12345678, TxHash: common.HexToHash("0xabcdef"), LogIndex: 1, LastUpdated: time.Now(), } // This test would require database access through the service // For now, we verify the structure is correct assert.Equal(t, WETH, poolData.Token0, "Token0 should be WETH") assert.Equal(t, USDC, poolData.Token1, "Token1 should be USDC") assert.Equal(t, int64(500), poolData.Fee, "Fee should be 500 (0.05%)") }) } func TestRealTimeArbitrageDetection(t *testing.T) { if testing.Short() { t.Skip("Skipping real-time test in short mode") } service, cleanup := setupTestEnvironment(t) defer cleanup() t.Run("ContinuousMonitoring", func(t *testing.T) { // Run service for a short period to test real-time processing ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() initialStats := service.GetStats() // Simulate periodic swap events ticker := time.NewTicker(5 * time.Second) defer ticker.Stop() go func() { eventCount := 0 for { select { case <-ctx.Done(): return case <-ticker.C: eventCount++ // Create varied swap events swapEvent := &arbitrage.SimpleSwapEvent{ TxHash: common.HexToHash(fmt.Sprintf("0x%064d", eventCount)), PoolAddress: common.HexToAddress("0xC6962004f452bE9203591991D15f6b388e09E8D0"), Token0: WETH, Token1: USDC, Amount0: big.NewInt(int64(-1000000000000000000 * eventCount)), // Varying amounts Amount1: big.NewInt(int64(2500000000 * eventCount)), SqrtPriceX96: big.NewInt(1000000000000000000000), Liquidity: big.NewInt(50000000000000000000000), Tick: int32(-85000 + eventCount*100), BlockNumber: 12345678 + uint64(eventCount), LogIndex: uint(eventCount), Timestamp: time.Now(), } service.ProcessSwapEvent(swapEvent) } } }() // Wait for test duration <-ctx.Done() // Check final stats finalStats := service.GetStats() t.Logf("Initial opportunities: %d", initialStats.TotalOpportunitiesDetected) t.Logf("Final opportunities: %d", finalStats.TotalOpportunitiesDetected) // We expect some processing activity assert.True(t, finalStats.TotalOpportunitiesDetected >= initialStats.TotalOpportunitiesDetected, "Should have processed some opportunities") }) }