- 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>
1156 lines
39 KiB
Go
1156 lines
39 KiB
Go
//go:build legacy_validation
|
|
// +build legacy_validation
|
|
|
|
package validation
|
|
|
|
import (
|
|
"context"
|
|
"math/big"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/ethereum/go-ethereum"
|
|
"github.com/ethereum/go-ethereum/common"
|
|
"github.com/ethereum/go-ethereum/core/types"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/mock"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/fraktal/mev-beta/internal/logger"
|
|
"github.com/fraktal/mev-beta/pkg/pools"
|
|
)
|
|
|
|
// MockEthClient is a mock implementation of ethclient.Client for testing
|
|
type MockEthClient struct {
|
|
mock.Mock
|
|
}
|
|
|
|
// CodeAt mocks the CodeAt method
|
|
func (m *MockEthClient) CodeAt(ctx context.Context, contract common.Address, blockNumber *big.Int) ([]byte, error) {
|
|
args := m.Called(ctx, contract, blockNumber)
|
|
return args.Get(0).([]byte), args.Error(1)
|
|
}
|
|
|
|
// CallContract mocks the CallContract method
|
|
func (m *MockEthClient) CallContract(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) {
|
|
args := m.Called(ctx, call, blockNumber)
|
|
return args.Get(0).([]byte), args.Error(1)
|
|
}
|
|
|
|
// PendingCodeAt mocks the PendingCodeAt method
|
|
func (m *MockEthClient) PendingCodeAt(ctx context.Context, account common.Address) ([]byte, error) {
|
|
args := m.Called(ctx, account)
|
|
return args.Get(0).([]byte), args.Error(1)
|
|
}
|
|
|
|
// PendingNonceAt mocks the PendingNonceAt method
|
|
func (m *MockEthClient) PendingNonceAt(ctx context.Context, account common.Address) (uint64, error) {
|
|
args := m.Called(ctx, account)
|
|
return args.Get(0).(uint64), args.Error(1)
|
|
}
|
|
|
|
// SuggestGasPrice mocks the SuggestGasPrice method
|
|
func (m *MockEthClient) SuggestGasPrice(ctx context.Context) (*big.Int, error) {
|
|
args := m.Called(ctx)
|
|
return args.Get(0).(*big.Int), args.Error(1)
|
|
}
|
|
|
|
// SuggestGasTipCap mocks the SuggestGasTipCap method
|
|
func (m *MockEthClient) SuggestGasTipCap(ctx context.Context) (*big.Int, error) {
|
|
args := m.Called(ctx)
|
|
return args.Get(0).(*big.Int), args.Error(1)
|
|
}
|
|
|
|
// EstimateGas mocks the EstimateGas method
|
|
func (m *MockEthClient) EstimateGas(ctx context.Context, call ethereum.CallMsg) (uint64, error) {
|
|
args := m.Called(ctx, call)
|
|
return args.Get(0).(uint64), args.Error(1)
|
|
}
|
|
|
|
// SendTransaction mocks the SendTransaction method
|
|
func (m *MockEthClient) SendTransaction(ctx context.Context, tx *types.Transaction) error {
|
|
args := m.Called(ctx, tx)
|
|
return args.Error(0)
|
|
}
|
|
|
|
// TransactionReceipt mocks the TransactionReceipt method
|
|
func (m *MockEthClient) TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) {
|
|
args := m.Called(ctx, txHash)
|
|
return args.Get(0).(*types.Receipt), args.Error(1)
|
|
}
|
|
|
|
// TransactionByHash mocks the TransactionByHash method
|
|
func (m *MockEthClient) TransactionByHash(ctx context.Context, hash common.Hash) (*types.Transaction, bool, error) {
|
|
args := m.Called(ctx, hash)
|
|
return args.Get(0).(*types.Transaction), args.Get(1).(bool), args.Error(2)
|
|
}
|
|
|
|
// BlockByNumber mocks the BlockByNumber method
|
|
func (m *MockEthClient) BlockByNumber(ctx context.Context, number *big.Int) (*types.Block, error) {
|
|
args := m.Called(ctx, number)
|
|
return args.Get(0).(*types.Block), args.Error(1)
|
|
}
|
|
|
|
// HeaderByNumber mocks the HeaderByNumber method
|
|
func (m *MockEthClient) HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) {
|
|
args := m.Called(ctx, number)
|
|
return args.Get(0).(*types.Header), args.Error(1)
|
|
}
|
|
|
|
// BalanceAt mocks the BalanceAt method
|
|
func (m *MockEthClient) BalanceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (*big.Int, error) {
|
|
args := m.Called(ctx, account, blockNumber)
|
|
return args.Get(0).(*big.Int), args.Error(1)
|
|
}
|
|
|
|
// StorageAt mocks the StorageAt method
|
|
func (m *MockEthClient) StorageAt(ctx context.Context, account common.Address, key common.Hash, blockNumber *big.Int) ([]byte, error) {
|
|
args := m.Called(ctx, account, key, blockNumber)
|
|
return args.Get(0).([]byte), args.Error(1)
|
|
}
|
|
|
|
// FilterLogs mocks the FilterLogs method
|
|
func (m *MockEthClient) FilterLogs(ctx context.Context, q ethereum.FilterQuery) ([]types.Log, error) {
|
|
args := m.Called(ctx, q)
|
|
return args.Get(0).([]types.Log), args.Error(1)
|
|
}
|
|
|
|
// SubscribeFilterLogs mocks the SubscribeFilterLogs method
|
|
func (m *MockEthClient) SubscribeFilterLogs(ctx context.Context, q ethereum.FilterQuery, ch chan<- types.Log) (ethereum.Subscription, error) {
|
|
args := m.Called(ctx, q, ch)
|
|
return args.Get(0).(ethereum.Subscription), args.Error(1)
|
|
}
|
|
|
|
// ChainID mocks the ChainID method
|
|
func (m *MockEthClient) ChainID(ctx context.Context) (*big.Int, error) {
|
|
args := m.Called(ctx)
|
|
return args.Get(0).(*big.Int), args.Error(1)
|
|
}
|
|
|
|
// Close mocks the Close method
|
|
func (m *MockEthClient) Close() {
|
|
m.Called()
|
|
}
|
|
|
|
// TestNewPoolValidator tests the creation of a new PoolValidator
|
|
func TestNewPoolValidator(t *testing.T) {
|
|
// Create a mock Ethereum client
|
|
client := &MockEthClient{}
|
|
|
|
// Create a logger
|
|
log := logger.New("info", "text", "")
|
|
|
|
// Create a new pool validator
|
|
validator := NewPoolValidator(client, log)
|
|
|
|
// Verify the validator was created correctly
|
|
require.NotNil(t, validator)
|
|
assert.Equal(t, client, validator.client)
|
|
assert.Equal(t, log, validator.logger)
|
|
assert.NotNil(t, validator.create2Calculator)
|
|
assert.NotNil(t, validator.trustedFactories)
|
|
assert.NotNil(t, validator.bannedAddresses)
|
|
assert.NotNil(t, validator.validationCache)
|
|
assert.Equal(t, 5*time.Minute, validator.cacheTimeout)
|
|
|
|
// Check that trusted factories were initialized
|
|
assert.NotEmpty(t, validator.trustedFactories)
|
|
assert.Contains(t, validator.trustedFactories, common.HexToAddress("0x1F98431c8aD98523631AE4a59f267346ea31F984")) // Uniswap V3
|
|
assert.Contains(t, validator.trustedFactories, common.HexToAddress("0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f")) // Uniswap V2
|
|
assert.Contains(t, validator.trustedFactories, common.HexToAddress("0xC0AEe478e3658e2610c5F7A4A2E1777cE9e4f2Ac")) // SushiSwap
|
|
assert.Contains(t, validator.trustedFactories, common.HexToAddress("0x1a3c9B1d2F0529D97f2afC5136Cc23e58f1FD35B")) // Camelot V3
|
|
}
|
|
|
|
// TestPoolValidator_GetDefaultConfig tests the default configuration
|
|
func TestPoolValidator_GetDefaultConfig(t *testing.T) {
|
|
validator := &PoolValidator{}
|
|
config := validator.getDefaultConfig()
|
|
|
|
assert.True(t, config.RequireFactoryVerification)
|
|
assert.Equal(t, 70, config.MinSecurityScore)
|
|
assert.Equal(t, 10*time.Second, config.MaxValidationTime)
|
|
assert.False(t, config.AllowUnknownFactories)
|
|
assert.True(t, config.RequireTokenValidation)
|
|
}
|
|
|
|
// TestPoolValidator_InitializeTrustedFactories tests trusted factory initialization
|
|
func TestPoolValidator_InitializeTrustedFactories(t *testing.T) {
|
|
validator := &PoolValidator{
|
|
trustedFactories: make(map[common.Address]string),
|
|
}
|
|
|
|
validator.initializeTrustedFactories()
|
|
|
|
// Check that all expected factories are present
|
|
assert.Contains(t, validator.trustedFactories, common.HexToAddress("0x1F98431c8aD98523631AE4a59f267346ea31F984"))
|
|
assert.Equal(t, "uniswap_v3", validator.trustedFactories[common.HexToAddress("0x1F98431c8aD98523631AE4a59f267346ea31F984")])
|
|
|
|
assert.Contains(t, validator.trustedFactories, common.HexToAddress("0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f"))
|
|
assert.Equal(t, "uniswap_v2", validator.trustedFactories[common.HexToAddress("0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f")])
|
|
|
|
assert.Contains(t, validator.trustedFactories, common.HexToAddress("0xC0AEe478e3658e2610c5F7A4A2E1777cE9e4f2Ac"))
|
|
assert.Equal(t, "sushiswap", validator.trustedFactories[common.HexToAddress("0xC0AEe478e3658e2610c5F7A4A2E1777cE9e4f2Ac")])
|
|
|
|
assert.Contains(t, validator.trustedFactories, common.HexToAddress("0x1a3c9B1d2F0529D97f2afC5136Cc23e58f1FD35B"))
|
|
assert.Equal(t, "camelot_v3", validator.trustedFactories[common.HexToAddress("0x1a3c9B1d2F0529D97f2afC5136Cc23e58f1FD35B")])
|
|
}
|
|
|
|
// TestPoolValidator_InitializeBannedAddresses tests banned address initialization
|
|
func TestPoolValidator_InitializeBannedAddresses(t *testing.T) {
|
|
validator := &PoolValidator{
|
|
bannedAddresses: make(map[common.Address]string),
|
|
}
|
|
|
|
validator.initializeBannedAddresses()
|
|
|
|
// The banned addresses map should be empty initially
|
|
// In production, this would be populated with known malicious addresses
|
|
assert.Empty(t, validator.bannedAddresses)
|
|
}
|
|
|
|
// TestPoolValidator_AddTrustedFactory tests adding a trusted factory
|
|
func TestPoolValidator_AddTrustedFactory(t *testing.T) {
|
|
log := logger.New("info", "text", "")
|
|
validator := &PoolValidator{
|
|
trustedFactories: make(map[common.Address]string),
|
|
logger: log,
|
|
}
|
|
|
|
factoryAddr := common.HexToAddress("0x1234567890123456789012345678901234567890")
|
|
factoryName := "test_factory"
|
|
|
|
validator.AddTrustedFactory(factoryAddr, factoryName)
|
|
|
|
assert.Contains(t, validator.trustedFactories, factoryAddr)
|
|
assert.Equal(t, factoryName, validator.trustedFactories[factoryAddr])
|
|
}
|
|
|
|
// TestPoolValidator_BanAddress tests banning an address
|
|
func TestPoolValidator_BanAddress(t *testing.T) {
|
|
log := logger.New("info", "text", "")
|
|
validator := &PoolValidator{
|
|
bannedAddresses: make(map[common.Address]string),
|
|
logger: log,
|
|
}
|
|
|
|
addr := common.HexToAddress("0x1234567890123456789012345678901234567890")
|
|
reason := "test_ban_reason"
|
|
|
|
validator.BanAddress(addr, reason)
|
|
|
|
assert.Contains(t, validator.bannedAddresses, addr)
|
|
assert.Equal(t, reason, validator.bannedAddresses[addr])
|
|
}
|
|
|
|
// TestPoolValidator_ValidateBasicExistence tests basic existence validation
|
|
func TestPoolValidator_ValidateBasicExistence(t *testing.T) {
|
|
// Create a mock client
|
|
client := &MockEthClient{}
|
|
|
|
// Create a logger
|
|
log := logger.New("info", "text", "")
|
|
|
|
// Create validator
|
|
validator := &PoolValidator{
|
|
client: client,
|
|
logger: log,
|
|
}
|
|
|
|
// Test with valid contract code
|
|
poolAddr := common.HexToAddress("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640")
|
|
validCode := make([]byte, 200) // 200 bytes of code (valid size)
|
|
|
|
client.On("CodeAt", mock.Anything, poolAddr, mock.Anything).Return(validCode, nil)
|
|
|
|
result := &ValidationResult{
|
|
Warnings: make([]string, 0),
|
|
Errors: make([]string, 0),
|
|
}
|
|
|
|
err := validator.validateBasicExistence(context.Background(), poolAddr, result)
|
|
assert.NoError(t, err)
|
|
assert.Empty(t, result.Errors)
|
|
|
|
// Test with empty code (no contract)
|
|
emptyCode := []byte{}
|
|
|
|
client.On("CodeAt", mock.Anything, poolAddr, mock.Anything).Return(emptyCode, nil).Once()
|
|
|
|
err = validator.validateBasicExistence(context.Background(), poolAddr, result)
|
|
assert.Error(t, err)
|
|
assert.Contains(t, err.Error(), "no contract code at address")
|
|
|
|
// Test with very small code (should generate warning)
|
|
smallCode := make([]byte, 50) // 50 bytes (small)
|
|
|
|
client.On("CodeAt", mock.Anything, poolAddr, mock.Anything).Return(smallCode, nil).Once()
|
|
|
|
result = &ValidationResult{
|
|
Warnings: make([]string, 0),
|
|
Errors: make([]string, 0),
|
|
}
|
|
|
|
err = validator.validateBasicExistence(context.Background(), poolAddr, result)
|
|
assert.NoError(t, err)
|
|
assert.Len(t, result.Warnings, 1)
|
|
assert.Contains(t, result.Warnings[0], "Contract has very small code size")
|
|
assert.Equal(t, -10, result.SecurityScore) // Should reduce security score
|
|
|
|
// Test with CodeAt error
|
|
client.On("CodeAt", mock.Anything, poolAddr, mock.Anything).Return([]byte{}, assert.AnError).Once()
|
|
|
|
err = validator.validateBasicExistence(context.Background(), poolAddr, result)
|
|
assert.Error(t, err)
|
|
assert.Contains(t, err.Error(), "failed to get contract code")
|
|
}
|
|
|
|
// TestPoolValidator_CheckBannedAddresses tests banned address checking
|
|
func TestPoolValidator_CheckBannedAddresses(t *testing.T) {
|
|
log := logger.New("info", "text", "")
|
|
validator := &PoolValidator{
|
|
bannedAddresses: make(map[common.Address]string),
|
|
logger: log,
|
|
}
|
|
|
|
// Test with non-banned address
|
|
poolAddr := common.HexToAddress("0x1234567890123456789012345678901234567890")
|
|
result := &ValidationResult{}
|
|
|
|
err := validator.checkBannedAddresses(poolAddr, result)
|
|
assert.NoError(t, err)
|
|
|
|
// Test with banned address
|
|
bannedAddr := common.HexToAddress("0xbanned1234567890123456789012345678901234")
|
|
banReason := "known malicious pool"
|
|
validator.bannedAddresses[bannedAddr] = banReason
|
|
|
|
result = &ValidationResult{}
|
|
err = validator.checkBannedAddresses(bannedAddr, result)
|
|
assert.Error(t, err)
|
|
assert.Contains(t, err.Error(), "pool")
|
|
assert.Contains(t, err.Error(), "is banned")
|
|
assert.Contains(t, err.Error(), banReason)
|
|
}
|
|
|
|
// TestPoolValidator_ValidatePoolInterface tests pool interface validation
|
|
func TestPoolValidator_ValidatePoolInterface(t *testing.T) {
|
|
// Create a mock client
|
|
client := &MockEthClient{}
|
|
|
|
// Create a logger
|
|
log := logger.New("info", "text", "")
|
|
|
|
// Create validator
|
|
validator := &PoolValidator{
|
|
client: client,
|
|
logger: log,
|
|
}
|
|
|
|
poolAddr := common.HexToAddress("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640")
|
|
|
|
// Test with successful interface detection (mock implementation)
|
|
result := &ValidationResult{
|
|
Warnings: make([]string, 0),
|
|
Errors: make([]string, 0),
|
|
}
|
|
|
|
// For now, just test that the function doesn't panic with valid inputs
|
|
// A full implementation would require more extensive mocking
|
|
ctx := context.Background()
|
|
|
|
// This is a simplified test - in practice, you'd need to mock the actual contract calls
|
|
err := validator.validatePoolInterface(ctx, poolAddr, result)
|
|
assert.NoError(t, err) // Should not error even if interface detection fails
|
|
}
|
|
|
|
// TestPoolValidator_IsUniswapV3Pool tests Uniswap V3 pool detection
|
|
func TestPoolValidator_IsUniswapV3Pool(t *testing.T) {
|
|
// Create a mock client
|
|
client := &MockEthClient{}
|
|
|
|
// Create a logger
|
|
log := logger.New("info", "text", "")
|
|
|
|
// Create validator
|
|
validator := &PoolValidator{
|
|
client: client,
|
|
logger: log,
|
|
}
|
|
|
|
poolAddr := common.HexToAddress("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640")
|
|
|
|
// Test with successful slot0 call (indicates V3 pool)
|
|
slot0Result := make([]byte, 100) // Mock slot0 result
|
|
|
|
client.On("CallContract", mock.Anything, mock.Anything, mock.Anything).Return(slot0Result, nil).Once()
|
|
|
|
isV3 := validator.isUniswapV3Pool(context.Background(), poolAddr)
|
|
assert.True(t, isV3)
|
|
|
|
// Test with failed slot0 call (indicates not V3 pool)
|
|
client.On("CallContract", mock.Anything, mock.Anything, mock.Anything).Return([]byte{}, assert.AnError).Once()
|
|
|
|
isV3 = validator.isUniswapV3Pool(context.Background(), poolAddr)
|
|
assert.False(t, isV3)
|
|
}
|
|
|
|
// TestPoolValidator_IsUniswapV2Pool tests Uniswap V2 pool detection
|
|
func TestPoolValidator_IsUniswapV2Pool(t *testing.T) {
|
|
// Create a mock client
|
|
client := &MockEthClient{}
|
|
|
|
// Create a logger
|
|
log := logger.New("info", "text", "")
|
|
|
|
// Create validator
|
|
validator := &PoolValidator{
|
|
client: client,
|
|
logger: log,
|
|
}
|
|
|
|
poolAddr := common.HexToAddress("0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc")
|
|
|
|
// Test with successful getReserves call (indicates V2 pool)
|
|
reservesResult := make([]byte, 100) // Mock reserves result
|
|
|
|
client.On("CallContract", mock.Anything, mock.Anything, mock.Anything).Return(reservesResult, nil).Once()
|
|
|
|
isV2 := validator.isUniswapV2Pool(context.Background(), poolAddr)
|
|
assert.True(t, isV2)
|
|
|
|
// Test with failed getReserves call (indicates not V2 pool)
|
|
client.On("CallContract", mock.Anything, mock.Anything, mock.Anything).Return([]byte{}, assert.AnError).Once()
|
|
|
|
isV2 = validator.isUniswapV2Pool(context.Background(), poolAddr)
|
|
assert.False(t, isV2)
|
|
}
|
|
|
|
// TestPoolValidator_ValidateUniswapV3Interface tests Uniswap V3 interface validation
|
|
func TestPoolValidator_ValidateUniswapV3Interface(t *testing.T) {
|
|
// Create a mock client
|
|
client := &MockEthClient{}
|
|
|
|
// Create a logger
|
|
log := logger.New("info", "text", "")
|
|
|
|
// Create validator
|
|
validator := &PoolValidator{
|
|
client: client,
|
|
logger: log,
|
|
}
|
|
|
|
poolAddr := common.HexToAddress("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640")
|
|
|
|
// Test with valid V3 pool info
|
|
result := &ValidationResult{
|
|
Warnings: make([]string, 0),
|
|
Errors: make([]string, 0),
|
|
}
|
|
|
|
// For now, just test that the function doesn't panic with valid inputs
|
|
// A full implementation would require more extensive mocking
|
|
ctx := context.Background()
|
|
|
|
// This is a simplified test - in practice, you'd need to mock the actual contract calls
|
|
err := validator.validateUniswapV3Interface(ctx, poolAddr, result)
|
|
assert.NoError(t, err) // Should not error even if validation fails
|
|
}
|
|
|
|
// TestPoolValidator_ValidateUniswapV2Interface tests Uniswap V2 interface validation
|
|
func TestPoolValidator_ValidateUniswapV2Interface(t *testing.T) {
|
|
// Create a mock client
|
|
client := &MockEthClient{}
|
|
|
|
// Create a logger
|
|
log := logger.New("info", "text", "")
|
|
|
|
// Create validator
|
|
validator := &PoolValidator{
|
|
client: client,
|
|
logger: log,
|
|
}
|
|
|
|
poolAddr := common.HexToAddress("0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc")
|
|
|
|
// Test with valid V2 pool info
|
|
result := &ValidationResult{
|
|
Warnings: make([]string, 0),
|
|
Errors: make([]string, 0),
|
|
}
|
|
|
|
// For now, just test that the function doesn't panic with valid inputs
|
|
// A full implementation would require more extensive mocking
|
|
ctx := context.Background()
|
|
|
|
// This is a simplified test - in practice, you'd need to mock the actual contract calls
|
|
err := validator.validateUniswapV2Interface(ctx, poolAddr, result)
|
|
assert.NoError(t, err) // Should not error even if validation fails
|
|
}
|
|
|
|
// TestPoolValidator_ValidateFactoryDeployment tests factory deployment validation
|
|
func TestPoolValidator_ValidateFactoryDeployment(t *testing.T) {
|
|
// Create a mock client
|
|
client := &MockEthClient{}
|
|
|
|
// Create a logger
|
|
log := logger.New("info", "text", "")
|
|
|
|
// Create validator with trusted factories
|
|
validator := &PoolValidator{
|
|
client: client,
|
|
logger: log,
|
|
trustedFactories: make(map[common.Address]string),
|
|
create2Calculator: pools.NewCREATE2Calculator(log),
|
|
}
|
|
|
|
// Add a trusted factory
|
|
factoryAddr := common.HexToAddress("0x1F98431c8aD98523631AE4a59f267346ea31F984")
|
|
validator.trustedFactories[factoryAddr] = "uniswap_v3"
|
|
|
|
poolAddr := common.HexToAddress("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640")
|
|
|
|
// Test with valid factory deployment
|
|
result := &ValidationResult{
|
|
Warnings: make([]string, 0),
|
|
Errors: make([]string, 0),
|
|
Token0: common.HexToAddress("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"), // USDC
|
|
Token1: common.HexToAddress("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"), // WETH
|
|
Fee: 3000,
|
|
}
|
|
|
|
// For now, just test that the function doesn't panic with valid inputs
|
|
// A full implementation would require more extensive mocking
|
|
ctx := context.Background()
|
|
|
|
// This is a simplified test - in practice, you'd need to mock the actual contract calls
|
|
err := validator.validateFactoryDeployment(ctx, poolAddr, result)
|
|
assert.NoError(t, err) // Should not error even if validation fails
|
|
}
|
|
|
|
// TestPoolValidator_VerifyFactoryDeployment tests factory deployment verification
|
|
func TestPoolValidator_VerifyFactoryDeployment(t *testing.T) {
|
|
// Create a logger
|
|
log := logger.New("info", "text", "")
|
|
|
|
// Create validator with CREATE2 calculator
|
|
validator := &PoolValidator{
|
|
logger: log,
|
|
create2Calculator: pools.NewCREATE2Calculator(log),
|
|
trustedFactories: make(map[common.Address]string),
|
|
}
|
|
|
|
factoryAddr := common.HexToAddress("0x1F98431c8aD98523631AE4a59f267346ea31F984")
|
|
factoryName := "uniswap_v3"
|
|
poolAddr := common.HexToAddress("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640")
|
|
|
|
result := &ValidationResult{
|
|
Token0: common.HexToAddress("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"), // USDC
|
|
Token1: common.HexToAddress("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"), // WETH
|
|
Fee: 3000,
|
|
}
|
|
|
|
// Test with valid factory deployment (should return false for mock implementation)
|
|
// In a real implementation, this would depend on the CREATE2 calculation
|
|
isValid := validator.verifyFactoryDeployment(factoryAddr, factoryName, poolAddr, result)
|
|
assert.False(t, isValid) // Should be false because we're using mock data
|
|
|
|
// Test with invalid token addresses
|
|
result.Token0 = common.Address{} // Zero address
|
|
isValid = validator.verifyFactoryDeployment(factoryAddr, factoryName, poolAddr, result)
|
|
assert.False(t, isValid) // Should be false because token0 is zero
|
|
}
|
|
|
|
// TestPoolValidator_ValidateTokenContracts tests token contract validation
|
|
func TestPoolValidator_ValidateTokenContracts(t *testing.T) {
|
|
// Create a mock client
|
|
client := &MockEthClient{}
|
|
|
|
// Create a logger
|
|
log := logger.New("info", "text", "")
|
|
|
|
// Create validator
|
|
validator := &PoolValidator{
|
|
client: client,
|
|
logger: log,
|
|
}
|
|
|
|
// Test with valid token contracts
|
|
result := &ValidationResult{
|
|
Token0: common.HexToAddress("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"), // USDC
|
|
Token1: common.HexToAddress("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"), // WETH
|
|
}
|
|
|
|
// Mock successful contract code retrieval
|
|
validCode := make([]byte, 200) // Valid contract code
|
|
|
|
client.On("CodeAt", mock.Anything, result.Token0, mock.Anything).Return(validCode, nil).Once()
|
|
client.On("CodeAt", mock.Anything, result.Token1, mock.Anything).Return(validCode, nil).Once()
|
|
|
|
// Mock successful totalSupply calls
|
|
totalSupplyResult := make([]byte, 32) // Mock totalSupply result
|
|
|
|
client.On("CallContract", mock.Anything, mock.AnythingOfType("ethereum.CallMsg"), mock.Anything).Return(totalSupplyResult, nil)
|
|
|
|
ctx := context.Background()
|
|
err := validator.validateTokenContracts(ctx, result)
|
|
assert.NoError(t, err)
|
|
|
|
// Test with missing token addresses
|
|
resultEmpty := &ValidationResult{
|
|
Token0: common.Address{},
|
|
Token1: common.Address{},
|
|
}
|
|
|
|
err = validator.validateTokenContracts(ctx, resultEmpty)
|
|
assert.Error(t, err)
|
|
assert.Contains(t, err.Error(), "token addresses not available")
|
|
}
|
|
|
|
// TestPoolValidator_ValidateTokenContract tests single token contract validation
|
|
func TestPoolValidator_ValidateTokenContract(t *testing.T) {
|
|
// Create a mock client
|
|
client := &MockEthClient{}
|
|
|
|
// Create a logger
|
|
log := logger.New("info", "text", "")
|
|
|
|
// Create validator
|
|
validator := &PoolValidator{
|
|
client: client,
|
|
logger: log,
|
|
}
|
|
|
|
tokenAddr := common.HexToAddress("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48") // USDC
|
|
|
|
// Test with valid token contract
|
|
validCode := make([]byte, 200) // Valid contract code
|
|
|
|
client.On("CodeAt", mock.Anything, tokenAddr, mock.Anything).Return(validCode, nil).Once()
|
|
|
|
// Mock successful totalSupply call
|
|
totalSupplyResult := make([]byte, 32) // Mock totalSupply result
|
|
|
|
client.On("CallContract", mock.Anything, mock.AnythingOfType("ethereum.CallMsg"), mock.Anything).Return(totalSupplyResult, nil).Once()
|
|
|
|
ctx := context.Background()
|
|
err := validator.validateTokenContract(ctx, tokenAddr)
|
|
assert.NoError(t, err)
|
|
|
|
// Test with no code at address
|
|
emptyCode := []byte{}
|
|
|
|
client.On("CodeAt", mock.Anything, tokenAddr, mock.Anything).Return(emptyCode, nil).Once()
|
|
|
|
err = validator.validateTokenContract(ctx, tokenAddr)
|
|
assert.Error(t, err)
|
|
assert.Contains(t, err.Error(), "no contract code at token address")
|
|
|
|
// Test with CodeAt error
|
|
client.On("CodeAt", mock.Anything, tokenAddr, mock.Anything).Return([]byte{}, assert.AnError).Once()
|
|
|
|
err = validator.validateTokenContract(ctx, tokenAddr)
|
|
assert.Error(t, err)
|
|
assert.Contains(t, err.Error(), "failed to get token contract code")
|
|
}
|
|
|
|
// TestPoolValidator_ValidateERC20Interface tests ERC20 interface validation
|
|
func TestPoolValidator_ValidateERC20Interface(t *testing.T) {
|
|
// Create a mock client
|
|
client := &MockEthClient{}
|
|
|
|
// Create a logger
|
|
log := logger.New("info", "text", "")
|
|
|
|
// Create validator
|
|
validator := &PoolValidator{
|
|
client: client,
|
|
logger: log,
|
|
}
|
|
|
|
tokenAddr := common.HexToAddress("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48") // USDC
|
|
|
|
// Test with successful totalSupply call
|
|
totalSupplyResult := make([]byte, 32) // Mock totalSupply result
|
|
|
|
client.On("CallContract", mock.Anything, mock.AnythingOfType("ethereum.CallMsg"), mock.Anything).Return(totalSupplyResult, nil).Once()
|
|
|
|
ctx := context.Background()
|
|
err := validator.validateERC20Interface(ctx, tokenAddr)
|
|
assert.NoError(t, err)
|
|
|
|
// Test with failed totalSupply call
|
|
client.On("CallContract", mock.Anything, mock.AnythingOfType("ethereum.CallMsg"), mock.Anything).Return([]byte{}, assert.AnError).Once()
|
|
|
|
err = validator.validateERC20Interface(ctx, tokenAddr)
|
|
assert.Error(t, err)
|
|
assert.Contains(t, err.Error(), "totalSupply call failed")
|
|
}
|
|
|
|
// TestPoolValidator_PerformAdditionalSecurityChecks tests additional security checks
|
|
func TestPoolValidator_PerformAdditionalSecurityChecks(t *testing.T) {
|
|
// Create a mock client
|
|
client := &MockEthClient{}
|
|
|
|
// Create a logger
|
|
log := logger.New("info", "text", "")
|
|
|
|
// Create validator
|
|
validator := &PoolValidator{
|
|
client: client,
|
|
logger: log,
|
|
}
|
|
|
|
poolAddr := common.HexToAddress("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640")
|
|
|
|
result := &ValidationResult{
|
|
Warnings: make([]string, 0),
|
|
Errors: make([]string, 0),
|
|
SecurityScore: 0,
|
|
}
|
|
|
|
// For now, just test that the function doesn't panic
|
|
// A full implementation would require more extensive mocking
|
|
ctx := context.Background()
|
|
|
|
validator.performAdditionalSecurityChecks(ctx, poolAddr, result)
|
|
|
|
// The function should complete without error
|
|
// Additional assertions would depend on the specific checks implemented
|
|
}
|
|
|
|
// TestPoolValidator_GetContractCreationBlock tests contract creation block retrieval
|
|
func TestPoolValidator_GetContractCreationBlock(t *testing.T) {
|
|
// Create a mock client
|
|
client := &MockEthClient{}
|
|
|
|
// Create a logger
|
|
log := logger.New("info", "text", "")
|
|
|
|
// Create validator
|
|
validator := &PoolValidator{
|
|
client: client,
|
|
logger: log,
|
|
}
|
|
|
|
poolAddr := common.HexToAddress("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640")
|
|
|
|
// Test that the function returns 0 (placeholder implementation)
|
|
ctx := context.Background()
|
|
blockNumber := validator.getContractCreationBlock(ctx, poolAddr)
|
|
assert.Equal(t, uint64(0), blockNumber) // Should return 0 for now
|
|
}
|
|
|
|
// TestPoolValidator_CheckForAttackPatterns tests attack pattern detection
|
|
func TestPoolValidator_CheckForAttackPatterns(t *testing.T) {
|
|
// Create a mock client
|
|
client := &MockEthClient{}
|
|
|
|
// Create a logger
|
|
log := logger.New("info", "text", "")
|
|
|
|
// Create validator
|
|
validator := &PoolValidator{
|
|
client: client,
|
|
logger: log,
|
|
}
|
|
|
|
poolAddr := common.HexToAddress("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640")
|
|
|
|
result := &ValidationResult{
|
|
Warnings: make([]string, 0),
|
|
Errors: make([]string, 0),
|
|
SecurityScore: 0,
|
|
}
|
|
|
|
// For now, just test that the function doesn't panic
|
|
// A full implementation would require more extensive mocking
|
|
ctx := context.Background()
|
|
|
|
validator.checkForAttackPatterns(ctx, poolAddr, result)
|
|
|
|
// The function should complete without error
|
|
// Additional assertions would depend on the specific checks implemented
|
|
}
|
|
|
|
// TestPoolValidator_IsProxyContract tests proxy contract detection
|
|
func TestPoolValidator_IsProxyContract(t *testing.T) {
|
|
// Create a mock client
|
|
client := &MockEthClient{}
|
|
|
|
// Create a logger
|
|
log := logger.New("info", "text", "")
|
|
|
|
// Create validator
|
|
validator := &PoolValidator{
|
|
client: client,
|
|
logger: log,
|
|
}
|
|
|
|
poolAddr := common.HexToAddress("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640")
|
|
|
|
// Test with valid contract code containing delegatecall (0xf4)
|
|
codeWithDelegateCall := []byte{0x00, 0x01, 0xf4, 0x02, 0x03} // Contains delegatecall
|
|
|
|
client.On("CodeAt", mock.Anything, poolAddr, mock.Anything).Return(codeWithDelegateCall, nil).Once()
|
|
|
|
ctx := context.Background()
|
|
isProxy := validator.isProxyContract(ctx, poolAddr)
|
|
assert.True(t, isProxy)
|
|
|
|
// Test with valid contract code without delegatecall
|
|
codeWithoutDelegateCall := []byte{0x00, 0x01, 0x02, 0x03, 0x04} // No delegatecall
|
|
|
|
client.On("CodeAt", mock.Anything, poolAddr, mock.Anything).Return(codeWithoutDelegateCall, nil).Once()
|
|
|
|
isProxy = validator.isProxyContract(ctx, poolAddr)
|
|
assert.False(t, isProxy)
|
|
|
|
// Test with CodeAt error
|
|
client.On("CodeAt", mock.Anything, poolAddr, mock.Anything).Return([]byte{}, assert.AnError).Once()
|
|
|
|
isProxy = validator.isProxyContract(ctx, poolAddr)
|
|
assert.False(t, isProxy)
|
|
|
|
// Test with empty code
|
|
client.On("CodeAt", mock.Anything, poolAddr, mock.Anything).Return([]byte{}, nil).Once()
|
|
|
|
isProxy = validator.isProxyContract(ctx, poolAddr)
|
|
assert.False(t, isProxy)
|
|
}
|
|
|
|
// TestPoolValidator_HasUnusualBytecode tests unusual bytecode detection
|
|
func TestPoolValidator_HasUnusualBytecode(t *testing.T) {
|
|
// Create a mock client
|
|
client := &MockEthClient{}
|
|
|
|
// Create a logger
|
|
log := logger.New("info", "text", "")
|
|
|
|
// Create validator
|
|
validator := &PoolValidator{
|
|
client: client,
|
|
logger: log,
|
|
}
|
|
|
|
poolAddr := common.HexToAddress("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640")
|
|
|
|
// Test with normal-sized contract code
|
|
normalCode := make([]byte, 1000) // Reasonable size
|
|
|
|
client.On("CodeAt", mock.Anything, poolAddr, mock.Anything).Return(normalCode, nil).Once()
|
|
|
|
ctx := context.Background()
|
|
hasUnusual := validator.hasUnusualBytecode(ctx, poolAddr)
|
|
assert.False(t, hasUnusual)
|
|
|
|
// Test with unusually large contract code
|
|
largeCode := make([]byte, 60000) // Very large code
|
|
|
|
client.On("CodeAt", mock.Anything, poolAddr, mock.Anything).Return(largeCode, nil).Once()
|
|
|
|
hasUnusual = validator.hasUnusualBytecode(ctx, poolAddr)
|
|
assert.True(t, hasUnusual)
|
|
|
|
// Test with CodeAt error
|
|
client.On("CodeAt", mock.Anything, poolAddr, mock.Anything).Return([]byte{}, assert.AnError).Once()
|
|
|
|
hasUnusual = validator.hasUnusualBytecode(ctx, poolAddr)
|
|
assert.False(t, hasUnusual)
|
|
|
|
// Test with empty code
|
|
client.On("CodeAt", mock.Anything, poolAddr, mock.Anything).Return([]byte{}, nil).Once()
|
|
|
|
hasUnusual = validator.hasUnusualBytecode(ctx, poolAddr)
|
|
assert.False(t, hasUnusual)
|
|
}
|
|
|
|
// TestPoolValidator_CalculateEntropy tests entropy calculation
|
|
func TestPoolValidator_CalculateEntropy(t *testing.T) {
|
|
validator := &PoolValidator{}
|
|
|
|
// Test with empty data
|
|
emptyData := []byte{}
|
|
entropy := validator.calculateEntropy(emptyData)
|
|
assert.Equal(t, 0.0, entropy)
|
|
|
|
// Test with uniform data (low entropy)
|
|
uniformData := []byte{0x00, 0x00, 0x00, 0x00, 0x00}
|
|
entropy = validator.calculateEntropy(uniformData)
|
|
assert.Equal(t, 0.0, entropy)
|
|
|
|
// Test with varied data (higher entropy)
|
|
variedData := []byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07}
|
|
entropy = validator.calculateEntropy(variedData)
|
|
assert.True(t, entropy > 0.0)
|
|
|
|
// Entropy should be between 0 and log2(length) for valid inputs
|
|
maxEntropy := 3.0 // log2(8) for 8 bytes
|
|
assert.True(t, entropy <= maxEntropy)
|
|
}
|
|
|
|
// TestPoolValidator_GetUniswapV3PoolInfo tests Uniswap V3 pool info retrieval
|
|
func TestPoolValidator_GetUniswapV3PoolInfo(t *testing.T) {
|
|
// Create a mock client
|
|
client := &MockEthClient{}
|
|
|
|
// Create a logger
|
|
log := logger.New("info", "text", "")
|
|
|
|
// Create validator
|
|
validator := &PoolValidator{
|
|
client: client,
|
|
logger: log,
|
|
}
|
|
|
|
poolAddr := common.HexToAddress("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640")
|
|
|
|
// Test with successful contract calls
|
|
token0Result := make([]byte, 32) // Mock token0 result
|
|
token1Result := make([]byte, 32) // Mock token1 result
|
|
feeResult := make([]byte, 32) // Mock fee result
|
|
|
|
// Fill with mock data
|
|
copy(token0Result[12:], common.HexToAddress("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48").Bytes()) // USDC
|
|
copy(token1Result[12:], common.HexToAddress("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2").Bytes()) // WETH
|
|
copy(feeResult[28:], []byte{0x00, 0x00, 0x0b, 0xb8}) // 3000 (0xbb8 in hex)
|
|
|
|
client.On("CallContract", mock.Anything, mock.AnythingOfType("ethereum.CallMsg"), mock.Anything).Return(token0Result, nil).Once()
|
|
client.On("CallContract", mock.Anything, mock.AnythingOfType("ethereum.CallMsg"), mock.Anything).Return(token1Result, nil).Once()
|
|
client.On("CallContract", mock.Anything, mock.AnythingOfType("ethereum.CallMsg"), mock.Anything).Return(feeResult, nil).Once()
|
|
|
|
ctx := context.Background()
|
|
token0, token1, fee, err := validator.getUniswapV3PoolInfo(ctx, poolAddr)
|
|
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, common.HexToAddress("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"), token0)
|
|
assert.Equal(t, common.HexToAddress("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"), token1)
|
|
assert.Equal(t, uint32(3000), fee)
|
|
}
|
|
|
|
// TestPoolValidator_GetUniswapV2PoolInfo tests Uniswap V2 pool info retrieval
|
|
func TestPoolValidator_GetUniswapV2PoolInfo(t *testing.T) {
|
|
// Create a mock client
|
|
client := &MockEthClient{}
|
|
|
|
// Create a logger
|
|
log := logger.New("info", "text", "")
|
|
|
|
// Create validator
|
|
validator := &PoolValidator{
|
|
client: client,
|
|
logger: log,
|
|
}
|
|
|
|
poolAddr := common.HexToAddress("0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc")
|
|
|
|
// Test with successful contract calls
|
|
token0Result := make([]byte, 32) // Mock token0 result
|
|
token1Result := make([]byte, 32) // Mock token1 result
|
|
|
|
// Fill with mock data
|
|
copy(token0Result[12:], common.HexToAddress("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48").Bytes()) // USDC
|
|
copy(token1Result[12:], common.HexToAddress("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2").Bytes()) // WETH
|
|
|
|
client.On("CallContract", mock.Anything, mock.AnythingOfType("ethereum.CallMsg"), mock.Anything).Return(token0Result, nil).Once()
|
|
client.On("CallContract", mock.Anything, mock.AnythingOfType("ethereum.CallMsg"), mock.Anything).Return(token1Result, nil).Once()
|
|
|
|
ctx := context.Background()
|
|
token0, token1, err := validator.getUniswapV2PoolInfo(ctx, poolAddr)
|
|
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, common.HexToAddress("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"), token0)
|
|
assert.Equal(t, common.HexToAddress("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"), token1)
|
|
}
|
|
|
|
// TestPoolValidator_GetCachedResult tests cached result retrieval
|
|
func TestPoolValidator_GetCachedResult(t *testing.T) {
|
|
log := logger.New("info", "text", "")
|
|
validator := &PoolValidator{
|
|
validationCache: make(map[common.Address]*ValidationResult),
|
|
cacheTimeout: 5 * time.Minute,
|
|
logger: log,
|
|
}
|
|
|
|
poolAddr := common.HexToAddress("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640")
|
|
|
|
// Test with no cached result
|
|
result := validator.getCachedResult(poolAddr)
|
|
assert.Nil(t, result)
|
|
|
|
// Test with valid cached result
|
|
cachedResult := &ValidationResult{
|
|
IsValid: true,
|
|
SecurityScore: 90,
|
|
ValidatedAt: time.Now(),
|
|
}
|
|
|
|
validator.validationCache[poolAddr] = cachedResult
|
|
|
|
result = validator.getCachedResult(poolAddr)
|
|
assert.Equal(t, cachedResult, result)
|
|
|
|
// Test with expired cached result
|
|
expiredResult := &ValidationResult{
|
|
IsValid: true,
|
|
SecurityScore: 90,
|
|
ValidatedAt: time.Now().Add(-10 * time.Minute), // 10 minutes ago (expired)
|
|
}
|
|
|
|
validator.validationCache[poolAddr] = expiredResult
|
|
|
|
result = validator.getCachedResult(poolAddr)
|
|
assert.Nil(t, result) // Should be nil because it's expired
|
|
|
|
// Verify that expired result was removed from cache
|
|
_, exists := validator.validationCache[poolAddr]
|
|
assert.False(t, exists)
|
|
}
|
|
|
|
// TestPoolValidator_CacheResult tests result caching
|
|
func TestPoolValidator_CacheResult(t *testing.T) {
|
|
log := logger.New("info", "text", "")
|
|
validator := &PoolValidator{
|
|
validationCache: make(map[common.Address]*ValidationResult),
|
|
cacheTimeout: 5 * time.Minute,
|
|
logger: log,
|
|
}
|
|
|
|
poolAddr := common.HexToAddress("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640")
|
|
result := &ValidationResult{
|
|
IsValid: true,
|
|
SecurityScore: 90,
|
|
ValidatedAt: time.Now(),
|
|
}
|
|
|
|
// Test caching a result
|
|
validator.cacheResult(poolAddr, result)
|
|
|
|
// Verify it was cached
|
|
cachedResult, exists := validator.validationCache[poolAddr]
|
|
assert.True(t, exists)
|
|
assert.Equal(t, result, cachedResult)
|
|
}
|
|
|
|
// TestValidationResult tests the ValidationResult struct
|
|
func TestValidationResult(t *testing.T) {
|
|
result := &ValidationResult{
|
|
IsValid: true,
|
|
SecurityScore: 95,
|
|
Warnings: []string{"Test warning"},
|
|
Errors: []string{},
|
|
PoolType: "uniswap_v3",
|
|
Factory: "0x1F98431c8aD98523631AE4a59f267346ea31F984",
|
|
Token0: common.HexToAddress("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"),
|
|
Token1: common.HexToAddress("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"),
|
|
Fee: 3000,
|
|
CreationBlock: 12345678,
|
|
ValidatedAt: time.Now(),
|
|
FactoryVerified: true,
|
|
InterfaceValid: true,
|
|
TokensValid: true,
|
|
}
|
|
|
|
assert.True(t, result.IsValid)
|
|
assert.Equal(t, 95, result.SecurityScore)
|
|
assert.Len(t, result.Warnings, 1)
|
|
assert.Equal(t, "Test warning", result.Warnings[0])
|
|
assert.Empty(t, result.Errors)
|
|
assert.Equal(t, "uniswap_v3", result.PoolType)
|
|
assert.Equal(t, "0x1F98431c8aD98523631AE4a59f267346ea31F984", result.Factory)
|
|
assert.Equal(t, common.HexToAddress("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"), result.Token0)
|
|
assert.Equal(t, common.HexToAddress("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"), result.Token1)
|
|
assert.Equal(t, uint32(3000), result.Fee)
|
|
assert.Equal(t, uint64(12345678), result.CreationBlock)
|
|
assert.True(t, result.FactoryVerified)
|
|
assert.True(t, result.InterfaceValid)
|
|
assert.True(t, result.TokensValid)
|
|
assert.WithinDuration(t, time.Now(), result.ValidatedAt, time.Second)
|
|
}
|
|
|
|
// TestValidationConfig tests the ValidationConfig struct
|
|
func TestValidationConfig(t *testing.T) {
|
|
config := &ValidationConfig{
|
|
RequireFactoryVerification: true,
|
|
MinSecurityScore: 80,
|
|
MaxValidationTime: 30 * time.Second,
|
|
AllowUnknownFactories: false,
|
|
RequireTokenValidation: true,
|
|
}
|
|
|
|
assert.True(t, config.RequireFactoryVerification)
|
|
assert.Equal(t, 80, config.MinSecurityScore)
|
|
assert.Equal(t, 30*time.Second, config.MaxValidationTime)
|
|
assert.False(t, config.AllowUnknownFactories)
|
|
assert.True(t, config.RequireTokenValidation)
|
|
}
|
|
|
|
// TestPoolInfo tests the PoolInfo struct
|
|
func TestPoolInfo(t *testing.T) {
|
|
now := time.Now()
|
|
|
|
poolInfo := &PoolInfo{
|
|
Address: common.HexToAddress("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640"),
|
|
Token0: common.HexToAddress("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"),
|
|
Token1: common.HexToAddress("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"),
|
|
Protocol: "UniswapV3",
|
|
Fee: 3000,
|
|
Liquidity: big.NewInt(1000000000000000000),
|
|
SqrtPriceX96: func() *big.Int { x, _ := new(big.Int).SetString("79228162514264337593543950336", 10); return x }(),
|
|
LastUpdated: now,
|
|
}
|
|
|
|
assert.Equal(t, common.HexToAddress("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640"), poolInfo.Address)
|
|
assert.Equal(t, common.HexToAddress("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"), poolInfo.Token0)
|
|
assert.Equal(t, common.HexToAddress("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"), poolInfo.Token1)
|
|
assert.Equal(t, "UniswapV3", poolInfo.Protocol)
|
|
assert.Equal(t, uint32(3000), poolInfo.Fee)
|
|
assert.Equal(t, int64(1000000000000000000), poolInfo.Liquidity.Int64())
|
|
expectedSqrtPrice, _ := new(big.Int).SetString("79228162514264337593543950336", 10)
|
|
assert.Equal(t, expectedSqrtPrice, poolInfo.SqrtPriceX96)
|
|
assert.Equal(t, now, poolInfo.LastUpdated)
|
|
}
|
|
|
|
// TestTokenGraph tests the TokenGraph struct
|
|
func TestTokenGraph(t *testing.T) {
|
|
graph := NewTokenGraph()
|
|
|
|
assert.NotNil(t, graph)
|
|
assert.NotNil(t, graph.adjacencyList)
|
|
assert.NotNil(t, &graph.mutex)
|
|
|
|
// Test adding edges to the graph
|
|
tokenA := common.HexToAddress("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48") // USDC
|
|
tokenB := common.HexToAddress("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2") // WETH
|
|
|
|
pool := &PoolInfo{
|
|
Address: common.HexToAddress("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640"),
|
|
Token0: tokenA,
|
|
Token1: tokenB,
|
|
Protocol: "UniswapV3",
|
|
Fee: 3000,
|
|
Liquidity: big.NewInt(1000000000000000000),
|
|
SqrtPriceX96: func() *big.Int { x, _ := new(big.Int).SetString("79228162514264337593543950336", 10); return x }(),
|
|
LastUpdated: time.Now(),
|
|
}
|
|
|
|
// Add the pool to the graph
|
|
graph.mutex.Lock()
|
|
if graph.adjacencyList[tokenA] == nil {
|
|
graph.adjacencyList[tokenA] = make(map[common.Address][]*PoolInfo)
|
|
}
|
|
graph.adjacencyList[tokenA][tokenB] = append(graph.adjacencyList[tokenA][tokenB], pool)
|
|
|
|
if graph.adjacencyList[tokenB] == nil {
|
|
graph.adjacencyList[tokenB] = make(map[common.Address][]*PoolInfo)
|
|
}
|
|
graph.adjacencyList[tokenB][tokenA] = append(graph.adjacencyList[tokenB][tokenA], pool)
|
|
graph.mutex.Unlock()
|
|
|
|
// Test retrieving adjacent tokens
|
|
adjacent := graph.GetAdjacentTokens(tokenA)
|
|
assert.Len(t, adjacent, 1)
|
|
assert.Contains(t, adjacent, tokenB)
|
|
assert.Len(t, adjacent[tokenB], 1)
|
|
assert.Equal(t, pool, adjacent[tokenB][0])
|
|
}
|