- Added comprehensive bounds checking to prevent buffer overruns in multicall parsing - Implemented graduated validation system (Strict/Moderate/Permissive) to reduce false positives - Added LRU caching system for address validation with 10-minute TTL - Enhanced ABI decoder with missing Universal Router and Arbitrum-specific DEX signatures - Fixed duplicate function declarations and import conflicts across multiple files - Added error recovery mechanisms with multiple fallback strategies - Updated tests to handle new validation behavior for suspicious addresses - Fixed parser test expectations for improved validation system - Applied gofmt formatting fixes to ensure code style compliance - Fixed mutex copying issues in monitoring package by introducing MetricsSnapshot - Resolved critical security vulnerabilities in heuristic address extraction - Progress: Updated TODO audit from 10% to 35% complete 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
291 lines
8.8 KiB
Go
291 lines
8.8 KiB
Go
//go:build integration
|
|
// +build integration
|
|
|
|
package integration
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"math/big"
|
|
"os"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/ethereum/go-ethereum/common"
|
|
"github.com/ethereum/go-ethereum/ethclient"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/fraktal/mev-beta/internal/config"
|
|
"github.com/fraktal/mev-beta/internal/logger"
|
|
"github.com/fraktal/mev-beta/pkg/arbitrage"
|
|
"github.com/fraktal/mev-beta/pkg/security"
|
|
)
|
|
|
|
// Test configuration for forked environment
|
|
const (
|
|
TestRPCEndpoint = "http://localhost:8545"
|
|
TestChainID = 31337
|
|
testEncryptionKey = "integration_key_32_chars_minimum_length"
|
|
)
|
|
|
|
// Arbitrum One token addresses for testing
|
|
var (
|
|
WETH = common.HexToAddress("0x82aF49447D8A07e3bd95BD0d56f35241523fBab1")
|
|
USDC = common.HexToAddress("0xA0b86a33E6417aB7d461a67E4d3F14F6b49d3e8B") // USDC.e
|
|
USDT = common.HexToAddress("0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9")
|
|
)
|
|
|
|
func TestMain(m *testing.M) {
|
|
// Check if we're in test mode
|
|
if os.Getenv("TEST_MODE") != "true" {
|
|
fmt.Println("Skipping integration tests - set TEST_MODE=true to run")
|
|
os.Exit(0)
|
|
}
|
|
|
|
// Run tests
|
|
code := m.Run()
|
|
os.Exit(code)
|
|
}
|
|
|
|
func setupTestEnvironment(t *testing.T) (*arbitrage.ArbitrageService, func()) {
|
|
t.Helper()
|
|
|
|
ctx := context.Background()
|
|
|
|
// Create test logger
|
|
log := logger.New("debug", "text", "")
|
|
|
|
// Create test configuration
|
|
cfg := &config.ArbitrageConfig{
|
|
Enabled: true,
|
|
ArbitrageContractAddress: "0x0000000000000000000000000000000000000001", // Placeholder
|
|
FlashSwapContractAddress: "0x0000000000000000000000000000000000000002", // Placeholder
|
|
MinProfitWei: 1000000000000000, // 0.001 ETH
|
|
MinROIPercent: 1.0, // 1%
|
|
MinSignificantSwapSize: 1000000000000000000, // 1 ETH
|
|
SlippageTolerance: 0.005, // 0.5%
|
|
MinScanAmountWei: 100000000000000000, // 0.1 ETH
|
|
MaxScanAmountWei: 9000000000000000000, // 9 ETH (fits int64)
|
|
MaxGasPriceWei: 100000000000, // 100 gwei
|
|
MaxConcurrentExecutions: 1, // Single execution for testing
|
|
MaxOpportunitiesPerEvent: 3,
|
|
OpportunityTTL: 30 * time.Second,
|
|
MaxPathAge: 60 * time.Second,
|
|
StatsUpdateInterval: 10 * time.Second,
|
|
}
|
|
|
|
// Create Ethereum client
|
|
client, err := ethclient.Dial(TestRPCEndpoint)
|
|
if err != nil {
|
|
t.Skipf("skipping integration test; unable to connect to %s: %v", TestRPCEndpoint, err)
|
|
}
|
|
|
|
// Create key manager
|
|
keyManagerConfig := &security.KeyManagerConfig{
|
|
KeystorePath: "test_keystore",
|
|
EncryptionKey: testEncryptionKey,
|
|
KeyRotationDays: 30,
|
|
MaxSigningRate: 100,
|
|
SessionTimeout: time.Hour,
|
|
}
|
|
keyManager, err := security.NewKeyManager(keyManagerConfig, log)
|
|
require.NoError(t, err, "Failed to create key manager")
|
|
|
|
// Create test database
|
|
database, err := arbitrage.NewSQLiteDatabase(":memory:", log)
|
|
require.NoError(t, err, "Failed to create test database")
|
|
|
|
// Create arbitrage service
|
|
service, err := arbitrage.NewArbitrageService(
|
|
ctx,
|
|
client,
|
|
log,
|
|
cfg,
|
|
keyManager,
|
|
database,
|
|
)
|
|
require.NoError(t, err, "Failed to create arbitrage service")
|
|
|
|
// Start the service
|
|
err = service.Start()
|
|
require.NoError(t, err, "Failed to start arbitrage service")
|
|
|
|
// Cleanup function
|
|
cleanup := func() {
|
|
service.Stop()
|
|
database.Close()
|
|
client.Close()
|
|
os.RemoveAll("test_keystore")
|
|
}
|
|
|
|
return service, cleanup
|
|
}
|
|
|
|
func TestArbitrageServiceIntegration(t *testing.T) {
|
|
service, cleanup := setupTestEnvironment(t)
|
|
defer cleanup()
|
|
|
|
t.Run("ServiceHealthCheck", func(t *testing.T) {
|
|
assert.True(t, service.IsRunning(), "Service should be running")
|
|
|
|
stats := service.GetStats()
|
|
assert.NotNil(t, stats, "Stats should not be nil")
|
|
assert.Equal(t, int64(0), stats.TotalOpportunitiesDetected, "Initial opportunities should be zero")
|
|
})
|
|
|
|
t.Run("TokenValidation", func(t *testing.T) {
|
|
// Test that we can validate token addresses
|
|
tokens := []common.Address{WETH, USDC, USDT}
|
|
|
|
for _, token := range tokens {
|
|
assert.False(t, token.String() == "0x0000000000000000000000000000000000000000",
|
|
"Token address should not be zero: %s", token.String())
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestSwapEventProcessing(t *testing.T) {
|
|
service, cleanup := setupTestEnvironment(t)
|
|
defer cleanup()
|
|
|
|
t.Run("ProcessLargeSwapEvent", func(t *testing.T) {
|
|
// Create a simulated large swap event
|
|
swapEvent := &arbitrage.SimpleSwapEvent{
|
|
TxHash: common.HexToHash("0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"),
|
|
PoolAddress: common.HexToAddress("0xC6962004f452bE9203591991D15f6b388e09E8D0"), // Example pool
|
|
Token0: WETH,
|
|
Token1: USDC,
|
|
Amount0: mustBigInt(t, "-5000000000000000000"), // -5 ETH
|
|
Amount1: mustBigInt(t, "12500000000"), // +12500 USDC
|
|
SqrtPriceX96: mustBigInt(t, "1000000000000000000000"), // Example price
|
|
Liquidity: mustBigInt(t, "50000000000000000000000"), // Example liquidity
|
|
Tick: int32(-85000), // Example tick
|
|
BlockNumber: 12345678,
|
|
LogIndex: 1,
|
|
Timestamp: time.Now(),
|
|
}
|
|
|
|
// Process the swap event
|
|
err := service.ProcessSwapEvent(swapEvent)
|
|
assert.NoError(t, err, "Should process swap event without error")
|
|
|
|
// Allow time for processing
|
|
time.Sleep(1 * time.Second)
|
|
|
|
// Check that the event was processed
|
|
stats := service.GetStats()
|
|
t.Logf("Opportunities detected: %d", stats.TotalOpportunitiesDetected)
|
|
})
|
|
}
|
|
|
|
func TestPoolDataRetrieval(t *testing.T) {
|
|
_, cleanup := setupTestEnvironment(t)
|
|
defer cleanup()
|
|
|
|
t.Run("SaveAndRetrievePoolData", func(t *testing.T) {
|
|
// Create test pool data
|
|
poolData := &arbitrage.SimplePoolData{
|
|
Address: common.HexToAddress("0xC6962004f452bE9203591991D15f6b388e09E8D0"),
|
|
Token0: WETH,
|
|
Token1: USDC,
|
|
Fee: 500, // 0.05%
|
|
Liquidity: mustBigInt(t, "50000000000000000000000"),
|
|
SqrtPriceX96: mustBigInt(t, "1000000000000000000000"),
|
|
Tick: -85000,
|
|
BlockNumber: 12345678,
|
|
TxHash: common.HexToHash("0xabcdef"),
|
|
LogIndex: 1,
|
|
LastUpdated: time.Now(),
|
|
}
|
|
|
|
// This test would require database access through the service
|
|
// For now, we verify the structure is correct
|
|
assert.Equal(t, WETH, poolData.Token0, "Token0 should be WETH")
|
|
assert.Equal(t, USDC, poolData.Token1, "Token1 should be USDC")
|
|
assert.Equal(t, int64(500), poolData.Fee, "Fee should be 500 (0.05%)")
|
|
})
|
|
}
|
|
|
|
func TestRealTimeArbitrageDetection(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("Skipping real-time test in short mode")
|
|
}
|
|
|
|
service, cleanup := setupTestEnvironment(t)
|
|
defer cleanup()
|
|
|
|
t.Run("ContinuousMonitoring", func(t *testing.T) {
|
|
// Run service for a short period to test real-time processing
|
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
defer cancel()
|
|
|
|
initialStats := service.GetStats()
|
|
|
|
// Simulate periodic swap events
|
|
ticker := time.NewTicker(5 * time.Second)
|
|
defer ticker.Stop()
|
|
|
|
go func() {
|
|
eventCount := 0
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
return
|
|
case <-ticker.C:
|
|
eventCount++
|
|
|
|
// Create varied swap events
|
|
swapEvent := &arbitrage.SimpleSwapEvent{
|
|
TxHash: common.HexToHash(fmt.Sprintf("0x%064d", eventCount)),
|
|
PoolAddress: common.HexToAddress("0xC6962004f452bE9203591991D15f6b388e09E8D0"),
|
|
Token0: WETH,
|
|
Token1: USDC,
|
|
Amount0: scaleBigInt(t, "-1000000000000000000", eventCount),
|
|
Amount1: scaleBigInt(t, "2500000000", eventCount),
|
|
SqrtPriceX96: mustBigInt(t, "1000000000000000000000"),
|
|
Liquidity: mustBigInt(t, "50000000000000000000000"),
|
|
Tick: int32(-85000 + eventCount*100),
|
|
BlockNumber: 12345678 + uint64(eventCount),
|
|
LogIndex: uint(eventCount),
|
|
Timestamp: time.Now(),
|
|
}
|
|
|
|
service.ProcessSwapEvent(swapEvent)
|
|
}
|
|
}
|
|
}()
|
|
|
|
// Wait for test duration
|
|
<-ctx.Done()
|
|
|
|
// Check final stats
|
|
finalStats := service.GetStats()
|
|
t.Logf("Initial opportunities: %d", initialStats.TotalOpportunitiesDetected)
|
|
t.Logf("Final opportunities: %d", finalStats.TotalOpportunitiesDetected)
|
|
|
|
// We expect some processing activity
|
|
assert.True(t, finalStats.TotalOpportunitiesDetected >= initialStats.TotalOpportunitiesDetected,
|
|
"Should have processed some opportunities")
|
|
})
|
|
}
|
|
|
|
func mustBigInt(t testing.TB, value string) *big.Int {
|
|
t.Helper()
|
|
bi, ok := new(big.Int).SetString(value, 10)
|
|
if !ok {
|
|
t.Fatalf("invalid big.Int value: %s", value)
|
|
}
|
|
return bi
|
|
}
|
|
|
|
func scaleBigInt(t testing.TB, base string, multiplier int) *big.Int {
|
|
t.Helper()
|
|
if multiplier == 0 {
|
|
return big.NewInt(0)
|
|
}
|
|
bi := mustBigInt(t, base)
|
|
factor := big.NewInt(int64(multiplier))
|
|
return bi.Mul(bi, factor)
|
|
}
|