synapsecns/sanguine

View on GitHub
ethergo/chain/gas/gas_setter.go

Summary

Maintainability
A
0 mins
Test Coverage
package gas

import (
    "context"
    "fmt"
    "github.com/ethereum/go-ethereum/accounts/abi/bind"
    "github.com/ethereum/go-ethereum/eth/ethconfig"
    "github.com/ethereum/go-ethereum/eth/gasprice"
    "github.com/ethereum/go-ethereum/params"
    "github.com/synapsecns/sanguine/core"
    "github.com/synapsecns/sanguine/ethergo/chain/client"
    "github.com/synapsecns/sanguine/ethergo/chain/gas/backend"
    "github.com/synapsecns/sanguine/ethergo/chain/gas/london"
    "math/big"
)

// feeCapBumpPercentage is how much the fee cap should bump (expressed as a percentage).
const feeCapBumpPercentage = 7

// Setter handles setting gas prices/fees in both pre (v0) and post (v2) eip-1559 transactions.
// this allows the setter to work on both ethereum and third party rpcs regardless of eip-1559 support.
type Setter interface {
    // SetGasFeeByBlock gets a deterministic gas price based on the block
    SetGasFeeByBlock(ctx context.Context, transactor *bind.TransactOpts, gasBlock uint64, maxPrice *big.Int) error
    // SetGasFee sets a gas fee non-deterministically. This is useful for speeding up tests
    SetGasFee(ctx context.Context, transactor *bind.TransactOpts, londonBlock uint64, maxPrice *big.Int) error
}

// GetConfig ges the gas price config.
func GetConfig() gasprice.Config {
    config := ethconfig.FullNodeGPO
    config.MaxPrice = big.NewInt(params.GWei * 750)
    config.IgnorePrice = core.CopyBigInt(ethconfig.FullNodeGPO.IgnorePrice)
    config.Percentile = 90
    config.Default = big.NewInt(params.GWei)
    return config
}

type gasSetterImpl struct {
    //nolint: containedctx
    ctx            context.Context
    oracleBackend  backend.OracleBackendChain
    priceEstimator PriceEstimator
}

// NewGasSetter creates a new gas setter for v0 & v2 transactions depending on the chain settings.
func NewGasSetter(ctx context.Context, oracle backend.OracleBackendChain) Setter {
    return &gasSetterImpl{
        ctx:            ctx,
        oracleBackend:  oracle,
        priceEstimator: NewGasPriceEstimator(ctx, oracle),
    }
}

func (g gasSetterImpl) SetGasFeeByBlock(ctx context.Context, transactor *bind.TransactOpts, gasBlock uint64, maxPrice *big.Int) (err error) {
    oracleConfig := GetConfig()
    oracleConfig.MaxPrice = maxPrice

    // if london is activated, we only care about the max tip cap. Max fee remains the same for every tx
    //nolint: nestif
    if client.UsesLondon(g.oracleBackend.ChainConfig(), gasBlock) {
        gasHeader, err := g.oracleBackend.HeaderByNumber(ctx, new(big.Int).SetUint64(gasBlock))
        if err != nil {
            return fmt.Errorf("could not get gas block %d on chain %d: %w", gasBlock, g.oracleBackend.ChainConfig().ChainID, err)
        }

        oracle := london.NewFeeOracle(g.oracleBackend, gasBlock, oracleConfig)

        tipCap, err := oracle.SuggestTipCap(ctx)
        if err != nil {
            return fmt.Errorf("could not get tip cap: %w", err)
        }

        transactor.GasFeeCap = maxPrice
        transactor.GasTipCap = BumpByPercent(tipCap, feeCapBumpPercentage)

        if transactor.GasTipCap.Cmp(gasHeader.BaseFee) > 0 {
            transactor.GasTipCap = new(big.Int).Sub(transactor.GasFeeCap, gasHeader.BaseFee)
        }
    } else {
        transactor.GasPrice, err = g.priceEstimator.EstimateGasPrice(ctx, gasBlock, oracleConfig)
        if err != nil {
            return fmt.Errorf("could not get gas price: %w", err)
        }
    }
    return nil
}

func (g gasSetterImpl) SetGasFee(ctx context.Context, transactor *bind.TransactOpts, londonBlock uint64, maxPrice *big.Int) (err error) {
    //nolint: nestif
    if client.UsesLondon(g.oracleBackend.ChainConfig(), londonBlock) {
        gasHeader, err := g.oracleBackend.HeaderByNumber(ctx, new(big.Int).SetUint64(londonBlock))
        if err != nil {
            return fmt.Errorf("could not get gas block %d on chain %d: %w", londonBlock, g.oracleBackend.ChainConfig().ChainID, err)
        }

        tipCap, err := g.oracleBackend.SuggestGasTipCap(ctx)
        if err != nil {
            return fmt.Errorf("could not calculate tip cap: %w", err)
        }

        // add the tip cap to the gas price and bump
        transactor.GasFeeCap = maxPrice
        transactor.GasTipCap = BumpByPercent(tipCap, feeCapBumpPercentage)

        if transactor.GasTipCap.Cmp(gasHeader.BaseFee) > 0 {
            transactor.GasTipCap = new(big.Int).Sub(transactor.GasFeeCap, gasHeader.BaseFee)
        }
    } else {
        gasPrice, err := g.oracleBackend.SuggestGasPrice(ctx)
        if err != nil {
            return fmt.Errorf("could not get gas price: %w", err)
        }

        transactor.GasPrice = min(BumpByPercent(gasPrice, feeCapBumpPercentage), maxPrice)
    }

    return nil
}