Restructured project for V2 refactor: **Structure Changes:** - Moved all V1 code to orig/ folder (preserved with git mv) - Created docs/planning/ directory - Added orig/README_V1.md explaining V1 preservation **Planning Documents:** - 00_V2_MASTER_PLAN.md: Complete architecture overview - Executive summary of critical V1 issues - High-level component architecture diagrams - 5-phase implementation roadmap - Success metrics and risk mitigation - 07_TASK_BREAKDOWN.md: Atomic task breakdown - 99+ hours of detailed tasks - Every task < 2 hours (atomic) - Clear dependencies and success criteria - Organized by implementation phase **V2 Key Improvements:** - Per-exchange parsers (factory pattern) - Multi-layer strict validation - Multi-index pool cache - Background validation pipeline - Comprehensive observability **Critical Issues Addressed:** - Zero address tokens (strict validation + cache enrichment) - Parsing accuracy (protocol-specific parsers) - No audit trail (background validation channel) - Inefficient lookups (multi-index cache) - Stats disconnection (event-driven metrics) Next Steps: 1. Review planning documents 2. Begin Phase 1: Foundation (P1-001 through P1-010) 3. Implement parsers in Phase 2 4. Build cache system in Phase 3 5. Add validation pipeline in Phase 4 6. Migrate and test in Phase 5 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
613 lines
19 KiB
Go
613 lines
19 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"
|
|
"github.com/fraktal/mev-beta/pkg/math"
|
|
)
|
|
|
|
// 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
|
|
gasLimitBigInt := new(big.Int).SetUint64(gasLimit)
|
|
l2ComputeFee := new(big.Int).Mul(gasPrice, gasLimitBigInt)
|
|
|
|
// 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) {
|
|
// Get current L1 gas price from Arbitrum's ArbGasInfo precompile
|
|
_, err := g.getL1GasPrice(ctx)
|
|
if err != nil {
|
|
g.logger.Debug(fmt.Sprintf("Failed to get L1 gas price, using fallback: %v", err))
|
|
// Fallback to estimated L1 gas price with historical average
|
|
_ = g.getEstimatedL1GasPrice(ctx)
|
|
}
|
|
|
|
// Get L1 data fee multiplier from ArbGasInfo
|
|
l1PricePerUnit, err := g.getL1PricePerUnit(ctx)
|
|
if err != nil {
|
|
g.logger.Debug(fmt.Sprintf("Failed to get L1 price per unit, using default: %v", err))
|
|
l1PricePerUnit = big.NewInt(1000000000) // 1 gwei default
|
|
}
|
|
|
|
// Serialize the transaction to get the exact L1 calldata
|
|
txData, err := g.serializeTransactionForL1(tx)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to serialize transaction: %w", err)
|
|
}
|
|
|
|
// Count zero and non-zero bytes (EIP-2028 pricing)
|
|
zeroBytes := 0
|
|
nonZeroBytes := 0
|
|
|
|
for _, b := range txData {
|
|
if b == 0 {
|
|
zeroBytes++
|
|
} else {
|
|
nonZeroBytes++
|
|
}
|
|
}
|
|
|
|
// Calculate L1 gas used based on EIP-2028 formula
|
|
// 4 gas per zero byte, 16 gas per non-zero byte
|
|
l1GasUsed := int64(zeroBytes*4 + nonZeroBytes*16)
|
|
|
|
// Add base transaction overhead (21000 gas)
|
|
l1GasUsed += 21000
|
|
|
|
// Add signature verification cost (additional cost for ECDSA signature)
|
|
l1GasUsed += 2000
|
|
|
|
// Apply Arbitrum's L1 data fee calculation
|
|
// L1 data fee = l1GasUsed * l1PricePerUnit * baseFeeScalar
|
|
baseFeeScalar, err := g.getBaseFeeScalar(ctx)
|
|
if err != nil {
|
|
g.logger.Debug(fmt.Sprintf("Failed to get base fee scalar, using default: %v", err))
|
|
baseFeeScalar = big.NewInt(1300000) // Default scalar of 1.3
|
|
}
|
|
|
|
// Calculate the L1 data fee
|
|
l1GasCost := new(big.Int).Mul(big.NewInt(l1GasUsed), l1PricePerUnit)
|
|
l1DataFee := new(big.Int).Mul(l1GasCost, baseFeeScalar)
|
|
l1DataFee = new(big.Int).Div(l1DataFee, big.NewInt(1000000)) // Scale down by 10^6
|
|
|
|
g.logger.Debug(fmt.Sprintf("L1 data fee calculation: gasUsed=%d, pricePerUnit=%s, scalar=%s, fee=%s",
|
|
l1GasUsed, l1PricePerUnit.String(), baseFeeScalar.String(), l1DataFee.String()))
|
|
|
|
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
|
|
gasLimitBigInt := new(big.Int).SetUint64(optimized.GasLimit)
|
|
l2Fee := new(big.Int).Mul(optimized.MaxFeePerGas, gasLimitBigInt)
|
|
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
|
|
gasLimitBigInt := new(big.Int).SetUint64(optimized.GasLimit)
|
|
l2Fee := new(big.Int).Mul(optimized.MaxFeePerGas, gasLimitBigInt)
|
|
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
|
|
}
|
|
|
|
// getL1GasPrice fetches the current L1 gas price from Arbitrum's ArbGasInfo precompile
|
|
func (g *L2GasEstimator) getL1GasPrice(ctx context.Context) (*big.Int, error) {
|
|
// ArbGasInfo precompile address on Arbitrum
|
|
arbGasInfoAddr := common.HexToAddress("0x000000000000000000000000000000000000006C")
|
|
|
|
// Call getL1BaseFeeEstimate() function (function selector: 0xf5d6ded7)
|
|
data := common.Hex2Bytes("f5d6ded7")
|
|
|
|
msg := ethereum.CallMsg{
|
|
To: &arbGasInfoAddr,
|
|
Data: data,
|
|
}
|
|
|
|
result, err := g.client.CallContract(ctx, msg, nil)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to call ArbGasInfo.getL1BaseFeeEstimate: %w", err)
|
|
}
|
|
|
|
if len(result) < 32 {
|
|
return nil, fmt.Errorf("invalid response length from ArbGasInfo")
|
|
}
|
|
|
|
l1GasPrice := new(big.Int).SetBytes(result[:32])
|
|
g.logger.Debug(fmt.Sprintf("Retrieved L1 gas price from ArbGasInfo: %s wei", l1GasPrice.String()))
|
|
|
|
return l1GasPrice, nil
|
|
}
|
|
|
|
// getEstimatedL1GasPrice provides a fallback L1 gas price estimate using historical data
|
|
func (g *L2GasEstimator) getEstimatedL1GasPrice(ctx context.Context) *big.Int {
|
|
// Try to get recent blocks to estimate average L1 gas price
|
|
latestBlock, err := g.client.BlockByNumber(ctx, nil)
|
|
if err != nil {
|
|
g.logger.Debug(fmt.Sprintf("Failed to get latest block for gas estimation: %v", err))
|
|
return big.NewInt(20000000000) // 20 gwei fallback
|
|
}
|
|
|
|
// Analyze last 10 blocks for gas price trend
|
|
blockCount := int64(10)
|
|
totalGasPrice := big.NewInt(0)
|
|
validBlocks := int64(0)
|
|
|
|
for i := int64(0); i < blockCount; i++ {
|
|
blockNum := new(big.Int).Sub(latestBlock.Number(), big.NewInt(i))
|
|
if blockNum.Sign() <= 0 {
|
|
break
|
|
}
|
|
|
|
block, err := g.client.BlockByNumber(ctx, blockNum)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
// Use base fee as proxy for gas price trend
|
|
if block.BaseFee() != nil {
|
|
totalGasPrice.Add(totalGasPrice, block.BaseFee())
|
|
validBlocks++
|
|
}
|
|
}
|
|
|
|
if validBlocks > 0 {
|
|
avgGasPrice := new(big.Int).Div(totalGasPrice, big.NewInt(validBlocks))
|
|
// Scale up for L1 (L1 typically 5-10x higher than L2)
|
|
l1Estimate := new(big.Int).Mul(avgGasPrice, big.NewInt(7))
|
|
|
|
g.logger.Debug(fmt.Sprintf("Estimated L1 gas price from %d blocks: %s wei", validBlocks, l1Estimate.String()))
|
|
return l1Estimate
|
|
}
|
|
|
|
// Final fallback
|
|
return big.NewInt(25000000000) // 25 gwei
|
|
}
|
|
|
|
// getL1PricePerUnit fetches the L1 price per unit from ArbGasInfo
|
|
func (g *L2GasEstimator) getL1PricePerUnit(ctx context.Context) (*big.Int, error) {
|
|
// ArbGasInfo precompile address
|
|
arbGasInfoAddr := common.HexToAddress("0x000000000000000000000000000000000000006C")
|
|
|
|
// Call getPerBatchGasCharge() function (function selector: 0x6eca253a)
|
|
data := common.Hex2Bytes("6eca253a")
|
|
|
|
msg := ethereum.CallMsg{
|
|
To: &arbGasInfoAddr,
|
|
Data: data,
|
|
}
|
|
|
|
result, err := g.client.CallContract(ctx, msg, nil)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to call ArbGasInfo.getPerBatchGasCharge: %w", err)
|
|
}
|
|
|
|
if len(result) < 32 {
|
|
return nil, fmt.Errorf("invalid response length from ArbGasInfo")
|
|
}
|
|
|
|
pricePerUnit := new(big.Int).SetBytes(result[:32])
|
|
g.logger.Debug(fmt.Sprintf("Retrieved L1 price per unit: %s", pricePerUnit.String()))
|
|
|
|
return pricePerUnit, nil
|
|
}
|
|
|
|
// getBaseFeeScalar fetches the base fee scalar from ArbGasInfo
|
|
func (g *L2GasEstimator) getBaseFeeScalar(ctx context.Context) (*big.Int, error) {
|
|
// ArbGasInfo precompile address
|
|
arbGasInfoAddr := common.HexToAddress("0x000000000000000000000000000000000000006C")
|
|
|
|
// Call getL1FeesAvailable() function (function selector: 0x5ca5a4d7) to get pricing info
|
|
data := common.Hex2Bytes("5ca5a4d7")
|
|
|
|
msg := ethereum.CallMsg{
|
|
To: &arbGasInfoAddr,
|
|
Data: data,
|
|
}
|
|
|
|
result, err := g.client.CallContract(ctx, msg, nil)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to call ArbGasInfo.getL1FeesAvailable: %w", err)
|
|
}
|
|
|
|
if len(result) < 32 {
|
|
return nil, fmt.Errorf("invalid response length from ArbGasInfo")
|
|
}
|
|
|
|
// Extract the scalar from the response (typically in the first 32 bytes)
|
|
scalar := new(big.Int).SetBytes(result[:32])
|
|
|
|
// Ensure scalar is reasonable (between 1.0 and 2.0, scaled by 10^6)
|
|
minScalar := big.NewInt(1000000) // 1.0
|
|
maxScalar := big.NewInt(2000000) // 2.0
|
|
|
|
if scalar.Cmp(minScalar) < 0 {
|
|
scalar = minScalar
|
|
}
|
|
if scalar.Cmp(maxScalar) > 0 {
|
|
scalar = maxScalar
|
|
}
|
|
|
|
g.logger.Debug(fmt.Sprintf("Retrieved base fee scalar: %s", scalar.String()))
|
|
return scalar, nil
|
|
}
|
|
|
|
// serializeTransactionForL1 serializes the transaction as it would appear on L1
|
|
func (g *L2GasEstimator) serializeTransactionForL1(tx *types.Transaction) ([]byte, error) {
|
|
// For L1 data fee calculation, we need the transaction as it would be serialized on L1
|
|
// This includes the complete transaction data including signature
|
|
|
|
// Get the transaction data
|
|
txData := tx.Data()
|
|
|
|
// Create a basic serialization that includes:
|
|
// - nonce (8 bytes)
|
|
// - gas price (32 bytes)
|
|
// - gas limit (8 bytes)
|
|
// - to address (20 bytes)
|
|
// - value (32 bytes)
|
|
// - data (variable)
|
|
// - v, r, s signature (65 bytes total)
|
|
|
|
serialized := make([]byte, 0, 165+len(txData))
|
|
|
|
// Add transaction fields (simplified encoding)
|
|
nonce := tx.Nonce()
|
|
nonceBigInt := new(big.Int).SetUint64(nonce)
|
|
serialized = append(serialized, nonceBigInt.Bytes()...)
|
|
|
|
if tx.GasPrice() != nil {
|
|
gasPrice := tx.GasPrice().Bytes()
|
|
serialized = append(serialized, gasPrice...)
|
|
}
|
|
|
|
gasLimit := tx.Gas()
|
|
gasLimitBigInt := new(big.Int).SetUint64(gasLimit)
|
|
serialized = append(serialized, gasLimitBigInt.Bytes()...)
|
|
|
|
if tx.To() != nil {
|
|
serialized = append(serialized, tx.To().Bytes()...)
|
|
} else {
|
|
// Contract creation - add 20 zero bytes
|
|
serialized = append(serialized, make([]byte, 20)...)
|
|
}
|
|
|
|
if tx.Value() != nil {
|
|
value := tx.Value().Bytes()
|
|
serialized = append(serialized, value...)
|
|
}
|
|
|
|
// Add the transaction data
|
|
serialized = append(serialized, txData...)
|
|
|
|
// Add signature components (v, r, s) - 65 bytes total
|
|
// For estimation purposes, we'll add placeholder signature bytes
|
|
v, r, s := tx.RawSignatureValues()
|
|
if v != nil && r != nil && s != nil {
|
|
serialized = append(serialized, v.Bytes()...)
|
|
serialized = append(serialized, r.Bytes()...)
|
|
serialized = append(serialized, s.Bytes()...)
|
|
} else {
|
|
// Add placeholder signature (65 bytes)
|
|
serialized = append(serialized, make([]byte, 65)...)
|
|
}
|
|
|
|
g.logger.Debug(fmt.Sprintf("Serialized transaction for L1 fee calculation: %d bytes", len(serialized)))
|
|
return serialized, nil
|
|
}
|
|
|
|
// EstimateSwapGas implements math.GasEstimator interface
|
|
func (g *L2GasEstimator) EstimateSwapGas(exchange math.ExchangeType, poolData *math.PoolData) (uint64, error) {
|
|
// Base gas for different exchange types on Arbitrum L2
|
|
baseGas := map[math.ExchangeType]uint64{
|
|
math.ExchangeUniswapV2: 120000, // Uniswap V2 swap
|
|
math.ExchangeUniswapV3: 150000, // Uniswap V3 swap (more complex)
|
|
math.ExchangeSushiSwap: 125000, // SushiSwap swap
|
|
math.ExchangeCamelot: 140000, // Camelot swap
|
|
math.ExchangeBalancer: 180000, // Balancer swap (complex)
|
|
math.ExchangeCurve: 160000, // Curve swap
|
|
math.ExchangeTraderJoe: 130000, // TraderJoe swap
|
|
math.ExchangeRamses: 135000, // Ramses swap
|
|
}
|
|
|
|
gas, exists := baseGas[exchange]
|
|
if !exists {
|
|
gas = 150000 // Default fallback
|
|
}
|
|
|
|
// Apply L2 gas limit multiplier
|
|
return uint64(float64(gas) * g.gasLimitMultiplier), nil
|
|
}
|
|
|
|
// EstimateFlashSwapGas implements math.GasEstimator interface
|
|
func (g *L2GasEstimator) EstimateFlashSwapGas(route []*math.PoolData) (uint64, error) {
|
|
// Base flash swap overhead on Arbitrum L2
|
|
baseGas := uint64(200000)
|
|
|
|
// Add gas for each hop in the route
|
|
hopGas := uint64(len(route)) * 50000
|
|
|
|
// Add complexity gas based on different exchanges
|
|
complexityGas := uint64(0)
|
|
for _, pool := range route {
|
|
switch pool.ExchangeType {
|
|
case math.ExchangeUniswapV3:
|
|
complexityGas += 30000 // V3 concentrated liquidity complexity
|
|
case math.ExchangeBalancer:
|
|
complexityGas += 50000 // Weighted pool complexity
|
|
case math.ExchangeCurve:
|
|
complexityGas += 40000 // Stable swap complexity
|
|
case math.ExchangeTraderJoe:
|
|
complexityGas += 25000 // TraderJoe complexity
|
|
case math.ExchangeRamses:
|
|
complexityGas += 35000 // Ramses complexity
|
|
default:
|
|
complexityGas += 20000 // Standard AMM
|
|
}
|
|
}
|
|
|
|
totalGas := baseGas + hopGas + complexityGas
|
|
|
|
// Apply L2 gas limit multiplier with safety margin for flash swaps
|
|
return uint64(float64(totalGas) * g.gasLimitMultiplier * 1.5), nil
|
|
}
|
|
|
|
// GetCurrentGasPrice implements math.GasEstimator interface
|
|
func (g *L2GasEstimator) GetCurrentGasPrice() (*math.UniversalDecimal, error) {
|
|
ctx := context.Background()
|
|
|
|
// Get current gas price from the network
|
|
gasPrice, err := g.client.Client.SuggestGasPrice(ctx)
|
|
if err != nil {
|
|
// Fallback to typical Arbitrum L2 gas price
|
|
gasPrice = big.NewInt(100000000) // 0.1 gwei
|
|
g.logger.Warn(fmt.Sprintf("Failed to get gas price, using fallback: %v", err))
|
|
}
|
|
|
|
// Apply base fee multiplier
|
|
adjustedGasPrice := new(big.Int).Mul(gasPrice, big.NewInt(int64(g.baseFeeMultiplier*100)))
|
|
adjustedGasPrice = new(big.Int).Div(adjustedGasPrice, big.NewInt(100))
|
|
|
|
// Convert to UniversalDecimal (gas price is in wei, so 18 decimals)
|
|
gasPriceDecimal, err := math.NewUniversalDecimal(adjustedGasPrice, 18, "GWEI")
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to convert gas price to decimal: %w", err)
|
|
}
|
|
|
|
return gasPriceDecimal, nil
|
|
}
|