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:
412
orig/tests/integration/full_pipeline_test.go
Normal file
412
orig/tests/integration/full_pipeline_test.go
Normal 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")
|
||||
}
|
||||
Reference in New Issue
Block a user