619 lines
14 KiB
Go
619 lines
14 KiB
Go
package sequencer
|
|
|
|
import (
|
|
"encoding/hex"
|
|
"testing"
|
|
|
|
"github.com/ethereum/go-ethereum/common"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// TestIsSwapTransaction_UniswapV2 tests detection of UniswapV2 swap transactions
|
|
func TestIsSwapTransaction_UniswapV2(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
selector string
|
|
wantSwap bool
|
|
}{
|
|
{
|
|
name: "swapExactTokensForTokens",
|
|
selector: "38ed1739",
|
|
wantSwap: true,
|
|
},
|
|
{
|
|
name: "swapTokensForExactTokens",
|
|
selector: "8803dbee",
|
|
wantSwap: true,
|
|
},
|
|
{
|
|
name: "swapExactETHForTokens",
|
|
selector: "7ff36ab5",
|
|
wantSwap: true,
|
|
},
|
|
{
|
|
name: "swapETHForExactTokens",
|
|
selector: "fb3bdb41",
|
|
wantSwap: true,
|
|
},
|
|
{
|
|
name: "swapExactTokensForETH",
|
|
selector: "18cbafe5",
|
|
wantSwap: true,
|
|
},
|
|
{
|
|
name: "swapTokensForExactETH",
|
|
selector: "4a25d94a",
|
|
wantSwap: true,
|
|
},
|
|
{
|
|
name: "direct pool swap",
|
|
selector: "022c0d9f",
|
|
wantSwap: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
// Create transaction data with selector + dummy parameters
|
|
data, err := hex.DecodeString(tt.selector + "000000000000000000000000000000000000000000000000")
|
|
require.NoError(t, err)
|
|
|
|
got := IsSwapTransaction(data)
|
|
assert.Equal(t, tt.wantSwap, got, "IsSwapTransaction() for %s", tt.name)
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestIsSwapTransaction_UniswapV3 tests detection of UniswapV3 swap transactions
|
|
func TestIsSwapTransaction_UniswapV3(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
selector string
|
|
wantSwap bool
|
|
}{
|
|
{
|
|
name: "exactInputSingle",
|
|
selector: "414bf389",
|
|
wantSwap: true,
|
|
},
|
|
{
|
|
name: "exactInput",
|
|
selector: "c04b8d59",
|
|
wantSwap: true,
|
|
},
|
|
{
|
|
name: "exactOutputSingle",
|
|
selector: "db3e2198",
|
|
wantSwap: true,
|
|
},
|
|
{
|
|
name: "exactOutput",
|
|
selector: "f28c0498",
|
|
wantSwap: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
data, err := hex.DecodeString(tt.selector + "000000000000000000000000000000000000000000000000")
|
|
require.NoError(t, err)
|
|
|
|
got := IsSwapTransaction(data)
|
|
assert.Equal(t, tt.wantSwap, got, "IsSwapTransaction() for %s", tt.name)
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestIsSwapTransaction_Curve tests detection of Curve swap transactions
|
|
func TestIsSwapTransaction_Curve(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
selector string
|
|
wantSwap bool
|
|
}{
|
|
{
|
|
name: "exchange",
|
|
selector: "3df02124",
|
|
wantSwap: true,
|
|
},
|
|
{
|
|
name: "exchange_underlying",
|
|
selector: "a6417ed6",
|
|
wantSwap: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
data, err := hex.DecodeString(tt.selector + "000000000000000000000000000000000000000000000000")
|
|
require.NoError(t, err)
|
|
|
|
got := IsSwapTransaction(data)
|
|
assert.Equal(t, tt.wantSwap, got, "IsSwapTransaction() for %s", tt.name)
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestIsSwapTransaction_1inch tests detection of 1inch swap transactions
|
|
func TestIsSwapTransaction_1inch(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
selector string
|
|
wantSwap bool
|
|
}{
|
|
{
|
|
name: "1inch swap",
|
|
selector: "7c025200",
|
|
wantSwap: true,
|
|
},
|
|
{
|
|
name: "1inch uniswapV3Swap",
|
|
selector: "e449022e",
|
|
wantSwap: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
data, err := hex.DecodeString(tt.selector + "000000000000000000000000000000000000000000000000")
|
|
require.NoError(t, err)
|
|
|
|
got := IsSwapTransaction(data)
|
|
assert.Equal(t, tt.wantSwap, got, "IsSwapTransaction() for %s", tt.name)
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestIsSwapTransaction_0xProtocol tests detection of 0x Protocol swap transactions
|
|
func TestIsSwapTransaction_0xProtocol(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
selector string
|
|
wantSwap bool
|
|
}{
|
|
{
|
|
name: "sellToUniswap",
|
|
selector: "d9627aa4",
|
|
wantSwap: true,
|
|
},
|
|
{
|
|
name: "fillRfqOrder",
|
|
selector: "415565b0",
|
|
wantSwap: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
data, err := hex.DecodeString(tt.selector + "000000000000000000000000000000000000000000000000")
|
|
require.NoError(t, err)
|
|
|
|
got := IsSwapTransaction(data)
|
|
assert.Equal(t, tt.wantSwap, got, "IsSwapTransaction() for %s", tt.name)
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestIsSwapTransaction_NonSwap tests that non-swap transactions are not detected
|
|
func TestIsSwapTransaction_NonSwap(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
selector string
|
|
wantSwap bool
|
|
}{
|
|
{
|
|
name: "transfer",
|
|
selector: "a9059cbb",
|
|
wantSwap: false,
|
|
},
|
|
{
|
|
name: "approve",
|
|
selector: "095ea7b3",
|
|
wantSwap: false,
|
|
},
|
|
{
|
|
name: "transferFrom",
|
|
selector: "23b872dd",
|
|
wantSwap: false,
|
|
},
|
|
{
|
|
name: "mint",
|
|
selector: "40c10f19",
|
|
wantSwap: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
data, err := hex.DecodeString(tt.selector + "000000000000000000000000000000000000000000000000")
|
|
require.NoError(t, err)
|
|
|
|
got := IsSwapTransaction(data)
|
|
assert.Equal(t, tt.wantSwap, got, "IsSwapTransaction() for %s", tt.name)
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestIsSwapTransaction_EdgeCases tests edge cases
|
|
func TestIsSwapTransaction_EdgeCases(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
data []byte
|
|
wantSwap bool
|
|
}{
|
|
{
|
|
name: "empty data",
|
|
data: []byte{},
|
|
wantSwap: false,
|
|
},
|
|
{
|
|
name: "data too short (3 bytes)",
|
|
data: []byte{0x01, 0x02, 0x03},
|
|
wantSwap: false,
|
|
},
|
|
{
|
|
name: "exactly 4 bytes - valid swap selector",
|
|
data: []byte{0x02, 0x2c, 0x0d, 0x9f}, // 022c0d9f = swap
|
|
wantSwap: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got := IsSwapTransaction(tt.data)
|
|
assert.Equal(t, tt.wantSwap, got, "IsSwapTransaction() for %s", tt.name)
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestGetSwapProtocol_BySelector tests protocol detection by function selector
|
|
func TestGetSwapProtocol_BySelector(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
selector string
|
|
wantName string
|
|
wantType string
|
|
shouldDetect bool
|
|
}{
|
|
{
|
|
name: "UniswapV2 direct pool swap",
|
|
selector: "022c0d9f",
|
|
wantName: "UniswapV2",
|
|
wantType: "pool",
|
|
shouldDetect: true,
|
|
},
|
|
{
|
|
name: "UniswapV2 router swapExactTokensForTokens",
|
|
selector: "38ed1739",
|
|
wantName: "UniswapV2",
|
|
wantType: "router",
|
|
shouldDetect: true,
|
|
},
|
|
{
|
|
name: "UniswapV3 exactInputSingle",
|
|
selector: "414bf389",
|
|
wantName: "UniswapV3",
|
|
wantType: "router",
|
|
shouldDetect: true,
|
|
},
|
|
{
|
|
name: "Curve exchange",
|
|
selector: "3df02124",
|
|
wantName: "Curve",
|
|
wantType: "pool",
|
|
shouldDetect: true,
|
|
},
|
|
{
|
|
name: "Balancer swap",
|
|
selector: "52bbbe29",
|
|
wantName: "Balancer",
|
|
wantType: "vault",
|
|
shouldDetect: true,
|
|
},
|
|
{
|
|
name: "Camelot V3 exactInputSingle",
|
|
selector: "128acb08",
|
|
wantName: "Camelot",
|
|
wantType: "router",
|
|
shouldDetect: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
// Create dummy address (not zero)
|
|
addr := common.HexToAddress("0x1234567890123456789012345678901234567890")
|
|
|
|
// Create transaction data with selector
|
|
data, err := hex.DecodeString(tt.selector + "000000000000000000000000000000000000000000000000")
|
|
require.NoError(t, err)
|
|
|
|
protocol := GetSwapProtocol(&addr, data)
|
|
require.NotNil(t, protocol)
|
|
|
|
if tt.shouldDetect {
|
|
assert.Equal(t, tt.wantName, protocol.Name, "Protocol name for %s", tt.name)
|
|
assert.Equal(t, tt.wantType, protocol.Type, "Protocol type for %s", tt.name)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestGetSwapProtocol_EdgeCases tests edge cases for protocol detection
|
|
func TestGetSwapProtocol_EdgeCases(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
to *common.Address
|
|
data []byte
|
|
wantName string
|
|
}{
|
|
{
|
|
name: "nil address",
|
|
to: nil,
|
|
data: []byte{0x01, 0x02, 0x03, 0x04, 0x05},
|
|
wantName: "unknown",
|
|
},
|
|
{
|
|
name: "zero address",
|
|
to: func() *common.Address {
|
|
addr := common.HexToAddress("0x0000000000000000000000000000000000000000")
|
|
return &addr
|
|
}(),
|
|
data: []byte{0x01, 0x02, 0x03, 0x04, 0x05},
|
|
wantName: "unknown",
|
|
},
|
|
{
|
|
name: "data too short",
|
|
to: func() *common.Address {
|
|
addr := common.HexToAddress("0x1234567890123456789012345678901234567890")
|
|
return &addr
|
|
}(),
|
|
data: []byte{0x01, 0x02, 0x03},
|
|
wantName: "unknown",
|
|
},
|
|
{
|
|
name: "unknown selector",
|
|
to: func() *common.Address {
|
|
addr := common.HexToAddress("0x1234567890123456789012345678901234567890")
|
|
return &addr
|
|
}(),
|
|
data: []byte{0xff, 0xff, 0xff, 0xff, 0x00},
|
|
wantName: "unknown",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
protocol := GetSwapProtocol(tt.to, tt.data)
|
|
require.NotNil(t, protocol)
|
|
assert.Equal(t, tt.wantName, protocol.Name, "Protocol name for %s", tt.name)
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestIsSupportedDEX tests supported DEX detection
|
|
func TestIsSupportedDEX(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
protocol *DEXProtocol
|
|
wantSupport bool
|
|
}{
|
|
{
|
|
name: "UniswapV2 supported",
|
|
protocol: &DEXProtocol{Name: "UniswapV2", Version: "", Type: "router"},
|
|
wantSupport: true,
|
|
},
|
|
{
|
|
name: "UniswapV3 supported",
|
|
protocol: &DEXProtocol{Name: "UniswapV3", Version: "", Type: "router"},
|
|
wantSupport: true,
|
|
},
|
|
{
|
|
name: "SushiSwap supported",
|
|
protocol: &DEXProtocol{Name: "SushiSwap", Version: "", Type: "router"},
|
|
wantSupport: true,
|
|
},
|
|
{
|
|
name: "Camelot supported",
|
|
protocol: &DEXProtocol{Name: "Camelot", Version: "V3", Type: "router"},
|
|
wantSupport: true,
|
|
},
|
|
{
|
|
name: "Curve supported",
|
|
protocol: &DEXProtocol{Name: "Curve", Version: "", Type: "pool"},
|
|
wantSupport: true,
|
|
},
|
|
{
|
|
name: "Balancer supported",
|
|
protocol: &DEXProtocol{Name: "Balancer", Version: "V2", Type: "vault"},
|
|
wantSupport: true,
|
|
},
|
|
{
|
|
name: "unknown DEX not supported",
|
|
protocol: &DEXProtocol{Name: "unknown", Version: "", Type: ""},
|
|
wantSupport: false,
|
|
},
|
|
{
|
|
name: "nil protocol not supported",
|
|
protocol: nil,
|
|
wantSupport: false,
|
|
},
|
|
{
|
|
name: "PancakeSwap not supported (not in list)",
|
|
protocol: &DEXProtocol{Name: "PancakeSwap", Version: "V2", Type: "router"},
|
|
wantSupport: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got := IsSupportedDEX(tt.protocol)
|
|
assert.Equal(t, tt.wantSupport, got, "IsSupportedDEX() for %s", tt.name)
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestDecodeArbitrumMessage tests decoding of Arbitrum sequencer messages
|
|
func TestDecodeArbitrumMessage(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
msgMap map[string]interface{}
|
|
wantErr bool
|
|
checks func(t *testing.T, msg *ArbitrumMessage)
|
|
}{
|
|
{
|
|
name: "valid message with L2 transaction",
|
|
msgMap: map[string]interface{}{
|
|
"sequenceNumber": float64(12345),
|
|
"message": map[string]interface{}{
|
|
"message": map[string]interface{}{
|
|
"header": map[string]interface{}{
|
|
"kind": float64(3),
|
|
"blockNumber": float64(100000),
|
|
"timestamp": float64(1234567890),
|
|
},
|
|
"l2Msg": "BAQ=", // Base64 for [4, 4] - invalid transaction but valid base64
|
|
},
|
|
},
|
|
},
|
|
wantErr: false,
|
|
checks: func(t *testing.T, msg *ArbitrumMessage) {
|
|
assert.Equal(t, uint64(12345), msg.SequenceNumber)
|
|
assert.Equal(t, uint8(3), msg.Kind)
|
|
assert.Equal(t, uint64(100000), msg.BlockNumber)
|
|
assert.Equal(t, uint64(1234567890), msg.Timestamp)
|
|
assert.Equal(t, "BAQ=", msg.L2MsgRaw)
|
|
},
|
|
},
|
|
{
|
|
name: "missing message wrapper",
|
|
msgMap: map[string]interface{}{
|
|
"sequenceNumber": float64(12345),
|
|
},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "missing inner message",
|
|
msgMap: map[string]interface{}{
|
|
"sequenceNumber": float64(12345),
|
|
"message": map[string]interface{}{},
|
|
},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "missing l2Msg",
|
|
msgMap: map[string]interface{}{
|
|
"sequenceNumber": float64(12345),
|
|
"message": map[string]interface{}{
|
|
"message": map[string]interface{}{
|
|
"header": map[string]interface{}{
|
|
"kind": float64(3),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
wantErr: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
msg, err := DecodeArbitrumMessage(tt.msgMap)
|
|
|
|
if tt.wantErr {
|
|
assert.Error(t, err)
|
|
return
|
|
}
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, msg)
|
|
|
|
if tt.checks != nil {
|
|
tt.checks(t, msg)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestDecodeL2Transaction tests Base64 decoding and RLP parsing
|
|
func TestDecodeL2Transaction(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
l2Msg string
|
|
wantErr bool
|
|
errMsg string
|
|
}{
|
|
{
|
|
name: "empty base64",
|
|
l2Msg: "",
|
|
wantErr: true,
|
|
errMsg: "illegal base64 data",
|
|
},
|
|
{
|
|
name: "invalid base64",
|
|
l2Msg: "not valid base64!!!",
|
|
wantErr: true,
|
|
errMsg: "illegal base64 data",
|
|
},
|
|
{
|
|
name: "valid base64 but not signed transaction (kind 0)",
|
|
l2Msg: "AAQ=", // Base64 for [0, 4]
|
|
wantErr: true,
|
|
errMsg: "not a signed transaction",
|
|
},
|
|
{
|
|
name: "valid base64, kind 4 (signed tx) but invalid RLP",
|
|
l2Msg: "BAQ=", // Base64 for [4, 4] - kind 4 (signed tx) but invalid RLP
|
|
wantErr: true,
|
|
errMsg: "RLP decode failed",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
tx, err := DecodeL2Transaction(tt.l2Msg)
|
|
|
|
if tt.wantErr {
|
|
assert.Error(t, err)
|
|
if tt.errMsg != "" {
|
|
assert.Contains(t, err.Error(), tt.errMsg)
|
|
}
|
|
return
|
|
}
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, tx)
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestAllSelectorsCovered ensures all selectors in IsSwapTransaction are tested
|
|
func TestAllSelectorsCovered(t *testing.T) {
|
|
// All selectors that should be recognized as swaps
|
|
allSelectors := []string{
|
|
// UniswapV2 Router
|
|
"38ed1739", "8803dbee", "7ff36ab5", "fb3bdb41", "18cbafe5", "4a25d94a",
|
|
// UniswapV3 Router
|
|
"414bf389", "c04b8d59", "db3e2198", "f28c0498",
|
|
// UniswapV2 Pair direct
|
|
"022c0d9f",
|
|
// Curve
|
|
"3df02124", "a6417ed6",
|
|
// 1inch
|
|
"7c025200", "e449022e",
|
|
// 0x Protocol
|
|
"d9627aa4", "415565b0",
|
|
}
|
|
|
|
for _, selector := range allSelectors {
|
|
t.Run(selector, func(t *testing.T) {
|
|
data, err := hex.DecodeString(selector + "0000000000000000000000000000000000000000")
|
|
require.NoError(t, err, "Failed to decode selector %s", selector)
|
|
|
|
isSwap := IsSwapTransaction(data)
|
|
assert.True(t, isSwap, "Selector %s should be recognized as swap", selector)
|
|
})
|
|
}
|
|
}
|