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>
263 lines
9.3 KiB
Go
263 lines
9.3 KiB
Go
package main
|
|
|
|
import (
|
|
"flag"
|
|
"fmt"
|
|
"log"
|
|
"math/big"
|
|
"os"
|
|
"path/filepath"
|
|
"time"
|
|
|
|
validation "mev-bot/tests/calculation-validation"
|
|
)
|
|
|
|
func main() {
|
|
// Parse command line flags
|
|
testDir := flag.String("dir", "tests/calculation-validation", "Test directory containing extracted logs")
|
|
tolerance := flag.Float64("tolerance", 1.0, "Tolerance percentage for validation")
|
|
verbose := flag.Bool("verbose", false, "Verbose output")
|
|
flag.Parse()
|
|
|
|
fmt.Println("═══════════════════════════════════════════════════════════════════════")
|
|
fmt.Println(" MEV Bot - Calculation Replay & Validation Tool")
|
|
fmt.Println("═══════════════════════════════════════════════════════════════════════")
|
|
fmt.Println()
|
|
|
|
// Initialize parser and validator
|
|
parser := validation.NewLogParser()
|
|
validator := validation.NewProfitValidator(*tolerance)
|
|
|
|
// Define file paths
|
|
extractedDir := filepath.Join(*testDir, "extracted")
|
|
executableFile := filepath.Join(extractedDir, "executable_opportunities.log")
|
|
detailsFile := filepath.Join(extractedDir, "opportunity_details.log")
|
|
v3CalcFile := filepath.Join(extractedDir, "v3_calculations.log")
|
|
thresholdFile := filepath.Join(extractedDir, "threshold_checks.log")
|
|
|
|
fmt.Println("📂 Loading test data...")
|
|
|
|
// Parse executable opportunities
|
|
executableOpps, err := parser.ParseExecutableOpportunities(executableFile)
|
|
if err != nil {
|
|
log.Printf("Warning: Could not parse executable opportunities: %v\n", err)
|
|
executableOpps = []*validation.OpportunityTestData{}
|
|
}
|
|
fmt.Printf(" ✓ Loaded %d executable opportunities\n", len(executableOpps))
|
|
|
|
// Parse detailed opportunities
|
|
detailedOpps, err := parser.ParseOpportunityDetails(detailsFile)
|
|
if err != nil {
|
|
log.Printf("Warning: Could not parse opportunity details: %v\n", err)
|
|
detailedOpps = []*validation.OpportunityTestData{}
|
|
}
|
|
fmt.Printf(" ✓ Loaded %d detailed opportunities\n", len(detailedOpps))
|
|
|
|
// Parse V3 calculations
|
|
v3Calcs, err := parser.ParseV3Calculations(v3CalcFile)
|
|
if err != nil {
|
|
log.Printf("Warning: Could not parse V3 calculations: %v\n", err)
|
|
v3Calcs = []validation.SwapCalculation{}
|
|
}
|
|
fmt.Printf(" ✓ Loaded %d V3 calculations\n", len(v3Calcs))
|
|
|
|
// Parse threshold checks
|
|
thresholdChecks, err := parser.ParseThresholdChecks(thresholdFile)
|
|
if err != nil {
|
|
log.Printf("Warning: Could not parse threshold checks: %v\n", err)
|
|
thresholdChecks = []*validation.ThresholdCheck{}
|
|
}
|
|
fmt.Printf(" ✓ Loaded %d threshold checks\n", len(thresholdChecks))
|
|
|
|
fmt.Println()
|
|
fmt.Println("🔍 Validating calculations...")
|
|
fmt.Println()
|
|
|
|
// Validate executable opportunities
|
|
if len(executableOpps) > 0 {
|
|
fmt.Println("━━━ Executable Opportunities ━━━")
|
|
report := validator.ValidateBatch(executableOpps)
|
|
printReport(report, *verbose)
|
|
}
|
|
|
|
// Validate detailed opportunities
|
|
if len(detailedOpps) > 0 {
|
|
fmt.Println("\n━━━ Detailed Opportunities ━━━")
|
|
report := validator.ValidateBatch(detailedOpps)
|
|
printReport(report, *verbose)
|
|
}
|
|
|
|
// Validate V3 calculations
|
|
if len(v3Calcs) > 0 {
|
|
fmt.Println("\n━━━ V3 Swap Calculations ━━━")
|
|
validV3 := 0
|
|
invalidV3 := 0
|
|
warningsV3 := 0
|
|
|
|
for i, calc := range v3Calcs {
|
|
result := validator.ValidateV3Calculation(calc)
|
|
if result.IsValid {
|
|
validV3++
|
|
} else {
|
|
invalidV3++
|
|
}
|
|
if len(result.Warnings) > 0 {
|
|
warningsV3++
|
|
}
|
|
|
|
if *verbose && (!result.IsValid || len(result.Warnings) > 0) {
|
|
fmt.Printf(" V3 Calc #%d:\n", i+1)
|
|
fmt.Printf(" AmountIn: %s\n", calc.AmountIn.String())
|
|
fmt.Printf(" AmountOut: %s\n", calc.AmountOut.String())
|
|
fmt.Printf(" Fee: %d\n", calc.Fee)
|
|
fmt.Printf(" FinalOut: %s\n", calc.FinalOut.String())
|
|
if len(result.Errors) > 0 {
|
|
fmt.Printf(" ❌ Errors: %v\n", result.Errors)
|
|
}
|
|
if len(result.Warnings) > 0 {
|
|
fmt.Printf(" ⚠️ Warnings: %v\n", result.Warnings)
|
|
}
|
|
}
|
|
}
|
|
|
|
fmt.Printf(" Total: %d\n", len(v3Calcs))
|
|
fmt.Printf(" ✅ Valid: %d\n", validV3)
|
|
fmt.Printf(" ❌ Invalid: %d\n", invalidV3)
|
|
fmt.Printf(" ⚠️ Warnings: %d\n", warningsV3)
|
|
}
|
|
|
|
// Validate threshold checks
|
|
if len(thresholdChecks) > 0 {
|
|
fmt.Println("\n━━━ Profit Threshold Checks ━━━")
|
|
validThresholds := 0
|
|
invalidThresholds := 0
|
|
|
|
for i, check := range thresholdChecks {
|
|
// Validate the threshold comparison logic
|
|
wasExecutable := check.Passed
|
|
result := validator.ValidateThresholdComparison(
|
|
check.NetProfit,
|
|
big.NewInt(100000000000000), // 0.0001 ETH in wei (default threshold)
|
|
wasExecutable,
|
|
)
|
|
|
|
if result.IsValid {
|
|
validThresholds++
|
|
} else {
|
|
invalidThresholds++
|
|
if *verbose {
|
|
fmt.Printf(" ❌ Threshold Check #%d FAILED:\n", i+1)
|
|
fmt.Printf(" Net Profit: %s ETH\n", check.NetProfit.String())
|
|
fmt.Printf(" Min Threshold: %s ETH\n", check.MinThreshold.String())
|
|
fmt.Printf(" Marked Executable: %v\n", wasExecutable)
|
|
fmt.Printf(" Errors: %v\n", result.Errors)
|
|
}
|
|
}
|
|
}
|
|
|
|
fmt.Printf(" Total: %d\n", len(thresholdChecks))
|
|
fmt.Printf(" ✅ Valid: %d\n", validThresholds)
|
|
fmt.Printf(" ❌ Invalid: %d\n", invalidThresholds)
|
|
fmt.Printf(" Success Rate: %.2f%%\n", float64(validThresholds)/float64(len(thresholdChecks))*100)
|
|
}
|
|
|
|
// Calculate and display profit statistics
|
|
if len(executableOpps) > 0 {
|
|
fmt.Println("\n━━━ Profit Statistics ━━━")
|
|
profits := validation.ExtractProfitValues(executableOpps)
|
|
total, average, max, min := validation.CalculateStatistics(profits)
|
|
|
|
totalFloat, _ := total.Float64()
|
|
avgFloat, _ := average.Float64()
|
|
maxFloat, _ := max.Float64()
|
|
minFloat, _ := min.Float64()
|
|
|
|
fmt.Printf(" Total Profit: %.6f ETH\n", totalFloat)
|
|
fmt.Printf(" Average Profit: %.6f ETH\n", avgFloat)
|
|
fmt.Printf(" Maximum Profit: %.6f ETH\n", maxFloat)
|
|
fmt.Printf(" Minimum Profit: %.6f ETH\n", minFloat)
|
|
fmt.Printf(" Opportunities: %d\n", len(profits))
|
|
}
|
|
|
|
// Test the CRITICAL bug fix
|
|
fmt.Println("\n━━━ Critical Bug Fix Validation ━━━")
|
|
testCriticalBugFix(validator)
|
|
|
|
fmt.Println()
|
|
fmt.Println("═══════════════════════════════════════════════════════════════════════")
|
|
fmt.Println(" Validation Complete!")
|
|
fmt.Println("═══════════════════════════════════════════════════════════════════════")
|
|
}
|
|
|
|
func printReport(report *validation.TestReport, verbose bool) {
|
|
fmt.Printf(" Total: %d\n", report.TotalOpportunities)
|
|
fmt.Printf(" ✅ Valid: %d\n", report.ValidCalculations)
|
|
fmt.Printf(" ❌ Invalid: %d\n", report.InvalidCalculations)
|
|
fmt.Printf(" Success Rate: %.2f%%\n", float64(report.ValidCalculations)/float64(report.TotalOpportunities)*100)
|
|
|
|
if verbose {
|
|
for _, result := range report.ValidationResults {
|
|
if !result.IsValid || len(result.Warnings) > 0 {
|
|
fmt.Printf("\n Opportunity: %s\n", result.OpportunityID)
|
|
if len(result.Errors) > 0 {
|
|
fmt.Printf(" ❌ Errors: %v\n", result.Errors)
|
|
}
|
|
if len(result.Warnings) > 0 {
|
|
fmt.Printf(" ⚠️ Warnings: %v\n", result.Warnings)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func testCriticalBugFix(validator *validation.ProfitValidator) {
|
|
// Test the exact bug scenario: 834.210302 ETH profit vs 0.0001 ETH threshold
|
|
netProfit := big.NewFloat(834.210302)
|
|
minThresholdWei := big.NewInt(100000000000000) // 0.0001 ETH in wei
|
|
|
|
fmt.Println(" Testing bug fix scenario:")
|
|
fmt.Printf(" Net Profit: %.6f ETH\n", mustFloat64(netProfit))
|
|
fmt.Printf(" Min Threshold: 0.0001 ETH (100000000000000 wei)\n")
|
|
|
|
// Before fix: netProfit.Int(nil) would return 834, comparing 834 < 100000000000000 = FALSE (rejected)
|
|
// After fix: Should compare 834.210302 >= 0.0001 = TRUE (executable)
|
|
|
|
result := validator.ValidateThresholdComparison(netProfit, minThresholdWei, true)
|
|
|
|
if result.IsValid {
|
|
fmt.Println(" ✅ BUG FIX VALIDATED: Correctly marked as executable")
|
|
} else {
|
|
fmt.Println(" ❌ BUG FIX FAILED: Incorrectly rejected")
|
|
fmt.Printf(" Errors: %v\n", result.Errors)
|
|
}
|
|
|
|
// Test edge case: profit exactly at threshold
|
|
edgeProfit := big.NewFloat(0.0001)
|
|
edgeResult := validator.ValidateThresholdComparison(edgeProfit, minThresholdWei, true)
|
|
|
|
fmt.Println("\n Testing edge case (profit == threshold):")
|
|
fmt.Printf(" Net Profit: %.6f ETH\n", mustFloat64(edgeProfit))
|
|
if edgeResult.IsValid {
|
|
fmt.Println(" ✅ Edge case handled correctly")
|
|
} else {
|
|
fmt.Println(" ❌ Edge case failed")
|
|
}
|
|
|
|
// Test rejection case: profit below threshold
|
|
lowProfit := big.NewFloat(0.00001)
|
|
lowResult := validator.ValidateThresholdComparison(lowProfit, minThresholdWei, false)
|
|
|
|
fmt.Println("\n Testing rejection case (profit < threshold):")
|
|
fmt.Printf(" Net Profit: %.6f ETH\n", mustFloat64(lowProfit))
|
|
if lowResult.IsValid {
|
|
fmt.Println(" ✅ Correctly rejected")
|
|
} else {
|
|
fmt.Println(" ❌ Should have been rejected")
|
|
}
|
|
}
|
|
|
|
func mustFloat64(f *big.Float) float64 {
|
|
val, _ := f.Float64()
|
|
return val
|
|
}
|