diff --git a/go-ethereum b/go-ethereum index 03910bc75..796834acb 160000 --- a/go-ethereum +++ b/go-ethereum @@ -1 +1 @@ -Subproject commit 03910bc750a2301be4c1410b9f3c4d3741df251e +Subproject commit 796834acba86e4d3622ed435091281abe86c3c99 diff --git a/token-price-oracle/client/l2_client.go b/token-price-oracle/client/l2_client.go index a69ea044c..1568b068c 100644 --- a/token-price-oracle/client/l2_client.go +++ b/token-price-oracle/client/l2_client.go @@ -22,6 +22,8 @@ type L2Client struct { opts *bind.TransactOpts signer *Signer externalSign bool + gasFeeCap *big.Int // Max gas fee cap (nil means no cap) + gasTipCap *big.Int // Max gas tip cap (nil means no cap) } // NewL2Client creates new L2 client @@ -50,6 +52,16 @@ func NewL2Client(rpcURL string, cfg *config.Config) (*L2Client, error) { externalSign: cfg.ExternalSign, } + // Set gas fee caps if configured (used as max cap, not fixed value) + if cfg.GasFeeCap != nil { + l2Client.gasFeeCap = new(big.Int).SetUint64(*cfg.GasFeeCap) + log.Info("Using gas fee cap limit", "maxGasFeeCap", *cfg.GasFeeCap) + } + if cfg.GasTipCap != nil { + l2Client.gasTipCap = new(big.Int).SetUint64(*cfg.GasTipCap) + log.Info("Using gas tip cap limit", "maxGasTipCap", *cfg.GasTipCap) + } + if cfg.ExternalSign { // External sign mode rsaPriv, err := externalsign.ParseRsaPrivateKey(cfg.ExternalSignRsaPriv) @@ -127,14 +139,14 @@ func (c *L2Client) GetClient() *ethclient.Client { // GetOpts returns a copy of transaction options // Returns a new instance to prevent concurrent modification +// Note: Gas caps are applied by TxManager.applyGasCaps(), not here func (c *L2Client) GetOpts() *bind.TransactOpts { - // Return a copy to prevent shared state issues return &bind.TransactOpts{ - From: c.opts.From, - Nonce: c.opts.Nonce, - Signer: c.opts.Signer, - Value: c.opts.Value, - GasPrice: c.opts.GasPrice, + From: c.opts.From, + Nonce: c.opts.Nonce, + Signer: c.opts.Signer, + Value: c.opts.Value, + GasPrice: c.opts.GasPrice, GasFeeCap: c.opts.GasFeeCap, GasTipCap: c.opts.GasTipCap, GasLimit: c.opts.GasLimit, @@ -167,3 +179,13 @@ func (c *L2Client) GetSigner() *Signer { func (c *L2Client) GetChainID() *big.Int { return c.chainID } + +// GetMaxGasFeeCap returns the max gas fee cap (nil if not configured) +func (c *L2Client) GetMaxGasFeeCap() *big.Int { + return c.gasFeeCap +} + +// GetMaxGasTipCap returns the max gas tip cap (nil if not configured) +func (c *L2Client) GetMaxGasTipCap() *big.Int { + return c.gasTipCap +} diff --git a/token-price-oracle/client/sign.go b/token-price-oracle/client/sign.go index 340e32140..10e928178 100644 --- a/token-price-oracle/client/sign.go +++ b/token-price-oracle/client/sign.go @@ -96,11 +96,17 @@ func (s *Signer) CreateAndSignTx( return nil, fmt.Errorf("failed to get nonce: %w", err) } - // Get gas tip cap + // Get gas tip cap (dynamic, then apply cap if configured) tip, err := client.GetClient().SuggestGasTipCap(ctx) if err != nil { return nil, fmt.Errorf("failed to get gas tip cap: %w", err) } + if maxTip := client.GetMaxGasTipCap(); maxTip != nil { + if tip.Cmp(maxTip) > 0 { + log.Debug("Applying gas tip cap limit", "dynamic", tip, "cap", maxTip) + tip = maxTip + } + } // Get base fee from latest block head, err := client.GetClient().HeaderByNumber(ctx, nil) @@ -108,6 +114,7 @@ func (s *Signer) CreateAndSignTx( return nil, fmt.Errorf("failed to get block header: %w", err) } + // Calculate dynamic gas fee cap var gasFeeCap *big.Int if head.BaseFee != nil { gasFeeCap = new(big.Int).Add( @@ -118,6 +125,14 @@ func (s *Signer) CreateAndSignTx( gasFeeCap = new(big.Int).Set(tip) } + // Apply gas fee cap limit if configured + if maxFeeCap := client.GetMaxGasFeeCap(); maxFeeCap != nil { + if gasFeeCap.Cmp(maxFeeCap) > 0 { + log.Debug("Applying gas fee cap limit", "dynamic", gasFeeCap, "cap", maxFeeCap) + gasFeeCap = maxFeeCap + } + } + // Estimate gas gas, err := client.GetClient().EstimateGas(ctx, ethereum.CallMsg{ From: from, diff --git a/token-price-oracle/config/config.go b/token-price-oracle/config/config.go index c66b69e9b..a97a54ec3 100644 --- a/token-price-oracle/config/config.go +++ b/token-price-oracle/config/config.go @@ -85,6 +85,10 @@ type Config struct { LogFileMaxSize int LogFileMaxAge int LogCompress bool + + // Gas fee caps (optional - if set, use as max cap) + GasFeeCap *uint64 // Max gas fee cap in wei (nil means no cap) + GasTipCap *uint64 // Max gas tip cap in wei (nil means no cap) } // LoadConfig loads configuration from cli.Context @@ -112,6 +116,16 @@ func LoadConfig(ctx *cli.Context) (*Config, error) { LogCompress: ctx.Bool(flags.LogCompressFlag.Name), } + // Gas fee caps (only set if flag is explicitly provided) + if ctx.IsSet(flags.GasFeeCapFlag.Name) { + v := ctx.Uint64(flags.GasFeeCapFlag.Name) + cfg.GasFeeCap = &v + } + if ctx.IsSet(flags.GasTipCapFlag.Name) { + v := ctx.Uint64(flags.GasTipCapFlag.Name) + cfg.GasTipCap = &v + } + // Parse token registry address (optional) cfg.L2TokenRegistryAddr = predeploys.L2TokenRegistryAddr diff --git a/token-price-oracle/flags/flags.go b/token-price-oracle/flags/flags.go index 785783e15..1692806b7 100644 --- a/token-price-oracle/flags/flags.go +++ b/token-price-oracle/flags/flags.go @@ -176,6 +176,19 @@ var ( Usage: "The RSA private key for external sign", EnvVar: prefixEnvVar("EXTERNAL_SIGN_RSA_PRIV"), } + + // Gas fee flags (optional - if set, use as max cap instead of dynamic) + GasFeeCapFlag = cli.Uint64Flag{ + Name: "gas-fee-cap", + Usage: "Max gas fee cap in wei (if set, actual fee = min(dynamic, this value))", + EnvVar: prefixEnvVar("GAS_FEE_CAP"), + } + + GasTipCapFlag = cli.Uint64Flag{ + Name: "gas-tip-cap", + Usage: "Max gas tip cap in wei (if set, actual tip = min(dynamic, this value))", + EnvVar: prefixEnvVar("GAS_TIP_CAP"), + } ) var requiredFlags = []cli.Flag{ @@ -210,6 +223,10 @@ var optionalFlags = []cli.Flag{ ExternalSignChainFlag, ExternalSignUrlFlag, ExternalSignRsaPrivFlag, + + // Gas fee + GasFeeCapFlag, + GasTipCapFlag, } // Flags contains the list of configuration options available to the binary. diff --git a/token-price-oracle/updater/tx_manager.go b/token-price-oracle/updater/tx_manager.go index ebed49395..d723e2965 100644 --- a/token-price-oracle/updater/tx_manager.go +++ b/token-price-oracle/updater/tx_manager.go @@ -3,6 +3,7 @@ package updater import ( "context" "fmt" + "math/big" "sync" "time" @@ -72,6 +73,11 @@ func (m *TxManager) sendWithLocalSign(ctx context.Context, txFunc func(*bind.Tra auth := m.l2Client.GetOpts() auth.Context = ctx + // Apply gas caps if configured (same logic as external sign) + if err := m.applyGasCaps(ctx, auth); err != nil { + return nil, fmt.Errorf("failed to apply gas caps: %w", err) + } + // First, estimate gas with GasLimit = 0 auth.GasLimit = 0 auth.NoSend = true @@ -193,6 +199,58 @@ func (m *TxManager) sendWithExternalSign(ctx context.Context, txFunc func(*bind. return receipt, nil } +// applyGasCaps applies configured gas caps as upper limits to dynamic gas prices +// This ensures consistent behavior between local sign and external sign +func (m *TxManager) applyGasCaps(ctx context.Context, auth *bind.TransactOpts) error { + maxTipCap := m.l2Client.GetMaxGasTipCap() + maxFeeCap := m.l2Client.GetMaxGasFeeCap() + + // If no caps configured, let bind package handle gas pricing dynamically + if maxTipCap == nil && maxFeeCap == nil { + return nil + } + + // Get dynamic gas tip cap + tip, err := m.l2Client.GetClient().SuggestGasTipCap(ctx) + if err != nil { + return fmt.Errorf("failed to get gas tip cap: %w", err) + } + + // Apply tip cap limit if configured + if maxTipCap != nil && tip.Cmp(maxTipCap) > 0 { + log.Debug("Applying gas tip cap limit", "dynamic", tip, "cap", maxTipCap) + tip = new(big.Int).Set(maxTipCap) + } + auth.GasTipCap = tip + + // Get base fee from latest block + head, err := m.l2Client.GetClient().HeaderByNumber(ctx, nil) + if err != nil { + return fmt.Errorf("failed to get block header: %w", err) + } + + // Calculate dynamic gas fee cap + var gasFeeCap *big.Int + if head.BaseFee != nil { + gasFeeCap = new(big.Int).Add( + tip, + new(big.Int).Mul(head.BaseFee, big.NewInt(2)), + ) + } else { + gasFeeCap = new(big.Int).Set(tip) + } + + // Apply fee cap limit if configured + if maxFeeCap != nil && gasFeeCap.Cmp(maxFeeCap) > 0 { + log.Debug("Applying gas fee cap limit", "dynamic", gasFeeCap, "cap", maxFeeCap) + gasFeeCap = new(big.Int).Set(maxFeeCap) + } + auth.GasFeeCap = gasFeeCap + + log.Debug("Gas caps applied", "tipCap", auth.GasTipCap, "feeCap", auth.GasFeeCap) + return nil +} + // waitForReceipt waits for a transaction receipt with timeout and custom polling interval func (m *TxManager) waitForReceipt(ctx context.Context, txHash common.Hash, timeout, pollInterval time.Duration) (*types.Receipt, error) { deadline := time.Now().Add(timeout)