- 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>
578 lines
18 KiB
Go
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
|
|
}
|