// Package integration provides integration tests for the MEV bot using a forked Arbitrum environment package integration import ( "context" "math/big" "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/contracts" "github.com/fraktal/mev-beta/pkg/database" "github.com/fraktal/mev-beta/pkg/pools" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) // TestContractExecutorInitialization tests that the contract executor can be initialized func TestContractExecutorInitialization(t *testing.T) { // Skip this test in short mode if testing.Short() { t.Skip("skipping contract executor test in short mode") } // Create test logger log := logger.New("debug", "text", "") // Create test configuration cfg := &config.Config{ Arbitrum: config.ArbitrumConfig{ RPCEndpoint: "http://localhost:8545", // Anvil default port ChainID: 31337, // Anvil default chain ID RateLimit: config.RateLimitConfig{ RequestsPerSecond: 10, MaxConcurrent: 5, Burst: 20, }, }, Bot: config.BotConfig{ Enabled: true, PollingInterval: 1, MinProfitThreshold: 0.01, // Lower threshold for testing GasPriceMultiplier: 1.2, MaxWorkers: 2, ChannelBufferSize: 10, RPCTimeout: 30, }, Ethereum: config.EthereumConfig{ PrivateKey: "", // Will be set by environment or test setup AccountAddress: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", // Default Anvil account GasPriceMultiplier: 1.2, }, Contracts: config.ContractsConfig{ ArbitrageExecutor: "0x0000000000000000000000000000000000000000", // Placeholder FlashSwapper: "0x0000000000000000000000000000000000000000", // Placeholder AuthorizedCallers: []string{ "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", // Default Anvil account }, AuthorizedDEXes: []string{ "0x1F98431c8aD98523631AE4a59f267346ea31F984", // Uniswap V3 Factory "0xf1D7CC64Fb4452F05c498126312eBE29f30Fbcf9", // Uniswap V2 Factory "0xc35DADB65012eC5796536bD9864eD8773aBc74C4", // SushiSwap Factory }, }, Database: config.DatabaseConfig{ File: ":memory:", MaxOpenConnections: 10, MaxIdleConnections: 5, }, Uniswap: config.UniswapConfig{ FactoryAddress: "0x1F98431c8aD98523631AE4a59f267346ea31F984", PositionManagerAddress: "0xC36442b4a4522E871399CD717aBDD847Ab11FE88", FeeTiers: []int64{500, 3000, 10000}, Cache: config.CacheConfig{ Enabled: true, Expiration: 300, MaxSize: 10000, }, }, } // Connect to the forked environment client, err := ethclient.Dial(cfg.Arbitrum.RPCEndpoint) if err != nil { t.Skipf("Skipping test: failed to connect to forked Arbitrum at %s", cfg.Arbitrum.RPCEndpoint) } defer client.Close() // Verify connection by getting chain ID chainID, err := client.ChainID(context.Background()) require.NoError(t, err, "failed to get chain ID") log.Info("Connected to forked Arbitrum chain ID:", chainID.String()) // Test contract executor creation (this might fail in testing but we can verify the setup) contractExecutor, err := contracts.NewContractExecutor(cfg, log) if err != nil { // This is expected in testing since we don't have real contracts deployed log.Warn("Contract executor creation failed (expected in testing):", err) } else { defer contractExecutor.Close() assert.NotNil(t, contractExecutor) log.Info("Contract executor created successfully") } log.Info("Contract executor initialization test completed") } // TestCREATE2Calculator tests the CREATE2 pool address calculation func TestCREATE2Calculator(t *testing.T) { // Create test logger log := logger.New("debug", "text", "") // Create CREATE2 calculator calculator := pools.NewCREATE2Calculator(log) // Test calculating pool addresses for known token pairs testCases := []struct { name string factoryName string token0 string token1 string fee uint32 expectedLen int }{ { name: "Uniswap V3 USDC/WETH", factoryName: "uniswap_v3", token0: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", // USDC token1: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", // WETH fee: 3000, expectedLen: 20, // Address should be 20 bytes }, { name: "Uniswap V2 USDC/WETH", factoryName: "uniswap_v2", token0: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", // USDC token1: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", // WETH fee: 0, // V2 doesn't use fee in pool calculation expectedLen: 20, }, { name: "SushiSwap USDC/WETH", factoryName: "sushiswap", token0: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", // USDC token1: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", // WETH fee: 0, // SushiSwap V2 doesn't use fee in pool calculation expectedLen: 20, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { // Convert hex addresses to common.Address token0 := common.HexToAddress(tc.token0) token1 := common.HexToAddress(tc.token1) // Calculate pool address poolAddr, err := calculator.CalculatePoolAddress(tc.factoryName, token0, token1, tc.fee) require.NoError(t, err, "failed to calculate pool address") // Verify the pool address assert.Equal(t, tc.expectedLen, len(poolAddr.Bytes()), "pool address should be 20 bytes") assert.NotEqual(t, common.Address{}, poolAddr, "pool address should not be zero") log.Info("Calculated pool address:", poolAddr.Hex(), "for", tc.name) }) } // Test finding pools for token pairs usdc := common.HexToAddress("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48") weth := common.HexToAddress("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2") pools, err := calculator.FindPoolsForTokenPair(usdc, weth) require.NoError(t, err, "failed to find pools for token pair") log.Info("Found", len(pools), "potential pools for USDC/WETH pair") // Verify we found some pools assert.True(t, len(pools) > 0, "should find at least one pool for USDC/WETH") // Verify each pool has valid data for _, pool := range pools { assert.NotEqual(t, "", pool.Factory, "factory should not be empty") assert.NotEqual(t, common.Address{}, pool.Token0, "token0 should not be zero") assert.NotEqual(t, common.Address{}, pool.Token1, "token1 should not be zero") assert.NotEqual(t, common.Address{}, pool.PoolAddr, "pool address should not be zero") assert.True(t, pool.Fee >= 0, "fee should be non-negative") } log.Info("CREATE2 calculator test completed successfully") } // TestDatabaseIntegration tests database integration func TestDatabaseIntegration(t *testing.T) { // Create test logger log := logger.New("debug", "text", "") // Create test configuration cfg := &config.DatabaseConfig{ File: ":memory:", // In-memory database for testing MaxOpenConnections: 10, MaxIdleConnections: 5, } // Create database db, err := database.NewDatabase(cfg, log) require.NoError(t, err, "failed to create database") defer db.Close() // Test inserting swap event swapEvent := &database.SwapEvent{ Timestamp: time.Now(), BlockNumber: 12345678, TxHash: common.HexToHash("0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"), PoolAddress: common.HexToAddress("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640"), Token0: common.HexToAddress("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"), // USDC Token1: common.HexToAddress("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"), // WETH Amount0In: big.NewInt(1000000000), // 1000 USDC Amount1In: big.NewInt(0), Amount0Out: big.NewInt(0), Amount1Out: big.NewInt(500000000000000000), // 0.5 WETH Sender: common.HexToAddress("0x1234567890abcdef1234567890abcdef12345678"), Recipient: common.HexToAddress("0x8765432109fedcba8765432109fedcba87654321"), Protocol: "uniswap_v3", } err = db.InsertSwapEvent(swapEvent) assert.NoError(t, err, "failed to insert swap event") // Test retrieving recent swap events swaps, err := db.GetRecentSwapEvents(10) assert.NoError(t, err, "failed to get recent swap events") assert.Len(t, swaps, 1, "expected 1 swap event") // Verify the retrieved swap event if len(swaps) > 0 { assert.Equal(t, swapEvent.PoolAddress, swaps[0].PoolAddress, "pool address mismatch") assert.Equal(t, swapEvent.Token0, swaps[0].Token0, "token0 mismatch") assert.Equal(t, swapEvent.Token1, swaps[0].Token1, "token1 mismatch") assert.Equal(t, swapEvent.Protocol, swaps[0].Protocol, "protocol mismatch") assert.Equal(t, swapEvent.Amount0In, swaps[0].Amount0In, "amount0 in mismatch") assert.Equal(t, swapEvent.Amount1Out, swaps[0].Amount1Out, "amount1 out mismatch") } // Test inserting pool data poolData := &database.PoolData{ Address: common.HexToAddress("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640"), Token0: common.HexToAddress("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"), // USDC Token1: common.HexToAddress("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"), // WETH Fee: 3000, // 0.3% Liquidity: big.NewInt(1000000000000000000), // 1 ETH equivalent SqrtPriceX96: big.NewInt(2505414483750470000), // Realistic price Tick: 200000, // Corresponding tick LastUpdated: time.Now(), Protocol: "uniswap_v3", } err = db.InsertPoolData(poolData) assert.NoError(t, err, "failed to insert pool data") // Test retrieving pool data retrievedPool, err := db.GetPoolData(poolData.Address) assert.NoError(t, err, "failed to get pool data") assert.Equal(t, poolData.Address, retrievedPool.Address, "pool address mismatch") assert.Equal(t, poolData.Token0, retrievedPool.Token0, "token0 mismatch") assert.Equal(t, poolData.Token1, retrievedPool.Token1, "token1 mismatch") assert.Equal(t, poolData.Fee, retrievedPool.Fee, "fee mismatch") assert.Equal(t, poolData.Protocol, retrievedPool.Protocol, "protocol mismatch") log.Info("Database integration test completed successfully") }