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:
327
orig/test/arbitrage_fork_test.go
Normal file
327
orig/test/arbitrage_fork_test.go
Normal file
@@ -0,0 +1,327 @@
|
||||
//go:build integration && forked
|
||||
// +build integration,forked
|
||||
|
||||
package test_main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"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"
|
||||
)
|
||||
|
||||
// TestArbitrageExecutionWithFork tests arbitrage execution using forked Arbitrum
|
||||
func TestArbitrageExecutionWithFork(t *testing.T) {
|
||||
// Skip if not running with fork
|
||||
if os.Getenv("TEST_WITH_FORK") != "true" {
|
||||
t.Skip("Skipping fork test. Set TEST_WITH_FORK=true to run")
|
||||
}
|
||||
|
||||
// Set up test environment
|
||||
os.Setenv("MEV_BOT_ENCRYPTION_KEY", "fork_integration_key_32_chars_length__")
|
||||
os.Setenv("MEV_BOT_ALLOW_LOCALHOST", "true")
|
||||
defer func() {
|
||||
os.Unsetenv("MEV_BOT_ENCRYPTION_KEY")
|
||||
os.Unsetenv("MEV_BOT_ALLOW_LOCALHOST")
|
||||
}()
|
||||
|
||||
// Connect to forked network
|
||||
rpcURL := "http://localhost:8545" // Anvil fork URL
|
||||
client, err := ethclient.Dial(rpcURL)
|
||||
require.NoError(t, err, "Failed to connect to forked network")
|
||||
defer client.Close()
|
||||
|
||||
// Verify we're connected to Arbitrum fork
|
||||
chainID, err := client.ChainID(context.Background())
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(42161), chainID.Int64(), "Should be connected to Arbitrum (chain ID 42161)")
|
||||
|
||||
t.Run("TestFlashSwapExecution", func(t *testing.T) {
|
||||
log := logger.New("debug", "text", "")
|
||||
|
||||
// Create secure key manager
|
||||
keyManagerConfig := &security.KeyManagerConfig{
|
||||
KeystorePath: "test_keystore_fork",
|
||||
EncryptionKey: os.Getenv("MEV_BOT_ENCRYPTION_KEY"),
|
||||
KeyRotationDays: 30,
|
||||
MaxSigningRate: 100,
|
||||
SessionTimeout: time.Hour,
|
||||
AuditLogPath: "test_audit_fork.log",
|
||||
BackupPath: "test_backups_fork",
|
||||
}
|
||||
|
||||
keyManager, err := security.NewKeyManager(keyManagerConfig, log)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create arbitrage configuration
|
||||
arbitrageConfig := &config.ArbitrageConfig{
|
||||
Enabled: true,
|
||||
ArbitrageContractAddress: "0x0000000000000000000000000000000000000001",
|
||||
FlashSwapContractAddress: "0x0000000000000000000000000000000000000002",
|
||||
MaxConcurrentExecutions: 1,
|
||||
MinProfitWei: 1_000_000_000_000_000, // 0.001 ETH
|
||||
MaxGasPriceWei: 50 * 1_000_000_000, // 50 gwei
|
||||
SlippageTolerance: 0.01, // 1%
|
||||
MinScanAmountWei: 100_000_000_000_000, // 0.0001 ETH baseline
|
||||
MaxScanAmountWei: 10_000_000_000_000_000_000, // 10 ETH cap
|
||||
MinSignificantSwapSize: 100_000_000_000_000, // 0.0001 ETH significance
|
||||
MaxOpportunitiesPerEvent: 5,
|
||||
OpportunityTTL: 5 * time.Second,
|
||||
}
|
||||
|
||||
// Create arbitrage database
|
||||
db, err := arbitrage.NewSQLiteDatabase(":memory:", log)
|
||||
require.NoError(t, err)
|
||||
defer db.Close()
|
||||
|
||||
// Create arbitrage executor
|
||||
executor, err := arbitrage.NewArbitrageExecutor(
|
||||
client,
|
||||
log,
|
||||
keyManager,
|
||||
common.HexToAddress(arbitrageConfig.ArbitrageContractAddress),
|
||||
common.HexToAddress(arbitrageConfig.FlashSwapContractAddress),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Test flash swap execution with real Arbitrum addresses
|
||||
testFlashSwap(t, executor, log)
|
||||
|
||||
// Clean up test files
|
||||
os.RemoveAll("test_keystore_fork")
|
||||
os.Remove("test_audit_fork.log")
|
||||
os.RemoveAll("test_backups_fork")
|
||||
})
|
||||
}
|
||||
|
||||
func testFlashSwap(t *testing.T, executor *arbitrage.ArbitrageExecutor, log *logger.Logger) {
|
||||
// Use real Arbitrum token addresses from our configuration
|
||||
wethAddress := common.HexToAddress("0x82af49447d8a07e3bd95bd0d56f35241523fbab1") // WETH
|
||||
usdcAddress := common.HexToAddress("0xaf88d065e77c8cc2239327c5edb3a432268e5831") // USDC
|
||||
|
||||
// Use Uniswap V3 WETH/USDC pool (0.05% fee tier)
|
||||
// This is a real pool address on Arbitrum
|
||||
poolAddress := common.HexToAddress("0xC31E54c7a869B9FcBEcc14363CF510d1c41fa443")
|
||||
|
||||
// Create flash swap parameters
|
||||
params := &arbitrage.FlashSwapParams{
|
||||
TokenPath: []common.Address{wethAddress, usdcAddress},
|
||||
PoolPath: []common.Address{poolAddress},
|
||||
AmountIn: big.NewInt(100000000000000000), // 0.1 WETH
|
||||
MinAmountOut: big.NewInt(150000000), // ~150 USDC (min expected)
|
||||
}
|
||||
|
||||
log.Info("Testing flash swap execution with real Arbitrum pool...")
|
||||
log.Debug("Flash swap params:", "weth", wethAddress.Hex(), "usdc", usdcAddress.Hex(), "pool", poolAddress.Hex())
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// Attempt to execute flash swap
|
||||
tx, err := executor.ExecuteFlashSwap(ctx, params)
|
||||
|
||||
// Note: This test will likely fail because we don't have the proper callback contract deployed
|
||||
// But it should at least validate that our code can construct the transaction properly
|
||||
if err != nil {
|
||||
log.Warn("Flash swap execution failed (expected without callback contract):", "error", err.Error())
|
||||
|
||||
// Check if error is due to missing callback contract (expected)
|
||||
if isCallbackError(err) {
|
||||
log.Info("✅ Flash swap construction successful - failure due to missing callback contract (expected)")
|
||||
return
|
||||
}
|
||||
|
||||
// If it's a different error, that's unexpected
|
||||
t.Logf("⚠️ Unexpected error (not callback-related): %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// If we got here, the transaction was successfully created
|
||||
assert.NotNil(t, tx, "Transaction should not be nil")
|
||||
log.Info("✅ Flash swap transaction created successfully:", "txHash", tx.Hash().Hex())
|
||||
}
|
||||
|
||||
// isCallbackError checks if the error is related to missing callback contract
|
||||
func isCallbackError(err error) bool {
|
||||
errorStr := err.Error()
|
||||
callbackErrors := []string{
|
||||
"callback",
|
||||
"revert",
|
||||
"execution reverted",
|
||||
"invalid callback",
|
||||
"unauthorized",
|
||||
}
|
||||
|
||||
for _, callbackErr := range callbackErrors {
|
||||
if contains(errorStr, callbackErr) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func contains(s, substr string) bool {
|
||||
return len(s) >= len(substr) && (s == substr || (len(s) > len(substr) &&
|
||||
(s[:len(substr)] == substr || s[len(s)-len(substr):] == substr ||
|
||||
indexOf(s, substr) >= 0)))
|
||||
}
|
||||
|
||||
func indexOf(s, substr string) int {
|
||||
for i := 0; i <= len(s)-len(substr); i++ {
|
||||
if s[i:i+len(substr)] == substr {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// TestPoolDiscoveryWithFork tests pool discovery using forked network
|
||||
func TestPoolDiscoveryWithFork(t *testing.T) {
|
||||
// Skip if not running with fork
|
||||
if os.Getenv("TEST_WITH_FORK") != "true" {
|
||||
t.Skip("Skipping fork test. Set TEST_WITH_FORK=true to run")
|
||||
}
|
||||
|
||||
// Connect to forked network
|
||||
rpcURL := "http://localhost:8545"
|
||||
client, err := ethclient.Dial(rpcURL)
|
||||
require.NoError(t, err)
|
||||
defer client.Close()
|
||||
|
||||
t.Run("TestUniswapV3PoolQuery", func(t *testing.T) {
|
||||
log := logger.New("debug", "text", "")
|
||||
|
||||
// Test querying real Uniswap V3 pool data
|
||||
wethUsdcPool := common.HexToAddress("0xC31E54c7a869B9FcBEcc14363CF510d1c41fa443")
|
||||
|
||||
// Try to get pool state (this tests our connection to real contracts)
|
||||
ctx := context.Background()
|
||||
|
||||
// Call a simple view function to verify pool exists
|
||||
code, err := client.CodeAt(ctx, wethUsdcPool, nil)
|
||||
require.NoError(t, err)
|
||||
assert.True(t, len(code) > 0, "Pool contract should exist and have code")
|
||||
|
||||
log.Info("✅ Successfully connected to real Uniswap V3 pool on forked network")
|
||||
log.Debug("Pool details:", "address", wethUsdcPool.Hex(), "codeSize", len(code))
|
||||
})
|
||||
}
|
||||
|
||||
// TestRealTokenBalances tests querying real token balances on fork
|
||||
func TestRealTokenBalances(t *testing.T) {
|
||||
// Skip if not running with fork
|
||||
if os.Getenv("TEST_WITH_FORK") != "true" {
|
||||
t.Skip("Skipping fork test. Set TEST_WITH_FORK=true to run")
|
||||
}
|
||||
|
||||
// Connect to forked network
|
||||
rpcURL := "http://localhost:8545"
|
||||
client, err := ethclient.Dial(rpcURL)
|
||||
require.NoError(t, err)
|
||||
defer client.Close()
|
||||
|
||||
t.Run("TestETHBalance", func(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
// Get accounts from anvil (funded accounts)
|
||||
accounts, err := client.PendingBalanceAt(ctx, common.HexToAddress("0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"))
|
||||
if err != nil {
|
||||
// Try a different method - just check that we can make RPC calls
|
||||
latestBlock, err := client.BlockNumber(ctx)
|
||||
require.NoError(t, err)
|
||||
assert.Greater(t, latestBlock, uint64(0), "Should be able to query block number")
|
||||
return
|
||||
}
|
||||
|
||||
assert.True(t, accounts.Cmp(big.NewInt(0)) > 0, "Test account should have ETH balance")
|
||||
})
|
||||
}
|
||||
|
||||
// TestArbitrageServiceWithFork tests the complete arbitrage service with fork
|
||||
func TestArbitrageServiceWithFork(t *testing.T) {
|
||||
// Skip if not running with fork
|
||||
if os.Getenv("TEST_WITH_FORK") != "true" {
|
||||
t.Skip("Skipping fork test. Set TEST_WITH_FORK=true to run")
|
||||
}
|
||||
|
||||
// Set up test environment
|
||||
os.Setenv("MEV_BOT_ENCRYPTION_KEY", "test-fork-service-key-32-chars")
|
||||
os.Setenv("MEV_BOT_ALLOW_LOCALHOST", "true")
|
||||
defer func() {
|
||||
os.Unsetenv("MEV_BOT_ENCRYPTION_KEY")
|
||||
os.Unsetenv("MEV_BOT_ALLOW_LOCALHOST")
|
||||
}()
|
||||
|
||||
// Connect to forked network
|
||||
rpcURL := "http://localhost:8545"
|
||||
client, err := ethclient.Dial(rpcURL)
|
||||
require.NoError(t, err)
|
||||
defer client.Close()
|
||||
|
||||
t.Run("TestServiceInitialization", func(t *testing.T) {
|
||||
log := logger.New("debug", "text", "")
|
||||
|
||||
// Create secure key manager
|
||||
keyManagerConfig := &security.KeyManagerConfig{
|
||||
KeystorePath: "test_keystore_service",
|
||||
EncryptionKey: os.Getenv("MEV_BOT_ENCRYPTION_KEY"),
|
||||
KeyRotationDays: 30,
|
||||
MaxSigningRate: 100,
|
||||
SessionTimeout: time.Hour,
|
||||
AuditLogPath: "test_audit_service.log",
|
||||
BackupPath: "test_backups_service",
|
||||
}
|
||||
|
||||
keyManager, err := security.NewKeyManager(keyManagerConfig, log)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create arbitrage configuration
|
||||
cfg := &config.ArbitrageConfig{
|
||||
Enabled: true,
|
||||
ArbitrageContractAddress: "0x0000000000000000000000000000000000000001",
|
||||
FlashSwapContractAddress: "0x0000000000000000000000000000000000000002",
|
||||
MaxConcurrentExecutions: 1,
|
||||
MinProfitWei: 1_000_000_000_000_000, // 0.001 ETH
|
||||
MaxGasPriceWei: 50 * 1_000_000_000, // 50 gwei
|
||||
SlippageTolerance: 0.01,
|
||||
MinScanAmountWei: 100_000_000_000_000,
|
||||
MaxScanAmountWei: 10_000_000_000_000_000_000,
|
||||
MinSignificantSwapSize: 100_000_000_000_000,
|
||||
MaxOpportunitiesPerEvent: 5,
|
||||
OpportunityTTL: 5 * time.Second,
|
||||
}
|
||||
|
||||
// Create arbitrage database
|
||||
db, err := arbitrage.NewSQLiteDatabase(":memory:", log)
|
||||
require.NoError(t, err)
|
||||
defer db.Close()
|
||||
|
||||
// Create arbitrage service
|
||||
service, err := arbitrage.NewArbitrageService(context.Background(), client, log, cfg, keyManager, db)
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, service)
|
||||
|
||||
log.Info("✅ Arbitrage service initialized successfully with forked network")
|
||||
|
||||
// Test service can get stats
|
||||
stats := service.GetStats()
|
||||
assert.NotNil(t, stats)
|
||||
assert.Equal(t, uint64(0), stats.TotalOpportunitiesDetected)
|
||||
|
||||
// Clean up test files
|
||||
os.RemoveAll("test_keystore_service")
|
||||
os.Remove("test_audit_service.log")
|
||||
os.RemoveAll("test_backups_service")
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user