- 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>
390 lines
12 KiB
Go
390 lines
12 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"flag"
|
|
"fmt"
|
|
"log"
|
|
"math/big"
|
|
"os"
|
|
"path/filepath"
|
|
"sort"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
type simulationMetadata struct {
|
|
Network string `json:"network"`
|
|
Window string `json:"window"`
|
|
Sources []string `json:"sources"`
|
|
Notes string `json:"notes"`
|
|
}
|
|
|
|
type opportunityVector struct {
|
|
ID string `json:"id"`
|
|
Timestamp string `json:"timestamp"`
|
|
Exchange string `json:"exchange"`
|
|
ExpectedProfitWei string `json:"expected_profit_wei"`
|
|
GasCostWei string `json:"gas_cost_wei"`
|
|
Executed bool `json:"executed"`
|
|
RealizedProfitWei string `json:"realized_profit_wei"`
|
|
SlippageLossWei string `json:"slippage_loss_wei"`
|
|
SkipReason string `json:"skip_reason"`
|
|
}
|
|
|
|
type simulationVectors struct {
|
|
Metadata simulationMetadata `json:"metadata"`
|
|
Opportunities []opportunityVector `json:"opportunities"`
|
|
}
|
|
|
|
type exchangeBreakdown struct {
|
|
Exchange string `json:"exchange"`
|
|
Executed int `json:"executed"`
|
|
Successful int `json:"successful"`
|
|
HitRate float64 `json:"hit_rate"`
|
|
GrossProfitETH string `json:"gross_profit_eth"`
|
|
NetProfitETH string `json:"net_profit_eth"`
|
|
GasCostETH string `json:"gas_cost_eth"`
|
|
}
|
|
|
|
type skipReason struct {
|
|
Reason string `json:"reason"`
|
|
Count int `json:"count"`
|
|
}
|
|
|
|
type simulationSummary struct {
|
|
GeneratedAt string `json:"generated_at"`
|
|
VectorPath string `json:"vector_path"`
|
|
Network string `json:"network"`
|
|
Window string `json:"window"`
|
|
Sources []string `json:"sources"`
|
|
Attempts int `json:"attempts"`
|
|
Executed int `json:"executed"`
|
|
ConversionRate float64 `json:"conversion_rate"`
|
|
Successful int `json:"successful"`
|
|
Failed int `json:"failed"`
|
|
HitRate float64 `json:"hit_rate"`
|
|
GrossProfitETH string `json:"gross_profit_eth"`
|
|
GasCostETH string `json:"gas_cost_eth"`
|
|
NetProfitETH string `json:"net_profit_eth"`
|
|
AverageProfitETH string `json:"average_profit_per_trade_eth"`
|
|
AverageGasCostETH string `json:"average_gas_cost_eth"`
|
|
ProfitFactor float64 `json:"profit_factor"`
|
|
ExchangeBreakdown []exchangeBreakdown `json:"exchange_breakdown"`
|
|
SkipReasons []skipReason `json:"skip_reasons"`
|
|
}
|
|
|
|
var weiToEthScale = big.NewRat(1, 1_000_000_000_000_000_000)
|
|
|
|
func main() {
|
|
vectorsPath := flag.String("vectors", "tools/simulation/vectors/default.json", "Path to simulation vector file")
|
|
reportDir := flag.String("report", "reports/simulation/latest", "Directory for generated reports")
|
|
flag.Parse()
|
|
|
|
dataset, err := loadVectors(*vectorsPath)
|
|
if err != nil {
|
|
log.Fatalf("failed to load vectors: %v", err)
|
|
}
|
|
|
|
summary, err := computeSummary(*vectorsPath, dataset)
|
|
if err != nil {
|
|
log.Fatalf("failed to compute summary: %v", err)
|
|
}
|
|
|
|
if err := writeReports(summary, *reportDir); err != nil {
|
|
log.Fatalf("failed to write reports: %v", err)
|
|
}
|
|
|
|
printSummary(summary, *reportDir)
|
|
}
|
|
|
|
func loadVectors(path string) (simulationVectors, error) {
|
|
raw, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return simulationVectors{}, err
|
|
}
|
|
|
|
var dataset simulationVectors
|
|
if err := json.Unmarshal(raw, &dataset); err != nil {
|
|
return simulationVectors{}, err
|
|
}
|
|
return dataset, nil
|
|
}
|
|
|
|
type accumulator struct {
|
|
executed int
|
|
successful int
|
|
grossWei *big.Int
|
|
gasWei *big.Int
|
|
netWei *big.Int
|
|
}
|
|
|
|
func newAccumulator() *accumulator {
|
|
return &accumulator{
|
|
grossWei: big.NewInt(0),
|
|
gasWei: big.NewInt(0),
|
|
netWei: big.NewInt(0),
|
|
}
|
|
}
|
|
|
|
func computeSummary(vectorPath string, dataset simulationVectors) (simulationSummary, error) {
|
|
totalOpportunities := len(dataset.Opportunities)
|
|
var executed, successful int
|
|
var grossWei, gasWei, netWei big.Int
|
|
|
|
exchangeStats := make(map[string]*accumulator)
|
|
skipReasonCounts := make(map[string]int)
|
|
|
|
for _, opp := range dataset.Opportunities {
|
|
exchangeKey := strings.ToLower(opp.Exchange)
|
|
if exchangeKey == "" {
|
|
exchangeKey = "unknown"
|
|
}
|
|
if _, ok := exchangeStats[exchangeKey]; !ok {
|
|
exchangeStats[exchangeKey] = newAccumulator()
|
|
}
|
|
|
|
if !opp.Executed {
|
|
reason := opp.SkipReason
|
|
if reason == "" {
|
|
reason = "unspecified"
|
|
}
|
|
skipReasonCounts[reason]++
|
|
continue
|
|
}
|
|
|
|
executed++
|
|
acc := exchangeStats[exchangeKey]
|
|
acc.executed++
|
|
|
|
expected := parseBigInt(opp.ExpectedProfitWei)
|
|
realized := parseBigInt(opp.RealizedProfitWei)
|
|
if opp.RealizedProfitWei == "" {
|
|
realized = expected
|
|
}
|
|
|
|
if opp.SlippageLossWei != "" {
|
|
realized.Sub(realized, parseBigInt(opp.SlippageLossWei))
|
|
if realized.Sign() < 0 {
|
|
realized = big.NewInt(0)
|
|
}
|
|
}
|
|
|
|
gas := parseBigInt(opp.GasCostWei)
|
|
|
|
grossWei.Add(&grossWei, realized)
|
|
gasWei.Add(&gasWei, gas)
|
|
|
|
net := new(big.Int).Sub(realized, gas)
|
|
netWei.Add(&netWei, net)
|
|
|
|
acc.grossWei.Add(acc.grossWei, realized)
|
|
acc.gasWei.Add(acc.gasWei, gas)
|
|
acc.netWei.Add(acc.netWei, net)
|
|
|
|
if net.Sign() > 0 {
|
|
successful++
|
|
acc.successful++
|
|
}
|
|
}
|
|
|
|
failed := executed - successful
|
|
conversionRate := safeRatio(float64(executed), float64(totalOpportunities))
|
|
hitRate := safeRatio(float64(successful), float64(executed))
|
|
|
|
avgProfit := big.NewRat(0, 1)
|
|
if executed > 0 {
|
|
avgProfit.SetFrac(&netWei, big.NewInt(int64(executed)))
|
|
}
|
|
|
|
avgGas := big.NewRat(0, 1)
|
|
if executed > 0 {
|
|
avgGas.SetFrac(&gasWei, big.NewInt(int64(executed)))
|
|
}
|
|
|
|
profitFactor := 0.0
|
|
if gasWei.Sign() > 0 {
|
|
gasRat := new(big.Rat).SetInt(&gasWei)
|
|
netRat := new(big.Rat).SetInt(&netWei)
|
|
profitFactor, _ = new(big.Rat).Quo(netRat, gasRat).Float64()
|
|
}
|
|
|
|
breakdown := make([]exchangeBreakdown, 0, len(exchangeStats))
|
|
for name, acc := range exchangeStats {
|
|
if acc.executed == 0 {
|
|
continue
|
|
}
|
|
breakdown = append(breakdown, exchangeBreakdown{
|
|
Exchange: name,
|
|
Executed: acc.executed,
|
|
Successful: acc.successful,
|
|
HitRate: safeRatio(float64(acc.successful), float64(acc.executed)),
|
|
GrossProfitETH: weiToEthString(acc.grossWei),
|
|
NetProfitETH: weiToEthString(acc.netWei),
|
|
GasCostETH: weiToEthString(acc.gasWei),
|
|
})
|
|
}
|
|
sort.Slice(breakdown, func(i, j int) bool {
|
|
return breakdown[i].Exchange < breakdown[j].Exchange
|
|
})
|
|
|
|
skipList := make([]skipReason, 0, len(skipReasonCounts))
|
|
for reason, count := range skipReasonCounts {
|
|
skipList = append(skipList, skipReason{Reason: reason, Count: count})
|
|
}
|
|
sort.Slice(skipList, func(i, j int) bool {
|
|
return skipList[i].Count > skipList[j].Count
|
|
})
|
|
|
|
summary := simulationSummary{
|
|
GeneratedAt: time.Now().UTC().Format(time.RFC3339),
|
|
VectorPath: vectorPath,
|
|
Network: dataset.Metadata.Network,
|
|
Window: dataset.Metadata.Window,
|
|
Sources: dataset.Metadata.Sources,
|
|
Attempts: totalOpportunities,
|
|
Executed: executed,
|
|
ConversionRate: conversionRate,
|
|
Successful: successful,
|
|
Failed: failed,
|
|
HitRate: hitRate,
|
|
GrossProfitETH: weiToEthString(&grossWei),
|
|
GasCostETH: weiToEthString(&gasWei),
|
|
NetProfitETH: weiToEthString(&netWei),
|
|
AverageProfitETH: weiRatToEthString(avgProfit),
|
|
AverageGasCostETH: weiRatToEthString(avgGas),
|
|
ProfitFactor: profitFactor,
|
|
ExchangeBreakdown: breakdown,
|
|
SkipReasons: skipList,
|
|
}
|
|
|
|
return summary, nil
|
|
}
|
|
|
|
func parseBigInt(value string) *big.Int {
|
|
if value == "" {
|
|
return big.NewInt(0)
|
|
}
|
|
if strings.HasPrefix(value, "0x") {
|
|
val := new(big.Int)
|
|
val.SetString(value[2:], 16)
|
|
return val
|
|
}
|
|
val := new(big.Int)
|
|
val.SetString(value, 10)
|
|
return val
|
|
}
|
|
|
|
func safeRatio(num, den float64) float64 {
|
|
if den == 0 {
|
|
return 0
|
|
}
|
|
return num / den
|
|
}
|
|
|
|
func weiToEthString(val *big.Int) string {
|
|
if val == nil {
|
|
return "0.000000"
|
|
}
|
|
rat := new(big.Rat).SetInt(val)
|
|
eth := new(big.Rat).Mul(rat, weiToEthScale)
|
|
return eth.FloatString(6)
|
|
}
|
|
|
|
func weiRatToEthString(rat *big.Rat) string {
|
|
if rat.Sign() == 0 {
|
|
return "0"
|
|
}
|
|
eth := new(big.Rat).Mul(rat, weiToEthScale)
|
|
return eth.FloatString(6)
|
|
}
|
|
|
|
func writeReports(summary simulationSummary, reportDir string) error {
|
|
if err := os.MkdirAll(reportDir, 0o755); err != nil {
|
|
return err
|
|
}
|
|
|
|
jsonPath := filepath.Join(reportDir, "summary.json")
|
|
jsonBytes, err := json.MarshalIndent(summary, "", " ")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := os.WriteFile(jsonPath, jsonBytes, 0o644); err != nil {
|
|
return err
|
|
}
|
|
|
|
markdownPath := filepath.Join(reportDir, "summary.md")
|
|
if err := os.WriteFile(markdownPath, []byte(buildMarkdown(summary)), 0o644); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func buildMarkdown(summary simulationSummary) string {
|
|
var b strings.Builder
|
|
b.WriteString("# Profitability Simulation Report\n\n")
|
|
b.WriteString(fmt.Sprintf("- Generated at: %s\n", summary.GeneratedAt))
|
|
b.WriteString(fmt.Sprintf("- Vector source: `%s`\n", summary.VectorPath))
|
|
if summary.Network != "" {
|
|
b.WriteString(fmt.Sprintf("- Network: **%s**\n", summary.Network))
|
|
}
|
|
if summary.Window != "" {
|
|
b.WriteString(fmt.Sprintf("- Window: %s\n", summary.Window))
|
|
}
|
|
if len(summary.Sources) > 0 {
|
|
b.WriteString(fmt.Sprintf("- Exchanges: %s\n", strings.Join(summary.Sources, ", ")))
|
|
}
|
|
|
|
b.WriteString("\n## Summary\n\n")
|
|
b.WriteString(fmt.Sprintf("- Opportunities analysed: **%d**\n", summary.Attempts))
|
|
b.WriteString(fmt.Sprintf("- Executed: **%d** (conversion %.1f%%)\n", summary.Executed, summary.ConversionRate*100))
|
|
b.WriteString(fmt.Sprintf("- Successes: **%d** / %d (hit rate %.1f%%)\n", summary.Successful, summary.Executed, summary.HitRate*100))
|
|
b.WriteString(fmt.Sprintf("- Gross profit: **%s ETH**\n", summary.GrossProfitETH))
|
|
b.WriteString(fmt.Sprintf("- Gas spent: **%s ETH**\n", summary.GasCostETH))
|
|
b.WriteString(fmt.Sprintf("- Net profit after gas: **%s ETH**\n", summary.NetProfitETH))
|
|
b.WriteString(fmt.Sprintf("- Avg profit per trade: **%s ETH**\n", summary.AverageProfitETH))
|
|
b.WriteString(fmt.Sprintf("- Avg gas cost per trade: **%s ETH**\n", summary.AverageGasCostETH))
|
|
b.WriteString(fmt.Sprintf("- Profit factor (net/gas): **%.2f**\n", summary.ProfitFactor))
|
|
|
|
if len(summary.ExchangeBreakdown) > 0 {
|
|
b.WriteString("\n## Exchange Breakdown\n\n")
|
|
b.WriteString("| Exchange | Executed | Success | Hit Rate | Gross Profit (ETH) | Gas (ETH) | Net Profit (ETH) |\n")
|
|
b.WriteString("| --- | ---:| ---:| ---:| ---:| ---:| ---:|\n")
|
|
for _, ex := range summary.ExchangeBreakdown {
|
|
b.WriteString(fmt.Sprintf("| %s | %d | %d | %.1f%% | %s | %s | %s |\n",
|
|
ex.Exchange, ex.Executed, ex.Successful, ex.HitRate*100, ex.GrossProfitETH, ex.GasCostETH, ex.NetProfitETH))
|
|
}
|
|
}
|
|
|
|
if len(summary.SkipReasons) > 0 {
|
|
b.WriteString("\n## Skip Reasons\n\n")
|
|
for _, reason := range summary.SkipReasons {
|
|
b.WriteString(fmt.Sprintf("- %s: %d\n", reason.Reason, reason.Count))
|
|
}
|
|
}
|
|
|
|
return b.String()
|
|
}
|
|
|
|
func printSummary(summary simulationSummary, reportDir string) {
|
|
fmt.Println("Profitability Simulation Summary")
|
|
fmt.Println("================================")
|
|
fmt.Printf("Opportunities: %d\n", summary.Attempts)
|
|
fmt.Printf("Executed: %d (conversion %.1f%%)\n", summary.Executed, summary.ConversionRate*100)
|
|
fmt.Printf("Hit Rate: %.1f%% (%d successes, %d failures)\n", summary.HitRate*100, summary.Successful, summary.Failed)
|
|
fmt.Printf("Gross Profit: %s ETH\n", summary.GrossProfitETH)
|
|
fmt.Printf("Gas Spent: %s ETH\n", summary.GasCostETH)
|
|
fmt.Printf("Net Profit: %s ETH\n", summary.NetProfitETH)
|
|
fmt.Printf("Average Profit/Trade: %s ETH\n", summary.AverageProfitETH)
|
|
fmt.Printf("Average Gas/Trade: %s ETH\n", summary.AverageGasCostETH)
|
|
fmt.Printf("Profit Factor: %.2f\n", summary.ProfitFactor)
|
|
if len(summary.ExchangeBreakdown) > 0 {
|
|
fmt.Println("\nPer Exchange:")
|
|
for _, ex := range summary.ExchangeBreakdown {
|
|
fmt.Printf("- %s: executed %d, hit rate %.1f%%, net %s ETH\n", ex.Exchange, ex.Executed, ex.HitRate*100, ex.NetProfitETH)
|
|
}
|
|
}
|
|
fmt.Println("\nReports written to", reportDir)
|
|
}
|