feat: create v2-prep branch with comprehensive planning
Restructured project for V2 refactor: **Structure Changes:** - Moved all V1 code to orig/ folder (preserved with git mv) - Created docs/planning/ directory - Added orig/README_V1.md explaining V1 preservation **Planning Documents:** - 00_V2_MASTER_PLAN.md: Complete architecture overview - Executive summary of critical V1 issues - High-level component architecture diagrams - 5-phase implementation roadmap - Success metrics and risk mitigation - 07_TASK_BREAKDOWN.md: Atomic task breakdown - 99+ hours of detailed tasks - Every task < 2 hours (atomic) - Clear dependencies and success criteria - Organized by implementation phase **V2 Key Improvements:** - Per-exchange parsers (factory pattern) - Multi-layer strict validation - Multi-index pool cache - Background validation pipeline - Comprehensive observability **Critical Issues Addressed:** - Zero address tokens (strict validation + cache enrichment) - Parsing accuracy (protocol-specific parsers) - No audit trail (background validation channel) - Inefficient lookups (multi-index cache) - Stats disconnection (event-driven metrics) Next Steps: 1. Review planning documents 2. Begin Phase 1: Foundation (P1-001 through P1-010) 3. Implement parsers in Phase 2 4. Build cache system in Phase 3 5. Add validation pipeline in Phase 4 6. Migrate and test in Phase 5 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
387
orig/pkg/arbitrum/parser_test.go
Normal file
387
orig/pkg/arbitrum/parser_test.go
Normal file
@@ -0,0 +1,387 @@
|
||||
package arbitrum
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/fraktal/mev-beta/internal/logger"
|
||||
)
|
||||
|
||||
// createValidRLPTransaction creates a valid RLP-encoded transaction for testing
|
||||
func createValidRLPTransaction() []byte {
|
||||
tx := types.NewTransaction(
|
||||
0, // nonce
|
||||
common.HexToAddress("0x742d35Cc"), // to
|
||||
big.NewInt(1000), // value
|
||||
21000, // gas
|
||||
big.NewInt(1000000000), // gas price
|
||||
[]byte{}, // data
|
||||
)
|
||||
|
||||
rlpData, _ := tx.MarshalBinary()
|
||||
return rlpData
|
||||
}
|
||||
|
||||
// createValidSwapCalldata creates valid swap function calldata
|
||||
func createValidSwapCalldata() []byte {
|
||||
// Create properly formatted ABI-encoded calldata for swapExactTokensForTokens
|
||||
data := make([]byte, 256) // More space for proper ABI encoding
|
||||
|
||||
// amountIn (1000 tokens) - right-aligned in 32 bytes
|
||||
amountIn := big.NewInt(1000000000000000000)
|
||||
amountInBytes := amountIn.Bytes()
|
||||
copy(data[32-len(amountInBytes):32], amountInBytes)
|
||||
|
||||
// amountOutMin (900 tokens) - right-aligned in 32 bytes
|
||||
amountOutMin := big.NewInt(900000000000000000)
|
||||
amountOutMinBytes := amountOutMin.Bytes()
|
||||
copy(data[64-len(amountOutMinBytes):64], amountOutMinBytes)
|
||||
|
||||
// path offset (0xa0 = 160 decimal, pointer to array) - right-aligned
|
||||
pathOffset := big.NewInt(160)
|
||||
pathOffsetBytes := pathOffset.Bytes()
|
||||
copy(data[96-len(pathOffsetBytes):96], pathOffsetBytes)
|
||||
|
||||
// recipient address - right-aligned in 32 bytes
|
||||
recipient := common.HexToAddress("0x742d35Cc6635C0532925a3b8D9C12CF345eEE40F")
|
||||
copy(data[96+12:128], recipient.Bytes())
|
||||
|
||||
// deadline - right-aligned in 32 bytes
|
||||
deadline := big.NewInt(1234567890)
|
||||
deadlineBytes := deadline.Bytes()
|
||||
copy(data[160-len(deadlineBytes):160], deadlineBytes)
|
||||
|
||||
// Add array length and tokens for path (simplified)
|
||||
// Array length = 2
|
||||
arrayLen := big.NewInt(2)
|
||||
arrayLenBytes := arrayLen.Bytes()
|
||||
copy(data[192-len(arrayLenBytes):192], arrayLenBytes)
|
||||
|
||||
// Token addresses would go here, but we'll keep it simple
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
// createValidExactInputSingleData creates valid exactInputSingle calldata
|
||||
func createValidExactInputSingleData() []byte {
|
||||
// Create properly formatted ABI-encoded calldata for exactInputSingle
|
||||
data := make([]byte, 256) // More space for proper ABI encoding
|
||||
|
||||
// tokenIn at position 0-31 (address in last 20 bytes)
|
||||
copy(data[12:32], common.HexToAddress("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48").Bytes()) // USDC
|
||||
|
||||
// tokenOut at position 32-63 (address in last 20 bytes)
|
||||
copy(data[44:64], common.HexToAddress("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2").Bytes()) // WETH
|
||||
|
||||
// recipient at position 96-127 (address in last 20 bytes)
|
||||
copy(data[108:128], common.HexToAddress("0x742d35Cc6635C0532925a3b8D9C12CF345eEE40F").Bytes())
|
||||
|
||||
// deadline at position 128-159 (uint64 in last 8 bytes)
|
||||
binary.BigEndian.PutUint64(data[152:160], 1234567890)
|
||||
|
||||
// amountIn at position 160-191
|
||||
amountIn := big.NewInt(1000000000) // 1000 USDC (6 decimals)
|
||||
amountInBytes := amountIn.Bytes()
|
||||
copy(data[192-len(amountInBytes):192], amountInBytes)
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
func TestL2MessageParser_ParseL2Message(t *testing.T) {
|
||||
logger := logger.New("info", "text", "")
|
||||
parser := NewL2MessageParser(logger)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
messageData []byte
|
||||
messageNumber *big.Int
|
||||
timestamp uint64
|
||||
expectError bool
|
||||
expectedType L2MessageType
|
||||
}{
|
||||
{
|
||||
name: "Empty message",
|
||||
messageData: []byte{},
|
||||
messageNumber: big.NewInt(1),
|
||||
timestamp: 1234567890,
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
name: "Short message",
|
||||
messageData: []byte{0x00, 0x00, 0x00},
|
||||
messageNumber: big.NewInt(2),
|
||||
timestamp: 1234567890,
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
name: "L2 Transaction message",
|
||||
messageData: append([]byte{0x00, 0x00, 0x00, 0x03}, createValidRLPTransaction()...),
|
||||
messageNumber: big.NewInt(3),
|
||||
timestamp: 1234567890,
|
||||
expectError: false,
|
||||
expectedType: L2Transaction,
|
||||
},
|
||||
{
|
||||
name: "L2 Batch message",
|
||||
messageData: append([]byte{0x00, 0x00, 0x00, 0x07}, make([]byte, 64)...),
|
||||
messageNumber: big.NewInt(4),
|
||||
timestamp: 1234567890,
|
||||
expectError: false,
|
||||
expectedType: L2BatchSubmission,
|
||||
},
|
||||
{
|
||||
name: "Unknown message type",
|
||||
messageData: append([]byte{0x00, 0x00, 0x00, 0xFF}, make([]byte, 32)...),
|
||||
messageNumber: big.NewInt(5),
|
||||
timestamp: 1234567890,
|
||||
expectError: false,
|
||||
expectedType: L2Unknown,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result, err := parser.ParseL2Message(tt.messageData, tt.messageNumber, tt.timestamp)
|
||||
|
||||
if tt.expectError {
|
||||
assert.Error(t, err)
|
||||
return
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, result)
|
||||
assert.Equal(t, tt.expectedType, result.Type)
|
||||
assert.Equal(t, tt.messageNumber, result.MessageNumber)
|
||||
assert.Equal(t, tt.timestamp, result.Timestamp)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestL2MessageParser_ParseDEXInteraction(t *testing.T) {
|
||||
logger := logger.New("info", "text", "")
|
||||
parser := NewL2MessageParser(logger)
|
||||
|
||||
// Create a mock transaction for testing
|
||||
createMockTx := func(to common.Address, data []byte) *types.Transaction {
|
||||
return types.NewTransaction(
|
||||
0,
|
||||
to,
|
||||
big.NewInt(0),
|
||||
21000,
|
||||
big.NewInt(1000000000),
|
||||
data,
|
||||
)
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
tx *types.Transaction
|
||||
expectError bool
|
||||
expectSwap bool
|
||||
}{
|
||||
{
|
||||
name: "Contract creation transaction",
|
||||
tx: types.NewContractCreation(0, big.NewInt(0), 21000, big.NewInt(1000000000), []byte{}),
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
name: "Unknown router address",
|
||||
tx: createMockTx(common.HexToAddress("0x1234567890123456789012345678901234567890"), []byte{0x38, 0xed, 0x17, 0x39}),
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
name: "Uniswap V3 router with exactInputSingle",
|
||||
tx: createMockTx(
|
||||
common.HexToAddress("0xE592427A0AEce92De3Edee1F18E0157C05861564"), // Uniswap V3 Router
|
||||
append([]byte{0x41, 0x4b, 0xf3, 0x89}, createValidExactInputSingleData()...), // exactInputSingle with proper data
|
||||
),
|
||||
expectError: false,
|
||||
expectSwap: true,
|
||||
},
|
||||
{
|
||||
name: "SushiSwap router - expect error due to complex ABI",
|
||||
tx: createMockTx(
|
||||
common.HexToAddress("0x1b02dA8Cb0d097eB8D57A175b88c7D8b47997506"), // SushiSwap Router
|
||||
[]byte{0x38, 0xed, 0x17, 0x39}, // swapExactTokensForTokens selector only
|
||||
),
|
||||
expectError: true, // Expected to fail due to insufficient ABI data
|
||||
expectSwap: false,
|
||||
},
|
||||
{
|
||||
name: "Unknown function selector",
|
||||
tx: createMockTx(
|
||||
common.HexToAddress("0xE592427A0AEce92De3Edee1F18E0157C05861564"), // Uniswap V3 Router
|
||||
[]byte{0xFF, 0xFF, 0xFF, 0xFF}, // Unknown selector
|
||||
),
|
||||
expectError: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result, err := parser.ParseDEXInteraction(tt.tx)
|
||||
|
||||
if tt.expectError {
|
||||
assert.Error(t, err)
|
||||
return
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, result)
|
||||
|
||||
if tt.expectSwap {
|
||||
assert.NotEmpty(t, result.Protocol)
|
||||
assert.Equal(t, *tt.tx.To(), result.Router)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestL2MessageParser_IsSignificantSwap(t *testing.T) {
|
||||
logger := logger.New("info", "text", "")
|
||||
parser := NewL2MessageParser(logger)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
interaction *DEXInteraction
|
||||
minAmountUSD float64
|
||||
expectSignificant bool
|
||||
}{
|
||||
{
|
||||
name: "Small swap - not significant",
|
||||
interaction: &DEXInteraction{
|
||||
AmountIn: big.NewInt(100000000000000000), // 0.1 ETH
|
||||
},
|
||||
minAmountUSD: 10.0,
|
||||
expectSignificant: false,
|
||||
},
|
||||
{
|
||||
name: "Large swap - significant",
|
||||
interaction: &DEXInteraction{
|
||||
AmountIn: big.NewInt(2000000000000000000), // 2 ETH
|
||||
},
|
||||
minAmountUSD: 10.0,
|
||||
expectSignificant: true,
|
||||
},
|
||||
{
|
||||
name: "Nil amount - not significant",
|
||||
interaction: &DEXInteraction{
|
||||
AmountIn: nil,
|
||||
},
|
||||
minAmountUSD: 10.0,
|
||||
expectSignificant: false,
|
||||
},
|
||||
{
|
||||
name: "Zero amount - not significant",
|
||||
interaction: &DEXInteraction{
|
||||
AmountIn: big.NewInt(0),
|
||||
},
|
||||
minAmountUSD: 10.0,
|
||||
expectSignificant: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := parser.IsSignificantSwap(tt.interaction, tt.minAmountUSD)
|
||||
assert.Equal(t, tt.expectSignificant, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestL2MessageParser_ParseExactInputSingle(t *testing.T) {
|
||||
logger := logger.New("info", "text", "")
|
||||
parser := NewL2MessageParser(logger)
|
||||
|
||||
// Create test data for exactInputSingle call
|
||||
// This is a simplified version - real data would be properly ABI encoded
|
||||
data := make([]byte, 256)
|
||||
|
||||
// tokenIn at position 0-31 (address in last 20 bytes)
|
||||
copy(data[12:32], common.HexToAddress("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48").Bytes()) // USDC
|
||||
|
||||
// tokenOut at position 32-63 (address in last 20 bytes)
|
||||
copy(data[44:64], common.HexToAddress("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2").Bytes()) // WETH
|
||||
|
||||
// recipient at position 96-127 (address in last 20 bytes)
|
||||
copy(data[108:128], common.HexToAddress("0x742d35Cc6635C0532925a3b8D9C12CF345eEE40F").Bytes())
|
||||
|
||||
// deadline at position 128-159 (uint64 in last 8 bytes)
|
||||
binary.BigEndian.PutUint64(data[152:160], 1234567890)
|
||||
|
||||
// amountIn at position 160-191
|
||||
amountIn := big.NewInt(1000000000) // 1000 USDC (6 decimals)
|
||||
amountInBytes := amountIn.Bytes()
|
||||
copy(data[192-len(amountInBytes):192], amountInBytes)
|
||||
|
||||
interaction := &DEXInteraction{}
|
||||
result, err := parser.parseExactInputSingle(interaction, data)
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, common.HexToAddress("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"), result.TokenIn)
|
||||
assert.Equal(t, common.HexToAddress("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"), result.TokenOut)
|
||||
assert.Equal(t, common.HexToAddress("0x742d35Cc6635C0532925a3b8D9C12CF345eEE40F"), result.Recipient)
|
||||
assert.Equal(t, uint64(1234567890), result.Deadline)
|
||||
// Note: AmountIn comparison might need adjustment based on how the data is packed
|
||||
}
|
||||
|
||||
func TestL2MessageParser_InitialSetup(t *testing.T) {
|
||||
logger := logger.New("info", "text", "")
|
||||
parser := NewL2MessageParser(logger)
|
||||
|
||||
// Test that we can add and identify known pools
|
||||
// This test verifies the internal pool tracking functionality
|
||||
|
||||
// The parser should have some pre-configured pools
|
||||
assert.NotNil(t, parser)
|
||||
|
||||
// Verify parser was created with proper initialization
|
||||
assert.NotNil(t, parser.logger)
|
||||
}
|
||||
|
||||
func BenchmarkL2MessageParser_ParseL2Message(b *testing.B) {
|
||||
logger := logger.New("info", "text", "")
|
||||
parser := NewL2MessageParser(logger)
|
||||
|
||||
// Create test message data
|
||||
messageData := append([]byte{0x00, 0x00, 0x00, 0x03}, make([]byte, 100)...)
|
||||
messageNumber := big.NewInt(1)
|
||||
timestamp := uint64(1234567890)
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := parser.ParseL2Message(messageData, messageNumber, timestamp)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkL2MessageParser_ParseDEXInteraction(b *testing.B) {
|
||||
logger := logger.New("info", "text", "")
|
||||
parser := NewL2MessageParser(logger)
|
||||
|
||||
// Create mock transaction
|
||||
tx := types.NewTransaction(
|
||||
0,
|
||||
common.HexToAddress("0xE592427A0AEce92De3Edee1F18E0157C05861564"), // Uniswap V3 Router
|
||||
big.NewInt(0),
|
||||
21000,
|
||||
big.NewInt(1000000000),
|
||||
[]byte{0x41, 0x4b, 0xf3, 0x89}, // exactInputSingle selector
|
||||
)
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := parser.ParseDEXInteraction(tx)
|
||||
if err != nil && err.Error() != "insufficient data for exactInputSingle" {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user