//go:build legacy_arbitrum // +build legacy_arbitrum package arbitrum import ( "context" "math/big" "testing" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/rpc" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/fraktal/mev-beta/internal/logger" ) // Mock RPC client for testing type mockRPCClient struct { responses map[string]interface{} } func (m *mockRPCClient) CallContext(ctx context.Context, result interface{}, method string, args ...interface{}) error { // Check if context is already cancelled select { case <-ctx.Done(): return ctx.Err() default: } // Mock successful responses based on method switch method { case "eth_call": // Mock token0/token1/fee responses call := args[0].(map[string]interface{}) data := call["data"].(string) // Mock responses for different function calls switch { case len(data) >= 10 && data[2:10] == "0d0e30db": // token0() *result.(*string) = "0x000000000000000000000000A0b86a33E6441f43E2e4A96439abFA2A69067ACD" // Mock token address case len(data) >= 10 && data[2:10] == "d21220a7": // token1() *result.(*string) = "0x000000000000000000000000af88d065e77c8cC2239327C5EDb3A432268e5831" // Mock token address case len(data) >= 10 && data[2:10] == "ddca3f43": // fee() *result.(*string) = "0x0000000000000000000000000000000000000000000000000000000000000bb8" // 3000 (0.3%) case len(data) >= 10 && data[2:10] == "fc0e74d1": // getTokenX() *result.(*string) = "0x000000000000000000000000A0b86a33E6441f43E2e4A96439abFA2A69067ACD" // Mock token address case len(data) >= 10 && data[2:10] == "8cc8b9a9": // getTokenY() *result.(*string) = "0x000000000000000000000000af88d065e77c8cC2239327C5EDb3A432268e5831" // Mock token address case len(data) >= 10 && data[2:10] == "69fe0e2d": // getBinStep() *result.(*string) = "0x0000000000000000000000000000000000000000000000000000000000000019" // 25 (bin step) default: *result.(*string) = "0x0000000000000000000000000000000000000000000000000000000000000000" } case "eth_getLogs": // Mock empty logs for pool discovery *result.(*[]interface{}) = []interface{}{} } return nil } func createMockLogger() *logger.Logger { return logger.New("debug", "text", "") } func createMockRPCClient() *rpc.Client { // Create a mock that satisfies the interface return &rpc.Client{} } // Test CamelotV3Parser func TestCamelotV3Parser_New(t *testing.T) { client := createMockRPCClient() logger := createMockLogger() parser := NewCamelotV3Parser(client, logger) require.NotNil(t, parser) camelotParser, ok := parser.(*CamelotV3Parser) require.True(t, ok, "Parser should be CamelotV3Parser type") assert.Equal(t, ProtocolCamelotV3, camelotParser.protocol) } func TestCamelotV3Parser_GetSupportedContractTypes(t *testing.T) { client := createMockRPCClient() logger := createMockLogger() parser := NewCamelotV3Parser(client, logger).(*CamelotV3Parser) types := parser.GetSupportedContractTypes() assert.Contains(t, types, ContractTypeFactory) assert.Contains(t, types, ContractTypeRouter) assert.Contains(t, types, ContractTypePool) } func TestCamelotV3Parser_GetSupportedEventTypes(t *testing.T) { client := createMockRPCClient() logger := createMockLogger() parser := NewCamelotV3Parser(client, logger).(*CamelotV3Parser) events := parser.GetSupportedEventTypes() assert.Contains(t, events, EventTypeSwap) assert.Contains(t, events, EventTypeLiquidityAdd) assert.Contains(t, events, EventTypeLiquidityRemove) assert.Contains(t, events, EventTypePoolCreated) assert.Contains(t, events, EventTypePositionUpdate) } func TestCamelotV3Parser_IsKnownContract(t *testing.T) { client := createMockRPCClient() logger := createMockLogger() parser := NewCamelotV3Parser(client, logger).(*CamelotV3Parser) // Test known contract (factory) factoryAddr := common.HexToAddress("0x1a3c9B1d2F0529D97f2afC5136Cc23e58f1FD35B") assert.True(t, parser.IsKnownContract(factoryAddr)) // Test unknown contract unknownAddr := common.HexToAddress("0x1234567890123456789012345678901234567890") assert.False(t, parser.IsKnownContract(unknownAddr)) } func TestCamelotV3Parser_ParseLog(t *testing.T) { client := createMockRPCClient() logger := createMockLogger() parser := NewCamelotV3Parser(client, logger).(*CamelotV3Parser) // Create mock swap log factoryAddr := common.HexToAddress("0x1a3c9B1d2F0529D97f2afC5136Cc23e58f1FD35B") _ = parser.eventSigs // Reference to avoid unused variable warning log := &types.Log{ Address: factoryAddr, Topics: []common.Hash{ common.HexToHash("0xe14ced199d67634c498b12b8ffc4244e2be5b5f2b3b7b0db5c35b2c73b89b3b8"), // Swap event topic common.HexToHash("0x000000000000000000000000742d35Cc6AaB8f5d6649c8C4F7C6b2d1234567890"), // sender common.HexToHash("0x000000000000000000000000742d35Cc6AaB8f5d6649c8C4F7C6b2d0987654321"), // recipient }, Data: make([]byte, 160), // 5 * 32 bytes for non-indexed params } event, err := parser.ParseLog(log) if err == nil && event != nil { assert.Equal(t, ProtocolCamelotV3, event.Protocol) assert.NotNil(t, event.DecodedParams) } } // Test TraderJoeV2Parser func TestTraderJoeV2Parser_New(t *testing.T) { client := createMockRPCClient() logger := createMockLogger() parser := NewTraderJoeV2Parser(client, logger) require.NotNil(t, parser) tjParser, ok := parser.(*TraderJoeV2Parser) require.True(t, ok, "Parser should be TraderJoeV2Parser type") assert.Equal(t, ProtocolTraderJoeV2, tjParser.protocol) } func TestTraderJoeV2Parser_GetSupportedContractTypes(t *testing.T) { client := createMockRPCClient() logger := createMockLogger() parser := NewTraderJoeV2Parser(client, logger).(*TraderJoeV2Parser) types := parser.GetSupportedContractTypes() assert.Contains(t, types, ContractTypeFactory) assert.Contains(t, types, ContractTypeRouter) assert.Contains(t, types, ContractTypePool) } func TestTraderJoeV2Parser_GetSupportedEventTypes(t *testing.T) { client := createMockRPCClient() logger := createMockLogger() parser := NewTraderJoeV2Parser(client, logger).(*TraderJoeV2Parser) events := parser.GetSupportedEventTypes() assert.Contains(t, events, EventTypeSwap) assert.Contains(t, events, EventTypeLiquidityAdd) assert.Contains(t, events, EventTypeLiquidityRemove) assert.Contains(t, events, EventTypePoolCreated) } func TestTraderJoeV2Parser_IsKnownContract(t *testing.T) { client := createMockRPCClient() logger := createMockLogger() parser := NewTraderJoeV2Parser(client, logger).(*TraderJoeV2Parser) // Test known contract (factory) factoryAddr := common.HexToAddress("0x8e42f2F4101563bF679975178e880FD87d3eFd4e") assert.True(t, parser.IsKnownContract(factoryAddr)) // Test unknown contract unknownAddr := common.HexToAddress("0x1234567890123456789012345678901234567890") assert.False(t, parser.IsKnownContract(unknownAddr)) } func TestTraderJoeV2Parser_ParseTransactionData(t *testing.T) { client := createMockRPCClient() logger := createMockLogger() parser := NewTraderJoeV2Parser(client, logger).(*TraderJoeV2Parser) // Create mock transaction data for swapExactTokensForTokens data := make([]byte, 324) copy(data[0:4], []byte{0x38, 0xed, 0x17, 0x39}) // Function selector // Add mock token addresses and amounts tokenX := common.HexToAddress("0xA0b86a33E6441f43E2e4A96439abFA2A69067ACD") tokenY := common.HexToAddress("0xaf88d065e77c8cC2239327C5EDb3A432268e5831") copy(data[16:32], tokenX.Bytes()) copy(data[48:64], tokenY.Bytes()) tx := types.NewTransaction(0, common.Address{}, big.NewInt(0), 0, big.NewInt(0), data) event, err := parser.ParseTransactionData(tx) if err == nil && event != nil { assert.Equal(t, ProtocolTraderJoeV2, event.Protocol) assert.Equal(t, EventTypeSwap, event.EventType) assert.NotNil(t, event.DecodedParams) } } // Test KyberElasticParser func TestKyberElasticParser_New(t *testing.T) { client := createMockRPCClient() logger := createMockLogger() parser := NewKyberElasticParser(client, logger) require.NotNil(t, parser) kyberParser, ok := parser.(*KyberElasticParser) require.True(t, ok, "Parser should be KyberElasticParser type") assert.Equal(t, ProtocolKyberElastic, kyberParser.protocol) } func TestKyberElasticParser_GetSupportedContractTypes(t *testing.T) { client := createMockRPCClient() logger := createMockLogger() parser := NewKyberElasticParser(client, logger).(*KyberElasticParser) types := parser.GetSupportedContractTypes() assert.Contains(t, types, ContractTypeFactory) assert.Contains(t, types, ContractTypeRouter) assert.Contains(t, types, ContractTypePool) } func TestKyberElasticParser_GetSupportedEventTypes(t *testing.T) { client := createMockRPCClient() logger := createMockLogger() parser := NewKyberElasticParser(client, logger).(*KyberElasticParser) events := parser.GetSupportedEventTypes() assert.Contains(t, events, EventTypeSwap) assert.Contains(t, events, EventTypeLiquidityAdd) assert.Contains(t, events, EventTypeLiquidityRemove) assert.Contains(t, events, EventTypePoolCreated) assert.Contains(t, events, EventTypePositionUpdate) } func TestKyberElasticParser_IsKnownContract(t *testing.T) { client := createMockRPCClient() logger := createMockLogger() parser := NewKyberElasticParser(client, logger).(*KyberElasticParser) // Test known contract (factory) factoryAddr := common.HexToAddress("0x5F1dddbf348aC2fbe22a163e30F99F9ECE3DD50a") assert.True(t, parser.IsKnownContract(factoryAddr)) // Test unknown contract unknownAddr := common.HexToAddress("0x1234567890123456789012345678901234567890") assert.False(t, parser.IsKnownContract(unknownAddr)) } func TestKyberElasticParser_DecodeFunctionCall(t *testing.T) { client := createMockRPCClient() logger := createMockLogger() parser := NewKyberElasticParser(client, logger).(*KyberElasticParser) // Create mock function call data for exactInputSingle data := make([]byte, 228) copy(data[0:4], []byte{0x04, 0xe4, 0x5a, 0xaf}) // Function selector // Add mock token addresses tokenA := common.HexToAddress("0xA0b86a33E6441f43E2e4A96439abFA2A69067ACD") tokenB := common.HexToAddress("0xaf88d065e77c8cC2239327C5EDb3A432268e5831") copy(data[16:32], tokenA.Bytes()) copy(data[48:64], tokenB.Bytes()) event, err := parser.DecodeFunctionCall(data) if err == nil && event != nil { assert.Equal(t, ProtocolKyberElastic, event.Protocol) assert.Equal(t, EventTypeSwap, event.EventType) assert.NotNil(t, event.DecodedParams) } } // Test GetPoolInfo with mock RPC responses func TestCamelotV3Parser_GetPoolInfo_WithMockRPC(t *testing.T) { logger := createMockLogger() parser := NewCamelotV3Parser(nil, logger).(*CamelotV3Parser) poolAddr := common.HexToAddress("0x1234567890123456789012345678901234567890") // Test that the method exists and has correct signature assert.NotNil(t, parser.GetPoolInfo) // Test with nil client should return error _, err := parser.GetPoolInfo(poolAddr) assert.Error(t, err) // Should fail due to nil client } // Integration test for DiscoverPools func TestTraderJoeV2Parser_DiscoverPools(t *testing.T) { client := createMockRPCClient() logger := createMockLogger() parser := NewTraderJoeV2Parser(client, logger).(*TraderJoeV2Parser) // Check that the parser was initialized correctly assert.NotNil(t, parser.BaseProtocolParser.contracts) assert.NotNil(t, parser.BaseProtocolParser.contracts[ContractTypeFactory]) assert.Greater(t, len(parser.BaseProtocolParser.contracts[ContractTypeFactory]), 0) // Test that the method exists assert.NotNil(t, parser.DiscoverPools) } // Test ParseTransactionLogs func TestKyberElasticParser_ParseTransactionLogs(t *testing.T) { client := createMockRPCClient() logger := createMockLogger() parser := NewKyberElasticParser(client, logger).(*KyberElasticParser) // Create mock transaction and receipt tx := types.NewTransaction(0, common.Address{}, big.NewInt(0), 0, big.NewInt(0), []byte{}) receipt := &types.Receipt{ BlockNumber: big.NewInt(1000000), Logs: []*types.Log{ { Address: common.HexToAddress("0x5F1dddbf348aC2fbe22a163e30F99F9ECE3DD50a"), Topics: []common.Hash{ common.HexToHash("0x1234567890123456789012345678901234567890123456789012345678901234"), }, Data: make([]byte, 32), }, }, } events, err := parser.ParseTransactionLogs(tx, receipt) assert.NoError(t, err) assert.NotNil(t, events) // Events might be empty due to unknown topic, but should not error } // Benchmark tests func BenchmarkCamelotV3Parser_ParseLog(b *testing.B) { client := createMockRPCClient() logger := createMockLogger() parser := NewCamelotV3Parser(client, logger).(*CamelotV3Parser) log := &types.Log{ Address: common.HexToAddress("0x1a3c9B1d2F0529D97f2afC5136Cc23e58f1FD35B"), Topics: []common.Hash{ common.HexToHash("0x1234567890123456789012345678901234567890123456789012345678901234"), }, Data: make([]byte, 160), } b.ResetTimer() for i := 0; i < b.N; i++ { parser.ParseLog(log) } } func BenchmarkTraderJoeV2Parser_DecodeFunctionCall(b *testing.B) { client := createMockRPCClient() logger := createMockLogger() parser := NewTraderJoeV2Parser(client, logger).(*TraderJoeV2Parser) data := make([]byte, 324) copy(data[0:4], []byte{0x38, 0xed, 0x17, 0x39}) // Function selector b.ResetTimer() for i := 0; i < b.N; i++ { parser.DecodeFunctionCall(data) } } // Test error handling func TestParsers_ErrorHandling(t *testing.T) { client := createMockRPCClient() logger := createMockLogger() parsers := []DEXParserInterface{ NewCamelotV3Parser(client, logger), NewTraderJoeV2Parser(client, logger), NewKyberElasticParser(client, logger), } for _, parser := range parsers { // Test with invalid data _, err := parser.DecodeFunctionCall([]byte{0x01, 0x02}) // Too short assert.Error(t, err, "Should error on too short data") // Test with unknown function selector _, err = parser.DecodeFunctionCall([]byte{0xFF, 0xFF, 0xFF, 0xFF, 0x00}) assert.Error(t, err, "Should error on unknown selector") // Test empty transaction data tx := types.NewTransaction(0, common.Address{}, big.NewInt(0), 0, big.NewInt(0), []byte{}) _, err = parser.ParseTransactionData(tx) assert.Error(t, err, "Should error on empty transaction data") } } // Test protocol-specific features func TestTraderJoeV2Parser_LiquidityBookFeatures(t *testing.T) { client := createMockRPCClient() logger := createMockLogger() parser := NewTraderJoeV2Parser(client, logger).(*TraderJoeV2Parser) // Test that liquidity book specific events are supported events := parser.GetSupportedEventTypes() assert.Contains(t, events, EventTypeSwap) assert.Contains(t, events, EventTypeLiquidityAdd) assert.Contains(t, events, EventTypeLiquidityRemove) // Test that event signatures are properly initialized assert.NotEmpty(t, parser.eventSigs) // Verify specific LB events exist hasSwapEvent := false hasDepositEvent := false hasWithdrawEvent := false for _, sig := range parser.eventSigs { switch sig.Name { case "Swap": hasSwapEvent = true case "DepositedToBins": hasDepositEvent = true case "WithdrawnFromBins": hasWithdrawEvent = true } } assert.True(t, hasSwapEvent, "Should have Swap event") assert.True(t, hasDepositEvent, "Should have DepositedToBins event") assert.True(t, hasWithdrawEvent, "Should have WithdrawnFromBins event") } func TestKyberElasticParser_ReinvestmentFeatures(t *testing.T) { client := createMockRPCClient() logger := createMockLogger() parser := NewKyberElasticParser(client, logger).(*KyberElasticParser) // Test that Kyber-specific events are supported events := parser.GetSupportedEventTypes() assert.Contains(t, events, EventTypeSwap) assert.Contains(t, events, EventTypePositionUpdate) // Test multiple router addresses (including meta router) routers := parser.contracts[ContractTypeRouter] assert.True(t, len(routers) >= 2, "Should have multiple router addresses") // Test that factory address is set correctly factories := parser.contracts[ContractTypeFactory] assert.Equal(t, 1, len(factories), "Should have one factory address") expectedFactory := common.HexToAddress("0x5F1dddbf348aC2fbe22a163e30F99F9ECE3DD50a") assert.Equal(t, expectedFactory, factories[0]) }