Skip to content

Commit

Permalink
feat(gas-oracle): support gas token volatile exchange rate (#1526)
Browse files Browse the repository at this point in the history
Co-authored-by: yiweichi <[email protected]>
Co-authored-by: colin <[email protected]>
  • Loading branch information
3 people authored Oct 14, 2024
1 parent d6b9176 commit f2a656d
Show file tree
Hide file tree
Showing 5 changed files with 137 additions and 1 deletion.
2 changes: 1 addition & 1 deletion common/version/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
10 changes: 10 additions & 0 deletions rollup/internal/config/relayer.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
26 changes: 26 additions & 0 deletions rollup/internal/controller/relayer/l1_relayer.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down
27 changes: 27 additions & 0 deletions rollup/internal/controller/relayer/l2_relayer.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"errors"
"fmt"
"math"
"math/big"
"sort"
"strings"
Expand Down Expand Up @@ -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
Expand Down
73 changes: 73 additions & 0 deletions rollup/internal/utils/exchange_rate.go
Original file line number Diff line number Diff line change
@@ -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)
}

0 comments on commit f2a656d

Please sign in to comment.