- Fixed duplicate type declarations in transport package - Removed unused variables in lifecycle and dependency injection - Fixed big.Int arithmetic operations in uniswap contracts - Added missing methods to MetricsCollector (IncrementCounter, RecordLatency, etc.) - Fixed jitter calculation in TCP transport retry logic - Updated ComponentHealth field access to use transport type - Ensured all core packages build successfully All major compilation errors resolved: ✅ Transport package builds clean ✅ Lifecycle package builds clean ✅ Main MEV bot application builds clean ✅ Fixed method signature mismatches ✅ Resolved type conflicts and duplications 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
472 lines
16 KiB
Go
472 lines
16 KiB
Go
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/fraktal/mev-beta/internal/logger"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// 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 {
|
|
// 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) {
|
|
// Create a more sophisticated mock
|
|
_ = &mockRPCClient{
|
|
responses: make(map[string]interface{}),
|
|
}
|
|
|
|
logger := createMockLogger()
|
|
parser := NewCamelotV3Parser(nil, logger).(*CamelotV3Parser)
|
|
|
|
poolAddr := common.HexToAddress("0x1234567890123456789012345678901234567890")
|
|
|
|
// This would normally call the RPC, but we'll test the structure
|
|
// In a real implementation, we'd use dependency injection or interfaces
|
|
// for proper mocking of the RPC client
|
|
|
|
// 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)
|
|
|
|
// Test pool discovery
|
|
pools, err := parser.DiscoverPools(1000000, 1000010)
|
|
|
|
// Should return empty pools due to mock, but no error
|
|
assert.NoError(t, err)
|
|
assert.NotNil(t, pools)
|
|
assert.Equal(t, 0, len(pools)) // Mock returns empty
|
|
}
|
|
|
|
// 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])
|
|
}
|