Files
mev-beta/orig/tools/gas-audit/internal/gas_auditor.go
Administrator c54c569f30 refactor: move all remaining files to orig/ directory
Completed clean root directory structure:
- Root now contains only: .git, .env, docs/, orig/
- Moved all remaining files and directories to orig/:
  - Config files (.claude, .dockerignore, .drone.yml, etc.)
  - All .env variants (except active .env)
  - Git config (.gitconfig, .github, .gitignore, etc.)
  - Tool configs (.golangci.yml, .revive.toml, etc.)
  - Documentation (*.md files, @prompts)
  - Build files (Dockerfiles, Makefile, go.mod, go.sum)
  - Docker compose files
  - All source directories (scripts, tests, tools, etc.)
  - Runtime directories (logs, monitoring, reports)
  - Dependency files (node_modules, lib, cache)
  - Special files (--delete)

- Removed empty runtime directories (bin/, data/)

V2 structure is now clean:
- docs/planning/ - V2 planning documents
- orig/ - Complete V1 codebase preserved
- .env - Active environment config (not in git)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-10 10:53:05 +01:00

578 lines
18 KiB
Go

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
}