mirror of
https://github.com/scroll-tech/scroll.git
synced 2026-01-09 22:18:00 -05:00
feat(gas-oracle): support gas token volatile exchange rate (#1526)
Co-authored-by: yiweichi <yiweichi@users.noreply.github.com> Co-authored-by: colin <102356659+colinlyguo@users.noreply.github.com>
This commit is contained in:
@@ -5,7 +5,7 @@ import (
|
||||
"runtime/debug"
|
||||
)
|
||||
|
||||
var tag = "v4.4.65"
|
||||
var tag = "v4.4.66"
|
||||
|
||||
var commit = func() string {
|
||||
if info, ok := debug.ReadBuildInfo(); ok {
|
||||
|
||||
@@ -68,12 +68,22 @@ type RelayerConfig struct {
|
||||
FinalizeBundleWithoutProofTimeoutSec uint64 `json:"finalize_bundle_without_proof_timeout_sec"`
|
||||
}
|
||||
|
||||
// AlternativeGasTokenConfig The configuration for handling token exchange rates when updating the gas price oracle.
|
||||
type AlternativeGasTokenConfig struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
Mode string `json:"mode"`
|
||||
FixedExchangeRate float64 `json:"fixed_exchange_rate"` // fixed exchange rate of L2 gas token / L1 gas token
|
||||
TokenSymbolPair string `json:"token_symbol_pair"` // The pair should be L2 gas token symbol + L1 gas token symbol
|
||||
}
|
||||
|
||||
// GasOracleConfig The config for updating gas price oracle.
|
||||
type GasOracleConfig struct {
|
||||
// MinGasPrice store the minimum gas price to set.
|
||||
MinGasPrice uint64 `json:"min_gas_price"`
|
||||
// GasPriceDiff is the minimum percentage of gas price difference to update gas oracle.
|
||||
GasPriceDiff uint64 `json:"gas_price_diff"`
|
||||
// AlternativeGasTokenConfig The configuration for handling token exchange rates when updating the gas price oracle.
|
||||
AlternativeGasTokenConfig *AlternativeGasTokenConfig `json:"alternative_gas_token_config"`
|
||||
|
||||
// The following configs are only for updating L1 gas price, used for sender in L2.
|
||||
// The weight for L1 base fee.
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"scroll-tech/rollup/internal/config"
|
||||
"scroll-tech/rollup/internal/controller/sender"
|
||||
"scroll-tech/rollup/internal/orm"
|
||||
rutils "scroll-tech/rollup/internal/utils"
|
||||
)
|
||||
|
||||
// Layer1Relayer is responsible for updating L1 gas price oracle contract on L2.
|
||||
@@ -151,6 +152,31 @@ func (r *Layer1Relayer) ProcessGasPriceOracle() {
|
||||
baseFee = block.BaseFee
|
||||
}
|
||||
|
||||
// include the token exchange rate in the fee data if alternative gas token enabled
|
||||
if r.cfg.GasOracleConfig.AlternativeGasTokenConfig != nil && r.cfg.GasOracleConfig.AlternativeGasTokenConfig.Enabled {
|
||||
// The exchange rate represent the number of native token on L1 required to exchange for 1 native token on L2.
|
||||
var exchangeRate float64
|
||||
switch r.cfg.GasOracleConfig.AlternativeGasTokenConfig.Mode {
|
||||
case "Fixed":
|
||||
exchangeRate = r.cfg.GasOracleConfig.AlternativeGasTokenConfig.FixedExchangeRate
|
||||
case "BinanceApi":
|
||||
exchangeRate, err = rutils.GetExchangeRateFromBinanceApi(r.cfg.GasOracleConfig.AlternativeGasTokenConfig.TokenSymbolPair, 5)
|
||||
if err != nil {
|
||||
log.Error("Failed to get gas token exchange rate from Binance api", "tokenSymbolPair", r.cfg.GasOracleConfig.AlternativeGasTokenConfig.TokenSymbolPair, "err", err)
|
||||
return
|
||||
}
|
||||
default:
|
||||
log.Error("Invalid alternative gas token mode", "mode", r.cfg.GasOracleConfig.AlternativeGasTokenConfig.Mode)
|
||||
return
|
||||
}
|
||||
if exchangeRate == 0 {
|
||||
log.Error("Invalid exchange rate", "exchangeRate", exchangeRate)
|
||||
return
|
||||
}
|
||||
baseFee = uint64(math.Ceil(float64(baseFee) / exchangeRate))
|
||||
blobBaseFee = uint64(math.Ceil(float64(blobBaseFee) / exchangeRate))
|
||||
}
|
||||
|
||||
if r.shouldUpdateGasOracle(baseFee, blobBaseFee, isCurie) {
|
||||
// It indicates the committing batch has been stuck for a long time, it's likely that the L1 gas fee spiked.
|
||||
// If we are not committing batches due to high fees then we shouldn't update fees to prevent users from paying high l1_data_fee
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"math/big"
|
||||
"sort"
|
||||
"strings"
|
||||
@@ -324,6 +325,32 @@ func (r *Layer2Relayer) ProcessGasPriceOracle() {
|
||||
return
|
||||
}
|
||||
suggestGasPriceUint64 := uint64(suggestGasPrice.Int64())
|
||||
|
||||
// include the token exchange rate in the fee data if alternative gas token enabled
|
||||
if r.cfg.GasOracleConfig.AlternativeGasTokenConfig != nil && r.cfg.GasOracleConfig.AlternativeGasTokenConfig.Enabled {
|
||||
// The exchange rate represent the number of native token on L1 required to exchange for 1 native token on L2.
|
||||
var exchangeRate float64
|
||||
switch r.cfg.GasOracleConfig.AlternativeGasTokenConfig.Mode {
|
||||
case "Fixed":
|
||||
exchangeRate = r.cfg.GasOracleConfig.AlternativeGasTokenConfig.FixedExchangeRate
|
||||
case "BinanceApi":
|
||||
exchangeRate, err = rutils.GetExchangeRateFromBinanceApi(r.cfg.GasOracleConfig.AlternativeGasTokenConfig.TokenSymbolPair, 5)
|
||||
if err != nil {
|
||||
log.Error("Failed to get gas token exchange rate from Binance api", "tokenSymbolPair", r.cfg.GasOracleConfig.AlternativeGasTokenConfig.TokenSymbolPair, "err", err)
|
||||
return
|
||||
}
|
||||
default:
|
||||
log.Error("Invalid alternative gas token mode", "mode", r.cfg.GasOracleConfig.AlternativeGasTokenConfig.Mode)
|
||||
return
|
||||
}
|
||||
if exchangeRate == 0 {
|
||||
log.Error("Invalid exchange rate", "exchangeRate", exchangeRate)
|
||||
return
|
||||
}
|
||||
suggestGasPriceUint64 = uint64(math.Ceil(float64(suggestGasPriceUint64) * exchangeRate))
|
||||
suggestGasPrice = new(big.Int).SetUint64(suggestGasPriceUint64)
|
||||
}
|
||||
|
||||
expectedDelta := r.lastGasPrice * r.gasPriceDiff / gasPriceDiffPrecision
|
||||
if r.lastGasPrice > 0 && expectedDelta == 0 {
|
||||
expectedDelta = 1
|
||||
|
||||
73
rollup/internal/utils/exchange_rate.go
Normal file
73
rollup/internal/utils/exchange_rate.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/scroll-tech/go-ethereum/log"
|
||||
)
|
||||
|
||||
var BinanceApiEndpoint string = "https://api.binance.com/api/v3/ticker/price?symbol=%s"
|
||||
|
||||
type BinanceResponse struct {
|
||||
Price string `json:"price"`
|
||||
}
|
||||
|
||||
func GetExchangeRateFromBinanceApi(tokenSymbolPair string, maxRetries int) (float64, error) {
|
||||
for i := 0; i < maxRetries; i++ {
|
||||
if i > 0 {
|
||||
time.Sleep(5 * time.Second)
|
||||
}
|
||||
|
||||
// make HTTP GET request
|
||||
resp, err := http.Get(fmt.Sprintf(BinanceApiEndpoint, tokenSymbolPair))
|
||||
if err != nil {
|
||||
log.Error("error making HTTP request", "err", err)
|
||||
continue
|
||||
}
|
||||
defer func() {
|
||||
err = resp.Body.Close()
|
||||
if err != nil {
|
||||
log.Error("error closing response body", "err", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// check for successful response
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
log.Error("unexpected status code", "code", resp.StatusCode)
|
||||
continue
|
||||
}
|
||||
|
||||
// read response body
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
log.Error("error reading response body", "err", err)
|
||||
continue
|
||||
}
|
||||
|
||||
// unmarshal JSON response
|
||||
var data BinanceResponse
|
||||
err = json.Unmarshal(body, &data)
|
||||
if err != nil {
|
||||
log.Error("error unmarshaling JSON", "err", err)
|
||||
continue
|
||||
}
|
||||
|
||||
// convert price string to float64
|
||||
price, err := strconv.ParseFloat(data.Price, 64)
|
||||
if err != nil {
|
||||
log.Error("error parsing price string", "err", err)
|
||||
continue
|
||||
}
|
||||
|
||||
// successful response, return price
|
||||
return price, nil
|
||||
}
|
||||
|
||||
// all retries failed, return error
|
||||
return 0, fmt.Errorf("failed to get exchange rate after %d retries", maxRetries)
|
||||
}
|
||||
Reference in New Issue
Block a user