293 lines
8.6 KiB
Go
293 lines
8.6 KiB
Go
package arbitrum
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"math/big"
|
|
|
|
"github.com/ethereum/go-ethereum"
|
|
"github.com/ethereum/go-ethereum/common"
|
|
"github.com/ethereum/go-ethereum/core/types"
|
|
"github.com/fraktal/mev-beta/internal/logger"
|
|
)
|
|
|
|
// L2GasEstimator provides Arbitrum-specific gas estimation and optimization
|
|
type L2GasEstimator struct {
|
|
client *ArbitrumClient
|
|
logger *logger.Logger
|
|
|
|
// L2 gas price configuration
|
|
baseFeeMultiplier float64
|
|
priorityFeeMin *big.Int
|
|
priorityFeeMax *big.Int
|
|
gasLimitMultiplier float64
|
|
}
|
|
|
|
// GasEstimate represents an L2 gas estimate with detailed breakdown
|
|
type GasEstimate struct {
|
|
GasLimit uint64
|
|
MaxFeePerGas *big.Int
|
|
MaxPriorityFee *big.Int
|
|
L1DataFee *big.Int
|
|
L2ComputeFee *big.Int
|
|
TotalFee *big.Int
|
|
Confidence float64 // 0-1 scale
|
|
}
|
|
|
|
// NewL2GasEstimator creates a new L2 gas estimator
|
|
func NewL2GasEstimator(client *ArbitrumClient, logger *logger.Logger) *L2GasEstimator {
|
|
return &L2GasEstimator{
|
|
client: client,
|
|
logger: logger,
|
|
baseFeeMultiplier: 1.1, // 10% buffer on base fee
|
|
priorityFeeMin: big.NewInt(100000000), // 0.1 gwei minimum
|
|
priorityFeeMax: big.NewInt(2000000000), // 2 gwei maximum
|
|
gasLimitMultiplier: 1.2, // 20% buffer on gas limit
|
|
}
|
|
}
|
|
|
|
// EstimateL2Gas provides comprehensive gas estimation for L2 transactions
|
|
func (g *L2GasEstimator) EstimateL2Gas(ctx context.Context, tx *types.Transaction) (*GasEstimate, error) {
|
|
// Get current gas price data
|
|
gasPrice, err := g.client.SuggestGasPrice(ctx)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get gas price: %v", err)
|
|
}
|
|
|
|
// Estimate gas limit
|
|
gasLimit, err := g.estimateGasLimit(ctx, tx)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to estimate gas limit: %v", err)
|
|
}
|
|
|
|
// Get L1 data fee (Arbitrum-specific)
|
|
l1DataFee, err := g.estimateL1DataFee(ctx, tx)
|
|
if err != nil {
|
|
g.logger.Warn(fmt.Sprintf("Failed to estimate L1 data fee: %v", err))
|
|
l1DataFee = big.NewInt(0)
|
|
}
|
|
|
|
// Calculate L2 compute fee
|
|
l2ComputeFee := new(big.Int).Mul(gasPrice, big.NewInt(int64(gasLimit)))
|
|
|
|
// Calculate priority fee
|
|
priorityFee := g.calculateOptimalPriorityFee(ctx, gasPrice)
|
|
|
|
// Calculate max fee per gas
|
|
maxFeePerGas := new(big.Int).Add(gasPrice, priorityFee)
|
|
|
|
// Total fee includes both L1 and L2 components
|
|
totalFee := new(big.Int).Add(l1DataFee, l2ComputeFee)
|
|
|
|
// Apply gas limit buffer
|
|
bufferedGasLimit := uint64(float64(gasLimit) * g.gasLimitMultiplier)
|
|
|
|
estimate := &GasEstimate{
|
|
GasLimit: bufferedGasLimit,
|
|
MaxFeePerGas: maxFeePerGas,
|
|
MaxPriorityFee: priorityFee,
|
|
L1DataFee: l1DataFee,
|
|
L2ComputeFee: l2ComputeFee,
|
|
TotalFee: totalFee,
|
|
Confidence: g.calculateConfidence(gasPrice, priorityFee),
|
|
}
|
|
|
|
return estimate, nil
|
|
}
|
|
|
|
// estimateGasLimit estimates the gas limit for an L2 transaction
|
|
func (g *L2GasEstimator) estimateGasLimit(ctx context.Context, tx *types.Transaction) (uint64, error) {
|
|
// Create a call message for gas estimation
|
|
msg := ethereum.CallMsg{
|
|
From: common.Address{}, // Will be overridden
|
|
To: tx.To(),
|
|
Value: tx.Value(),
|
|
Data: tx.Data(),
|
|
GasPrice: tx.GasPrice(),
|
|
}
|
|
|
|
// Estimate gas using the client
|
|
gasLimit, err := g.client.EstimateGas(ctx, msg)
|
|
if err != nil {
|
|
// Fallback to default gas limits based on transaction type
|
|
return g.getDefaultGasLimit(tx), nil
|
|
}
|
|
|
|
return gasLimit, nil
|
|
}
|
|
|
|
// estimateL1DataFee calculates the L1 data fee component (Arbitrum-specific)
|
|
func (g *L2GasEstimator) estimateL1DataFee(ctx context.Context, tx *types.Transaction) (*big.Int, error) {
|
|
// Arbitrum L1 data fee calculation
|
|
// This is based on the calldata size and L1 gas price
|
|
|
|
calldata := tx.Data()
|
|
|
|
// Count zero and non-zero bytes (different costs)
|
|
zeroBytes := 0
|
|
nonZeroBytes := 0
|
|
|
|
for _, b := range calldata {
|
|
if b == 0 {
|
|
zeroBytes++
|
|
} else {
|
|
nonZeroBytes++
|
|
}
|
|
}
|
|
|
|
// Arbitrum L1 data fee formula (simplified)
|
|
// Actual implementation would need to fetch current L1 gas price
|
|
l1GasPrice := big.NewInt(20000000000) // 20 gwei estimate
|
|
|
|
// Gas cost: 4 per zero byte, 16 per non-zero byte
|
|
gasCost := int64(zeroBytes*4 + nonZeroBytes*16)
|
|
|
|
// Add base transaction cost
|
|
gasCost += 21000
|
|
|
|
l1DataFee := new(big.Int).Mul(l1GasPrice, big.NewInt(gasCost))
|
|
|
|
return l1DataFee, nil
|
|
}
|
|
|
|
// calculateOptimalPriorityFee calculates an optimal priority fee for fast inclusion
|
|
func (g *L2GasEstimator) calculateOptimalPriorityFee(ctx context.Context, baseFee *big.Int) *big.Int {
|
|
// Try to get recent priority fees from the network
|
|
priorityFee, err := g.getSuggestedPriorityFee(ctx)
|
|
if err != nil {
|
|
// Fallback to base fee percentage
|
|
priorityFee = new(big.Int).Div(baseFee, big.NewInt(10)) // 10% of base fee
|
|
}
|
|
|
|
// Ensure within bounds
|
|
if priorityFee.Cmp(g.priorityFeeMin) < 0 {
|
|
priorityFee = new(big.Int).Set(g.priorityFeeMin)
|
|
}
|
|
if priorityFee.Cmp(g.priorityFeeMax) > 0 {
|
|
priorityFee = new(big.Int).Set(g.priorityFeeMax)
|
|
}
|
|
|
|
return priorityFee
|
|
}
|
|
|
|
// getSuggestedPriorityFee gets suggested priority fee from the network
|
|
func (g *L2GasEstimator) getSuggestedPriorityFee(ctx context.Context) (*big.Int, error) {
|
|
// Use eth_maxPriorityFeePerGas if available
|
|
var result string
|
|
err := g.client.rpcClient.CallContext(ctx, &result, "eth_maxPriorityFeePerGas")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
priorityFee := new(big.Int)
|
|
if _, success := priorityFee.SetString(result[2:], 16); !success {
|
|
return nil, fmt.Errorf("invalid priority fee response")
|
|
}
|
|
|
|
return priorityFee, nil
|
|
}
|
|
|
|
// calculateConfidence calculates confidence level for the gas estimate
|
|
func (g *L2GasEstimator) calculateConfidence(gasPrice, priorityFee *big.Int) float64 {
|
|
// Higher priority fee relative to gas price = higher confidence
|
|
ratio := new(big.Float).Quo(new(big.Float).SetInt(priorityFee), new(big.Float).SetInt(gasPrice))
|
|
ratioFloat, _ := ratio.Float64()
|
|
|
|
// Confidence scale: 0.1 ratio = 0.5 confidence, 0.5 ratio = 0.9 confidence
|
|
confidence := 0.3 + (ratioFloat * 1.2)
|
|
if confidence > 1.0 {
|
|
confidence = 1.0
|
|
}
|
|
if confidence < 0.1 {
|
|
confidence = 0.1
|
|
}
|
|
|
|
return confidence
|
|
}
|
|
|
|
// getDefaultGasLimit returns default gas limits based on transaction type
|
|
func (g *L2GasEstimator) getDefaultGasLimit(tx *types.Transaction) uint64 {
|
|
dataSize := len(tx.Data())
|
|
|
|
switch {
|
|
case dataSize == 0:
|
|
// Simple transfer
|
|
return 21000
|
|
case dataSize < 100:
|
|
// Simple contract interaction
|
|
return 50000
|
|
case dataSize < 1000:
|
|
// Complex contract interaction
|
|
return 150000
|
|
case dataSize < 5000:
|
|
// Very complex interaction (e.g., DEX swap)
|
|
return 300000
|
|
default:
|
|
// Extremely complex interaction
|
|
return 500000
|
|
}
|
|
}
|
|
|
|
// OptimizeForSpeed adjusts gas parameters for fastest execution
|
|
func (g *L2GasEstimator) OptimizeForSpeed(estimate *GasEstimate) *GasEstimate {
|
|
optimized := *estimate
|
|
|
|
// Increase priority fee by 50%
|
|
speedPriorityFee := new(big.Int).Mul(estimate.MaxPriorityFee, big.NewInt(150))
|
|
optimized.MaxPriorityFee = new(big.Int).Div(speedPriorityFee, big.NewInt(100))
|
|
|
|
// Increase max fee per gas accordingly
|
|
optimized.MaxFeePerGas = new(big.Int).Add(
|
|
new(big.Int).Sub(estimate.MaxFeePerGas, estimate.MaxPriorityFee),
|
|
optimized.MaxPriorityFee,
|
|
)
|
|
|
|
// Increase gas limit by 10% more
|
|
optimized.GasLimit = uint64(float64(estimate.GasLimit) * 1.1)
|
|
|
|
// Recalculate total fee
|
|
l2Fee := new(big.Int).Mul(optimized.MaxFeePerGas, big.NewInt(int64(optimized.GasLimit)))
|
|
optimized.TotalFee = new(big.Int).Add(estimate.L1DataFee, l2Fee)
|
|
|
|
// Higher confidence due to aggressive pricing
|
|
optimized.Confidence = estimate.Confidence * 1.2
|
|
if optimized.Confidence > 1.0 {
|
|
optimized.Confidence = 1.0
|
|
}
|
|
|
|
return &optimized
|
|
}
|
|
|
|
// OptimizeForCost adjusts gas parameters for lowest cost
|
|
func (g *L2GasEstimator) OptimizeForCost(estimate *GasEstimate) *GasEstimate {
|
|
optimized := *estimate
|
|
|
|
// Use minimum priority fee
|
|
optimized.MaxPriorityFee = new(big.Int).Set(g.priorityFeeMin)
|
|
|
|
// Reduce max fee per gas
|
|
optimized.MaxFeePerGas = new(big.Int).Add(
|
|
new(big.Int).Sub(estimate.MaxFeePerGas, estimate.MaxPriorityFee),
|
|
optimized.MaxPriorityFee,
|
|
)
|
|
|
|
// Use exact gas limit (no buffer)
|
|
optimized.GasLimit = uint64(float64(estimate.GasLimit) / g.gasLimitMultiplier)
|
|
|
|
// Recalculate total fee
|
|
l2Fee := new(big.Int).Mul(optimized.MaxFeePerGas, big.NewInt(int64(optimized.GasLimit)))
|
|
optimized.TotalFee = new(big.Int).Add(estimate.L1DataFee, l2Fee)
|
|
|
|
// Lower confidence due to minimal gas pricing
|
|
optimized.Confidence = estimate.Confidence * 0.7
|
|
|
|
return &optimized
|
|
}
|
|
|
|
// IsL2TransactionViable checks if an L2 transaction is economically viable
|
|
func (g *L2GasEstimator) IsL2TransactionViable(estimate *GasEstimate, expectedProfit *big.Int) bool {
|
|
// Compare total fee to expected profit
|
|
return estimate.TotalFee.Cmp(expectedProfit) < 0
|
|
}
|
|
|