package parsers import ( "context" "math/big" "testing" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/your-org/mev-bot/pkg/cache" mevtypes "github.com/your-org/mev-bot/pkg/types" ) // mockLogger implements mevtypes.Logger for testing type mockLogger struct{} func (m *mockLogger) Debug(msg string, args ...any) {} func (m *mockLogger) Info(msg string, args ...any) {} func (m *mockLogger) Warn(msg string, args ...any) {} func (m *mockLogger) Error(msg string, args ...any) {} func (m *mockLogger) With(args ...any) mevtypes.Logger { return m } func (m *mockLogger) WithContext(ctx context.Context) mevtypes.Logger { return m } func TestNewUniswapV2Parser(t *testing.T) { cache := cache.NewPoolCache() logger := &mockLogger{} parser := NewUniswapV2Parser(cache, logger) if parser == nil { t.Fatal("NewUniswapV2Parser returned nil") } if parser.cache != cache { t.Error("NewUniswapV2Parser cache not set correctly") } if parser.logger != logger { t.Error("NewUniswapV2Parser logger not set correctly") } } func TestUniswapV2Parser_Protocol(t *testing.T) { parser := NewUniswapV2Parser(cache.NewPoolCache(), &mockLogger{}) if parser.Protocol() != mevtypes.ProtocolUniswapV2 { t.Errorf("Protocol() = %v, want %v", parser.Protocol(), mevtypes.ProtocolUniswapV2) } } func TestUniswapV2Parser_SupportsLog(t *testing.T) { parser := NewUniswapV2Parser(cache.NewPoolCache(), &mockLogger{}) tests := []struct { name string log types.Log want bool }{ { name: "valid Swap event", log: types.Log{ Topics: []common.Hash{SwapEventSignature}, }, want: true, }, { name: "empty topics", log: types.Log{ Topics: []common.Hash{}, }, want: false, }, { name: "wrong event signature", log: types.Log{ Topics: []common.Hash{common.HexToHash("0x1234")}, }, want: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := parser.SupportsLog(tt.log); got != tt.want { t.Errorf("SupportsLog() = %v, want %v", got, tt.want) } }) } } func TestUniswapV2Parser_ParseLog(t *testing.T) { ctx := context.Background() // Create pool cache and add test pool poolCache := cache.NewPoolCache() poolAddress := common.HexToAddress("0x1111111111111111111111111111111111111111") token0 := common.HexToAddress("0x2222222222222222222222222222222222222222") token1 := common.HexToAddress("0x3333333333333333333333333333333333333333") testPool := &mevtypes.PoolInfo{ Address: poolAddress, Protocol: mevtypes.ProtocolUniswapV2, Token0: token0, Token1: token1, Token0Decimals: 18, Token1Decimals: 6, Reserve0: big.NewInt(1000000), Reserve1: big.NewInt(500000), Fee: 30, // 0.3% in basis points IsActive: true, } err := poolCache.Add(ctx, testPool) if err != nil { t.Fatalf("Failed to add test pool: %v", err) } parser := NewUniswapV2Parser(poolCache, &mockLogger{}) // Create test transaction tx := types.NewTransaction( 0, poolAddress, big.NewInt(0), 0, big.NewInt(0), []byte{}, ) // Encode event data: amount0In, amount1In, amount0Out, amount1Out amount0In := big.NewInt(1000000000000000000) // 1 token0 (18 decimals) amount1In := big.NewInt(0) amount0Out := big.NewInt(0) amount1Out := big.NewInt(500000) // 0.5 token1 (6 decimals) // ABI encode the amounts data := make([]byte, 128) // 4 * 32 bytes amount0In.FillBytes(data[0:32]) amount1In.FillBytes(data[32:64]) amount0Out.FillBytes(data[64:96]) amount1Out.FillBytes(data[96:128]) sender := common.HexToAddress("0x4444444444444444444444444444444444444444") recipient := common.HexToAddress("0x5555555555555555555555555555555555555555") tests := []struct { name string log types.Log wantErr bool }{ { name: "valid swap event", log: types.Log{ Address: poolAddress, Topics: []common.Hash{ SwapEventSignature, common.BytesToHash(sender.Bytes()), common.BytesToHash(recipient.Bytes()), }, Data: data, BlockNumber: 1000, Index: 0, }, wantErr: false, }, { name: "unsupported log", log: types.Log{ Address: poolAddress, Topics: []common.Hash{ common.HexToHash("0x1234"), }, Data: data, BlockNumber: 1000, Index: 0, }, wantErr: true, }, { name: "invalid number of topics", log: types.Log{ Address: poolAddress, Topics: []common.Hash{ SwapEventSignature, common.BytesToHash(sender.Bytes()), // Missing recipient topic }, Data: data, BlockNumber: 1000, Index: 0, }, wantErr: true, }, { name: "pool not in cache", log: types.Log{ Address: common.HexToAddress("0x9999999999999999999999999999999999999999"), Topics: []common.Hash{ SwapEventSignature, common.BytesToHash(sender.Bytes()), common.BytesToHash(recipient.Bytes()), }, Data: data, BlockNumber: 1000, Index: 0, }, wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { event, err := parser.ParseLog(ctx, tt.log, tx) if tt.wantErr { if err == nil { t.Error("ParseLog() expected error, got nil") } return } if err != nil { t.Fatalf("ParseLog() unexpected error: %v", err) } if event == nil { t.Fatal("ParseLog() returned nil event") } // Verify event fields if event.TxHash != tx.Hash() { t.Errorf("TxHash = %v, want %v", event.TxHash, tx.Hash()) } if event.BlockNumber != tt.log.BlockNumber { t.Errorf("BlockNumber = %v, want %v", event.BlockNumber, tt.log.BlockNumber) } if event.LogIndex != uint(tt.log.Index) { t.Errorf("LogIndex = %v, want %v", event.LogIndex, tt.log.Index) } if event.PoolAddress != poolAddress { t.Errorf("PoolAddress = %v, want %v", event.PoolAddress, poolAddress) } if event.Protocol != mevtypes.ProtocolUniswapV2 { t.Errorf("Protocol = %v, want %v", event.Protocol, mevtypes.ProtocolUniswapV2) } if event.Token0 != token0 { t.Errorf("Token0 = %v, want %v", event.Token0, token0) } if event.Token1 != token1 { t.Errorf("Token1 = %v, want %v", event.Token1, token1) } if event.Token0Decimals != 18 { t.Errorf("Token0Decimals = %v, want 18", event.Token0Decimals) } if event.Token1Decimals != 6 { t.Errorf("Token1Decimals = %v, want 6", event.Token1Decimals) } if event.Sender != sender { t.Errorf("Sender = %v, want %v", event.Sender, sender) } if event.Recipient != recipient { t.Errorf("Recipient = %v, want %v", event.Recipient, recipient) } // Verify amounts are scaled to 18 decimals expectedAmount0In := amount0In // Already 18 decimals expectedAmount1Out := mevtypes.ScaleToDecimals(amount1Out, 6, 18) if event.Amount0In.Cmp(expectedAmount0In) != 0 { t.Errorf("Amount0In = %v, want %v", event.Amount0In, expectedAmount0In) } if event.Amount1Out.Cmp(expectedAmount1Out) != 0 { t.Errorf("Amount1Out = %v, want %v", event.Amount1Out, expectedAmount1Out) } if event.Amount1In.Cmp(big.NewInt(0)) != 0 { t.Errorf("Amount1In = %v, want 0", event.Amount1In) } if event.Amount0Out.Cmp(big.NewInt(0)) != 0 { t.Errorf("Amount0Out = %v, want 0", event.Amount0Out) } }) } } func TestUniswapV2Parser_ParseReceipt(t *testing.T) { ctx := context.Background() // Create pool cache and add test pool poolCache := cache.NewPoolCache() poolAddress := common.HexToAddress("0x1111111111111111111111111111111111111111") token0 := common.HexToAddress("0x2222222222222222222222222222222222222222") token1 := common.HexToAddress("0x3333333333333333333333333333333333333333") testPool := &mevtypes.PoolInfo{ Address: poolAddress, Protocol: mevtypes.ProtocolUniswapV2, Token0: token0, Token1: token1, Token0Decimals: 18, Token1Decimals: 6, Reserve0: big.NewInt(1000000), Reserve1: big.NewInt(500000), Fee: 30, // 0.3% in basis points IsActive: true, } err := poolCache.Add(ctx, testPool) if err != nil { t.Fatalf("Failed to add test pool: %v", err) } parser := NewUniswapV2Parser(poolCache, &mockLogger{}) // Create test transaction tx := types.NewTransaction( 0, poolAddress, big.NewInt(0), 0, big.NewInt(0), []byte{}, ) // Encode event data amount0In := big.NewInt(1000000000000000000) amount1In := big.NewInt(0) amount0Out := big.NewInt(0) amount1Out := big.NewInt(500000) data := make([]byte, 128) amount0In.FillBytes(data[0:32]) amount1In.FillBytes(data[32:64]) amount0Out.FillBytes(data[64:96]) amount1Out.FillBytes(data[96:128]) sender := common.HexToAddress("0x4444444444444444444444444444444444444444") recipient := common.HexToAddress("0x5555555555555555555555555555555555555555") tests := []struct { name string receipt *types.Receipt wantCount int }{ { name: "receipt with single swap event", receipt: &types.Receipt{ Logs: []*types.Log{ { Address: poolAddress, Topics: []common.Hash{ SwapEventSignature, common.BytesToHash(sender.Bytes()), common.BytesToHash(recipient.Bytes()), }, Data: data, BlockNumber: 1000, Index: 0, }, }, }, wantCount: 1, }, { name: "receipt with multiple swap events", receipt: &types.Receipt{ Logs: []*types.Log{ { Address: poolAddress, Topics: []common.Hash{ SwapEventSignature, common.BytesToHash(sender.Bytes()), common.BytesToHash(recipient.Bytes()), }, Data: data, BlockNumber: 1000, Index: 0, }, { Address: poolAddress, Topics: []common.Hash{ SwapEventSignature, common.BytesToHash(sender.Bytes()), common.BytesToHash(recipient.Bytes()), }, Data: data, BlockNumber: 1000, Index: 1, }, }, }, wantCount: 2, }, { name: "receipt with mixed events", receipt: &types.Receipt{ Logs: []*types.Log{ { Address: poolAddress, Topics: []common.Hash{ SwapEventSignature, common.BytesToHash(sender.Bytes()), common.BytesToHash(recipient.Bytes()), }, Data: data, BlockNumber: 1000, Index: 0, }, { Address: poolAddress, Topics: []common.Hash{ common.HexToHash("0x1234"), // Different event }, Data: []byte{}, BlockNumber: 1000, Index: 1, }, }, }, wantCount: 1, // Only the Swap event }, { name: "empty receipt", receipt: &types.Receipt{ Logs: []*types.Log{}, }, wantCount: 0, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { events, err := parser.ParseReceipt(ctx, tt.receipt, tx) if err != nil { t.Fatalf("ParseReceipt() unexpected error: %v", err) } if len(events) != tt.wantCount { t.Errorf("ParseReceipt() returned %d events, want %d", len(events), tt.wantCount) } // Verify all returned events are valid for i, event := range events { if event == nil { t.Errorf("Event %d is nil", i) continue } if event.Protocol != mevtypes.ProtocolUniswapV2 { t.Errorf("Event %d Protocol = %v, want %v", i, event.Protocol, mevtypes.ProtocolUniswapV2) } } }) } } func TestSwapEventSignature(t *testing.T) { // Verify the event signature is correct expected := crypto.Keccak256Hash([]byte("Swap(address,uint256,uint256,uint256,uint256,address)")) if SwapEventSignature != expected { t.Errorf("SwapEventSignature = %v, want %v", SwapEventSignature, expected) } }