- Added comprehensive bounds checking to prevent buffer overruns in multicall parsing - Implemented graduated validation system (Strict/Moderate/Permissive) to reduce false positives - Added LRU caching system for address validation with 10-minute TTL - Enhanced ABI decoder with missing Universal Router and Arbitrum-specific DEX signatures - Fixed duplicate function declarations and import conflicts across multiple files - Added error recovery mechanisms with multiple fallback strategies - Updated tests to handle new validation behavior for suspicious addresses - Fixed parser test expectations for improved validation system - Applied gofmt formatting fixes to ensure code style compliance - Fixed mutex copying issues in monitoring package by introducing MetricsSnapshot - Resolved critical security vulnerabilities in heuristic address extraction - Progress: Updated TODO audit from 10% to 35% complete 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
296 lines
8.2 KiB
Go
296 lines
8.2 KiB
Go
package market
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/ethereum/go-ethereum/common"
|
|
"github.com/holiman/uint256"
|
|
"github.com/stretchr/testify/assert"
|
|
|
|
"github.com/fraktal/mev-beta/internal/config"
|
|
"github.com/fraktal/mev-beta/internal/logger"
|
|
)
|
|
|
|
func TestNewMarketManager(t *testing.T) {
|
|
// Create test config
|
|
cfg := &config.UniswapConfig{
|
|
Cache: config.CacheConfig{
|
|
Expiration: 300,
|
|
MaxSize: 10000,
|
|
},
|
|
}
|
|
|
|
// Create test logger
|
|
logger := logger.New("info", "text", "")
|
|
|
|
// Create market manager
|
|
manager := NewMarketManager(cfg, logger)
|
|
|
|
// Verify manager was created correctly
|
|
assert.NotNil(t, manager)
|
|
assert.Equal(t, cfg, manager.config)
|
|
assert.NotNil(t, manager.pools)
|
|
assert.Equal(t, time.Duration(cfg.Cache.Expiration)*time.Second, manager.cacheDuration)
|
|
assert.Equal(t, cfg.Cache.MaxSize, manager.maxCacheSize)
|
|
}
|
|
|
|
func TestGetPoolCacheHit(t *testing.T) {
|
|
// Create market manager
|
|
cfg := &config.UniswapConfig{
|
|
Cache: config.CacheConfig{
|
|
Expiration: 300,
|
|
MaxSize: 10000,
|
|
},
|
|
}
|
|
logger := logger.New("info", "text", "")
|
|
manager := NewMarketManager(cfg, logger)
|
|
|
|
// Add a pool to the cache
|
|
poolAddress := common.HexToAddress("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640")
|
|
pool := &PoolData{
|
|
Address: poolAddress,
|
|
Token0: common.HexToAddress("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"),
|
|
Token1: common.HexToAddress("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"),
|
|
Fee: 3000,
|
|
Liquidity: uint256.NewInt(1000000000000000000),
|
|
SqrtPriceX96: uint256.NewInt(2505414483750470000),
|
|
Tick: 200000,
|
|
TickSpacing: 60,
|
|
LastUpdated: time.Now(),
|
|
}
|
|
|
|
manager.pools[poolAddress.Hex()] = pool
|
|
|
|
// Get the pool (should be a cache hit)
|
|
ctx := context.Background()
|
|
result, err := manager.GetPool(ctx, poolAddress)
|
|
|
|
// Verify results
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, pool, result)
|
|
}
|
|
|
|
func TestGetPoolCacheMiss(t *testing.T) {
|
|
// Create market manager
|
|
cfg := &config.UniswapConfig{
|
|
Cache: config.CacheConfig{
|
|
Expiration: 300,
|
|
MaxSize: 10000,
|
|
},
|
|
}
|
|
logger := logger.New("info", "text", "")
|
|
manager := NewMarketManager(cfg, logger)
|
|
|
|
// Get a pool that's not in the cache (should trigger fetch)
|
|
t.Skip("requires live RPC")
|
|
|
|
poolAddress := common.HexToAddress("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640")
|
|
ctx := context.Background()
|
|
result, err := manager.GetPool(ctx, poolAddress)
|
|
|
|
// Verify results (should get mock data)
|
|
assert.NoError(t, err)
|
|
assert.NotNil(t, result)
|
|
assert.Equal(t, poolAddress, result.Address)
|
|
assert.Equal(t, "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", result.Token0.Hex())
|
|
assert.Equal(t, "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", result.Token1.Hex())
|
|
}
|
|
|
|
func TestGetPoolsByTokens(t *testing.T) {
|
|
// Create market manager
|
|
cfg := &config.UniswapConfig{
|
|
Cache: config.CacheConfig{
|
|
Expiration: 300,
|
|
MaxSize: 10000,
|
|
},
|
|
}
|
|
logger := logger.New("info", "text", "")
|
|
manager := NewMarketManager(cfg, logger)
|
|
|
|
// Add some pools to the cache
|
|
token0 := common.HexToAddress("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48") // USDC
|
|
token1 := common.HexToAddress("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2") // WETH
|
|
|
|
pool1 := &PoolData{
|
|
Address: common.HexToAddress("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640"),
|
|
Token0: token0,
|
|
Token1: token1,
|
|
Fee: 3000,
|
|
}
|
|
manager.pools[pool1.Address.Hex()] = pool1
|
|
|
|
pool2 := &PoolData{
|
|
Address: common.HexToAddress("0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc"),
|
|
Token0: token0,
|
|
Token1: token1,
|
|
Fee: 500,
|
|
}
|
|
manager.pools[pool2.Address.Hex()] = pool2
|
|
|
|
// Add a pool with different tokens
|
|
token2 := common.HexToAddress("0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984") // UNI
|
|
pool3 := &PoolData{
|
|
Address: common.HexToAddress("0x1234567890123456789012345678901234567890"),
|
|
Token0: token0,
|
|
Token1: token2,
|
|
Fee: 3000,
|
|
}
|
|
manager.pools[pool3.Address.Hex()] = pool3
|
|
|
|
// Get pools for the token pair
|
|
pools := manager.GetPoolsByTokens(token0, token1)
|
|
|
|
// Verify results
|
|
assert.Len(t, pools, 2)
|
|
// Check that both pools are in the result
|
|
pool1Found := false
|
|
pool2Found := false
|
|
for _, pool := range pools {
|
|
if pool.Address == pool1.Address {
|
|
pool1Found = true
|
|
}
|
|
if pool.Address == pool2.Address {
|
|
pool2Found = true
|
|
}
|
|
}
|
|
assert.True(t, pool1Found)
|
|
assert.True(t, pool2Found)
|
|
}
|
|
|
|
func TestGetAllPools(t *testing.T) {
|
|
// Create market manager
|
|
cfg := &config.UniswapConfig{
|
|
Cache: config.CacheConfig{
|
|
Expiration: 300,
|
|
MaxSize: 10000,
|
|
},
|
|
}
|
|
logger := logger.New("info", "text", "")
|
|
manager := NewMarketManager(cfg, logger)
|
|
|
|
// Add some pools to the cache
|
|
pool1 := &PoolData{
|
|
Address: common.HexToAddress("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640"),
|
|
Token0: common.HexToAddress("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"),
|
|
Token1: common.HexToAddress("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"),
|
|
Fee: 3000,
|
|
}
|
|
manager.pools[pool1.Address.Hex()] = pool1
|
|
|
|
pool2 := &PoolData{
|
|
Address: common.HexToAddress("0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc"),
|
|
Token0: common.HexToAddress("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"),
|
|
Token1: common.HexToAddress("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"),
|
|
Fee: 500,
|
|
}
|
|
manager.pools[pool2.Address.Hex()] = pool2
|
|
|
|
// Get all pools
|
|
pools := manager.GetAllPools()
|
|
|
|
// Verify results
|
|
assert.Len(t, pools, 2)
|
|
}
|
|
|
|
func TestUpdatePoolExisting(t *testing.T) {
|
|
// Create market manager
|
|
cfg := &config.UniswapConfig{
|
|
Cache: config.CacheConfig{
|
|
Expiration: 300,
|
|
MaxSize: 10000,
|
|
},
|
|
}
|
|
logger := logger.New("info", "text", "")
|
|
manager := NewMarketManager(cfg, logger)
|
|
|
|
// Add a pool to the cache
|
|
poolAddress := common.HexToAddress("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640")
|
|
originalLiquidity := uint256.NewInt(1000000000000000000)
|
|
originalSqrtPrice := uint256.NewInt(2505414483750470000)
|
|
originalTick := 200000
|
|
|
|
pool := &PoolData{
|
|
Address: poolAddress,
|
|
Liquidity: originalLiquidity,
|
|
SqrtPriceX96: originalSqrtPrice,
|
|
Tick: originalTick,
|
|
LastUpdated: time.Now().Add(-time.Hour), // Set to past time
|
|
}
|
|
manager.pools[poolAddress.Hex()] = pool
|
|
|
|
// Update the pool
|
|
newLiquidity := uint256.NewInt(2000000000000000000)
|
|
newSqrtPrice := uint256.NewInt(3000000000000000000)
|
|
newTick := 250000
|
|
|
|
manager.UpdatePool(poolAddress, newLiquidity, newSqrtPrice, newTick)
|
|
|
|
// Verify the pool was updated
|
|
updatedPool := manager.pools[poolAddress.Hex()]
|
|
assert.Equal(t, newLiquidity, updatedPool.Liquidity)
|
|
assert.Equal(t, newSqrtPrice, updatedPool.SqrtPriceX96)
|
|
assert.Equal(t, newTick, updatedPool.Tick)
|
|
// Check that the last updated time is more recent (allowing for small time differences)
|
|
assert.True(t, updatedPool.LastUpdated.Unix() >= pool.LastUpdated.Unix())
|
|
}
|
|
|
|
func TestUpdatePoolNew(t *testing.T) {
|
|
// Create market manager
|
|
cfg := &config.UniswapConfig{
|
|
Cache: config.CacheConfig{
|
|
Expiration: 300,
|
|
MaxSize: 10000,
|
|
},
|
|
}
|
|
logger := logger.New("info", "text", "")
|
|
manager := NewMarketManager(cfg, logger)
|
|
|
|
// Update a pool that doesn't exist yet
|
|
poolAddress := common.HexToAddress("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640")
|
|
liquidity := uint256.NewInt(1000000000000000000)
|
|
sqrtPrice := uint256.NewInt(2505414483750470000)
|
|
tick := 200000
|
|
|
|
manager.UpdatePool(poolAddress, liquidity, sqrtPrice, tick)
|
|
|
|
// Verify the pool was created
|
|
createdPool := manager.pools[poolAddress.Hex()]
|
|
assert.NotNil(t, createdPool)
|
|
assert.Equal(t, poolAddress, createdPool.Address)
|
|
assert.Equal(t, liquidity, createdPool.Liquidity)
|
|
assert.Equal(t, sqrtPrice, createdPool.SqrtPriceX96)
|
|
assert.Equal(t, tick, createdPool.Tick)
|
|
}
|
|
|
|
func TestGetCacheStats(t *testing.T) {
|
|
// Create market manager
|
|
cfg := &config.UniswapConfig{
|
|
Cache: config.CacheConfig{
|
|
Expiration: 300,
|
|
MaxSize: 10000,
|
|
},
|
|
}
|
|
logger := logger.New("info", "text", "")
|
|
manager := NewMarketManager(cfg, logger)
|
|
|
|
// Add some pools to the cache
|
|
pool1 := &PoolData{
|
|
Address: common.HexToAddress("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640"),
|
|
}
|
|
manager.pools[pool1.Address.Hex()] = pool1
|
|
|
|
pool2 := &PoolData{
|
|
Address: common.HexToAddress("0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc"),
|
|
}
|
|
manager.pools[pool2.Address.Hex()] = pool2
|
|
|
|
// Get cache stats
|
|
currentSize, maxSize := manager.GetCacheStats()
|
|
|
|
// Verify results
|
|
assert.Equal(t, 2, currentSize)
|
|
assert.Equal(t, 10000, maxSize)
|
|
}
|