Some checks failed
V2 CI/CD Pipeline / Pre-Flight Checks (push) Has been cancelled
V2 CI/CD Pipeline / Build & Dependencies (push) Has been cancelled
V2 CI/CD Pipeline / Code Quality & Linting (push) Has been cancelled
V2 CI/CD Pipeline / Unit Tests (100% Coverage Required) (push) Has been cancelled
V2 CI/CD Pipeline / Integration Tests (push) Has been cancelled
V2 CI/CD Pipeline / Performance Benchmarks (push) Has been cancelled
V2 CI/CD Pipeline / Decimal Precision Validation (push) Has been cancelled
V2 CI/CD Pipeline / Modularity Validation (push) Has been cancelled
V2 CI/CD Pipeline / Final Validation Summary (push) Has been cancelled
Add comprehensive unit tests for all execution engine components: Component Test Coverage: - UniswapV2 encoder: 15 test cases + benchmarks - UniswapV3 encoder: 20 test cases + benchmarks - Curve encoder: 16 test cases + benchmarks - Flashloan manager: 18 test cases + benchmarks - Transaction builder: 15 test cases + benchmarks - Risk manager: 25 test cases + benchmarks - Executor: 20 test cases + benchmarks Test Categories: - Happy path scenarios - Error handling and edge cases - Zero/invalid inputs - Boundary conditions (max amounts, limits) - Concurrent operations (nonce management) - Configuration validation - State management Key Test Features: - Protocol-specific encoding validation - ABI encoding correctness - Gas calculation accuracy - Slippage calculation - Nonce management thread safety - Circuit breaker behavior - Risk assessment rules - Transaction lifecycle Total: 129 test cases + performance benchmarks Target: 100% test coverage for execution engine Related to Phase 4 (Execution Engine) implementation. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
634 lines
18 KiB
Go
634 lines
18 KiB
Go
package execution
|
|
|
|
import (
|
|
"context"
|
|
"log/slog"
|
|
"math/big"
|
|
"os"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/ethereum/go-ethereum/common"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/your-org/mev-bot/pkg/arbitrage"
|
|
)
|
|
|
|
func TestDefaultRiskManagerConfig(t *testing.T) {
|
|
config := DefaultRiskManagerConfig()
|
|
|
|
assert.NotNil(t, config)
|
|
assert.True(t, config.Enabled)
|
|
assert.NotNil(t, config.MaxPositionSize)
|
|
assert.NotNil(t, config.MaxDailyVolume)
|
|
assert.NotNil(t, config.MinProfitThreshold)
|
|
assert.Equal(t, float64(0.01), config.MinROI)
|
|
assert.Equal(t, uint16(200), config.MaxSlippageBPS)
|
|
assert.Equal(t, uint64(5), config.MaxConcurrentTxs)
|
|
}
|
|
|
|
func TestNewRiskManager(t *testing.T) {
|
|
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
|
|
manager := NewRiskManager(nil, nil, logger)
|
|
|
|
assert.NotNil(t, manager)
|
|
assert.NotNil(t, manager.config)
|
|
assert.NotNil(t, manager.activeTxs)
|
|
assert.NotNil(t, manager.dailyVolume)
|
|
assert.NotNil(t, manager.recentFailures)
|
|
assert.False(t, manager.circuitBreakerOpen)
|
|
}
|
|
|
|
func TestRiskManager_AssessRisk_Success(t *testing.T) {
|
|
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
|
|
config := DefaultRiskManagerConfig()
|
|
config.SimulationEnabled = false // Disable simulation for unit test
|
|
|
|
manager := NewRiskManager(config, nil, logger)
|
|
|
|
opp := &arbitrage.Opportunity{
|
|
InputAmount: big.NewInt(1e18),
|
|
OutputAmount: big.NewInt(1.1e18),
|
|
NetProfit: big.NewInt(0.1e18),
|
|
ROI: 0.1, // 10%
|
|
EstimatedGas: 150000,
|
|
}
|
|
|
|
tx := &SwapTransaction{
|
|
MaxFeePerGas: big.NewInt(50e9), // 50 gwei
|
|
MaxPriorityFeePerGas: big.NewInt(2e9), // 2 gwei
|
|
GasLimit: 180000,
|
|
Slippage: 50, // 0.5%
|
|
}
|
|
|
|
assessment, err := manager.AssessRisk(context.Background(), opp, tx)
|
|
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, assessment)
|
|
assert.True(t, assessment.Approved)
|
|
assert.Empty(t, assessment.Warnings)
|
|
}
|
|
|
|
func TestRiskManager_AssessRisk_CircuitBreakerOpen(t *testing.T) {
|
|
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
|
|
config := DefaultRiskManagerConfig()
|
|
config.SimulationEnabled = false
|
|
|
|
manager := NewRiskManager(config, nil, logger)
|
|
|
|
// Open circuit breaker
|
|
manager.openCircuitBreaker()
|
|
|
|
opp := &arbitrage.Opportunity{
|
|
InputAmount: big.NewInt(1e18),
|
|
OutputAmount: big.NewInt(1.1e18),
|
|
NetProfit: big.NewInt(0.1e18),
|
|
ROI: 0.1,
|
|
EstimatedGas: 150000,
|
|
}
|
|
|
|
tx := &SwapTransaction{
|
|
MaxFeePerGas: big.NewInt(50e9),
|
|
MaxPriorityFeePerGas: big.NewInt(2e9),
|
|
GasLimit: 180000,
|
|
Slippage: 50,
|
|
}
|
|
|
|
assessment, err := manager.AssessRisk(context.Background(), opp, tx)
|
|
|
|
require.NoError(t, err)
|
|
assert.False(t, assessment.Approved)
|
|
assert.Contains(t, assessment.Reason, "circuit breaker")
|
|
}
|
|
|
|
func TestRiskManager_AssessRisk_PositionSizeExceeded(t *testing.T) {
|
|
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
|
|
config := DefaultRiskManagerConfig()
|
|
config.SimulationEnabled = false
|
|
|
|
manager := NewRiskManager(config, nil, logger)
|
|
|
|
// Create opportunity with amount exceeding max position size
|
|
largeAmount := new(big.Int).Add(config.MaxPositionSize, big.NewInt(1))
|
|
|
|
opp := &arbitrage.Opportunity{
|
|
InputAmount: largeAmount,
|
|
OutputAmount: new(big.Int).Mul(largeAmount, big.NewInt(11)),
|
|
NetProfit: big.NewInt(1e18),
|
|
ROI: 0.1,
|
|
EstimatedGas: 150000,
|
|
}
|
|
|
|
tx := &SwapTransaction{
|
|
MaxFeePerGas: big.NewInt(50e9),
|
|
MaxPriorityFeePerGas: big.NewInt(2e9),
|
|
GasLimit: 180000,
|
|
Slippage: 50,
|
|
}
|
|
|
|
assessment, err := manager.AssessRisk(context.Background(), opp, tx)
|
|
|
|
require.NoError(t, err)
|
|
assert.False(t, assessment.Approved)
|
|
assert.Contains(t, assessment.Reason, "position size")
|
|
}
|
|
|
|
func TestRiskManager_AssessRisk_DailyVolumeExceeded(t *testing.T) {
|
|
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
|
|
config := DefaultRiskManagerConfig()
|
|
config.SimulationEnabled = false
|
|
|
|
manager := NewRiskManager(config, nil, logger)
|
|
|
|
// Set daily volume to max
|
|
manager.dailyVolume = config.MaxDailyVolume
|
|
|
|
opp := &arbitrage.Opportunity{
|
|
InputAmount: big.NewInt(1e18),
|
|
OutputAmount: big.NewInt(1.1e18),
|
|
NetProfit: big.NewInt(0.1e18),
|
|
ROI: 0.1,
|
|
EstimatedGas: 150000,
|
|
}
|
|
|
|
tx := &SwapTransaction{
|
|
MaxFeePerGas: big.NewInt(50e9),
|
|
MaxPriorityFeePerGas: big.NewInt(2e9),
|
|
GasLimit: 180000,
|
|
Slippage: 50,
|
|
}
|
|
|
|
assessment, err := manager.AssessRisk(context.Background(), opp, tx)
|
|
|
|
require.NoError(t, err)
|
|
assert.False(t, assessment.Approved)
|
|
assert.Contains(t, assessment.Reason, "daily volume")
|
|
}
|
|
|
|
func TestRiskManager_AssessRisk_GasPriceTooHigh(t *testing.T) {
|
|
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
|
|
config := DefaultRiskManagerConfig()
|
|
config.SimulationEnabled = false
|
|
|
|
manager := NewRiskManager(config, nil, logger)
|
|
|
|
opp := &arbitrage.Opportunity{
|
|
InputAmount: big.NewInt(1e18),
|
|
OutputAmount: big.NewInt(1.1e18),
|
|
NetProfit: big.NewInt(0.1e18),
|
|
ROI: 0.1,
|
|
EstimatedGas: 150000,
|
|
}
|
|
|
|
// Set gas price above max
|
|
tx := &SwapTransaction{
|
|
MaxFeePerGas: new(big.Int).Add(config.MaxGasPrice, big.NewInt(1)),
|
|
MaxPriorityFeePerGas: big.NewInt(2e9),
|
|
GasLimit: 180000,
|
|
Slippage: 50,
|
|
}
|
|
|
|
assessment, err := manager.AssessRisk(context.Background(), opp, tx)
|
|
|
|
require.NoError(t, err)
|
|
assert.False(t, assessment.Approved)
|
|
assert.Contains(t, assessment.Reason, "gas price")
|
|
}
|
|
|
|
func TestRiskManager_AssessRisk_ProfitTooLow(t *testing.T) {
|
|
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
|
|
config := DefaultRiskManagerConfig()
|
|
config.SimulationEnabled = false
|
|
|
|
manager := NewRiskManager(config, nil, logger)
|
|
|
|
// Profit below threshold
|
|
lowProfit := new(big.Int).Sub(config.MinProfitThreshold, big.NewInt(1))
|
|
|
|
opp := &arbitrage.Opportunity{
|
|
InputAmount: big.NewInt(1e18),
|
|
OutputAmount: new(big.Int).Add(big.NewInt(1e18), lowProfit),
|
|
NetProfit: lowProfit,
|
|
ROI: 0.00001,
|
|
EstimatedGas: 150000,
|
|
}
|
|
|
|
tx := &SwapTransaction{
|
|
MaxFeePerGas: big.NewInt(50e9),
|
|
MaxPriorityFeePerGas: big.NewInt(2e9),
|
|
GasLimit: 180000,
|
|
Slippage: 50,
|
|
}
|
|
|
|
assessment, err := manager.AssessRisk(context.Background(), opp, tx)
|
|
|
|
require.NoError(t, err)
|
|
assert.False(t, assessment.Approved)
|
|
assert.Contains(t, assessment.Reason, "profit")
|
|
}
|
|
|
|
func TestRiskManager_AssessRisk_ROITooLow(t *testing.T) {
|
|
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
|
|
config := DefaultRiskManagerConfig()
|
|
config.SimulationEnabled = false
|
|
|
|
manager := NewRiskManager(config, nil, logger)
|
|
|
|
opp := &arbitrage.Opportunity{
|
|
InputAmount: big.NewInt(1e18),
|
|
OutputAmount: big.NewInt(1.005e18),
|
|
NetProfit: big.NewInt(0.005e18), // 0.5% ROI, below 1% threshold
|
|
ROI: 0.005,
|
|
EstimatedGas: 150000,
|
|
}
|
|
|
|
tx := &SwapTransaction{
|
|
MaxFeePerGas: big.NewInt(50e9),
|
|
MaxPriorityFeePerGas: big.NewInt(2e9),
|
|
GasLimit: 180000,
|
|
Slippage: 50,
|
|
}
|
|
|
|
assessment, err := manager.AssessRisk(context.Background(), opp, tx)
|
|
|
|
require.NoError(t, err)
|
|
assert.False(t, assessment.Approved)
|
|
assert.Contains(t, assessment.Reason, "ROI")
|
|
}
|
|
|
|
func TestRiskManager_AssessRisk_SlippageTooHigh(t *testing.T) {
|
|
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
|
|
config := DefaultRiskManagerConfig()
|
|
config.SimulationEnabled = false
|
|
|
|
manager := NewRiskManager(config, nil, logger)
|
|
|
|
opp := &arbitrage.Opportunity{
|
|
InputAmount: big.NewInt(1e18),
|
|
OutputAmount: big.NewInt(1.1e18),
|
|
NetProfit: big.NewInt(0.1e18),
|
|
ROI: 0.1,
|
|
EstimatedGas: 150000,
|
|
}
|
|
|
|
tx := &SwapTransaction{
|
|
MaxFeePerGas: big.NewInt(50e9),
|
|
MaxPriorityFeePerGas: big.NewInt(2e9),
|
|
GasLimit: 180000,
|
|
Slippage: 300, // 3%, above 2% max
|
|
}
|
|
|
|
assessment, err := manager.AssessRisk(context.Background(), opp, tx)
|
|
|
|
require.NoError(t, err)
|
|
assert.False(t, assessment.Approved)
|
|
assert.Contains(t, assessment.Reason, "slippage")
|
|
}
|
|
|
|
func TestRiskManager_AssessRisk_ConcurrentLimitExceeded(t *testing.T) {
|
|
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
|
|
config := DefaultRiskManagerConfig()
|
|
config.SimulationEnabled = false
|
|
config.MaxConcurrentTxs = 2
|
|
|
|
manager := NewRiskManager(config, nil, logger)
|
|
|
|
// Add max concurrent transactions
|
|
manager.activeTxs[common.HexToHash("0x01")] = &ActiveTransaction{}
|
|
manager.activeTxs[common.HexToHash("0x02")] = &ActiveTransaction{}
|
|
|
|
opp := &arbitrage.Opportunity{
|
|
InputAmount: big.NewInt(1e18),
|
|
OutputAmount: big.NewInt(1.1e18),
|
|
NetProfit: big.NewInt(0.1e18),
|
|
ROI: 0.1,
|
|
EstimatedGas: 150000,
|
|
}
|
|
|
|
tx := &SwapTransaction{
|
|
MaxFeePerGas: big.NewInt(50e9),
|
|
MaxPriorityFeePerGas: big.NewInt(2e9),
|
|
GasLimit: 180000,
|
|
Slippage: 50,
|
|
}
|
|
|
|
assessment, err := manager.AssessRisk(context.Background(), opp, tx)
|
|
|
|
require.NoError(t, err)
|
|
assert.False(t, assessment.Approved)
|
|
assert.Contains(t, assessment.Reason, "concurrent")
|
|
}
|
|
|
|
func TestRiskManager_TrackTransaction(t *testing.T) {
|
|
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
|
|
manager := NewRiskManager(nil, nil, logger)
|
|
|
|
hash := common.HexToHash("0x123")
|
|
opp := &arbitrage.Opportunity{
|
|
InputAmount: big.NewInt(1e18),
|
|
NetProfit: big.NewInt(0.1e18),
|
|
}
|
|
gasPrice := big.NewInt(50e9)
|
|
|
|
manager.TrackTransaction(hash, opp, gasPrice)
|
|
|
|
// Check transaction is tracked
|
|
manager.mu.RLock()
|
|
tx, exists := manager.activeTxs[hash]
|
|
manager.mu.RUnlock()
|
|
|
|
assert.True(t, exists)
|
|
assert.NotNil(t, tx)
|
|
assert.Equal(t, hash, tx.Hash)
|
|
assert.Equal(t, opp.InputAmount, tx.Amount)
|
|
assert.Equal(t, gasPrice, tx.GasPrice)
|
|
}
|
|
|
|
func TestRiskManager_UntrackTransaction(t *testing.T) {
|
|
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
|
|
manager := NewRiskManager(nil, nil, logger)
|
|
|
|
hash := common.HexToHash("0x123")
|
|
manager.activeTxs[hash] = &ActiveTransaction{Hash: hash}
|
|
|
|
manager.UntrackTransaction(hash)
|
|
|
|
// Check transaction is no longer tracked
|
|
manager.mu.RLock()
|
|
_, exists := manager.activeTxs[hash]
|
|
manager.mu.RUnlock()
|
|
|
|
assert.False(t, exists)
|
|
}
|
|
|
|
func TestRiskManager_RecordFailure(t *testing.T) {
|
|
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
|
|
config := DefaultRiskManagerConfig()
|
|
config.CircuitBreakerFailures = 3
|
|
config.CircuitBreakerWindow = 1 * time.Minute
|
|
|
|
manager := NewRiskManager(config, nil, logger)
|
|
|
|
hash := common.HexToHash("0x123")
|
|
|
|
// Record failures below threshold
|
|
manager.RecordFailure(hash, "test failure 1")
|
|
assert.False(t, manager.circuitBreakerOpen)
|
|
|
|
manager.RecordFailure(hash, "test failure 2")
|
|
assert.False(t, manager.circuitBreakerOpen)
|
|
|
|
// Third failure should open circuit breaker
|
|
manager.RecordFailure(hash, "test failure 3")
|
|
assert.True(t, manager.circuitBreakerOpen)
|
|
}
|
|
|
|
func TestRiskManager_RecordSuccess(t *testing.T) {
|
|
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
|
|
manager := NewRiskManager(nil, nil, logger)
|
|
|
|
hash := common.HexToHash("0x123")
|
|
actualProfit := big.NewInt(0.1e18)
|
|
|
|
manager.RecordSuccess(hash, actualProfit)
|
|
|
|
// Check that recent failures were cleared
|
|
assert.Empty(t, manager.recentFailures)
|
|
}
|
|
|
|
func TestRiskManager_CircuitBreaker_Cooldown(t *testing.T) {
|
|
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
|
|
config := DefaultRiskManagerConfig()
|
|
config.CircuitBreakerCooldown = 1 * time.Millisecond
|
|
|
|
manager := NewRiskManager(config, nil, logger)
|
|
|
|
// Open circuit breaker
|
|
manager.openCircuitBreaker()
|
|
assert.True(t, manager.circuitBreakerOpen)
|
|
|
|
// Wait for cooldown
|
|
time.Sleep(2 * time.Millisecond)
|
|
|
|
// Circuit breaker should be closed after cooldown
|
|
assert.False(t, manager.checkCircuitBreaker())
|
|
}
|
|
|
|
func TestRiskManager_checkConcurrentLimit(t *testing.T) {
|
|
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
|
|
config := DefaultRiskManagerConfig()
|
|
config.MaxConcurrentTxs = 3
|
|
|
|
manager := NewRiskManager(config, nil, logger)
|
|
|
|
// Add transactions below limit
|
|
manager.activeTxs[common.HexToHash("0x01")] = &ActiveTransaction{}
|
|
manager.activeTxs[common.HexToHash("0x02")] = &ActiveTransaction{}
|
|
|
|
assert.True(t, manager.checkConcurrentLimit())
|
|
|
|
// Add transaction at limit
|
|
manager.activeTxs[common.HexToHash("0x03")] = &ActiveTransaction{}
|
|
|
|
assert.False(t, manager.checkConcurrentLimit())
|
|
}
|
|
|
|
func TestRiskManager_checkPositionSize(t *testing.T) {
|
|
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
|
|
config := DefaultRiskManagerConfig()
|
|
config.MaxPositionSize = big.NewInt(10e18)
|
|
|
|
manager := NewRiskManager(config, nil, logger)
|
|
|
|
assert.True(t, manager.checkPositionSize(big.NewInt(5e18)))
|
|
assert.True(t, manager.checkPositionSize(big.NewInt(10e18)))
|
|
assert.False(t, manager.checkPositionSize(big.NewInt(11e18)))
|
|
}
|
|
|
|
func TestRiskManager_checkDailyVolume(t *testing.T) {
|
|
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
|
|
config := DefaultRiskManagerConfig()
|
|
config.MaxDailyVolume = big.NewInt(100e18)
|
|
|
|
manager := NewRiskManager(config, nil, logger)
|
|
manager.dailyVolume = big.NewInt(90e18)
|
|
|
|
assert.True(t, manager.checkDailyVolume(big.NewInt(5e18)))
|
|
assert.True(t, manager.checkDailyVolume(big.NewInt(10e18)))
|
|
assert.False(t, manager.checkDailyVolume(big.NewInt(15e18)))
|
|
}
|
|
|
|
func TestRiskManager_updateDailyVolume(t *testing.T) {
|
|
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
|
|
manager := NewRiskManager(nil, nil, logger)
|
|
|
|
initialVolume := big.NewInt(10e18)
|
|
manager.dailyVolume = initialVolume
|
|
|
|
addAmount := big.NewInt(5e18)
|
|
manager.updateDailyVolume(addAmount)
|
|
|
|
expectedVolume := new(big.Int).Add(initialVolume, addAmount)
|
|
assert.Equal(t, expectedVolume, manager.dailyVolume)
|
|
}
|
|
|
|
func TestRiskManager_checkGasPrice(t *testing.T) {
|
|
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
|
|
config := DefaultRiskManagerConfig()
|
|
config.MaxGasPrice = big.NewInt(100e9) // 100 gwei
|
|
|
|
manager := NewRiskManager(config, nil, logger)
|
|
|
|
assert.True(t, manager.checkGasPrice(big.NewInt(50e9)))
|
|
assert.True(t, manager.checkGasPrice(big.NewInt(100e9)))
|
|
assert.False(t, manager.checkGasPrice(big.NewInt(101e9)))
|
|
}
|
|
|
|
func TestRiskManager_checkGasCost(t *testing.T) {
|
|
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
|
|
config := DefaultRiskManagerConfig()
|
|
config.MaxGasCost = big.NewInt(0.1e18) // 0.1 ETH
|
|
|
|
manager := NewRiskManager(config, nil, logger)
|
|
|
|
assert.True(t, manager.checkGasCost(big.NewInt(0.05e18)))
|
|
assert.True(t, manager.checkGasCost(big.NewInt(0.1e18)))
|
|
assert.False(t, manager.checkGasCost(big.NewInt(0.11e18)))
|
|
}
|
|
|
|
func TestRiskManager_checkMinProfit(t *testing.T) {
|
|
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
|
|
config := DefaultRiskManagerConfig()
|
|
config.MinProfitThreshold = big.NewInt(0.01e18) // 0.01 ETH
|
|
|
|
manager := NewRiskManager(config, nil, logger)
|
|
|
|
assert.False(t, manager.checkMinProfit(big.NewInt(0.005e18)))
|
|
assert.True(t, manager.checkMinProfit(big.NewInt(0.01e18)))
|
|
assert.True(t, manager.checkMinProfit(big.NewInt(0.02e18)))
|
|
}
|
|
|
|
func TestRiskManager_checkMinROI(t *testing.T) {
|
|
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
|
|
config := DefaultRiskManagerConfig()
|
|
config.MinROI = 0.01 // 1%
|
|
|
|
manager := NewRiskManager(config, nil, logger)
|
|
|
|
assert.False(t, manager.checkMinROI(0.005)) // 0.5%
|
|
assert.True(t, manager.checkMinROI(0.01)) // 1%
|
|
assert.True(t, manager.checkMinROI(0.02)) // 2%
|
|
}
|
|
|
|
func TestRiskManager_checkSlippage(t *testing.T) {
|
|
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
|
|
config := DefaultRiskManagerConfig()
|
|
config.MaxSlippageBPS = 200 // 2%
|
|
|
|
manager := NewRiskManager(config, nil, logger)
|
|
|
|
assert.True(t, manager.checkSlippage(100)) // 1%
|
|
assert.True(t, manager.checkSlippage(200)) // 2%
|
|
assert.False(t, manager.checkSlippage(300)) // 3%
|
|
}
|
|
|
|
func TestRiskManager_GetActiveTransactions(t *testing.T) {
|
|
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
|
|
manager := NewRiskManager(nil, nil, logger)
|
|
|
|
// Add some active transactions
|
|
manager.activeTxs[common.HexToHash("0x01")] = &ActiveTransaction{Hash: common.HexToHash("0x01")}
|
|
manager.activeTxs[common.HexToHash("0x02")] = &ActiveTransaction{Hash: common.HexToHash("0x02")}
|
|
|
|
activeTxs := manager.GetActiveTransactions()
|
|
|
|
assert.Len(t, activeTxs, 2)
|
|
}
|
|
|
|
func TestRiskManager_GetStats(t *testing.T) {
|
|
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
|
|
manager := NewRiskManager(nil, nil, logger)
|
|
|
|
// Add some state
|
|
manager.activeTxs[common.HexToHash("0x01")] = &ActiveTransaction{}
|
|
manager.dailyVolume = big.NewInt(50e18)
|
|
manager.circuitBreakerOpen = true
|
|
|
|
stats := manager.GetStats()
|
|
|
|
assert.NotNil(t, stats)
|
|
assert.Equal(t, 1, stats["active_transactions"])
|
|
assert.Equal(t, "50000000000000000000", stats["daily_volume"])
|
|
assert.Equal(t, true, stats["circuit_breaker_open"])
|
|
}
|
|
|
|
func TestRiskManager_AssessRisk_WithWarnings(t *testing.T) {
|
|
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
|
|
config := DefaultRiskManagerConfig()
|
|
config.SimulationEnabled = false
|
|
|
|
manager := NewRiskManager(config, nil, logger)
|
|
|
|
// Create opportunity with high gas cost (should generate warning)
|
|
opp := &arbitrage.Opportunity{
|
|
InputAmount: big.NewInt(1e18),
|
|
OutputAmount: big.NewInt(1.1e18),
|
|
NetProfit: big.NewInt(0.1e18),
|
|
ROI: 0.1,
|
|
EstimatedGas: 2000000, // Very high gas
|
|
}
|
|
|
|
tx := &SwapTransaction{
|
|
MaxFeePerGas: big.NewInt(50e9),
|
|
MaxPriorityFeePerGas: big.NewInt(2e9),
|
|
GasLimit: 2400000,
|
|
Slippage: 50,
|
|
}
|
|
|
|
assessment, err := manager.AssessRisk(context.Background(), opp, tx)
|
|
|
|
require.NoError(t, err)
|
|
assert.True(t, assessment.Approved) // Should still be approved
|
|
assert.NotEmpty(t, assessment.Warnings) // But with warnings
|
|
}
|
|
|
|
// Benchmark tests
|
|
func BenchmarkRiskManager_AssessRisk(b *testing.B) {
|
|
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
|
|
config := DefaultRiskManagerConfig()
|
|
config.SimulationEnabled = false
|
|
|
|
manager := NewRiskManager(config, nil, logger)
|
|
|
|
opp := &arbitrage.Opportunity{
|
|
InputAmount: big.NewInt(1e18),
|
|
OutputAmount: big.NewInt(1.1e18),
|
|
NetProfit: big.NewInt(0.1e18),
|
|
ROI: 0.1,
|
|
EstimatedGas: 150000,
|
|
}
|
|
|
|
tx := &SwapTransaction{
|
|
MaxFeePerGas: big.NewInt(50e9),
|
|
MaxPriorityFeePerGas: big.NewInt(2e9),
|
|
GasLimit: 180000,
|
|
Slippage: 50,
|
|
}
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
_, _ = manager.AssessRisk(context.Background(), opp, tx)
|
|
}
|
|
}
|
|
|
|
func BenchmarkRiskManager_checkCircuitBreaker(b *testing.B) {
|
|
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
|
|
manager := NewRiskManager(nil, nil, logger)
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
_ = manager.checkCircuitBreaker()
|
|
}
|
|
}
|