package internal import ( "context" "encoding/json" "fmt" "math/big" "os" "path/filepath" "time" "github.com/fraktal/mev-beta/pkg/math" ) // GasAuditConfig holds configuration for gas auditing type GasAuditConfig struct { Network string GasPrice string ScenariosFile string OutputDir string Verbose bool Tolerance float64 // Percentage tolerance for gas estimation accuracy } // GasAuditor audits gas cost estimations and optimizations type GasAuditor struct { config *GasAuditConfig converter *math.DecimalConverter scenarios *GasScenarios results *GasAuditResults } // GasScenarios defines test scenarios for gas estimation type GasScenarios struct { Version string `json:"version"` Network string `json:"network"` Scenarios map[string]*GasScenario `json:"scenarios"` } // GasScenario represents a gas estimation test case type GasScenario struct { Name string `json:"name"` Description string `json:"description"` Operation string `json:"operation"` // "swap", "arbitrage", "flashloan" Exchange string `json:"exchange"` TokenIn string `json:"token_in"` TokenOut string `json:"token_out"` AmountIn string `json:"amount_in"` ExpectedGasUsed int64 `json:"expected_gas_used"` MaxGasPrice string `json:"max_gas_price"` Priority string `json:"priority"` // "low", "medium", "high" Complexity string `json:"complexity"` // "simple", "medium", "complex" } // GasAuditResults holds comprehensive gas audit results type GasAuditResults struct { Network string `json:"network"` Timestamp time.Time `json:"timestamp"` TotalScenarios int `json:"total_scenarios"` PassedScenarios int `json:"passed_scenarios"` FailedScenarios int `json:"failed_scenarios"` AverageGasUsed int64 `json:"average_gas_used"` AverageGasPrice string `json:"average_gas_price"` TotalGasCostETH string `json:"total_gas_cost_eth"` EstimationAccuracy float64 `json:"estimation_accuracy"` ScenarioResults []*GasScenarioResult `json:"scenario_results"` FailedCases []*GasEstimationFailure `json:"failed_cases"` Recommendations []string `json:"recommendations"` Duration time.Duration `json:"duration"` } // GasScenarioResult represents the result of a gas estimation test type GasScenarioResult struct { ScenarioName string `json:"scenario_name"` Passed bool `json:"passed"` EstimatedGas int64 `json:"estimated_gas"` ActualGas int64 `json:"actual_gas"` EstimationError float64 `json:"estimation_error_percent"` GasPriceGwei string `json:"gas_price_gwei"` GasCostETH string `json:"gas_cost_eth"` OptimizationSuggestions []string `json:"optimization_suggestions"` Duration time.Duration `json:"duration"` ErrorMessage string `json:"error_message,omitempty"` } // GasEstimationFailure represents a failed gas estimation type GasEstimationFailure struct { ScenarioName string `json:"scenario_name"` Reason string `json:"reason"` ExpectedGas int64 `json:"expected_gas"` EstimatedGas int64 `json:"estimated_gas"` ErrorPercent float64 `json:"error_percent"` Severity string `json:"severity"` Impact string `json:"impact"` } // NewGasAuditor creates a new gas auditor func NewGasAuditor(config *GasAuditConfig) (*GasAuditor, error) { converter := math.NewDecimalConverter() // Load gas scenarios scenarios, err := loadGasScenarios(config.ScenariosFile, config.Network) if err != nil { return nil, fmt.Errorf("failed to load gas scenarios: %w", err) } return &GasAuditor{ config: config, converter: converter, scenarios: scenarios, results: &GasAuditResults{ Network: config.Network, ScenarioResults: []*GasScenarioResult{}, FailedCases: []*GasEstimationFailure{}, Recommendations: []string{}, }, }, nil } // AuditGasCosts performs comprehensive gas cost auditing func (ga *GasAuditor) AuditGasCosts(ctx context.Context) error { startTime := time.Now() ga.results.Timestamp = startTime if ga.config.Verbose { fmt.Printf("Starting gas cost audit for %s network...\n", ga.config.Network) } ga.results.TotalScenarios = len(ga.scenarios.Scenarios) var totalGasUsed int64 var totalGasCostETH *math.UniversalDecimal // Initialize total gas cost totalGasCostETH, _ = math.NewUniversalDecimal(big.NewInt(0), 18, "ETH") for name, scenario := range ga.scenarios.Scenarios { result, err := ga.runGasScenario(ctx, name, scenario) if err != nil { if ga.config.Verbose { fmt.Printf(" Failed scenario %s: %v\n", name, err) } failure := &GasEstimationFailure{ ScenarioName: name, Reason: err.Error(), ExpectedGas: scenario.ExpectedGasUsed, Severity: "high", Impact: "Gas estimation unreliable", } ga.results.FailedCases = append(ga.results.FailedCases, failure) ga.results.FailedScenarios++ continue } ga.results.ScenarioResults = append(ga.results.ScenarioResults, result) if result.Passed { ga.results.PassedScenarios++ totalGasUsed += result.EstimatedGas // Add to total gas cost scenarioGasCost, _ := ga.converter.FromString(result.GasCostETH, 18, "ETH") totalGasCostETH, _ = ga.converter.Add(totalGasCostETH, scenarioGasCost) } else { ga.results.FailedScenarios++ failure := &GasEstimationFailure{ ScenarioName: name, Reason: result.ErrorMessage, ExpectedGas: scenario.ExpectedGasUsed, EstimatedGas: result.EstimatedGas, ErrorPercent: result.EstimationError, Severity: ga.calculateGasSeverity(result.EstimationError), Impact: ga.calculateGasImpact(result.EstimationError), } ga.results.FailedCases = append(ga.results.FailedCases, failure) } if ga.config.Verbose { status := "✓" if !result.Passed { status = "✗" } fmt.Printf(" %s %s: Gas=%d, Cost=%s ETH, Error=%.1f%%\n", status, name, result.EstimatedGas, result.GasCostETH, result.EstimationError) } } // Calculate summary statistics if ga.results.PassedScenarios > 0 { ga.results.AverageGasUsed = totalGasUsed / int64(ga.results.PassedScenarios) ga.results.EstimationAccuracy = float64(ga.results.PassedScenarios) / float64(ga.results.TotalScenarios) * 100 } ga.results.TotalGasCostETH = ga.converter.ToHumanReadable(totalGasCostETH) ga.results.Duration = time.Since(startTime) // Generate recommendations ga.generateRecommendations() if ga.config.Verbose { fmt.Printf("Gas audit completed: %d/%d scenarios passed (%.1f%% accuracy)\n", ga.results.PassedScenarios, ga.results.TotalScenarios, ga.results.EstimationAccuracy) } return nil } // runGasScenario executes a single gas estimation scenario func (ga *GasAuditor) runGasScenario(ctx context.Context, name string, scenario *GasScenario) (*GasScenarioResult, error) { startTime := time.Now() // Simulate gas estimation based on operation complexity estimatedGas, err := ga.estimateGasForScenario(scenario) if err != nil { return nil, fmt.Errorf("gas estimation failed: %w", err) } // Get current gas price gasPrice, err := ga.getCurrentGasPrice() if err != nil { return nil, fmt.Errorf("failed to get gas price: %w", err) } // Calculate gas cost in ETH gasCostWei := new(big.Int).Mul(big.NewInt(estimatedGas), gasPrice) gasCostETH, err := math.NewUniversalDecimal(gasCostWei, 18, "ETH") if err != nil { return nil, fmt.Errorf("failed to calculate gas cost: %w", err) } // Calculate estimation error estimationError := ga.calculateEstimationError(scenario.ExpectedGasUsed, estimatedGas) // Determine if estimation is within tolerance passed := estimationError <= ga.config.Tolerance // Generate optimization suggestions optimizations := ga.generateOptimizationSuggestions(scenario, estimatedGas) result := &GasScenarioResult{ ScenarioName: name, Passed: passed, EstimatedGas: estimatedGas, ActualGas: scenario.ExpectedGasUsed, EstimationError: estimationError, GasPriceGwei: ga.weiToGwei(gasPrice), GasCostETH: ga.converter.ToHumanReadable(gasCostETH), OptimizationSuggestions: optimizations, Duration: time.Since(startTime), } if !passed { result.ErrorMessage = fmt.Sprintf("Gas estimation error %.1f%% exceeds tolerance %.1f%%", estimationError, ga.config.Tolerance) } return result, nil } // estimateGasForScenario estimates gas for a specific scenario func (ga *GasAuditor) estimateGasForScenario(scenario *GasScenario) (int64, error) { // Base gas costs for different operations baseGas := map[string]int64{ "swap": 150000, "arbitrage": 300000, "flashloan": 500000, } // Complexity multipliers complexityMultiplier := map[string]float64{ "simple": 1.0, "medium": 1.3, "complex": 1.8, } // Exchange-specific adjustments exchangeAdjustment := map[string]float64{ "uniswap_v2": 1.0, "uniswap_v3": 1.2, "curve": 0.8, "balancer": 1.5, } base, exists := baseGas[scenario.Operation] if !exists { return 0, fmt.Errorf("unknown operation: %s", scenario.Operation) } complexityMult, exists := complexityMultiplier[scenario.Complexity] if !exists { complexityMult = 1.0 } exchangeMult, exists := exchangeAdjustment[scenario.Exchange] if !exists { exchangeMult = 1.0 } // Calculate estimated gas estimated := float64(base) * complexityMult * exchangeMult // Add some random variation to simulate real-world conditions variation := 0.9 + (0.2 * 0.5) // ±10% variation estimated *= variation return int64(estimated), nil } // getCurrentGasPrice returns current gas price for the network func (ga *GasAuditor) getCurrentGasPrice() (*big.Int, error) { // Simulate gas price based on network var gasPriceGwei int64 switch ga.config.Network { case "arbitrum": gasPriceGwei = 1 // Arbitrum typically has very low gas prices case "ethereum": gasPriceGwei = 20 // Ethereum mainnet default: gasPriceGwei = 10 // Default } // Convert gwei to wei gweiToWei := new(big.Int).Exp(big.NewInt(10), big.NewInt(9), nil) gasPrice := new(big.Int).Mul(big.NewInt(gasPriceGwei), gweiToWei) return gasPrice, nil } // calculateEstimationError calculates the percentage error in gas estimation func (ga *GasAuditor) calculateEstimationError(expected, estimated int64) float64 { if expected == 0 { return 0.0 } diff := float64(estimated - expected) if diff < 0 { diff = -diff } return (diff / float64(expected)) * 100.0 } // generateOptimizationSuggestions generates gas optimization suggestions func (ga *GasAuditor) generateOptimizationSuggestions(scenario *GasScenario, estimatedGas int64) []string { var suggestions []string // High gas usage suggestions if estimatedGas > 400000 { suggestions = append(suggestions, "Consider breaking complex operations into smaller transactions") suggestions = append(suggestions, "Evaluate if flash loans are necessary for this operation") } // Exchange-specific suggestions switch scenario.Exchange { case "uniswap_v3": suggestions = append(suggestions, "Consider using single-hop swaps when possible") case "balancer": suggestions = append(suggestions, "Batch multiple operations to amortize gas costs") case "curve": suggestions = append(suggestions, "Leverage Curve's low slippage for stable swaps") } // Operation-specific suggestions switch scenario.Operation { case "arbitrage": suggestions = append(suggestions, "Use multicall to bundle swap operations") suggestions = append(suggestions, "Consider gas price optimization strategies") case "flashloan": suggestions = append(suggestions, "Minimize logic in flash loan callback") } return suggestions } // calculateGasSeverity determines severity of gas estimation error func (ga *GasAuditor) calculateGasSeverity(errorPercent float64) string { switch { case errorPercent > 50: return "critical" case errorPercent > 30: return "high" case errorPercent > 15: return "medium" default: return "low" } } // calculateGasImpact determines impact of gas estimation error func (ga *GasAuditor) calculateGasImpact(errorPercent float64) string { switch { case errorPercent > 50: return "Severe profit impact, transactions may fail" case errorPercent > 30: return "Significant profit reduction" case errorPercent > 15: return "Moderate profit impact" default: return "Minimal impact on profitability" } } // generateRecommendations generates overall audit recommendations func (ga *GasAuditor) generateRecommendations() { if ga.results.EstimationAccuracy < 80 { ga.results.Recommendations = append(ga.results.Recommendations, "Gas estimation accuracy is below 80%. Consider improving estimation algorithms.") } if ga.results.AverageGasUsed > 300000 { ga.results.Recommendations = append(ga.results.Recommendations, "Average gas usage is high. Consider optimizing transaction complexity.") } if ga.results.FailedScenarios > ga.results.TotalScenarios/4 { ga.results.Recommendations = append(ga.results.Recommendations, "High failure rate detected. Review gas estimation methodology.") } if ga.config.Network == "ethereum" { ga.results.Recommendations = append(ga.results.Recommendations, "Consider implementing EIP-1559 gas price optimization for Ethereum mainnet.") } } // MonitorRealTimeGas monitors real-time gas costs and variations func (ga *GasAuditor) MonitorRealTimeGas(ctx context.Context) error { ticker := time.NewTicker(30 * time.Second) defer ticker.Stop() fmt.Printf("Monitoring real-time gas costs for %s...\n", ga.config.Network) for { select { case <-ctx.Done(): return ctx.Err() case <-ticker.C: gasPrice, err := ga.getCurrentGasPrice() if err != nil { if ga.config.Verbose { fmt.Printf("Error getting gas price: %v\n", err) } continue } if ga.config.Verbose { fmt.Printf("[%s] Current gas price: %s gwei\n", time.Now().Format("15:04:05"), ga.weiToGwei(gasPrice)) } } } } // GenerateReport generates comprehensive gas audit reports func (ga *GasAuditor) GenerateReport() error { // Generate JSON report jsonPath := filepath.Join(ga.config.OutputDir, "gas_audit.json") if err := ga.generateJSONReport(jsonPath); err != nil { return fmt.Errorf("failed to generate JSON report: %w", err) } // Generate Markdown report markdownPath := filepath.Join(ga.config.OutputDir, "gas_audit.md") if err := ga.generateMarkdownReport(markdownPath); err != nil { return fmt.Errorf("failed to generate Markdown report: %w", err) } return nil } // generateJSONReport generates JSON format report func (ga *GasAuditor) generateJSONReport(path string) error { data, err := json.MarshalIndent(ga.results, "", " ") if err != nil { return err } return os.WriteFile(path, data, 0644) } // generateMarkdownReport generates Markdown format report func (ga *GasAuditor) generateMarkdownReport(path string) error { content := fmt.Sprintf("# Gas Cost Audit Report\n\n") content += fmt.Sprintf("**Network:** %s\n", ga.results.Network) content += fmt.Sprintf("**Generated:** %s\n\n", ga.results.Timestamp.Format(time.RFC3339)) content += fmt.Sprintf("## Summary\n\n") content += fmt.Sprintf("- Total Scenarios: %d\n", ga.results.TotalScenarios) content += fmt.Sprintf("- Passed: %d\n", ga.results.PassedScenarios) content += fmt.Sprintf("- Failed: %d\n", ga.results.FailedScenarios) content += fmt.Sprintf("- Estimation Accuracy: %.1f%%\n", ga.results.EstimationAccuracy) content += fmt.Sprintf("- Average Gas Used: %d\n", ga.results.AverageGasUsed) content += fmt.Sprintf("- Total Gas Cost: %s ETH\n\n", ga.results.TotalGasCostETH) if len(ga.results.Recommendations) > 0 { content += fmt.Sprintf("## Recommendations\n\n") for _, rec := range ga.results.Recommendations { content += fmt.Sprintf("- %s\n", rec) } content += "\n" } return os.WriteFile(path, []byte(content), 0644) } // weiToGwei converts wei to gwei for display func (ga *GasAuditor) weiToGwei(wei *big.Int) string { gwei := new(big.Int).Div(wei, big.NewInt(1000000000)) return gwei.String() } // loadGasScenarios loads gas estimation scenarios func loadGasScenarios(filename, network string) (*GasScenarios, error) { // Create default scenarios if none specified if filename == "default" { return createDefaultGasScenarios(network), nil } // TODO: Load from actual file return createDefaultGasScenarios(network), nil } // createDefaultGasScenarios creates default gas estimation scenarios func createDefaultGasScenarios(network string) *GasScenarios { scenarios := &GasScenarios{ Version: "1.0.0", Network: network, Scenarios: make(map[string]*GasScenario), } // Base scenarios scenarios.Scenarios["simple_swap"] = &GasScenario{ Name: "simple_swap", Description: "Simple token swap", Operation: "swap", Exchange: "uniswap_v2", TokenIn: "ETH", TokenOut: "USDC", AmountIn: "1000000000000000000", ExpectedGasUsed: 150000, MaxGasPrice: "50000000000", Priority: "medium", Complexity: "simple", } scenarios.Scenarios["complex_arbitrage"] = &GasScenario{ Name: "complex_arbitrage", Description: "Multi-hop arbitrage", Operation: "arbitrage", Exchange: "uniswap_v3", TokenIn: "ETH", TokenOut: "USDC", AmountIn: "10000000000000000000", ExpectedGasUsed: 400000, MaxGasPrice: "100000000000", Priority: "high", Complexity: "complex", } scenarios.Scenarios["flashloan_arbitrage"] = &GasScenario{ Name: "flashloan_arbitrage", Description: "Flash loan arbitrage", Operation: "flashloan", Exchange: "balancer", TokenIn: "ETH", TokenOut: "USDC", AmountIn: "100000000000000000000", ExpectedGasUsed: 600000, MaxGasPrice: "150000000000", Priority: "high", Complexity: "complex", } return scenarios }