Files
mev-beta/tests/integration/arbitrage_test.go
Krypto Kajun 850223a953 fix(multicall): resolve critical multicall parsing corruption issues
- 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>
2025-10-17 00:12:55 -05:00

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)
}