fix(multicall): resolve critical multicall parsing corruption issues
- 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>
This commit is contained in:
7
tools/profitability-audit/go.mod
Normal file
7
tools/profitability-audit/go.mod
Normal file
@@ -0,0 +1,7 @@
|
||||
module github.com/fraktal/mev-beta/tools/profitability-audit
|
||||
|
||||
go 1.24
|
||||
|
||||
replace github.com/fraktal/mev-beta => ../../
|
||||
|
||||
require github.com/fraktal/mev-beta v0.0.0-00010101000000-000000000000
|
||||
511
tools/profitability-audit/internal/profitability_auditor.go
Normal file
511
tools/profitability-audit/internal/profitability_auditor.go
Normal file
@@ -0,0 +1,511 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/fraktal/mev-beta/pkg/math"
|
||||
)
|
||||
|
||||
// Config holds configuration for the profitability auditor
|
||||
type Config struct {
|
||||
MinProfitBP float64
|
||||
MaxSlippageBP float64
|
||||
OutputDir string
|
||||
Verbose bool
|
||||
ScenariosFile string
|
||||
}
|
||||
|
||||
// ProfitabilityAuditor audits profit calculations and arbitrage opportunities
|
||||
type ProfitabilityAuditor struct {
|
||||
config *Config
|
||||
converter *math.DecimalConverter
|
||||
calculator *math.ArbitrageCalculator
|
||||
scenarios *ProfitabilityScenarios
|
||||
results map[string]*ExchangeProfitResult
|
||||
}
|
||||
|
||||
// ProfitabilityScenarios defines test scenarios for profit validation
|
||||
type ProfitabilityScenarios struct {
|
||||
Version string `json:"version"`
|
||||
Scenarios map[string]*ProfitabilityTest `json:"scenarios"`
|
||||
}
|
||||
|
||||
// ProfitabilityTest represents a profit calculation test case
|
||||
type ProfitabilityTest struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Exchange string `json:"exchange"`
|
||||
AmountIn string `json:"amount_in"`
|
||||
TokenIn string `json:"token_in"`
|
||||
TokenOut string `json:"token_out"`
|
||||
ExpectedProfit string `json:"expected_profit"`
|
||||
MaxSlippage float64 `json:"max_slippage"`
|
||||
GasCost string `json:"gas_cost"`
|
||||
MinROI float64 `json:"min_roi"`
|
||||
TestType string `json:"test_type"` // "static", "stress", "edge_case"
|
||||
}
|
||||
|
||||
// ExchangeProfitResult holds profitability audit results for an exchange
|
||||
type ExchangeProfitResult struct {
|
||||
Exchange string `json:"exchange"`
|
||||
TotalTests int `json:"total_tests"`
|
||||
PassedTests int `json:"passed_tests"`
|
||||
FailedTests int `json:"failed_tests"`
|
||||
AverageProfitBP float64 `json:"average_profit_bp"`
|
||||
MaxProfitBP float64 `json:"max_profit_bp"`
|
||||
MinProfitBP float64 `json:"min_profit_bp"`
|
||||
AverageROI float64 `json:"average_roi"`
|
||||
TestResults []*ProfitTestResult `json:"test_results"`
|
||||
FailedCases []*ProfitTestFailure `json:"failed_cases"`
|
||||
Duration time.Duration `json:"duration"`
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
}
|
||||
|
||||
// ProfitTestResult represents the result of a single profit test
|
||||
type ProfitTestResult struct {
|
||||
TestName string `json:"test_name"`
|
||||
Passed bool `json:"passed"`
|
||||
ActualProfitBP float64 `json:"actual_profit_bp"`
|
||||
ExpectedProfitBP float64 `json:"expected_profit_bp"`
|
||||
ActualROI float64 `json:"actual_roi"`
|
||||
ExpectedROI float64 `json:"expected_roi"`
|
||||
SlippageBP float64 `json:"slippage_bp"`
|
||||
GasCostETH string `json:"gas_cost_eth"`
|
||||
NetProfitETH string `json:"net_profit_eth"`
|
||||
Duration time.Duration `json:"duration"`
|
||||
ErrorMessage string `json:"error_message,omitempty"`
|
||||
}
|
||||
|
||||
// ProfitTestFailure represents a failed profit test
|
||||
type ProfitTestFailure struct {
|
||||
TestName string `json:"test_name"`
|
||||
Reason string `json:"reason"`
|
||||
Expected float64 `json:"expected"`
|
||||
Actual float64 `json:"actual"`
|
||||
Difference float64 `json:"difference"`
|
||||
Severity string `json:"severity"` // "low", "medium", "high", "critical"
|
||||
}
|
||||
|
||||
// NewProfitabilityAuditor creates a new profitability auditor
|
||||
func NewProfitabilityAuditor(config *Config) (*ProfitabilityAuditor, error) {
|
||||
converter := math.NewDecimalConverter()
|
||||
calculator := math.NewArbitrageCalculator(nil) // TODO: Add proper gas estimator
|
||||
|
||||
// Load test scenarios
|
||||
scenarios, err := loadProfitabilityScenarios(config.ScenariosFile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load scenarios: %w", err)
|
||||
}
|
||||
|
||||
return &ProfitabilityAuditor{
|
||||
config: config,
|
||||
converter: converter,
|
||||
calculator: calculator,
|
||||
scenarios: scenarios,
|
||||
results: make(map[string]*ExchangeProfitResult),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// AuditExchange performs profitability audit for a specific exchange
|
||||
func (pa *ProfitabilityAuditor) AuditExchange(ctx context.Context, exchange string) error {
|
||||
startTime := time.Now()
|
||||
|
||||
if pa.config.Verbose {
|
||||
fmt.Printf("Starting profitability audit for %s...\n", exchange)
|
||||
}
|
||||
|
||||
result := &ExchangeProfitResult{
|
||||
Exchange: exchange,
|
||||
TestResults: []*ProfitTestResult{},
|
||||
FailedCases: []*ProfitTestFailure{},
|
||||
Timestamp: startTime,
|
||||
}
|
||||
|
||||
// Get scenarios for this exchange
|
||||
exchangeScenarios := pa.getExchangeScenarios(exchange)
|
||||
result.TotalTests = len(exchangeScenarios)
|
||||
|
||||
var totalProfitBP, totalROI float64
|
||||
maxProfitBP := -1000.0
|
||||
minProfitBP := 1000.0
|
||||
|
||||
for _, scenario := range exchangeScenarios {
|
||||
testResult, err := pa.runProfitTest(ctx, scenario)
|
||||
if err != nil {
|
||||
if pa.config.Verbose {
|
||||
fmt.Printf(" Failed test %s: %v\n", scenario.Name, err)
|
||||
}
|
||||
|
||||
failure := &ProfitTestFailure{
|
||||
TestName: scenario.Name,
|
||||
Reason: err.Error(),
|
||||
Severity: "high",
|
||||
}
|
||||
result.FailedCases = append(result.FailedCases, failure)
|
||||
result.FailedTests++
|
||||
continue
|
||||
}
|
||||
|
||||
result.TestResults = append(result.TestResults, testResult)
|
||||
|
||||
if testResult.Passed {
|
||||
result.PassedTests++
|
||||
totalProfitBP += testResult.ActualProfitBP
|
||||
totalROI += testResult.ActualROI
|
||||
|
||||
if testResult.ActualProfitBP > maxProfitBP {
|
||||
maxProfitBP = testResult.ActualProfitBP
|
||||
}
|
||||
if testResult.ActualProfitBP < minProfitBP {
|
||||
minProfitBP = testResult.ActualProfitBP
|
||||
}
|
||||
} else {
|
||||
result.FailedTests++
|
||||
|
||||
failure := &ProfitTestFailure{
|
||||
TestName: scenario.Name,
|
||||
Reason: testResult.ErrorMessage,
|
||||
Expected: testResult.ExpectedProfitBP,
|
||||
Actual: testResult.ActualProfitBP,
|
||||
Difference: testResult.ActualProfitBP - testResult.ExpectedProfitBP,
|
||||
Severity: pa.calculateSeverity(testResult.ActualProfitBP, testResult.ExpectedProfitBP),
|
||||
}
|
||||
result.FailedCases = append(result.FailedCases, failure)
|
||||
}
|
||||
|
||||
if pa.config.Verbose {
|
||||
status := "✓"
|
||||
if !testResult.Passed {
|
||||
status = "✗"
|
||||
}
|
||||
fmt.Printf(" %s %s: Profit=%.2f bp, ROI=%.2f%%\n",
|
||||
status, scenario.Name, testResult.ActualProfitBP, testResult.ActualROI)
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate averages
|
||||
if result.PassedTests > 0 {
|
||||
result.AverageProfitBP = totalProfitBP / float64(result.PassedTests)
|
||||
result.AverageROI = totalROI / float64(result.PassedTests)
|
||||
result.MaxProfitBP = maxProfitBP
|
||||
result.MinProfitBP = minProfitBP
|
||||
}
|
||||
|
||||
result.Duration = time.Since(startTime)
|
||||
pa.results[exchange] = result
|
||||
|
||||
if pa.config.Verbose {
|
||||
fmt.Printf("Completed %s audit: %d/%d tests passed (%.1f%%)\n",
|
||||
exchange, result.PassedTests, result.TotalTests,
|
||||
float64(result.PassedTests)/float64(result.TotalTests)*100)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// runProfitTest executes a single profit test
|
||||
func (pa *ProfitabilityAuditor) runProfitTest(ctx context.Context, scenario *ProfitabilityTest) (*ProfitTestResult, error) {
|
||||
startTime := time.Now()
|
||||
|
||||
// Convert input amounts to UniversalDecimal
|
||||
amountIn, err := pa.converter.FromString(scenario.AmountIn, 18, scenario.TokenIn)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid amount_in: %w", err)
|
||||
}
|
||||
|
||||
gasCost, err := pa.converter.FromString(scenario.GasCost, 18, "ETH")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid gas_cost: %w", err)
|
||||
}
|
||||
|
||||
expectedProfit, err := pa.converter.FromString(scenario.ExpectedProfit, 18, "ETH")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid expected_profit: %w", err)
|
||||
}
|
||||
|
||||
// Simulate arbitrage calculation
|
||||
// TODO: Integrate with actual exchange data and calculation engine
|
||||
actualProfit, netProfit, slippage, err := pa.simulateProfitCalculation(scenario, amountIn, gasCost)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("profit simulation failed: %w", err)
|
||||
}
|
||||
|
||||
// Calculate metrics
|
||||
actualProfitBP := pa.calculateProfitBP(actualProfit, amountIn)
|
||||
expectedProfitBP := pa.calculateProfitBP(expectedProfit, amountIn)
|
||||
actualROI := actualProfitBP / 100.0 // Convert BP to percentage
|
||||
|
||||
// Determine if test passed
|
||||
profitDiff := actualProfitBP - expectedProfitBP
|
||||
profitPassed := actualProfitBP >= pa.config.MinProfitBP
|
||||
slippagePassed := slippage <= pa.config.MaxSlippageBP
|
||||
roiPassed := actualROI >= scenario.MinROI
|
||||
|
||||
passed := profitPassed && slippagePassed && roiPassed
|
||||
|
||||
result := &ProfitTestResult{
|
||||
TestName: scenario.Name,
|
||||
Passed: passed,
|
||||
ActualProfitBP: actualProfitBP,
|
||||
ExpectedProfitBP: expectedProfitBP,
|
||||
ActualROI: actualROI,
|
||||
ExpectedROI: scenario.MinROI,
|
||||
SlippageBP: slippage,
|
||||
GasCostETH: pa.converter.ToHumanReadable(gasCost),
|
||||
NetProfitETH: pa.converter.ToHumanReadable(netProfit),
|
||||
Duration: time.Since(startTime),
|
||||
}
|
||||
|
||||
if !passed {
|
||||
var reasons []string
|
||||
if !profitPassed {
|
||||
reasons = append(reasons, fmt.Sprintf("profit %.2f bp below minimum %.2f bp", actualProfitBP, pa.config.MinProfitBP))
|
||||
}
|
||||
if !slippagePassed {
|
||||
reasons = append(reasons, fmt.Sprintf("slippage %.2f bp exceeds maximum %.2f bp", slippage, pa.config.MaxSlippageBP))
|
||||
}
|
||||
if !roiPassed {
|
||||
reasons = append(reasons, fmt.Sprintf("ROI %.2f%% below minimum %.2f%%", actualROI, scenario.MinROI))
|
||||
}
|
||||
result.ErrorMessage = fmt.Sprintf("Test failed: %v", reasons)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// simulateProfitCalculation simulates profit calculation for a scenario
|
||||
func (pa *ProfitabilityAuditor) simulateProfitCalculation(scenario *ProfitabilityTest, amountIn, gasCost *math.UniversalDecimal) (*math.UniversalDecimal, *math.UniversalDecimal, float64, error) {
|
||||
// This is a simplified simulation - in production, this would integrate with real exchange APIs
|
||||
|
||||
// Simulate price impact and slippage based on exchange type
|
||||
var slippageBP float64
|
||||
var profitMultiplier float64
|
||||
|
||||
switch scenario.Exchange {
|
||||
case "uniswap_v2":
|
||||
slippageBP = 15.0 // Simulate 15 bp slippage
|
||||
profitMultiplier = 1.025 // 2.5% profit
|
||||
case "uniswap_v3":
|
||||
slippageBP = 8.0 // Lower slippage for V3
|
||||
profitMultiplier = 1.032 // 3.2% profit
|
||||
case "curve":
|
||||
slippageBP = 3.0 // Very low slippage for stable swaps
|
||||
profitMultiplier = 1.008 // 0.8% profit
|
||||
case "balancer":
|
||||
slippageBP = 25.0 // Higher slippage for weighted pools
|
||||
profitMultiplier = 1.045 // 4.5% profit
|
||||
default:
|
||||
return nil, nil, 0, fmt.Errorf("unsupported exchange: %s", scenario.Exchange)
|
||||
}
|
||||
|
||||
// Calculate gross profit
|
||||
profitAmount := new(big.Int).Mul(amountIn.Value, big.NewInt(int64(profitMultiplier*1000)))
|
||||
profitAmount.Div(profitAmount, big.NewInt(1000))
|
||||
profitAmount.Sub(profitAmount, amountIn.Value)
|
||||
|
||||
grossProfit, err := math.NewUniversalDecimal(profitAmount, 18, "ETH")
|
||||
if err != nil {
|
||||
return nil, nil, 0, err
|
||||
}
|
||||
|
||||
// Calculate net profit (gross - gas)
|
||||
netProfitAmount := new(big.Int).Sub(grossProfit.Value, gasCost.Value)
|
||||
netProfit, err := math.NewUniversalDecimal(netProfitAmount, 18, "ETH")
|
||||
if err != nil {
|
||||
return nil, nil, 0, err
|
||||
}
|
||||
|
||||
return grossProfit, netProfit, slippageBP, nil
|
||||
}
|
||||
|
||||
// calculateProfitBP calculates profit in basis points
|
||||
func (pa *ProfitabilityAuditor) calculateProfitBP(profit, amountIn *math.UniversalDecimal) float64 {
|
||||
if amountIn.Value.Cmp(big.NewInt(0)) == 0 {
|
||||
return 0.0
|
||||
}
|
||||
|
||||
// Convert to float for calculation
|
||||
profitFloat, _ := new(big.Float).SetInt(profit.Value).Float64()
|
||||
amountFloat, _ := new(big.Float).SetInt(amountIn.Value).Float64()
|
||||
|
||||
return (profitFloat / amountFloat) * 10000.0 // Convert to basis points
|
||||
}
|
||||
|
||||
// calculateSeverity determines the severity of a test failure
|
||||
func (pa *ProfitabilityAuditor) calculateSeverity(actual, expected float64) string {
|
||||
diff := expected - actual
|
||||
diffPercent := (diff / expected) * 100
|
||||
|
||||
switch {
|
||||
case diffPercent > 50:
|
||||
return "critical"
|
||||
case diffPercent > 25:
|
||||
return "high"
|
||||
case diffPercent > 10:
|
||||
return "medium"
|
||||
default:
|
||||
return "low"
|
||||
}
|
||||
}
|
||||
|
||||
// getExchangeScenarios returns test scenarios for a specific exchange
|
||||
func (pa *ProfitabilityAuditor) getExchangeScenarios(exchange string) []*ProfitabilityTest {
|
||||
var scenarios []*ProfitabilityTest
|
||||
|
||||
for _, scenario := range pa.scenarios.Scenarios {
|
||||
if scenario.Exchange == exchange || scenario.Exchange == "all" {
|
||||
scenarios = append(scenarios, scenario)
|
||||
}
|
||||
}
|
||||
|
||||
// If no scenarios found, create default ones
|
||||
if len(scenarios) == 0 {
|
||||
scenarios = pa.createDefaultScenarios(exchange)
|
||||
}
|
||||
|
||||
return scenarios
|
||||
}
|
||||
|
||||
// createDefaultScenarios creates default test scenarios for an exchange
|
||||
func (pa *ProfitabilityAuditor) createDefaultScenarios(exchange string) []*ProfitabilityTest {
|
||||
baseScenarios := []*ProfitabilityTest{
|
||||
{
|
||||
Name: fmt.Sprintf("%s_small_arbitrage", exchange),
|
||||
Description: "Small arbitrage opportunity",
|
||||
Exchange: exchange,
|
||||
AmountIn: "1000000000000000000", // 1 ETH
|
||||
TokenIn: "ETH",
|
||||
TokenOut: "USDC",
|
||||
ExpectedProfit: "25000000000000000", // 0.025 ETH
|
||||
MaxSlippage: 50.0,
|
||||
GasCost: "5000000000000000", // 0.005 ETH
|
||||
MinROI: 1.0,
|
||||
TestType: "static",
|
||||
},
|
||||
{
|
||||
Name: fmt.Sprintf("%s_medium_arbitrage", exchange),
|
||||
Description: "Medium arbitrage opportunity",
|
||||
Exchange: exchange,
|
||||
AmountIn: "10000000000000000000", // 10 ETH
|
||||
TokenIn: "ETH",
|
||||
TokenOut: "USDC",
|
||||
ExpectedProfit: "300000000000000000", // 0.3 ETH
|
||||
MaxSlippage: 100.0,
|
||||
GasCost: "8000000000000000", // 0.008 ETH
|
||||
MinROI: 2.5,
|
||||
TestType: "static",
|
||||
},
|
||||
{
|
||||
Name: fmt.Sprintf("%s_large_arbitrage", exchange),
|
||||
Description: "Large arbitrage opportunity",
|
||||
Exchange: exchange,
|
||||
AmountIn: "100000000000000000000", // 100 ETH
|
||||
TokenIn: "ETH",
|
||||
TokenOut: "USDC",
|
||||
ExpectedProfit: "5000000000000000000", // 5 ETH
|
||||
MaxSlippage: 200.0,
|
||||
GasCost: "15000000000000000", // 0.015 ETH
|
||||
MinROI: 4.5,
|
||||
TestType: "stress",
|
||||
},
|
||||
}
|
||||
|
||||
return baseScenarios
|
||||
}
|
||||
|
||||
// MonitorRealTimeProfit monitors real-time profit opportunities
|
||||
func (pa *ProfitabilityAuditor) MonitorRealTimeProfit(ctx context.Context, exchange string) error {
|
||||
// TODO: Implement real-time monitoring with live market data
|
||||
ticker := time.NewTicker(10 * time.Second)
|
||||
defer ticker.Stop()
|
||||
|
||||
fmt.Printf("Monitoring real-time profitability for %s...\n", exchange)
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-ticker.C:
|
||||
// Simulate real-time profit check
|
||||
if pa.config.Verbose {
|
||||
fmt.Printf("[%s] Checking real-time opportunities for %s...\n",
|
||||
time.Now().Format("15:04:05"), exchange)
|
||||
}
|
||||
|
||||
// TODO: Integrate with live market data and opportunity detection
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GenerateReport generates comprehensive profitability audit reports
|
||||
func (pa *ProfitabilityAuditor) GenerateReport() error {
|
||||
// Generate JSON report
|
||||
reportPath := filepath.Join(pa.config.OutputDir, "profitability_audit.json")
|
||||
if err := pa.generateJSONReport(reportPath); err != nil {
|
||||
return fmt.Errorf("failed to generate JSON report: %w", err)
|
||||
}
|
||||
|
||||
// Generate Markdown report
|
||||
markdownPath := filepath.Join(pa.config.OutputDir, "profitability_audit.md")
|
||||
if err := pa.generateMarkdownReport(markdownPath); err != nil {
|
||||
return fmt.Errorf("failed to generate Markdown report: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// generateJSONReport generates a JSON format report
|
||||
func (pa *ProfitabilityAuditor) generateJSONReport(path string) error {
|
||||
report := map[string]interface{}{
|
||||
"timestamp": time.Now(),
|
||||
"config": pa.config,
|
||||
"exchange_results": pa.results,
|
||||
}
|
||||
|
||||
data, err := json.MarshalIndent(report, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return os.WriteFile(path, data, 0644)
|
||||
}
|
||||
|
||||
// generateMarkdownReport generates a Markdown format report
|
||||
func (pa *ProfitabilityAuditor) generateMarkdownReport(path string) error {
|
||||
// TODO: Implement detailed Markdown report generation
|
||||
content := fmt.Sprintf("# Profitability Audit Report\n\nGenerated: %s\n\n", time.Now().Format(time.RFC3339))
|
||||
|
||||
for exchange, result := range pa.results {
|
||||
content += fmt.Sprintf("## %s\n\n", exchange)
|
||||
content += fmt.Sprintf("- Total Tests: %d\n", result.TotalTests)
|
||||
content += fmt.Sprintf("- Passed: %d\n", result.PassedTests)
|
||||
content += fmt.Sprintf("- Failed: %d\n", result.FailedTests)
|
||||
content += fmt.Sprintf("- Average Profit: %.2f bp\n", result.AverageProfitBP)
|
||||
content += fmt.Sprintf("- Average ROI: %.2f%%\n\n", result.AverageROI)
|
||||
}
|
||||
|
||||
return os.WriteFile(path, []byte(content), 0644)
|
||||
}
|
||||
|
||||
// loadProfitabilityScenarios loads test scenarios from file
|
||||
func loadProfitabilityScenarios(filename string) (*ProfitabilityScenarios, error) {
|
||||
// If filename is "default", create default scenarios
|
||||
if filename == "default" {
|
||||
return &ProfitabilityScenarios{
|
||||
Version: "1.0.0",
|
||||
Scenarios: make(map[string]*ProfitabilityTest),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// TODO: Load from actual file
|
||||
return &ProfitabilityScenarios{
|
||||
Version: "1.0.0",
|
||||
Scenarios: make(map[string]*ProfitabilityTest),
|
||||
}, nil
|
||||
}
|
||||
90
tools/profitability-audit/main.go
Normal file
90
tools/profitability-audit/main.go
Normal file
@@ -0,0 +1,90 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/fraktal/mev-beta/tools/profitability-audit/internal"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var (
|
||||
exchange = flag.String("exchange", "", "Exchange to audit (uniswap_v2, uniswap_v3, curve, balancer, all)")
|
||||
minProfitBP = flag.Float64("min-profit", 10.0, "Minimum profit threshold in basis points")
|
||||
maxSlippage = flag.Float64("max-slippage", 50.0, "Maximum acceptable slippage in basis points")
|
||||
scenarios = flag.String("scenarios", "default", "Test scenarios file (default, stress, production)")
|
||||
outputDir = flag.String("output", "reports/profitability", "Output directory for reports")
|
||||
verbose = flag.Bool("verbose", false, "Enable verbose output")
|
||||
realtime = flag.Bool("realtime", false, "Enable real-time profit monitoring")
|
||||
duration = flag.Duration("duration", 5*time.Minute, "Duration for real-time monitoring")
|
||||
)
|
||||
flag.Parse()
|
||||
|
||||
if *exchange == "" {
|
||||
fmt.Println("Usage: profitability-audit -exchange <exchange> [options]")
|
||||
flag.PrintDefaults()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Create output directory
|
||||
if err := os.MkdirAll(*outputDir, 0755); err != nil {
|
||||
log.Fatalf("Failed to create output directory: %v", err)
|
||||
}
|
||||
|
||||
// Initialize profitability auditor
|
||||
auditor, err := internal.NewProfitabilityAuditor(&internal.Config{
|
||||
MinProfitBP: *minProfitBP,
|
||||
MaxSlippageBP: *maxSlippage,
|
||||
OutputDir: *outputDir,
|
||||
Verbose: *verbose,
|
||||
ScenariosFile: *scenarios,
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to initialize auditor: %v", err)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
// Run profitability audit
|
||||
if *realtime {
|
||||
fmt.Printf("Starting real-time profitability monitoring for %v...\n", *duration)
|
||||
if err := runRealtimeAudit(ctx, auditor, *exchange, *duration); err != nil {
|
||||
log.Fatalf("Real-time audit failed: %v", err)
|
||||
}
|
||||
} else {
|
||||
fmt.Printf("Running profitability audit for exchange: %s\n", *exchange)
|
||||
if err := runStaticAudit(ctx, auditor, *exchange); err != nil {
|
||||
log.Fatalf("Static audit failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("Audit complete. Reports saved to: %s\n", *outputDir)
|
||||
}
|
||||
|
||||
func runStaticAudit(ctx context.Context, auditor *internal.ProfitabilityAuditor, exchange string) error {
|
||||
if exchange == "all" {
|
||||
exchanges := []string{"uniswap_v2", "uniswap_v3", "curve", "balancer"}
|
||||
for _, ex := range exchanges {
|
||||
if err := auditor.AuditExchange(ctx, ex); err != nil {
|
||||
return fmt.Errorf("failed to audit %s: %w", ex, err)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if err := auditor.AuditExchange(ctx, exchange); err != nil {
|
||||
return fmt.Errorf("failed to audit %s: %w", exchange, err)
|
||||
}
|
||||
}
|
||||
|
||||
return auditor.GenerateReport()
|
||||
}
|
||||
|
||||
func runRealtimeAudit(ctx context.Context, auditor *internal.ProfitabilityAuditor, exchange string, duration time.Duration) error {
|
||||
ctx, cancel := context.WithTimeout(ctx, duration)
|
||||
defer cancel()
|
||||
|
||||
return auditor.MonitorRealTimeProfit(ctx, exchange)
|
||||
}
|
||||
Reference in New Issue
Block a user