Files
mev-beta/pkg/exchanges/exchanges_test.go
Krypto Kajun 8cba462024 feat(prod): complete production deployment with Podman containerization
- Migrate from Docker to Podman for enhanced security (rootless containers)
- Add production-ready Dockerfile with multi-stage builds
- Configure production environment with Arbitrum mainnet RPC endpoints
- Add comprehensive test coverage for core modules (exchanges, execution, profitability)
- Implement production audit and deployment documentation
- Update deployment scripts for production environment
- Add container runtime and health monitoring scripts
- Document RPC limitations and remediation strategies
- Implement token metadata caching and pool validation

This commit prepares the MEV bot for production deployment on Arbitrum
with full containerization, security hardening, and operational tooling.

🤖 Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-08 10:15:22 -06:00

351 lines
10 KiB
Go

package exchanges
import (
"math/big"
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/assert"
"github.com/fraktal/mev-beta/internal/logger"
"github.com/fraktal/mev-beta/pkg/math"
)
func TestTokenInfo(t *testing.T) {
token := TokenInfo{
Address: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
Symbol: "USDC",
Name: "USD Coin",
Decimals: 6,
}
assert.Equal(t, "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", token.Address)
assert.Equal(t, "USDC", token.Symbol)
assert.Equal(t, "USD Coin", token.Name)
assert.Equal(t, uint8(6), token.Decimals)
}
func TestTokenPair(t *testing.T) {
pair := TokenPair{
Token0: TokenInfo{
Address: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
Symbol: "USDC",
Name: "USD Coin",
Decimals: 6,
},
Token1: TokenInfo{
Address: "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
Symbol: "WETH",
Name: "Wrapped Ether",
Decimals: 18,
},
}
assert.Equal(t, "USDC", pair.Token0.Symbol)
assert.Equal(t, "WETH", pair.Token1.Symbol)
assert.Equal(t, uint8(6), pair.Token0.Decimals)
assert.Equal(t, uint8(18), pair.Token1.Decimals)
}
func TestExchangeConfig(t *testing.T) {
config := &ExchangeConfig{
Type: math.ExchangeType("UniswapV3"),
Name: "Uniswap V3",
FactoryAddress: common.HexToAddress("0x1F98431c8aD98523631AE4a59f267346ea31F984"),
RouterAddress: common.HexToAddress("0xE592427A0AEce92De3Edee1F18E0157C05861564"),
PoolInitCodeHash: "0xe34f199b19b2b4d5f547212444eee88396eaf8a3b7d1f1da4c3f27f65c13bfe9",
SwapSelector: []byte{0x41, 0x4b, 0xf3, 0x89},
StableSwapSelector: []byte{0x41, 0x4b, 0xf3, 0x8a},
ChainID: 42161, // Arbitrum
SupportsFlashSwaps: true,
RequiresApproval: true,
MaxHops: 3,
DefaultSlippagePercent: 0.01,
Url: "https://app.uniswap.org",
ApiUrl: "https://api.uniswap.org",
}
assert.Equal(t, math.ExchangeType("UniswapV3"), config.Type)
assert.Equal(t, "Uniswap V3", config.Name)
assert.Equal(t, int64(42161), config.ChainID)
assert.True(t, config.SupportsFlashSwaps)
assert.True(t, config.RequiresApproval)
assert.Equal(t, 3, config.MaxHops)
assert.Equal(t, 0.01, config.DefaultSlippagePercent)
}
func TestNewExchangeRegistry(t *testing.T) {
log := logger.New("info", "text", "")
registry := NewExchangeRegistry(nil, log)
assert.NotNil(t, registry)
assert.NotNil(t, registry.exchanges)
assert.NotNil(t, registry.poolDetectors)
assert.NotNil(t, registry.liquidityFetchers)
assert.NotNil(t, registry.swapRouters)
assert.Equal(t, log, registry.logger)
assert.Equal(t, 0, len(registry.exchanges))
}
func TestExchangeRegistryRegisterExchange(t *testing.T) {
log := logger.New("info", "text", "")
registry := NewExchangeRegistry(nil, log)
config := &ExchangeConfig{
Type: math.ExchangeType("UniswapV3"),
Name: "Uniswap V3",
FactoryAddress: common.HexToAddress("0x1F98431c8aD98523631AE4a59f267346ea31F984"),
MaxHops: 3,
}
// Test manual registration (if method exists)
config.Type = math.ExchangeType(config.Type)
registry.exchanges[config.Type] = config
assert.NotNil(t, registry.exchanges[config.Type])
assert.Equal(t, "Uniswap V3", registry.exchanges[config.Type].Name)
}
func TestExchangeConfigUniswapV2(t *testing.T) {
config := &ExchangeConfig{
Type: math.ExchangeType("UniswapV2"),
Name: "Uniswap V2",
FactoryAddress: common.HexToAddress("0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f"),
RouterAddress: common.HexToAddress("0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D"),
SupportsFlashSwaps: false,
RequiresApproval: true,
MaxHops: 3,
DefaultSlippagePercent: 0.03,
}
assert.Equal(t, math.ExchangeType("UniswapV2"), config.Type)
assert.False(t, config.SupportsFlashSwaps)
assert.Equal(t, 0.03, config.DefaultSlippagePercent)
}
func TestExchangeConfigSushiSwap(t *testing.T) {
config := &ExchangeConfig{
Type: math.ExchangeType("SushiSwap"),
Name: "SushiSwap",
FactoryAddress: common.HexToAddress("0xc35DADB65012eC5796536bD9864eD8773aBc74C4"),
RouterAddress: common.HexToAddress("0x1b02dA8Cb0d097eB8D57A175b88c7D8b47997506"),
SupportsFlashSwaps: false,
RequiresApproval: true,
MaxHops: 3,
DefaultSlippagePercent: 0.03,
}
assert.Equal(t, math.ExchangeType("SushiSwap"), config.Type)
assert.Equal(t, "SushiSwap", config.Name)
}
func TestExchangeConfigBalancer(t *testing.T) {
config := &ExchangeConfig{
Type: math.ExchangeType("Balancer"),
Name: "Balancer",
FactoryAddress: common.HexToAddress("0xBA12222222228d8Ba445958a75a0704d566BF2C8"),
SupportsFlashSwaps: true,
RequiresApproval: true,
MaxHops: 4,
DefaultSlippagePercent: 0.02,
}
assert.Equal(t, math.ExchangeType("Balancer"), config.Type)
assert.True(t, config.SupportsFlashSwaps)
assert.Equal(t, 4, config.MaxHops)
}
func TestExchangeConfigCamelot(t *testing.T) {
config := &ExchangeConfig{
Type: math.ExchangeType("Camelot"),
Name: "Camelot",
FactoryAddress: common.HexToAddress("0x1a3c9B1d2F0529D97f2afC5136Cc23e58f1FD35B"),
SupportsFlashSwaps: false,
RequiresApproval: true,
MaxHops: 3,
DefaultSlippagePercent: 0.03,
}
assert.Equal(t, math.ExchangeType("Camelot"), config.Type)
assert.False(t, config.SupportsFlashSwaps)
}
func TestPoolDetectorInterface(t *testing.T) {
// Verify interface is defined correctly
var _ PoolDetector = (*mockPoolDetector)(nil)
}
func TestLiquidityFetcherInterface(t *testing.T) {
// Verify interface is defined correctly
var _ LiquidityFetcher = (*mockLiquidityFetcher)(nil)
}
func TestSwapRouterInterface(t *testing.T) {
// Verify interface is defined correctly
var _ SwapRouter = (*mockSwapRouter)(nil)
}
func TestExchangeRegistryMultipleExchanges(t *testing.T) {
log := logger.New("info", "text", "")
registry := NewExchangeRegistry(nil, log)
configs := []struct {
exchangeType string
name string
maxHops int
}{
{"UniswapV3", "Uniswap V3", 3},
{"UniswapV2", "Uniswap V2", 3},
{"SushiSwap", "SushiSwap", 3},
{"Balancer", "Balancer", 4},
{"Camelot", "Camelot", 3},
}
for _, tc := range configs {
config := &ExchangeConfig{
Type: math.ExchangeType(tc.exchangeType),
Name: tc.name,
MaxHops: tc.maxHops,
}
registry.exchanges[config.Type] = config
}
assert.Equal(t, 5, len(registry.exchanges))
assert.Equal(t, "Uniswap V3", registry.exchanges[math.ExchangeType("UniswapV3")].Name)
assert.Equal(t, "Balancer", registry.exchanges[math.ExchangeType("Balancer")].Name)
}
func TestTokenInfoDecimalHandling(t *testing.T) {
tests := []struct {
name string
decimals uint8
symbol string
}{
{"USDC (6 decimals)", 6, "USDC"},
{"USDT (6 decimals)", 6, "USDT"},
{"DAI (18 decimals)", 18, "DAI"},
{"WETH (18 decimals)", 18, "WETH"},
{"WBTC (8 decimals)", 8, "WBTC"},
{"Custom (12 decimals)", 12, "CUSTOM"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
token := TokenInfo{
Symbol: tt.symbol,
Decimals: tt.decimals,
}
assert.Equal(t, tt.decimals, token.Decimals)
assert.Equal(t, tt.symbol, token.Symbol)
})
}
}
func TestExchangeConfigSlippagePercentages(t *testing.T) {
tests := []struct {
name string
slippage float64
}{
{"Conservative (0.5%)", 0.005},
{"Standard (1%)", 0.01},
{"Moderate (3%)", 0.03},
{"Aggressive (5%)", 0.05},
{"Max (10%)", 0.10},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
config := &ExchangeConfig{
DefaultSlippagePercent: tt.slippage,
}
assert.Equal(t, tt.slippage, config.DefaultSlippagePercent)
assert.True(t, config.DefaultSlippagePercent > 0)
assert.True(t, config.DefaultSlippagePercent < 0.5) // Reasonable upper bound
})
}
}
func TestExchangeConfigArbitrumChainID(t *testing.T) {
config := &ExchangeConfig{
Name: "Arbitrum Exchange",
ChainID: 42161, // Arbitrum mainnet
}
assert.Equal(t, int64(42161), config.ChainID)
}
func TestTokenPairSwapped(t *testing.T) {
token0 := TokenInfo{
Address: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
Symbol: "USDC",
Decimals: 6,
}
token1 := TokenInfo{
Address: "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
Symbol: "WETH",
Decimals: 18,
}
pair := TokenPair{Token0: token0, Token1: token1}
swapped := TokenPair{Token0: token1, Token1: token0}
assert.NotEqual(t, pair.Token0.Symbol, swapped.Token0.Symbol)
assert.Equal(t, pair.Token0.Symbol, swapped.Token1.Symbol)
assert.Equal(t, pair.Token1.Symbol, swapped.Token0.Symbol)
}
// Mock implementations for interface testing
type mockPoolDetector struct{}
func (m *mockPoolDetector) GetAllPools(token0, token1 common.Address) ([]common.Address, error) {
return nil, nil
}
func (m *mockPoolDetector) GetPoolForPair(token0, token1 common.Address) (common.Address, error) {
return common.Address{}, nil
}
func (m *mockPoolDetector) GetSupportedFeeTiers() []int64 {
return nil
}
func (m *mockPoolDetector) GetPoolType() string {
return "mock"
}
type mockLiquidityFetcher struct{}
func (m *mockLiquidityFetcher) GetPoolData(poolAddress common.Address) (*math.PoolData, error) {
return nil, nil
}
func (m *mockLiquidityFetcher) GetTokenReserves(poolAddress, token0, token1 common.Address) (*big.Int, *big.Int, error) {
return nil, nil, nil
}
func (m *mockLiquidityFetcher) GetPoolPrice(poolAddress common.Address) (*big.Float, error) {
return nil, nil
}
func (m *mockLiquidityFetcher) GetLiquidityDepth(poolAddress, tokenIn common.Address, amount *big.Int) (*big.Int, error) {
return nil, nil
}
type mockSwapRouter struct{}
func (m *mockSwapRouter) CalculateSwap(tokenIn, tokenOut common.Address, amountIn *big.Int) (*big.Int, error) {
return nil, nil
}
func (m *mockSwapRouter) GenerateSwapData(tokenIn, tokenOut common.Address, amountIn, minAmountOut *big.Int, deadline *big.Int) ([]byte, error) {
return nil, nil
}
func (m *mockSwapRouter) GetSwapRoute(tokenIn, tokenOut common.Address) ([]common.Address, error) {
return nil, nil
}
func (m *mockSwapRouter) ValidateSwap(tokenIn, tokenOut common.Address, amountIn *big.Int) error {
return nil
}