Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ecotone gas price #12584

Merged
merged 12 commits into from
Mar 26, 2024
5 changes: 5 additions & 0 deletions .changeset/rude-paws-cross.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"chainlink": patch
---

L1Oracle handles OP Stack Ecotone encoded l1 gas price
54 changes: 45 additions & 9 deletions core/chains/evm/gas/rollups/l1_oracle.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/rpc"

"github.com/smartcontractkit/chainlink-common/pkg/logger"
"github.com/smartcontractkit/chainlink-common/pkg/services"
Expand All @@ -28,6 +29,12 @@
//go:generate mockery --quiet --name ethClient --output ./mocks/ --case=underscore --structname ETHClient
type ethClient interface {
CallContract(ctx context.Context, msg ethereum.CallMsg, blockNumber *big.Int) ([]byte, error)
BatchCallContext(ctx context.Context, b []rpc.BatchElem) error
}

//go:generate mockery --quiet --name daPriceReader --output ./mocks/ --case=underscore --structname DAPriceReader
type daPriceReader interface {
GetDAGasPrice(ctx context.Context) (*big.Int, error)
}

type priceEntry struct {
Expand All @@ -53,6 +60,8 @@
gasCostMethod string
l1GasCostMethodAbi abi.ABI

priceReader daPriceReader

chInitialised chan struct{}
chStop services.StopChan
chDone chan struct{}
Expand Down Expand Up @@ -109,9 +118,21 @@
}

func NewL1GasOracle(lggr logger.Logger, ethClient ethClient, chainType config.ChainType) L1Oracle {
var priceReader daPriceReader
switch chainType {

Check failure on line 122 in core/chains/evm/gas/rollups/l1_oracle.go

View workflow job for this annotation

GitHub Actions / lint

missing cases in switch of type config.ChainType: config.ChainArbitrum, config.ChainCelo, config.ChainGnosis, config.ChainMetis, config.ChainScroll, config.ChainWeMix, config.ChainXDai, config.ChainZkSync (exhaustive)
case config.ChainOptimismBedrock:
priceReader = newOPPriceReader(lggr, ethClient, chainType, OPGasOracleAddress)
case config.ChainKroma:
priceReader = newOPPriceReader(lggr, ethClient, chainType, KromaGasOracleAddress)
amit-momin marked this conversation as resolved.
Show resolved Hide resolved
}
return newL1GasOracle(lggr, ethClient, chainType, priceReader)
}

func newL1GasOracle(lggr logger.Logger, ethClient ethClient, chainType config.ChainType, priceReader daPriceReader) L1Oracle {
var l1GasPriceAddress, gasPriceMethod, l1GasCostAddress, gasCostMethod string
var l1GasPriceMethodAbi, l1GasCostMethodAbi abi.ABI
var gasPriceErr, gasCostErr error

switch chainType {
case config.ChainArbitrum:
l1GasPriceAddress = ArbGasInfoAddress
Expand Down Expand Up @@ -164,6 +185,8 @@
gasCostMethod: gasCostMethod,
l1GasCostMethodAbi: l1GasCostMethodAbi,

priceReader: priceReader,

chInitialised: make(chan struct{}),
chStop: make(chan struct{}),
chDone: make(chan struct{}),
Expand Down Expand Up @@ -222,13 +245,30 @@
ctx, cancel := o.chStop.CtxCancel(evmclient.ContextWithDefaultTimeout())
defer cancel()

price, err := o.fetchL1GasPrice(ctx)
if err != nil {
return t, err
}

o.l1GasPriceMu.Lock()
defer o.l1GasPriceMu.Unlock()
o.l1GasPrice = priceEntry{price: assets.NewWei(price), timestamp: time.Now()}
return
}

func (o *l1Oracle) fetchL1GasPrice(ctx context.Context) (price *big.Int, err error) {
// if dedicated priceReader exists, use the reader
if o.priceReader != nil {
return o.priceReader.GetDAGasPrice(ctx)
}

var callData, b []byte
precompile := common.HexToAddress(o.l1GasPriceAddress)
callData, err = o.l1GasPriceMethodAbi.Pack(o.gasPriceMethod)
if err != nil {
errMsg := fmt.Sprintf("failed to pack calldata for %s L1 gas price method", o.chainType)
o.logger.Errorf(errMsg)
return t, fmt.Errorf("%s: %w", errMsg, err)
return nil, fmt.Errorf("%s: %w", errMsg, err)
}
b, err = o.client.CallContract(ctx, ethereum.CallMsg{
To: &precompile,
Expand All @@ -237,20 +277,16 @@
if err != nil {
errMsg := "gas oracle contract call failed"
o.logger.Errorf(errMsg)
return t, fmt.Errorf("%s: %w", errMsg, err)
return nil, fmt.Errorf("%s: %w", errMsg, err)
}

if len(b) != 32 { // returns uint256;
errMsg := fmt.Sprintf("return data length (%d) different than expected (%d)", len(b), 32)
o.logger.Criticalf(errMsg)
return t, fmt.Errorf(errMsg)
return nil, fmt.Errorf(errMsg)
}
price := new(big.Int).SetBytes(b)

o.l1GasPriceMu.Lock()
defer o.l1GasPriceMu.Unlock()
o.l1GasPrice = priceEntry{price: assets.NewWei(price), timestamp: time.Now()}
return
price = new(big.Int).SetBytes(b)
return price, nil
}

func (o *l1Oracle) GasPrice(_ context.Context) (l1GasPrice *assets.Wei, err error) {
Expand Down
4 changes: 4 additions & 0 deletions core/chains/evm/gas/rollups/l1_oracle_abi.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,7 @@ const GasEstimateL1ComponentAbiString = `[{"inputs":[{"internalType":"address","
// All ABIs found at https://optimistic.etherscan.io/address/0xc0d3c0d3c0d3c0d3c0d3c0d3c0d3c0d3c0d3000f#code
const L1BaseFeeAbiString = `[{"inputs":[],"name":"l1BaseFee","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}]`
const GetL1FeeAbiString = `[{"inputs":[{"internalType":"bytes","name":"_data","type":"bytes"}],"name":"getL1Fee","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}]`

// ABIs for OP Stack Ecotone GasPriceOracle methods needed to calculated encoded gas price
const OPIsEcotoneAbiString = `[{"inputs":[],"name":"isEcotone","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"}]`
const OPGetL1GasUsedAbiString = `[{"inputs":[{"internalType":"bytes","name":"_data","type":"bytes"}],"name":"getL1GasUsed","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}]`
32 changes: 6 additions & 26 deletions core/chains/evm/gas/rollups/l1_oracle_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,21 +72,11 @@ func TestL1Oracle_GasPrice(t *testing.T) {

t.Run("Calling GasPrice on started Kroma L1Oracle returns Kroma l1GasPrice", func(t *testing.T) {
l1BaseFee := big.NewInt(100)
l1GasPriceMethodAbi, err := abi.JSON(strings.NewReader(L1BaseFeeAbiString))
require.NoError(t, err)

ethClient := mocks.NewETHClient(t)
ethClient.On("CallContract", mock.Anything, mock.IsType(ethereum.CallMsg{}), mock.IsType(&big.Int{})).Run(func(args mock.Arguments) {
callMsg := args.Get(1).(ethereum.CallMsg)
blockNumber := args.Get(2).(*big.Int)
var payload []byte
payload, err = l1GasPriceMethodAbi.Pack("l1BaseFee")
require.NoError(t, err)
require.Equal(t, payload, callMsg.Data)
assert.Nil(t, blockNumber)
}).Return(common.BigToHash(l1BaseFee).Bytes(), nil)
priceReader := mocks.NewDAPriceReader(t)
priceReader.On("GetDAGasPrice", mock.Anything).Return(l1BaseFee, nil)

oracle := NewL1GasOracle(logger.Test(t), ethClient, config.ChainKroma)
oracle := newL1GasOracle(logger.Test(t), nil, config.ChainKroma, priceReader)
servicetest.RunHealthy(t, oracle)

gasPrice, err := oracle.GasPrice(testutils.Context(t))
Expand All @@ -97,21 +87,11 @@ func TestL1Oracle_GasPrice(t *testing.T) {

t.Run("Calling GasPrice on started OPStack L1Oracle returns OPStack l1GasPrice", func(t *testing.T) {
l1BaseFee := big.NewInt(100)
l1GasPriceMethodAbi, err := abi.JSON(strings.NewReader(L1BaseFeeAbiString))
require.NoError(t, err)

ethClient := mocks.NewETHClient(t)
ethClient.On("CallContract", mock.Anything, mock.IsType(ethereum.CallMsg{}), mock.IsType(&big.Int{})).Run(func(args mock.Arguments) {
callMsg := args.Get(1).(ethereum.CallMsg)
blockNumber := args.Get(2).(*big.Int)
var payload []byte
payload, err = l1GasPriceMethodAbi.Pack("l1BaseFee")
require.NoError(t, err)
require.Equal(t, payload, callMsg.Data)
assert.Nil(t, blockNumber)
}).Return(common.BigToHash(l1BaseFee).Bytes(), nil)
priceReader := mocks.NewDAPriceReader(t)
priceReader.On("GetDAGasPrice", mock.Anything).Return(l1BaseFee, nil)

oracle := NewL1GasOracle(logger.Test(t), ethClient, config.ChainOptimismBedrock)
oracle := newL1GasOracle(logger.Test(t), nil, config.ChainOptimismBedrock, priceReader)
servicetest.RunHealthy(t, oracle)

gasPrice, err := oracle.GasPrice(testutils.Context(t))
Expand Down
59 changes: 59 additions & 0 deletions core/chains/evm/gas/rollups/mocks/da_price_reader.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 20 additions & 0 deletions core/chains/evm/gas/rollups/mocks/eth_client.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading