Files
mev-beta/pkg/pools/discovery_test.go
2025-09-16 11:05:47 -05:00

323 lines
11 KiB
Go

package pools
import (
"math/big"
"testing"
"time"
"github.com/fraktal/mev-beta/internal/logger"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// TestNewPoolDiscovery tests the creation of a new PoolDiscovery
func TestNewPoolDiscovery(t *testing.T) {
logger := logger.New("info", "text", "")
// Test with nil client (for testing purposes)
pd := NewPoolDiscovery(nil, logger)
require.NotNil(t, pd)
assert.NotNil(t, pd.pools)
assert.NotNil(t, pd.exchanges)
assert.NotNil(t, pd.eventSignatures)
assert.NotNil(t, pd.knownFactories)
assert.NotNil(t, pd.minLiquidityThreshold)
assert.Equal(t, 0.01, pd.priceImpactThreshold)
}
// TestInitializeEventSignatures tests the initialization of event signatures
func TestInitializeEventSignatures(t *testing.T) {
logger := logger.New("info", "text", "")
pd := NewPoolDiscovery(nil, logger)
// Check that key event signatures are present
assert.Contains(t, pd.eventSignatures, "0x0d3648bd0f6ba80134a33ba9275ac585d9d315f0ad8355cddefde31afa28d0e9") // PairCreated
assert.Contains(t, pd.eventSignatures, "0x783cca1c0412dd0d695e784568c96da2e9c22ff989357a2e8b1d9b2b4e6b7118") // PoolCreated
assert.Contains(t, pd.eventSignatures, "0xd78ad95fa46c994b6551d0da85fc275fe613ce37657fb8d5e3d130840159d822") // Swap
}
// TestInitializeKnownFactories tests the initialization of known factories
func TestInitializeKnownFactories(t *testing.T) {
logger := logger.New("info", "text", "")
pd := NewPoolDiscovery(nil, logger)
// Check that key factories are present
assert.Contains(t, pd.knownFactories, "0xf1d7cc64fb4452f05c498126312ebe29f30fbcf9") // Uniswap V2
assert.Contains(t, pd.knownFactories, "0x1f98431c8ad98523631ae4a59f267346ea31f984") // Uniswap V3
assert.Contains(t, pd.knownFactories, "0xc35dadb65012ec5796536bd9864ed8773abc74c4") // SushiSwap
}
// TestPoolDiscovery_GetPoolCount tests getting pool count
func TestPoolDiscovery_GetPoolCount(t *testing.T) {
logger := logger.New("info", "text", "")
pd := NewPoolDiscovery(nil, logger)
// Initially should be zero
count := pd.GetPoolCount()
assert.Equal(t, 0, count)
// Add a test pool
pool := &Pool{
Address: "0x1234567890123456789012345678901234567890",
Token0: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
Token1: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
Fee: 3000,
Protocol: "UniswapV3",
Factory: "0x1f98431c8ad98523631ae4a59f267346ea31f984",
LastUpdated: time.Now(),
TotalVolume: big.NewInt(0),
SwapCount: 0,
}
pd.pools["0x1234567890123456789012345678901234567890"] = pool
// Should now be one
count = pd.GetPoolCount()
assert.Equal(t, 1, count)
}
// TestPoolDiscovery_GetExchangeCount tests getting exchange count
func TestPoolDiscovery_GetExchangeCount(t *testing.T) {
logger := logger.New("info", "text", "")
pd := NewPoolDiscovery(nil, logger)
// Initially should be zero
count := pd.GetExchangeCount()
assert.Equal(t, 0, count)
// Add a test exchange
exchange := &Exchange{
Name: "TestExchange",
Router: "0x1234567890123456789012345678901234567890",
Factory: "0x1f98431c8ad98523631ae4a59f267346ea31f984",
Protocol: "UniswapV3",
Version: "1.0",
Discovered: time.Now(),
PoolCount: 0,
TotalVolume: big.NewInt(0),
}
pd.exchanges["0x1234567890123456789012345678901234567890"] = exchange
// Should now be one
count = pd.GetExchangeCount()
assert.Equal(t, 1, count)
}
// TestPoolDiscovery_GetPool tests getting a pool by address
func TestPoolDiscovery_GetPool(t *testing.T) {
logger := logger.New("info", "text", "")
pd := NewPoolDiscovery(nil, logger)
// Test getting non-existent pool
pool, exists := pd.GetPool("0x1234567890123456789012345678901234567890")
assert.False(t, exists)
assert.Nil(t, pool)
// Add a test pool
testPool := &Pool{
Address: "0x1234567890123456789012345678901234567890",
Token0: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
Token1: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
Fee: 3000,
Protocol: "UniswapV3",
Factory: "0x1f98431c8ad98523631ae4a59f267346ea31f984",
LastUpdated: time.Now(),
TotalVolume: big.NewInt(1000000000000000000),
SwapCount: 5,
}
pd.pools["0x1234567890123456789012345678901234567890"] = testPool
// Test getting existing pool
pool, exists = pd.GetPool("0x1234567890123456789012345678901234567890")
assert.True(t, exists)
assert.NotNil(t, pool)
assert.Equal(t, "0x1234567890123456789012345678901234567890", pool.Address)
assert.Equal(t, "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", pool.Token0)
assert.Equal(t, "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", pool.Token1)
assert.Equal(t, uint32(3000), pool.Fee)
assert.Equal(t, "UniswapV3", pool.Protocol)
assert.Equal(t, "0x1f98431c8ad98523631ae4a59f267346ea31f984", pool.Factory)
assert.Equal(t, int64(1000000000000000000), pool.TotalVolume.Int64())
assert.Equal(t, uint64(5), pool.SwapCount)
}
// TestPoolDiscovery_GetAllPools tests getting all pools
func TestPoolDiscovery_GetAllPools(t *testing.T) {
logger := logger.New("info", "text", "")
pd := NewPoolDiscovery(nil, logger)
// Test getting all pools when empty
pools := pd.GetAllPools()
assert.Empty(t, pools)
// Add test pools
pool1 := &Pool{
Address: "0x1234567890123456789012345678901234567890",
Token0: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
Token1: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
Fee: 3000,
Protocol: "UniswapV3",
Factory: "0x1f98431c8ad98523631ae4a59f267346ea31f984",
LastUpdated: time.Now(),
TotalVolume: big.NewInt(1000000000000000000),
SwapCount: 5,
}
pool2 := &Pool{
Address: "0x0987654321098765432109876543210987654321",
Token0: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
Token1: "0x1f98431c8ad98523631ae4a59f267346ea31f984",
Fee: 500,
Protocol: "UniswapV2",
Factory: "0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f",
LastUpdated: time.Now(),
TotalVolume: big.NewInt(2000000000000000000),
SwapCount: 10,
}
pd.pools["0x1234567890123456789012345678901234567890"] = pool1
pd.pools["0x0987654321098765432109876543210987654321"] = pool2
// Test getting all pools
pools = pd.GetAllPools()
assert.Len(t, pools, 2)
assert.Contains(t, pools, "0x1234567890123456789012345678901234567890")
assert.Contains(t, pools, "0x0987654321098765432109876543210987654321")
// Verify pool data
retrievedPool1 := pools["0x1234567890123456789012345678901234567890"]
assert.Equal(t, pool1.Address, retrievedPool1.Address)
assert.Equal(t, pool1.Token0, retrievedPool1.Token0)
assert.Equal(t, pool1.Token1, retrievedPool1.Token1)
assert.Equal(t, pool1.Fee, retrievedPool1.Fee)
assert.Equal(t, pool1.Protocol, retrievedPool1.Protocol)
assert.Equal(t, pool1.Factory, retrievedPool1.Factory)
retrievedPool2 := pools["0x0987654321098765432109876543210987654321"]
assert.Equal(t, pool2.Address, retrievedPool2.Address)
assert.Equal(t, pool2.Token0, retrievedPool2.Token0)
assert.Equal(t, pool2.Token1, retrievedPool2.Token1)
assert.Equal(t, pool2.Fee, retrievedPool2.Fee)
assert.Equal(t, pool2.Protocol, retrievedPool2.Protocol)
assert.Equal(t, pool2.Factory, retrievedPool2.Factory)
}
// TestPoolDiscovery_PersistData tests data persistence functionality
func TestPoolDiscovery_PersistData(t *testing.T) {
logger := logger.New("info", "text", "")
pd := NewPoolDiscovery(nil, logger)
// Add test data
pool := &Pool{
Address: "0x1234567890123456789012345678901234567890",
Token0: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
Token1: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
Fee: 3000,
Protocol: "UniswapV3",
Factory: "0x1f98431c8ad98523631ae4a59f267346ea31f984",
LastUpdated: time.Now(),
TotalVolume: big.NewInt(1000000000000000000),
SwapCount: 5,
}
pd.pools["0x1234567890123456789012345678901234567890"] = pool
// Test persistence (this will create files in the data directory)
// Note: This test doesn't verify file contents, but ensures the method doesn't panic
pd.persistData()
// Test loading persisted data
pd.loadPersistedData()
// Verify data is still there
assert.Len(t, pd.pools, 1)
_, exists := pd.pools["0x1234567890123456789012345678901234567890"]
assert.True(t, exists)
}
// TestCalculatePriceImpact tests price impact calculation
func TestCalculatePriceImpact(t *testing.T) {
logger := logger.New("info", "text", "")
pd := NewPoolDiscovery(nil, logger)
// Create a test pool with reserves
pool := &Pool{
Reserves0: func() *big.Int { val, _ := big.NewInt(0).SetString("10000000000000000000", 10); return val }(), // 10 tokens
Reserves1: func() *big.Int { val, _ := big.NewInt(0).SetString("20000000000000000000", 10); return val }(), // 20 tokens
}
// Test with zero reserves (should return 0)
emptyPool := &Pool{}
priceImpact := pd.calculatePriceImpact(emptyPool, big.NewInt(1000000000000000000), big.NewInt(1000000000000000000))
assert.Equal(t, 0.0, priceImpact)
// Test with valid reserves
amountIn := big.NewInt(1000000000000000000) // 1 token
amountOut := big.NewInt(1990000000000000000) // ~1.99 tokens (due to 0.5% fee)
priceImpact = pd.calculatePriceImpact(pool, amountIn, amountOut)
// Price impact should be positive (small value due to small trade size)
assert.True(t, priceImpact >= 0.0)
// Test with larger trade
largeAmountIn := func() *big.Int { val, _ := big.NewInt(0).SetString("1000000000000000000", 10); return val }() // 1 token
largeAmountOut := func() *big.Int { val, _ := big.NewInt(0).SetString("1800000000000000000", 10); return val }() // ~1.8 tokens (larger price impact)
priceImpact = pd.calculatePriceImpact(pool, largeAmountIn, largeAmountOut)
// Larger trade should have larger price impact
assert.True(t, priceImpact >= 0.0)
}
// TestParseSwapData tests parsing of swap data
func TestParseSwapData(t *testing.T) {
logger := logger.New("info", "text", "")
pd := NewPoolDiscovery(nil, logger)
// Test with empty data
result := pd.parseSwapData("", "UniswapV2")
assert.Nil(t, result)
// Test with short data
result = pd.parseSwapData("0x", "UniswapV2")
assert.Nil(t, result)
// Test parseLiquidityData
data := "0x0000000000000000000000000000000000000000000000000000000000000001" +
"0000000000000000000000000000000000000000000000000000000000000002"
result2 := pd.parseLiquidityData(data, "Mint")
assert.NotNil(t, result2)
assert.Equal(t, int64(1), result2.AmountIn.Int64())
assert.Equal(t, int64(2), result2.AmountOut.Int64())
// Test parseSyncData
syncData := "0000000000000000000000000000000000000000000000000000000000000001" +
"0000000000000000000000000000000000000000000000000000000000000002"
result3 := pd.parseSyncData(syncData)
assert.NotNil(t, result3)
assert.Equal(t, int64(1), result3.Reserve0.Int64())
assert.Equal(t, int64(2), result3.Reserve1.Int64())
}
// TestPoolDiscovery_AddressFromTopic tests address extraction from topics
func TestPoolDiscovery_AddressFromTopic(t *testing.T) {
logger := logger.New("info", "text", "")
pd := NewPoolDiscovery(nil, logger)
// Test with invalid topic
result := pd.addressFromTopic(nil)
assert.Equal(t, "", result)
// Test with short topic
result = pd.addressFromTopic("0x1234")
assert.Equal(t, "", result)
// Test with valid topic (this function expects the full 32-byte topic)
result = pd.addressFromTopic("0x0000000000000000000000001234567890123456789012345678901234567890")
assert.Equal(t, "0x1234567890123456789012345678901234567890", result)
}