refactor: move all remaining files to orig/ directory

Completed clean root directory structure:
- Root now contains only: .git, .env, docs/, orig/
- Moved all remaining files and directories to orig/:
  - Config files (.claude, .dockerignore, .drone.yml, etc.)
  - All .env variants (except active .env)
  - Git config (.gitconfig, .github, .gitignore, etc.)
  - Tool configs (.golangci.yml, .revive.toml, etc.)
  - Documentation (*.md files, @prompts)
  - Build files (Dockerfiles, Makefile, go.mod, go.sum)
  - Docker compose files
  - All source directories (scripts, tests, tools, etc.)
  - Runtime directories (logs, monitoring, reports)
  - Dependency files (node_modules, lib, cache)
  - Special files (--delete)

- Removed empty runtime directories (bin/, data/)

V2 structure is now clean:
- docs/planning/ - V2 planning documents
- orig/ - Complete V1 codebase preserved
- .env - Active environment config (not in git)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Administrator
2025-11-10 10:53:05 +01:00
parent 803de231ba
commit c54c569f30
718 changed files with 8304 additions and 8281 deletions

View File

@@ -0,0 +1,290 @@
//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)
}

View File

@@ -0,0 +1,286 @@
//go:build integration && legacy && forked
// +build integration,legacy,forked
// Package integration provides integration tests for the MEV bot using a forked Arbitrum environment
package integration
import (
"context"
"math/big"
"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/contracts"
"github.com/fraktal/mev-beta/pkg/database"
"github.com/fraktal/mev-beta/pkg/pools"
"github.com/fraktal/mev-beta/pkg/security"
)
// TestContractExecutorInitialization tests that the contract executor can be initialized
func TestContractExecutorInitialization(t *testing.T) {
// Skip this test in short mode
if testing.Short() {
t.Skip("skipping contract executor test in short mode")
}
// Create test logger
log := logger.New("debug", "text", "")
// Create test configuration
cfg := &config.Config{
Arbitrum: config.ArbitrumConfig{
RPCEndpoint: "http://localhost:8545", // Anvil default port
ChainID: 31337, // Anvil default chain ID
RateLimit: config.RateLimitConfig{
RequestsPerSecond: 10,
MaxConcurrent: 5,
Burst: 20,
},
},
Bot: config.BotConfig{
Enabled: true,
PollingInterval: 1,
MinProfitThreshold: 0.01, // Lower threshold for testing
GasPriceMultiplier: 1.2,
MaxWorkers: 2,
ChannelBufferSize: 10,
RPCTimeout: 30,
},
Ethereum: config.EthereumConfig{
PrivateKey: "", // Will be set by environment or test setup
AccountAddress: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", // Default Anvil account
GasPriceMultiplier: 1.2,
},
Contracts: config.ContractsConfig{
ArbitrageExecutor: "0x0000000000000000000000000000000000000000", // Placeholder
FlashSwapper: "0x0000000000000000000000000000000000000000", // Placeholder
AuthorizedCallers: []string{
"0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", // Default Anvil account
},
AuthorizedDEXes: []string{
"0x1F98431c8aD98523631AE4a59f267346ea31F984", // Uniswap V3 Factory
"0xf1D7CC64Fb4452F05c498126312eBE29f30Fbcf9", // Uniswap V2 Factory
"0xc35DADB65012eC5796536bD9864eD8773aBc74C4", // SushiSwap Factory
},
},
Database: config.DatabaseConfig{
File: ":memory:",
MaxOpenConnections: 10,
MaxIdleConnections: 5,
},
Uniswap: config.UniswapConfig{
FactoryAddress: "0x1F98431c8aD98523631AE4a59f267346ea31F984",
PositionManagerAddress: "0xC36442b4a4522E871399CD717aBDD847Ab11FE88",
FeeTiers: []int64{500, 3000, 10000},
Cache: config.CacheConfig{
Enabled: true,
Expiration: 300,
MaxSize: 10000,
},
},
}
// Connect to the forked environment
client, err := ethclient.Dial(cfg.Arbitrum.RPCEndpoint)
if err != nil {
t.Skipf("Skipping test: failed to connect to forked Arbitrum at %s", cfg.Arbitrum.RPCEndpoint)
}
defer client.Close()
// Verify connection by getting chain ID
chainID, err := client.ChainID(context.Background())
require.NoError(t, err, "failed to get chain ID")
log.Info("Connected to forked Arbitrum chain ID:", chainID.String())
// Test contract executor creation (this might fail in testing but we can verify the setup)
// Create a mock key manager for testing
keyManager, err := security.NewKeyManager(&security.KeyManagerConfig{
KeystorePath: "/tmp/test_keys",
EncryptionKey: "test_encryption_key",
}, log)
require.NoError(t, err)
contractExecutor, err := contracts.NewContractExecutor(cfg, log, keyManager)
if err != nil {
// This is expected in testing since we don't have real contracts deployed
log.Warn("Contract executor creation failed (expected in testing):", err)
} else {
defer contractExecutor.Close()
assert.NotNil(t, contractExecutor)
log.Info("Contract executor created successfully")
}
log.Info("Contract executor initialization test completed")
}
// TestCREATE2Calculator tests the CREATE2 pool address calculation
func TestCREATE2Calculator(t *testing.T) {
// Create test logger
log := logger.New("debug", "text", "")
// Create CREATE2 calculator
calculator := pools.NewCREATE2Calculator(log)
// Test calculating pool addresses for known token pairs
testCases := []struct {
name string
factoryName string
token0 string
token1 string
fee uint32
expectedLen int
}{
{
name: "Uniswap V3 USDC/WETH",
factoryName: "uniswap_v3",
token0: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", // USDC
token1: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", // WETH
fee: 3000,
expectedLen: 20, // Address should be 20 bytes
},
{
name: "Uniswap V2 USDC/WETH",
factoryName: "uniswap_v2",
token0: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", // USDC
token1: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", // WETH
fee: 0, // V2 doesn't use fee in pool calculation
expectedLen: 20,
},
{
name: "SushiSwap USDC/WETH",
factoryName: "sushiswap",
token0: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", // USDC
token1: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", // WETH
fee: 0, // SushiSwap V2 doesn't use fee in pool calculation
expectedLen: 20,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
// Convert hex addresses to common.Address
token0 := common.HexToAddress(tc.token0)
token1 := common.HexToAddress(tc.token1)
// Calculate pool address
poolAddr, err := calculator.CalculatePoolAddress(tc.factoryName, token0, token1, tc.fee)
require.NoError(t, err, "failed to calculate pool address")
// Verify the pool address
assert.Equal(t, tc.expectedLen, len(poolAddr.Bytes()), "pool address should be 20 bytes")
assert.NotEqual(t, common.Address{}, poolAddr, "pool address should not be zero")
log.Info("Calculated pool address:", poolAddr.Hex(), "for", tc.name)
})
}
// Test finding pools for token pairs
usdc := common.HexToAddress("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48")
weth := common.HexToAddress("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2")
pools, err := calculator.FindPoolsForTokenPair(usdc, weth)
require.NoError(t, err, "failed to find pools for token pair")
log.Info("Found", len(pools), "potential pools for USDC/WETH pair")
// Verify we found some pools
assert.True(t, len(pools) > 0, "should find at least one pool for USDC/WETH")
// Verify each pool has valid data
for _, pool := range pools {
assert.NotEqual(t, "", pool.Factory, "factory should not be empty")
assert.NotEqual(t, common.Address{}, pool.Token0, "token0 should not be zero")
assert.NotEqual(t, common.Address{}, pool.Token1, "token1 should not be zero")
assert.NotEqual(t, common.Address{}, pool.PoolAddr, "pool address should not be zero")
assert.True(t, pool.Fee >= 0, "fee should be non-negative")
}
log.Info("CREATE2 calculator test completed successfully")
}
// TestDatabaseIntegration tests database integration
func TestDatabaseIntegration(t *testing.T) {
// Create test logger
log := logger.New("debug", "text", "")
// Create test configuration
cfg := &config.DatabaseConfig{
File: ":memory:", // In-memory database for testing
MaxOpenConnections: 10,
MaxIdleConnections: 5,
}
// Create database
db, err := database.NewDatabase(cfg, log)
require.NoError(t, err, "failed to create database")
defer db.Close()
// Test inserting swap event
swapEvent := &database.SwapEvent{
Timestamp: time.Now(),
BlockNumber: 12345678,
TxHash: common.HexToHash("0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"),
PoolAddress: common.HexToAddress("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640"),
Token0: common.HexToAddress("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"), // USDC
Token1: common.HexToAddress("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"), // WETH
Amount0In: big.NewInt(1000000000), // 1000 USDC
Amount1In: big.NewInt(0),
Amount0Out: big.NewInt(0),
Amount1Out: big.NewInt(500000000000000000), // 0.5 WETH
Sender: common.HexToAddress("0x1234567890abcdef1234567890abcdef12345678"),
Recipient: common.HexToAddress("0x8765432109fedcba8765432109fedcba87654321"),
Protocol: "uniswap_v3",
}
err = db.InsertSwapEvent(swapEvent)
assert.NoError(t, err, "failed to insert swap event")
// Test retrieving recent swap events
swaps, err := db.GetRecentSwapEvents(10)
assert.NoError(t, err, "failed to get recent swap events")
assert.Len(t, swaps, 1, "expected 1 swap event")
// Verify the retrieved swap event
if len(swaps) > 0 {
assert.Equal(t, swapEvent.PoolAddress, swaps[0].PoolAddress, "pool address mismatch")
assert.Equal(t, swapEvent.Token0, swaps[0].Token0, "token0 mismatch")
assert.Equal(t, swapEvent.Token1, swaps[0].Token1, "token1 mismatch")
assert.Equal(t, swapEvent.Protocol, swaps[0].Protocol, "protocol mismatch")
assert.Equal(t, swapEvent.Amount0In, swaps[0].Amount0In, "amount0 in mismatch")
assert.Equal(t, swapEvent.Amount1Out, swaps[0].Amount1Out, "amount1 out mismatch")
}
// Test inserting pool data
poolData := &database.PoolData{
Address: common.HexToAddress("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640"),
Token0: common.HexToAddress("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"), // USDC
Token1: common.HexToAddress("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"), // WETH
Fee: 3000, // 0.3%
Liquidity: big.NewInt(1000000000000000000), // 1 ETH equivalent
SqrtPriceX96: big.NewInt(2505414483750470000), // Realistic price
Tick: 200000, // Corresponding tick
LastUpdated: time.Now(),
Protocol: "uniswap_v3",
}
err = db.InsertPoolData(poolData)
assert.NoError(t, err, "failed to insert pool data")
// Test retrieving pool data
retrievedPool, err := db.GetPoolData(poolData.Address)
assert.NoError(t, err, "failed to get pool data")
assert.Equal(t, poolData.Address, retrievedPool.Address, "pool address mismatch")
assert.Equal(t, poolData.Token0, retrievedPool.Token0, "token0 mismatch")
assert.Equal(t, poolData.Token1, retrievedPool.Token1, "token1 mismatch")
assert.Equal(t, poolData.Fee, retrievedPool.Fee, "fee mismatch")
assert.Equal(t, poolData.Protocol, retrievedPool.Protocol, "protocol mismatch")
log.Info("Database integration test completed successfully")
}

View File

@@ -0,0 +1,335 @@
package integration
import (
"context"
"testing"
"time"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/stretchr/testify/require"
// NOTE: The following imports will be needed once the commented test code is uncommented:
// "math/big"
// "github.com/ethereum/go-ethereum/common"
// "github.com/yourusername/mev-beta/bindings/contracts"
// "github.com/yourusername/mev-beta/bindings/interfaces"
)
const (
// Arbitrum Mainnet RPC for forking
ArbitrumRPC = "https://arb1.arbitrum.io/rpc"
// Known Arbitrum addresses
WETH_ADDRESS = "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1"
USDC_ADDRESS = "0xaf88d065e77c8cC2239327C5EDb3A432268e5831"
UNISWAP_V3_FACTORY = "0x1F98431c8aD98523631AE4a59f267346ea31F984"
WETH_USDC_POOL_500_FEE = "0xC6962004f452bE9203591991D15f6b388e09E8D0"
)
// TestForkContractDeployment tests deploying contracts to an Arbitrum fork
func TestForkContractDeployment(t *testing.T) {
// Skip if not running integration tests
if testing.Short() {
t.Skip("Skipping integration test in short mode")
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
defer cancel()
// Connect to Arbitrum fork
client, err := ethclient.DialContext(ctx, ArbitrumRPC)
require.NoError(t, err, "Failed to connect to Arbitrum RPC")
defer client.Close()
chainID, err := client.ChainID(ctx)
require.NoError(t, err, "Failed to get chain ID")
t.Logf("Connected to chain ID: %s", chainID.String())
// Create test account with private key
privateKey, err := crypto.GenerateKey()
require.NoError(t, err, "Failed to generate private key")
auth, err := bind.NewKeyedTransactorWithChainID(privateKey, chainID)
require.NoError(t, err, "Failed to create transactor")
// Set gas limits
auth.GasLimit = 5000000
t.Logf("Test account: %s", auth.From.Hex())
// Note: Actual deployment would happen here using generated bindings
// Example (uncomment after binding generation):
/*
// Deploy UniswapV3FlashSwapper
flashSwapperAddr, tx, flashSwapper, err := contracts.DeployUniswapV3FlashSwapper(
auth,
client,
common.HexToAddress(UNISWAP_V3_FACTORY),
)
require.NoError(t, err, "Failed to deploy UniswapV3FlashSwapper")
t.Logf("UniswapV3FlashSwapper deployed at: %s", flashSwapperAddr.Hex())
t.Logf("Deployment tx: %s", tx.Hash().Hex())
// Wait for deployment
receipt, err := bind.WaitMined(ctx, client, tx)
require.NoError(t, err, "Failed to wait for deployment")
require.Equal(t, uint64(1), receipt.Status, "Deployment failed")
// Deploy ArbitrageExecutor
arbExecutorAddr, tx, arbExecutor, err := contracts.DeployArbitrageExecutor(
auth,
client,
flashSwapperAddr,
)
require.NoError(t, err, "Failed to deploy ArbitrageExecutor")
t.Logf("ArbitrageExecutor deployed at: %s", arbExecutorAddr.Hex())
receipt, err = bind.WaitMined(ctx, client, tx)
require.NoError(t, err, "Failed to wait for deployment")
require.Equal(t, uint64(1), receipt.Status, "Deployment failed")
*/
t.Log("Contract deployment test structure ready")
}
// TestForkFlashSwapFeeCalculation tests flash swap fee calculation on fork
func TestForkFlashSwapFeeCalculation(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test in short mode")
}
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
defer cancel()
client, err := ethclient.DialContext(ctx, ArbitrumRPC)
require.NoError(t, err, "Failed to connect to Arbitrum RPC")
defer client.Close()
// Note: After binding generation, this would use the actual contract binding
/*
flashSwapper, err := contracts.NewUniswapV3FlashSwapper(
common.HexToAddress("YOUR_DEPLOYED_ADDRESS"),
client,
)
require.NoError(t, err, "Failed to create flash swapper binding")
// Test fee calculation for 0.05% pool
amount0 := big.NewInt(100_000_000) // 100 USDC (6 decimals)
amount1 := big.NewInt(0)
fee0, fee1, err := flashSwapper.CalculateFlashSwapFee(
&bind.CallOpts{Context: ctx},
common.HexToAddress(WETH_USDC_POOL_500_FEE),
amount0,
amount1,
)
require.NoError(t, err, "Failed to calculate flash swap fee")
t.Logf("Borrow amount: %s USDC", amount0.String())
t.Logf("Flash loan fee: %s", fee0.String())
// For 0.05% fee tier on 100 USDC: fee = 100 * 0.0005 = 0.05 USDC = 50000 (6 decimals)
expectedFee := big.NewInt(50000)
require.Equal(t, expectedFee.String(), fee0.String(), "Fee calculation incorrect")
*/
t.Log("Flash swap fee calculation test structure ready")
}
// TestForkArbitrageCalculation tests arbitrage profit calculation
func TestForkArbitrageCalculation(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test in short mode")
}
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
defer cancel()
client, err := ethclient.DialContext(ctx, ArbitrumRPC)
require.NoError(t, err, "Failed to connect to Arbitrum RPC")
defer client.Close()
// Note: After binding generation
/*
arbExecutor, err := contracts.NewArbitrageExecutor(
common.HexToAddress("YOUR_DEPLOYED_ADDRESS"),
client,
)
require.NoError(t, err, "Failed to create arbitrage executor binding")
// Create test arbitrage params
params := contracts.IArbitrageArbitrageParams{
Tokens: []common.Address{
common.HexToAddress(WETH_ADDRESS),
common.HexToAddress(USDC_ADDRESS),
common.HexToAddress(WETH_ADDRESS),
},
Pools: []common.Address{
common.HexToAddress("POOL_1"),
common.HexToAddress("POOL_2"),
},
Amounts: []*big.Int{
big.NewInt(1000000000000000000), // 1 WETH
big.NewInt(2000000000), // 2000 USDC
},
SwapData: [][]byte{
[]byte("swap_data_1"),
[]byte("swap_data_2"),
},
MinProfit: big.NewInt(1000000000000000), // 0.001 WETH minimum profit
}
expectedProfit, err := arbExecutor.CalculateArbitrageProfit(
&bind.CallOpts{Context: ctx},
params,
)
require.NoError(t, err, "Failed to calculate arbitrage profit")
t.Logf("Expected arbitrage profit: %s", expectedProfit.String())
require.True(t, expectedProfit.Cmp(big.NewInt(0)) >= 0, "Expected profit should be non-negative")
*/
t.Log("Arbitrage calculation test structure ready")
}
// TestForkEndToEndArbitrage tests the complete arbitrage flow
func TestForkEndToEndArbitrage(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test in short mode")
}
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
defer cancel()
t.Log("=== End-to-End Arbitrage Test ===")
// 1. Connect to fork
client, err := ethclient.DialContext(ctx, ArbitrumRPC)
require.NoError(t, err, "Failed to connect to Arbitrum RPC")
defer client.Close()
chainID, err := client.ChainID(ctx)
require.NoError(t, err)
t.Logf("Connected to chain ID: %s", chainID.String())
// 2. Setup test account
privateKey, err := crypto.GenerateKey()
require.NoError(t, err)
auth, err := bind.NewKeyedTransactorWithChainID(privateKey, chainID)
require.NoError(t, err)
auth.GasLimit = 5000000
t.Logf("Test account: %s", auth.From.Hex())
// 3. Deploy contracts (placeholder)
t.Log("Step 1: Deploy contracts")
// Actual deployment with bindings goes here
// 4. Configure contracts
t.Log("Step 2: Configure contracts")
// Set authorized callers, DEXes, pools, etc.
// 5. Fund contracts with test tokens
t.Log("Step 3: Fund contracts with test tokens")
// Transfer WETH, USDC to test account and contracts
// 6. Execute test arbitrage
t.Log("Step 4: Execute arbitrage")
/*
// Build arbitrage params
params := contracts.IArbitrageArbitrageParams{
Tokens: []common.Address{
common.HexToAddress(WETH_ADDRESS),
common.HexToAddress(USDC_ADDRESS),
common.HexToAddress(WETH_ADDRESS),
},
Pools: []common.Address{
common.HexToAddress(WETH_USDC_POOL_500_FEE),
common.HexToAddress("SECOND_POOL"),
},
Amounts: []*big.Int{
big.NewInt(1000000000000000000), // 1 WETH
big.NewInt(2000000000), // 2000 USDC
},
SwapData: [][]byte{
buildSwapData(),
buildSwapData(),
},
MinProfit: big.NewInt(1000000000000000), // 0.001 WETH
}
// Execute arbitrage
tx, err := arbExecutor.ExecuteArbitrage(auth, params)
require.NoError(t, err, "Failed to execute arbitrage")
t.Logf("Arbitrage tx: %s", tx.Hash().Hex())
// Wait for confirmation
receipt, err := bind.WaitMined(ctx, client, tx)
require.NoError(t, err, "Failed to wait for tx")
require.Equal(t, uint64(1), receipt.Status, "Arbitrage transaction failed")
// Check events
t.Log("Step 5: Verify arbitrage events")
// Parse ArbitrageExecuted event
*/
// 7. Verify profit
t.Log("Step 5: Verify profit")
// Check final balances
t.Log("=== End-to-End Test Complete ===")
}
// TestForkDataFetcher tests the DataFetcher contract
func TestForkDataFetcher(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test in short mode")
}
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
defer cancel()
client, err := ethclient.DialContext(ctx, ArbitrumRPC)
require.NoError(t, err)
defer client.Close()
/*
dataFetcher, err := contracts.NewDataFetcher(
common.HexToAddress("YOUR_DEPLOYED_ADDRESS"),
client,
)
require.NoError(t, err)
// Build batch request
req := contracts.DataFetcherBatchRequest{
V2Pools: []common.Address{},
V3Pools: []common.Address{
common.HexToAddress(WETH_USDC_POOL_500_FEE),
},
}
// Fetch batch data
response, err := dataFetcher.BatchFetchAllData(
&bind.CallOpts{Context: ctx},
req,
)
require.NoError(t, err, "Failed to fetch batch data")
t.Logf("Fetched %d V3 pool data entries", len(response.V3PoolData))
for i, poolData := range response.V3PoolData {
t.Logf("Pool %d:", i)
t.Logf(" Token0: %s", poolData.Token0.Hex())
t.Logf(" Token1: %s", poolData.Token1.Hex())
t.Logf(" Liquidity: %s", poolData.Liquidity.String())
}
*/
t.Log("DataFetcher test structure ready")
}

View File

@@ -0,0 +1,412 @@
//go:build integration && legacy && forked
// +build integration,legacy,forked
// Package integration provides integration tests for the MEV bot using a forked Arbitrum environment
package integration
import (
"context"
"fmt"
"math/big"
"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/internal/ratelimit"
"github.com/fraktal/mev-beta/pkg/contracts"
"github.com/fraktal/mev-beta/pkg/database"
"github.com/fraktal/mev-beta/pkg/market"
"github.com/fraktal/mev-beta/pkg/monitor"
"github.com/fraktal/mev-beta/pkg/orchestrator"
"github.com/fraktal/mev-beta/pkg/pools"
"github.com/fraktal/mev-beta/pkg/scanner"
)
// TestFullArbitragePipeline tests the complete arbitrage detection and execution pipeline
// using a forked Arbitrum environment
func TestFullArbitragePipeline(t *testing.T) {
// Skip this test in short mode
if testing.Short() {
t.Skip("skipping integration test in short mode")
}
// Create test logger
log := logger.New("debug", "text", "")
// Create test configuration
cfg := createTestConfig()
// Connect to the forked environment
client, err := ethclient.Dial(cfg.Arbitrum.RPCEndpoint)
require.NoError(t, err, "failed to connect to forked Arbitrum")
defer client.Close()
// Verify connection by getting chain ID
chainID, err := client.ChainID(context.Background())
require.NoError(t, err, "failed to get chain ID")
log.Info(fmt.Sprintf("Connected to forked Arbitrum chain ID: %s", chainID.String()))
// Create rate limiter
rateLimiter := ratelimit.NewLimiterManager(&cfg.Arbitrum)
// Create market manager
marketMgr := market.NewMarketManager(&cfg.Uniswap, log)
// Create database (in-memory for testing)
dbCfg := &config.DatabaseConfig{
File: ":memory:",
MaxOpenConnections: 10,
MaxIdleConnections: 5,
}
db, err := database.NewDatabase(dbCfg, log)
require.NoError(t, err, "failed to create database")
defer db.Close()
// Create contract executor
contractExecutor, err := contracts.NewContractExecutor(cfg, log)
require.NoError(t, err, "failed to create contract executor")
defer contractExecutor.Close()
// Create market scanner
scanner := scanner.NewMarketScanner(&cfg.Bot, log, contractExecutor, db)
// Create MEV coordinator
coordinator := orchestrator.NewMEVCoordinator(cfg, log, marketMgr, scanner, db)
// Create Arbitrum monitor
monitor, err := monitor.NewArbitrumMonitor(
&cfg.Arbitrum,
&cfg.Bot,
log,
rateLimiter,
marketMgr,
scanner,
coordinator,
)
require.NoError(t, err, "failed to create Arbitrum monitor")
// Test the full pipeline
t.Run("TestArbitrageDetection", func(t *testing.T) {
testArbitrageDetection(t, client, monitor, scanner, marketMgr, log)
})
t.Run("TestPoolDiscovery", func(t *testing.T) {
testPoolDiscovery(t, client, marketMgr, log)
})
t.Run("TestArbitrageExecution", func(t *testing.T) {
testArbitrageExecution(t, client, contractExecutor, log)
})
// Cleanup
monitor.Stop()
scanner.Stop()
coordinator.Stop()
}
// createTestConfig creates a test configuration for the integration tests
func createTestConfig() *config.Config {
return &config.Config{
Arbitrum: config.ArbitrumConfig{
RPCEndpoint: "http://localhost:8545", // Anvil default port
WSEndpoint: "",
ChainID: 31337, // Anvil default chain ID
RateLimit: config.RateLimitConfig{
RequestsPerSecond: 10,
MaxConcurrent: 5,
Burst: 20,
},
FallbackEndpoints: []config.EndpointConfig{},
},
Bot: config.BotConfig{
Enabled: true,
PollingInterval: 1,
MinProfitThreshold: 0.01, // Lower threshold for testing
GasPriceMultiplier: 1.2,
MaxWorkers: 2,
ChannelBufferSize: 10,
RPCTimeout: 30,
},
Uniswap: config.UniswapConfig{
FactoryAddress: "0x1F98431c8aD98523631AE4a59f267346ea31F984",
PositionManagerAddress: "0xC36442b4a4522E871399CD717aBDD847Ab11FE88",
FeeTiers: []int64{500, 3000, 10000},
Cache: config.CacheConfig{
Enabled: true,
Expiration: 300,
MaxSize: 10000,
},
},
Log: config.LogConfig{
Level: "debug",
Format: "text",
File: "",
},
Database: config.DatabaseConfig{
File: ":memory:",
MaxOpenConnections: 10,
MaxIdleConnections: 5,
},
Ethereum: config.EthereumConfig{
PrivateKey: "", // Will be set by environment or test setup
AccountAddress: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", // Default Anvil account
GasPriceMultiplier: 1.2,
},
Contracts: config.ContractsConfig{
ArbitrageExecutor: "0x...", // Will be deployed during test setup
FlashSwapper: "0x...", // Will be deployed during test setup
AuthorizedCallers: []string{
"0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", // Default Anvil account
},
AuthorizedDEXes: []string{
"0x1F98431c8aD98523631AE4a59f267346ea31F984", // Uniswap V3 Factory
"0xf1D7CC64Fb4452F05c498126312eBE29f30Fbcf9", // Uniswap V2 Factory
"0xc35DADB65012eC5796536bD9864eD8773aBc74C4", // SushiSwap Factory
},
},
}
}
// testArbitrageDetection tests arbitrage opportunity detection
func testArbitrageDetection(t *testing.T, client *ethclient.Client, monitor *monitor.ArbitrumMonitor, scanner *scanner.MarketScanner, marketMgr *market.MarketManager, log *logger.Logger) {
log.Info("Testing arbitrage detection...")
// Create context with timeout
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
// Start monitoring in a goroutine
monitorDone := make(chan error, 1)
go func() {
monitorDone <- monitor.Start(ctx)
}()
// Let the monitor run for a bit to detect events
time.Sleep(5 * time.Second)
// Check if any arbitrage opportunities were detected
// In a real test, we would simulate price movements to create opportunities
log.Info("Arbitrage detection test completed")
}
// testPoolDiscovery tests pool discovery functionality
func testPoolDiscovery(t *testing.T, client *ethclient.Client, marketMgr *market.MarketManager, log *logger.Logger) {
log.Info("Testing pool discovery...")
// Test discovering pools for common token pairs
knownPairs := []struct {
token0 common.Address
token1 common.Address
}{
{
token0: common.HexToAddress("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"), // USDC
token1: common.HexToAddress("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"), // WETH
},
{
token0: common.HexToAddress("0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8"), // USDT
token1: common.HexToAddress("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"), // WETH
},
}
// Create CREATE2 calculator for pool discovery
calculator := pools.NewCREATE2Calculator(log)
// Test pool discovery for each pair
for _, pair := range knownPairs {
log.Info(fmt.Sprintf("Discovering pools for %s-%s", pair.token0.Hex(), pair.token1.Hex()))
// Use CREATE2 calculator to find potential pools
pools, err := calculator.FindPoolsForTokenPair(pair.token0, pair.token1)
if err != nil {
log.Warn(fmt.Sprintf("Failed to discover pools for %s-%s: %v", pair.token0.Hex(), pair.token1.Hex(), err))
continue
}
log.Info(fmt.Sprintf("Found %d potential pools for %s-%s", len(pools), pair.token0.Hex(), pair.token1.Hex()))
// Validate each pool
for _, pool := range pools {
log.Debug(fmt.Sprintf("Validating pool: %s (factory: %s)", pool.PoolAddr.Hex(), pool.Factory))
// In a real implementation, we would validate that the pool actually exists
// and has liquidity. For now, we just log the discovery.
}
}
log.Info("Pool discovery test completed")
}
// testArbitrageExecution tests arbitrage execution functionality
func testArbitrageExecution(t *testing.T, client *ethclient.Client, contractExecutor *contracts.ContractExecutor, log *logger.Logger) {
log.Info("Testing arbitrage execution...")
// Create a mock arbitrage opportunity for testing
mockOpportunity := scanner.ArbitrageOpportunity{
Path: []string{
"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", // USDC
"0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", // WETH
},
Pools: []string{
"0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640", // Known USDC/WETH pool
},
Profit: big.NewInt(1000000000000000000), // 1 ETH profit estimate
GasEstimate: big.NewInt(300000), // Estimated gas cost
ROI: 5.0, // 5% ROI
Protocol: "UniswapV3",
}
// Test execution (this will fail in testing but we can verify the setup)
log.Info("Setting up arbitrage execution test...")
// In a real test, we would:
// 1. Deploy the contracts to the forked environment
// 2. Fund the test account with tokens
// 3. Create actual arbitrage opportunities by manipulating pool states
// 4. Execute the arbitrage and verify profits
log.Info("Arbitrage execution test setup completed")
}
// TestContractBindings tests that all contract bindings are working correctly
func TestContractBindings(t *testing.T) {
// Skip this test in short mode
if testing.Short() {
t.Skip("skipping contract binding test in short mode")
}
log := logger.New("debug", "text", "")
cfg := createTestConfig()
// Connect to the forked environment
client, err := ethclient.Dial(cfg.Arbitrum.RPCEndpoint)
require.NoError(t, err, "failed to connect to forked Arbitrum")
defer client.Close()
// Test contract executor creation
contractExecutor, err := contracts.NewContractExecutor(cfg, log)
require.NoError(t, err, "failed to create contract executor")
defer contractExecutor.Close()
// Verify contract executor was created successfully
assert.NotNil(t, contractExecutor)
assert.NotNil(t, contractExecutor.Client())
log.Info("Contract bindings test completed successfully")
}
// TestDatabaseIntegration tests database integration with the scanner
func TestDatabaseIntegration(t *testing.T) {
// Skip this test in short mode
if testing.Short() {
t.Skip("skipping database integration test in short mode")
}
log := logger.New("debug", "text", "")
// Create in-memory database for testing
dbCfg := &config.DatabaseConfig{
File: ":memory:",
MaxOpenConnections: 10,
MaxIdleConnections: 5,
}
db, err := database.NewDatabase(dbCfg, log)
require.NoError(t, err, "failed to create database")
defer db.Close()
// Test inserting swap event
swapEvent := &database.SwapEvent{
Timestamp: time.Now(),
BlockNumber: 12345678,
TxHash: common.HexToHash("0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"),
PoolAddress: common.HexToAddress("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640"),
Token0: common.HexToAddress("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"), // USDC
Token1: common.HexToAddress("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"), // WETH
Amount0In: big.NewInt(1000000000), // 1000 USDC
Amount1In: big.NewInt(0),
Amount0Out: big.NewInt(0),
Amount1Out: big.NewInt(500000000000000000), // 0.5 WETH
Sender: common.HexToAddress("0x1234567890abcdef1234567890abcdef12345678"),
Recipient: common.HexToAddress("0x8765432109fedcba8765432109fedcba87654321"),
Protocol: "uniswap_v3",
}
err = db.InsertSwapEvent(swapEvent)
assert.NoError(t, err, "failed to insert swap event")
// Test inserting liquidity event
liquidityEvent := &database.LiquidityEvent{
Timestamp: time.Now(),
BlockNumber: 12345679,
TxHash: common.HexToHash("0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890"),
PoolAddress: common.HexToAddress("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640"),
Token0: common.HexToAddress("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"), // USDC
Token1: common.HexToAddress("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"), // WETH
Liquidity: big.NewInt(1000000000000000000), // 1 ETH equivalent
Amount0: big.NewInt(2000000000), // 2000 USDC
Amount1: big.NewInt(1000000000000000000), // 1 WETH
Sender: common.HexToAddress("0x1234567890abcdef1234567890abcdef12345678"),
Recipient: common.HexToAddress("0x8765432109fedcba8765432109fedcba87654321"),
EventType: "add",
Protocol: "uniswap_v3",
}
err = db.InsertLiquidityEvent(liquidityEvent)
assert.NoError(t, err, "failed to insert liquidity event")
// Test inserting pool data
poolData := &database.PoolData{
Address: common.HexToAddress("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640"),
Token0: common.HexToAddress("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"), // USDC
Token1: common.HexToAddress("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"), // WETH
Fee: 3000, // 0.3%
Liquidity: big.NewInt(1000000000000000000), // 1 ETH equivalent
SqrtPriceX96: big.NewInt(2505414483750470000), // Realistic price
Tick: 200000, // Corresponding tick
LastUpdated: time.Now(),
Protocol: "uniswap_v3",
}
err = db.InsertPoolData(poolData)
assert.NoError(t, err, "failed to insert pool data")
// Test retrieving recent swap events
swaps, err := db.GetRecentSwapEvents(10)
assert.NoError(t, err, "failed to get recent swap events")
assert.Len(t, swaps, 1, "expected 1 swap event")
if len(swaps) > 0 {
assert.Equal(t, swapEvent.PoolAddress, swaps[0].PoolAddress, "pool address mismatch")
assert.Equal(t, swapEvent.Token0, swaps[0].Token0, "token0 mismatch")
assert.Equal(t, swapEvent.Token1, swaps[0].Token1, "token1 mismatch")
assert.Equal(t, swapEvent.Protocol, swaps[0].Protocol, "protocol mismatch")
}
// Test retrieving recent liquidity events
liquidityEvents, err := db.GetRecentLiquidityEvents(10)
assert.NoError(t, err, "failed to get recent liquidity events")
assert.Len(t, liquidityEvents, 1, "expected 1 liquidity event")
if len(liquidityEvents) > 0 {
assert.Equal(t, liquidityEvent.PoolAddress, liquidityEvents[0].PoolAddress, "pool address mismatch")
assert.Equal(t, liquidityEvent.Token0, liquidityEvents[0].Token0, "token0 mismatch")
assert.Equal(t, liquidityEvent.Token1, liquidityEvents[0].Token1, "token1 mismatch")
assert.Equal(t, liquidityEvent.EventType, liquidityEvents[0].EventType, "event type mismatch")
assert.Equal(t, liquidityEvent.Protocol, liquidityEvents[0].Protocol, "protocol mismatch")
}
// Test retrieving pool data
retrievedPool, err := db.GetPoolData(poolData.Address)
assert.NoError(t, err, "failed to get pool data")
assert.Equal(t, poolData.Address, retrievedPool.Address, "pool address mismatch")
assert.Equal(t, poolData.Token0, retrievedPool.Token0, "token0 mismatch")
assert.Equal(t, poolData.Token1, retrievedPool.Token1, "token1 mismatch")
assert.Equal(t, poolData.Fee, retrievedPool.Fee, "fee mismatch")
assert.Equal(t, poolData.Protocol, retrievedPool.Protocol, "protocol mismatch")
log.Info("Database integration test completed successfully")
}

View File

@@ -0,0 +1,180 @@
//go:build integration && legacy && forked
// +build integration,legacy,forked
// Package integration provides integration tests for the MEV bot using a forked Arbitrum environment
package integration
import (
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/fraktal/mev-beta/internal/logger"
"github.com/fraktal/mev-beta/pkg/pools"
)
// TestPoolDiscovery tests that the pool discovery mechanism works correctly
func TestPoolDiscovery(t *testing.T) {
// Skip this test in short mode
if testing.Short() {
t.Skip("skipping pool discovery test in short mode")
}
// Create test logger
log := logger.New("debug", "text", "")
// Create CREATE2 calculator
calculator := pools.NewCREATE2Calculator(log)
// Test discovering pools for common token pairs on Arbitrum
testCases := []struct {
name string
token0 string
token1 string
expected int
}{
{
name: "USDC/WETH",
token0: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", // USDC
token1: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1", // WETH
expected: 3, // Uniswap V3, SushiSwap, Camelot V3
},
{
name: "USDT/WETH",
token0: "0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9", // USDT
token1: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1", // WETH
expected: 3, // Uniswap V3, SushiSwap, Camelot V3
},
{
name: "ARB/WETH",
token0: "0x912CE59144191C1204E64559FE8253a0e49E6548", // ARB
token1: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1", // WETH
expected: 3, // Uniswap V3, SushiSwap, Camelot V3
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
// Convert hex addresses to common.Address
token0 := common.HexToAddress(tc.token0)
token1 := common.HexToAddress(tc.token1)
// Discover pools for this token pair
pools, err := calculator.FindPoolsForTokenPair(token0, token1)
require.NoError(t, err, "failed to discover pools for token pair")
// Verify we found the expected number of pools
assert.GreaterOrEqual(t, len(pools), tc.expected, "should find at least %d pools for %s", tc.expected, tc.name)
// Verify each pool has valid data
for i, pool := range pools {
assert.NotEqual(t, "", pool.Factory, "pool %d should have a factory name", i)
assert.NotEqual(t, common.Address{}, pool.Token0, "pool %d should have token0", i)
assert.NotEqual(t, common.Address{}, pool.Token1, "pool %d should have token1", i)
assert.NotEqual(t, common.Address{}, pool.PoolAddr, "pool %d should have a pool address", i)
assert.True(t, pool.Fee >= 0, "pool %d should have a non-negative fee", i)
log.Info("Discovered pool:", pool.Factory, pool.PoolAddr.Hex())
}
log.Info("Found", len(pools), "pools for", tc.name)
})
}
}
// TestFactoryConfigurations tests that all factory configurations are properly loaded
func TestFactoryConfigurations(t *testing.T) {
// Create test logger
log := logger.New("debug", "text", "")
// Create CREATE2 calculator
calculator := pools.NewCREATE2Calculator(log)
// Get list of all factories
factories := calculator.ListFactories()
assert.Greater(t, len(factories), 0, "should have at least one factory configured")
// Verify each factory has valid configuration
for _, factoryName := range factories {
t.Run(factoryName, func(t *testing.T) {
config, err := calculator.GetFactoryConfig(factoryName)
assert.NoError(t, err, "should be able to get config for factory %s", factoryName)
assert.NotNil(t, config, "config should not be nil for factory %s", factoryName)
assert.NotEqual(t, common.Address{}, config.Address, "factory %s should have a valid address", factoryName)
assert.NotEqual(t, common.Hash{}, config.InitCodeHash, "factory %s should have a valid init code hash", factoryName)
assert.Greater(t, len(config.FeeStructure.DefaultFees), 0, "factory %s should have default fees", factoryName)
})
}
log.Info("Verified", len(factories), "factories")
}
// TestPoolAddressCalculation tests that pool addresses are calculated correctly
func TestPoolAddressCalculation(t *testing.T) {
// Create test logger
log := logger.New("debug", "text", "")
// Create CREATE2 calculator
calculator := pools.NewCREATE2Calculator(log)
// Test known pool addresses on Arbitrum
testCases := []struct {
name string
factory string
token0 string
token1 string
fee uint32
expectedAddr string
}{
{
name: "Uniswap V3 USDC/WETH 0.05%",
factory: "uniswap_v3",
token0: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", // USDC
token1: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1", // WETH
fee: 500, // 0.05%
expectedAddr: "0xC6962004f452bE9203591991D15f6b388e09E8D0",
},
{
name: "Uniswap V3 USDC/WETH 0.3%",
factory: "uniswap_v3",
token0: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", // USDC
token1: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1", // WETH
fee: 3000, // 0.3%
expectedAddr: "0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640",
},
{
name: "Uniswap V3 USDC/WETH 1%",
factory: "uniswap_v3",
token0: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", // USDC
token1: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1", // WETH
fee: 10000, // 1%
expectedAddr: "0x7f90122BF0700F9E7e1F688fe926940E8839F353",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
// Convert hex addresses to common.Address
token0 := common.HexToAddress(tc.token0)
token1 := common.HexToAddress(tc.token1)
expectedAddr := common.HexToAddress(tc.expectedAddr)
// Calculate pool address
calculatedAddr, err := calculator.CalculatePoolAddress(tc.factory, token0, token1, tc.fee)
require.NoError(t, err, "should be able to calculate pool address")
// Verify the calculated address matches expected (this may fail for placeholder addresses)
if tc.expectedAddr != "0x0000000000000000000000000000000000000000" {
// For now, just log the addresses for verification
log.Info("Calculated pool address:", calculatedAddr.Hex())
log.Info("Expected pool address: ", expectedAddr.Hex())
log.Info("Match:", calculatedAddr == expectedAddr)
} else {
// Just verify the address is not zero
assert.NotEqual(t, common.Address{}, calculatedAddr, "calculated address should not be zero")
}
})
}
}