feat(core): implement core MEV bot functionality with market scanning and Uniswap V3 pricing

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
Krypto Kajun
2025-09-14 10:16:29 -05:00
parent 5db7587923
commit c16182d80c
1364 changed files with 473970 additions and 1202 deletions

View File

@@ -12,7 +12,7 @@ import (
func BenchmarkSqrtPriceX96ToPrice(b *testing.B) {
sqrtPriceX96 := new(big.Int)
sqrtPriceX96.SetString("79228162514264337593543950336", 10) // 2^96
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = uniswap.SqrtPriceX96ToPrice(sqrtPriceX96)
@@ -22,7 +22,7 @@ func BenchmarkSqrtPriceX96ToPrice(b *testing.B) {
// BenchmarkPriceToSqrtPriceX96 benchmarks the PriceToSqrtPriceX96 function
func BenchmarkPriceToSqrtPriceX96(b *testing.B) {
price := new(big.Float).SetFloat64(1.0)
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = uniswap.PriceToSqrtPriceX96(price)
@@ -41,7 +41,7 @@ func BenchmarkTickToSqrtPriceX96(b *testing.B) {
func BenchmarkSqrtPriceX96ToTick(b *testing.B) {
sqrtPriceX96 := new(big.Int)
sqrtPriceX96.SetString("79228162514264337593543950336", 10) // 2^96
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = uniswap.SqrtPriceX96ToTick(sqrtPriceX96)
@@ -51,7 +51,7 @@ func BenchmarkSqrtPriceX96ToTick(b *testing.B) {
// BenchmarkPricingConversionsSequential benchmarks sequential pricing conversions
func BenchmarkPricingConversionsSequential(b *testing.B) {
price := new(big.Float).SetFloat64(1.0)
b.ResetTimer()
for i := 0; i < b.N; i++ {
sqrtPriceX96 := uniswap.PriceToSqrtPriceX96(price)
@@ -69,22 +69,22 @@ func BenchmarkPricingCalculationRealistic(b *testing.B) {
}{
{"ETH_USDC_1800", "2231455953840924584200896000"}, // ~1800 USDC per ETH
{"ETH_USDC_3000", "2890903041336652768307200000"}, // ~3000 USDC per ETH
{"WBTC_ETH_15", "977228162514264337593543950"}, // ~15 ETH per WBTC
{"WBTC_ETH_15", "977228162514264337593543950"}, // ~15 ETH per WBTC
{"DAI_USDC_1", "79228162514264337593543950336"}, // ~1 DAI per USDC
}
for _, tc := range testCases {
b.Run(tc.name, func(b *testing.B) {
sqrtPriceX96 := new(big.Int)
sqrtPriceX96.SetString(tc.sqrtPriceX96, 10)
b.ResetTimer()
for i := 0; i < b.N; i++ {
price := uniswap.SqrtPriceX96ToPrice(sqrtPriceX96)
tick := uniswap.SqrtPriceX96ToTick(sqrtPriceX96)
backToSqrt := uniswap.TickToSqrtPriceX96(tick)
_ = uniswap.SqrtPriceX96ToPrice(backToSqrt)
// Verify we get similar price back (within reasonable precision)
require.NotNil(b, price)
}
@@ -104,11 +104,11 @@ func BenchmarkExtremePriceValues(b *testing.B) {
{"High_100.0", 100.0},
{"VeryHigh_1000000.0", 1000000.0},
}
for _, tc := range extremeCases {
b.Run(tc.name, func(b *testing.B) {
price := new(big.Float).SetFloat64(tc.price)
b.ResetTimer()
for i := 0; i < b.N; i++ {
sqrtPriceX96 := uniswap.PriceToSqrtPriceX96(price)
@@ -126,32 +126,32 @@ func BenchmarkBigIntOperations(b *testing.B) {
x := big.NewInt(1000000)
y := big.NewInt(2000000)
result := new(big.Int)
b.ResetTimer()
for i := 0; i < b.N; i++ {
result.Mul(x, y)
}
})
b.Run("BigInt_Division", func(b *testing.B) {
x := big.NewInt(1000000000000)
y := big.NewInt(1000000)
result := new(big.Int)
b.ResetTimer()
for i := 0; i < b.N; i++ {
result.Div(x, y)
}
})
b.Run("BigFloat_Operations", func(b *testing.B) {
x := big.NewFloat(1000000.5)
y := big.NewFloat(2000000.3)
result := new(big.Float)
b.ResetTimer()
for i := 0; i < b.N; i++ {
result.Mul(x, y)
}
})
}
}

View File

@@ -6,14 +6,14 @@ import (
"testing"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"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/market"
"github.com/fraktal/mev-beta/pkg/monitor"
"github.com/fraktal/mev-beta/pkg/scanner"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/stretchr/testify/assert"
)
@@ -128,4 +128,4 @@ func TestConfigurationLoading(t *testing.T) {
assert.Equal(t, "0x1F98431c8aD98523631AE4a59f267346ea31F984", cfg.Uniswap.FactoryAddress)
assert.Equal(t, "info", cfg.Log.Level)
assert.Equal(t, "mev-bot.db", cfg.Database.File)
}
}

View File

@@ -38,7 +38,7 @@ func FuzzPricingConversions(f *testing.F) {
// Convert price to sqrtPriceX96 and back
priceBigFloat := new(big.Float).SetFloat64(price)
sqrtPriceX96 := uniswap.PriceToSqrtPriceX96(priceBigFloat)
// Verify sqrtPriceX96 is positive
require.True(t, sqrtPriceX96.Sign() > 0, "sqrtPriceX96 must be positive")
@@ -50,7 +50,7 @@ func FuzzPricingConversions(f *testing.F) {
require.True(t, convertedPriceFloat > 0, "Converted price must be positive")
// Check round-trip consistency (allow some tolerance for floating point precision)
tolerance := 0.01 // 1% tolerance
tolerance := 0.01 // 1% tolerance
if price > 0.01 && price < 100000 { // For reasonable price ranges
relativeError := abs(price-convertedPriceFloat) / price
assert.True(t, relativeError < tolerance,
@@ -77,7 +77,7 @@ func FuzzTickConversions(f *testing.F) {
// Convert tick to sqrtPriceX96 and back
sqrtPriceX96 := uniswap.TickToSqrtPriceX96(tick)
// Verify sqrtPriceX96 is positive
require.True(t, sqrtPriceX96.Sign() > 0, "sqrtPriceX96 must be positive")
@@ -94,11 +94,11 @@ func FuzzTickConversions(f *testing.F) {
// FuzzSqrtPriceX96Operations performs fuzz testing on sqrtPriceX96 operations
func FuzzSqrtPriceX96Operations(f *testing.F) {
// Add seed values for fuzzing
f.Add("79228162514264337593543950336") // 2^96 (price = 1)
f.Add("158456325028528675187087900672") // 2 * 2^96 (price = 4)
f.Add("39614081257132168796771975168") // 2^95 (price = 0.25)
f.Add("1122334455667788990011223344") // Random value
f.Add("999888777666555444333222111") // Another random value
f.Add("79228162514264337593543950336") // 2^96 (price = 1)
f.Add("158456325028528675187087900672") // 2 * 2^96 (price = 4)
f.Add("39614081257132168796771975168") // 2^95 (price = 0.25)
f.Add("1122334455667788990011223344") // Random value
f.Add("999888777666555444333222111") // Another random value
f.Fuzz(func(t *testing.T, sqrtPriceX96Str string) {
sqrtPriceX96 := new(big.Int)
@@ -139,18 +139,18 @@ func FuzzSqrtPriceX96Operations(f *testing.F) {
// Convert sqrtPriceX96 to tick
tick := uniswap.SqrtPriceX96ToTick(sqrtPriceX96)
// Verify tick is in valid range
assert.True(t, tick >= -887272 && tick <= 887272,
"Tick %d outside valid range", tick)
// Convert back to sqrtPriceX96
backToSqrtPrice := uniswap.TickToSqrtPriceX96(tick)
// Check consistency (allow small difference due to rounding)
diff := new(big.Int).Sub(sqrtPriceX96, backToSqrtPrice)
diff.Abs(diff)
// Allow difference of up to 0.01% of original value
tolerance := new(big.Int).Div(sqrtPriceX96, big.NewInt(10000))
assert.True(t, diff.Cmp(tolerance) <= 0,
@@ -162,10 +162,10 @@ func FuzzSqrtPriceX96Operations(f *testing.F) {
// FuzzPriceImpactCalculations performs fuzz testing on price impact calculations
func FuzzPriceImpactCalculations(f *testing.F) {
// Add seed values for fuzzing
f.Add(int64(1000000), int64(1000000000000000000)) // Small swap, large liquidity
f.Add(int64(1000000), int64(1000000000000000000)) // Small swap, large liquidity
f.Add(int64(1000000000), int64(1000000000000000000)) // Large swap, large liquidity
f.Add(int64(1000000), int64(1000000000)) // Small swap, small liquidity
f.Add(int64(100000000), int64(1000000000)) // Large swap, small liquidity
f.Add(int64(1000000), int64(1000000000)) // Small swap, small liquidity
f.Add(int64(100000000), int64(1000000000)) // Large swap, small liquidity
f.Fuzz(func(t *testing.T, swapAmount int64, liquidity int64) {
// Skip invalid inputs
@@ -240,9 +240,9 @@ func FuzzMathematicalProperties(f *testing.F) {
if geometricMean > 0 {
geometricMeanSqrt := new(big.Float).SetFloat64(geometricMean)
geometricMeanSqrt.Sqrt(geometricMeanSqrt)
geometricMeanSqrtX96 := uniswap.PriceToSqrtPriceX96(geometricMeanSqrt)
// Calculate geometric mean of sqrtPrices
// This is complex with big integers, so we'll skip this test for extreme values
if sqrtPrice1.BitLen() < 200 && sqrtPrice2.BitLen() < 200 {
@@ -267,4 +267,4 @@ func abs64(x int64) int64 {
return -x
}
return x
}
}

View File

@@ -5,10 +5,10 @@ import (
"testing"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/fraktal/mev-beta/internal/logger"
"github.com/fraktal/mev-beta/pkg/arbitrum"
"github.com/fraktal/mev-beta/test/mocks"
"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@@ -57,7 +57,7 @@ func TestL2MessageParsingAccuracy(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
// Create mock transaction with DEX interaction
poolAddress := common.HexToAddress("0xE592427A0AEce92De3Edee1F18E0157C05861564") // Uniswap V3 Router
// Create mock transaction data for swap
swapData := createMockSwapData(tc.expectedTokens[0], tc.expectedTokens[1], tc.expectedFee)
tx := mocks.CreateMockTransaction(poolAddress, swapData)
@@ -93,16 +93,16 @@ func TestL2MessageLatency(t *testing.T) {
for i := 0; i < numMessages; i++ {
// Create L2 message
l2Message := mocks.CreateMockL2Message()
// Measure parsing time
startTime := time.Now()
if l2Message.ParsedTx != nil {
_, err := parser.ParseDEXInteraction(l2Message.ParsedTx)
// Error is expected for mock data, just measure timing
_ = err
}
latency := time.Since(startTime)
latencyMs := latency.Nanoseconds() / 1000000
@@ -118,7 +118,7 @@ func TestMultiProtocolDetection(t *testing.T) {
parser := arbitrum.NewL2MessageParser(log)
protocols := []string{"UniswapV3", "SushiSwap", "Camelot", "Balancer", "Curve"}
for _, protocol := range protocols {
t.Run(protocol, func(t *testing.T) {
// Create mock transaction for each protocol

View File

@@ -6,21 +6,21 @@ import (
"testing"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/fraktal/mev-beta/internal/config"
"github.com/fraktal/mev-beta/internal/logger"
"github.com/fraktal/mev-beta/pkg/events"
"github.com/fraktal/mev-beta/pkg/market"
"github.com/fraktal/mev-beta/pkg/scanner"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/stretchr/testify/assert"
)
func TestPipelineIntegration(t *testing.T) {
// Create test config
cfg := &config.BotConfig{
MaxWorkers: 2,
ChannelBufferSize: 5,
MaxWorkers: 2,
ChannelBufferSize: 5,
MinProfitThreshold: 10.0,
}
@@ -138,4 +138,4 @@ func TestEventParserAndPipelineIntegration(t *testing.T) {
assert.Equal(t, to, event.PoolAddress)
assert.Equal(t, blockNumber, event.BlockNumber)
assert.Equal(t, timestamp, event.Timestamp)
}
}

View File

@@ -10,7 +10,7 @@ import (
// TestMockDEXInteraction tests mock DEX interaction creation
func TestMockDEXInteraction(t *testing.T) {
dexInteraction := CreateMockDEXInteraction()
assert.Equal(t, "UniswapV3", dexInteraction.Protocol)
assert.Equal(t, common.HexToAddress("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640"), dexInteraction.Pool)
assert.Equal(t, common.HexToAddress("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"), dexInteraction.TokenIn)
@@ -23,10 +23,10 @@ func TestMockDEXInteraction(t *testing.T) {
func TestMockTransaction(t *testing.T) {
to := common.HexToAddress("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640")
data := []byte("test_data")
tx := CreateMockTransaction(to, data)
assert.Equal(t, to, *tx.To())
assert.Equal(t, data, tx.Data())
assert.Equal(t, uint64(1), tx.Nonce())
}
}

View File

@@ -32,14 +32,14 @@ func CreateMockL2Message() *arbitrum.L2Message {
// CreateMockDEXInteraction creates a realistic DEX interaction for testing
func CreateMockDEXInteraction() *arbitrum.DEXInteraction {
return &arbitrum.DEXInteraction{
Protocol: "UniswapV3",
Pool: common.HexToAddress("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640"),
TokenIn: common.HexToAddress("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"), // USDC
TokenOut: common.HexToAddress("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"), // WETH
AmountIn: big.NewInt(1000000000), // 1000 USDC
AmountOut: big.NewInt(500000000000000000), // 0.5 ETH
Protocol: "UniswapV3",
Pool: common.HexToAddress("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640"),
TokenIn: common.HexToAddress("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"), // USDC
TokenOut: common.HexToAddress("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"), // WETH
AmountIn: big.NewInt(1000000000), // 1000 USDC
AmountOut: big.NewInt(500000000000000000), // 0.5 ETH
MessageNumber: big.NewInt(12345),
Timestamp: uint64(1234567890),
Timestamp: uint64(1234567890),
}
}
@@ -53,4 +53,4 @@ func CreateMockTransaction(to common.Address, data []byte) *types.Transaction {
big.NewInt(20000000000), // gas price (20 gwei)
data, // data
)
}
}

View File

@@ -170,10 +170,10 @@ func TestPriceSymmetry(t *testing.T) {
// TestEdgeCases tests edge cases and boundary conditions
func TestEdgeCases(t *testing.T) {
testCases := []struct {
name string
tick int
shouldPass bool
description string
name string
tick int
shouldPass bool
description string
}{
{"MinTick", -887272, true, "Minimum valid tick"},
{"MaxTick", 887272, true, "Maximum valid tick"},
@@ -196,7 +196,7 @@ func TestEdgeCases(t *testing.T) {
// Test round-trip: tick -> sqrt -> tick
convertedTick := uniswap.SqrtPriceX96ToTick(sqrtPriceX96)
tickDiff := abs64(int64(tc.tick) - int64(convertedTick))
assert.True(t, tickDiff <= 1, "Round-trip tick conversion failed for %s: original=%d, converted=%d",
assert.True(t, tickDiff <= 1, "Round-trip tick conversion failed for %s: original=%d, converted=%d",
tc.description, tc.tick, convertedTick)
}
})
@@ -206,20 +206,20 @@ func TestEdgeCases(t *testing.T) {
// TestPricePrecision verifies precision of price calculations
func TestPricePrecision(t *testing.T) {
knownCases := []struct {
name string
sqrtPriceX96 string
name string
sqrtPriceX96 string
expectedPrice float64
tolerance float64
}{
{
name: "Price_1_ETH_USDC",
sqrtPriceX96: "79228162514264337593543950336", // 2^96, price = 1
name: "Price_1_ETH_USDC",
sqrtPriceX96: "79228162514264337593543950336", // 2^96, price = 1
expectedPrice: 1.0,
tolerance: 0.0001,
},
{
name: "Price_4_ETH_USDC",
sqrtPriceX96: "158456325028528675187087900672", // 2 * 2^96, price = 4
name: "Price_4_ETH_USDC",
sqrtPriceX96: "158456325028528675187087900672", // 2 * 2^96, price = 4
expectedPrice: 4.0,
tolerance: 0.01,
},
@@ -254,4 +254,4 @@ func abs64(x int64) int64 {
return -x
}
return x
}
}

View File

@@ -12,4 +12,4 @@ func TestAllPackages(t *testing.T) {
// Example of how to run tests with coverage:
// go test -coverprofile=coverage.out ./...
// go tool cover -html=coverage.out -o coverage.html
// go tool cover -html=coverage.out -o coverage.html

View File

@@ -5,13 +5,13 @@ import (
"math/big"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/fraktal/mev-beta/internal/config"
"github.com/fraktal/mev-beta/internal/logger"
"github.com/fraktal/mev-beta/pkg/events"
"github.com/fraktal/mev-beta/pkg/market"
"github.com/fraktal/mev-beta/pkg/scanner"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/holiman/uint256"
)
@@ -28,13 +28,13 @@ func CreateTestConfig() *config.Config {
},
},
Bot: config.BotConfig{
Enabled: true,
PollingInterval: 1,
MinProfitThreshold: 10.0,
GasPriceMultiplier: 1.2,
MaxWorkers: 10,
ChannelBufferSize: 100,
RPCTimeout: 30,
Enabled: true,
PollingInterval: 1,
MinProfitThreshold: 10.0,
GasPriceMultiplier: 1.2,
MaxWorkers: 10,
ChannelBufferSize: 100,
RPCTimeout: 30,
},
Uniswap: config.UniswapConfig{
FactoryAddress: "0x1F98431c8aD98523631AE4a59f267346ea31F984",
@@ -52,9 +52,9 @@ func CreateTestConfig() *config.Config {
File: "",
},
Database: config.DatabaseConfig{
File: "mev-bot.db",
MaxOpenConnections: 10,
MaxIdleConnections: 5,
File: "mev-bot.db",
MaxOpenConnections: 10,
MaxIdleConnections: 5,
},
}
}
@@ -67,19 +67,19 @@ func CreateTestLogger() *logger.Logger {
// CreateTestEvent creates a test event
func CreateTestEvent() *events.Event {
return &events.Event{
Type: events.Swap,
Protocol: "UniswapV3",
PoolAddress: common.HexToAddress("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640"),
Token0: common.HexToAddress("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"),
Token1: common.HexToAddress("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"),
Amount0: big.NewInt(1000000000),
Amount1: big.NewInt(500000000000000000),
SqrtPriceX96: uint256.NewInt(2505414483750470000),
Liquidity: uint256.NewInt(1000000000000000000),
Tick: 200000,
Timestamp: uint64(time.Now().Unix()),
Type: events.Swap,
Protocol: "UniswapV3",
PoolAddress: common.HexToAddress("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640"),
Token0: common.HexToAddress("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"),
Token1: common.HexToAddress("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"),
Amount0: big.NewInt(1000000000),
Amount1: big.NewInt(500000000000000000),
SqrtPriceX96: uint256.NewInt(2505414483750470000),
Liquidity: uint256.NewInt(1000000000000000000),
Tick: 200000,
Timestamp: uint64(time.Now().Unix()),
TransactionHash: common.HexToHash("0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"),
BlockNumber: 12345,
BlockNumber: 12345,
}
}
@@ -104,9 +104,9 @@ func CreateTestMarketManager() *market.MarketManager {
// CreateTestScanner creates a test market scanner
func CreateTestScanner() *scanner.MarketScanner {
cfg := &config.BotConfig{
MaxWorkers: 5,
ChannelBufferSize: 10,
RPCTimeout: 30,
MaxWorkers: 5,
ChannelBufferSize: 10,
RPCTimeout: 30,
MinProfitThreshold: 10.0,
}
logger := CreateTestLogger()
@@ -128,4 +128,4 @@ func CreateTestPipeline() *market.Pipeline {
// CreateTestContext creates a test context
func CreateTestContext() context.Context {
return context.Background()
}
}