diff --git a/.changeset/curly-onions-tell.md b/.changeset/curly-onions-tell.md new file mode 100644 index 00000000000..249f616c012 --- /dev/null +++ b/.changeset/curly-onions-tell.md @@ -0,0 +1,5 @@ +--- +"chainlink": patch +--- + +#changed Make Mantle use default OP stack l1 gas oracle in core diff --git a/core/chains/evm/gas/rollups/arbitrum_l1_oracle.go b/core/chains/evm/gas/rollups/arbitrum_l1_oracle.go index c01244db70c..d758dc711e9 100644 --- a/core/chains/evm/gas/rollups/arbitrum_l1_oracle.go +++ b/core/chains/evm/gas/rollups/arbitrum_l1_oracle.go @@ -112,6 +112,10 @@ func (o *arbitrumL1Oracle) Name() string { return o.logger.Name() } +func (o *arbitrumL1Oracle) ChainType(_ context.Context) chaintype.ChainType { + return o.chainType +} + func (o *arbitrumL1Oracle) Start(ctx context.Context) error { return o.StartOnce(o.Name(), func() error { go o.run() diff --git a/core/chains/evm/gas/rollups/l1_oracle.go b/core/chains/evm/gas/rollups/l1_oracle.go index e1249fdb7e9..41951755986 100644 --- a/core/chains/evm/gas/rollups/l1_oracle.go +++ b/core/chains/evm/gas/rollups/l1_oracle.go @@ -26,6 +26,7 @@ type L1Oracle interface { GasPrice(ctx context.Context) (*assets.Wei, error) GetGasCost(ctx context.Context, tx *types.Transaction, blockNum *big.Int) (*assets.Wei, error) + ChainType(ctx context.Context) chaintype.ChainType } type l1OracleClient interface { diff --git a/core/chains/evm/gas/rollups/l1_oracle_abi.go b/core/chains/evm/gas/rollups/l1_oracle_abi.go index 848957ce53a..fa5d9c85391 100644 --- a/core/chains/evm/gas/rollups/l1_oracle_abi.go +++ b/core/chains/evm/gas/rollups/l1_oracle_abi.go @@ -19,4 +19,3 @@ const OPBaseFeeScalarAbiString = `[{"inputs":[],"name":"baseFeeScalar","outputs" const OPBlobBaseFeeAbiString = `[{"inputs":[],"name":"blobBaseFee","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}]` const OPBlobBaseFeeScalarAbiString = `[{"inputs":[],"name":"blobBaseFeeScalar","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"}]` const OPDecimalsAbiString = `[{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"pure","type":"function"}]` -const MantleTokenRatioAbiString = `[{"inputs":[],"name":"tokenRatio","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}]` diff --git a/core/chains/evm/gas/rollups/mocks/l1_oracle.go b/core/chains/evm/gas/rollups/mocks/l1_oracle.go index e82cb4ee903..25bb3a2db59 100644 --- a/core/chains/evm/gas/rollups/mocks/l1_oracle.go +++ b/core/chains/evm/gas/rollups/mocks/l1_oracle.go @@ -7,6 +7,8 @@ import ( assets "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" + chaintype "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/chaintype" + context "context" mock "github.com/stretchr/testify/mock" @@ -27,6 +29,52 @@ func (_m *L1Oracle) EXPECT() *L1Oracle_Expecter { return &L1Oracle_Expecter{mock: &_m.Mock} } +// ChainType provides a mock function with given fields: ctx +func (_m *L1Oracle) ChainType(ctx context.Context) chaintype.ChainType { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for ChainType") + } + + var r0 chaintype.ChainType + if rf, ok := ret.Get(0).(func(context.Context) chaintype.ChainType); ok { + r0 = rf(ctx) + } else { + r0 = ret.Get(0).(chaintype.ChainType) + } + + return r0 +} + +// L1Oracle_ChainType_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ChainType' +type L1Oracle_ChainType_Call struct { + *mock.Call +} + +// ChainType is a helper method to define mock.On call +// - ctx context.Context +func (_e *L1Oracle_Expecter) ChainType(ctx interface{}) *L1Oracle_ChainType_Call { + return &L1Oracle_ChainType_Call{Call: _e.mock.On("ChainType", ctx)} +} + +func (_c *L1Oracle_ChainType_Call) Run(run func(ctx context.Context)) *L1Oracle_ChainType_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *L1Oracle_ChainType_Call) Return(_a0 chaintype.ChainType) *L1Oracle_ChainType_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *L1Oracle_ChainType_Call) RunAndReturn(run func(context.Context) chaintype.ChainType) *L1Oracle_ChainType_Call { + _c.Call.Return(run) + return _c +} + // Close provides a mock function with given fields: func (_m *L1Oracle) Close() error { ret := _m.Called() diff --git a/core/chains/evm/gas/rollups/op_l1_oracle.go b/core/chains/evm/gas/rollups/op_l1_oracle.go index 1b93f8fc3f9..11babc5ca5d 100644 --- a/core/chains/evm/gas/rollups/op_l1_oracle.go +++ b/core/chains/evm/gas/rollups/op_l1_oracle.go @@ -88,10 +88,6 @@ const ( // decimals is a hex encoded call to: // `function decimals() public pure returns (uint256);` decimalsMethod = "decimals" - // tokenRatio fetches the tokenRatio used for Mantle's gas price calculation - // tokenRatio is a hex encoded call to: - // `function tokenRatio() public pure returns (uint256);` - tokenRatioMethod = "tokenRatio" // OPGasOracleAddress is the address of the precompiled contract that exists on Optimism, Base and Mantle. OPGasOracleAddress = "0x420000000000000000000000000000000000000F" // KromaGasOracleAddress is the address of the precompiled contract that exists on Kroma. @@ -192,16 +188,6 @@ func newOpStackL1GasOracle(lggr logger.Logger, ethClient l1OracleClient, chainTy return nil, fmt.Errorf("failed to parse GasPriceOracle %s() calldata for chain: %s; %w", decimalsMethod, chainType, err) } - // Encode calldata for tokenRatio method - tokenRatioMethodAbi, err := abi.JSON(strings.NewReader(MantleTokenRatioAbiString)) - if err != nil { - return nil, fmt.Errorf("failed to parse GasPriceOracle %s() method ABI for chain: %s; %w", tokenRatioMethod, chainType, err) - } - tokenRatioCalldata, err := tokenRatioMethodAbi.Pack(tokenRatioMethod) - if err != nil { - return nil, fmt.Errorf("failed to parse GasPriceOracle %s() calldata for chain: %s; %w", tokenRatioMethod, chainType, err) - } - return &optimismL1Oracle{ client: ethClient, pollPeriod: PollPeriod, @@ -223,7 +209,6 @@ func newOpStackL1GasOracle(lggr logger.Logger, ethClient l1OracleClient, chainTy blobBaseFeeCalldata: blobBaseFeeCalldata, blobBaseFeeScalarCalldata: blobBaseFeeScalarCalldata, decimalsCalldata: decimalsCalldata, - tokenRatioCalldata: tokenRatioCalldata, isEcotoneCalldata: isEcotoneCalldata, isEcotoneMethodAbi: isEcotoneMethodAbi, isFjordCalldata: isFjordCalldata, @@ -235,6 +220,10 @@ func (o *optimismL1Oracle) Name() string { return o.logger.Name() } +func (o *optimismL1Oracle) ChainType(_ context.Context) chaintype.ChainType { + return o.chainType +} + func (o *optimismL1Oracle) Start(ctx context.Context) error { return o.StartOnce(o.Name(), func() error { go o.run() @@ -362,10 +351,6 @@ func (o *optimismL1Oracle) GetGasCost(ctx context.Context, tx *gethtypes.Transac } func (o *optimismL1Oracle) GetDAGasPrice(ctx context.Context) (*big.Int, error) { - if o.chainType == chaintype.ChainMantle { - return o.getMantleGasPrice(ctx) - } - err := o.checkForUpgrade(ctx) if err != nil { return nil, err @@ -463,69 +448,6 @@ func (o *optimismL1Oracle) getV1GasPrice(ctx context.Context) (*big.Int, error) return new(big.Int).SetBytes(b), nil } -// Returns the gas price for Mantle. The formula is the same as Optimism Bedrock (getV1GasPrice), but the tokenRatio parameter is multiplied -func (o *optimismL1Oracle) getMantleGasPrice(ctx context.Context) (*big.Int, error) { - // call oracle to get l1BaseFee and tokenRatio - rpcBatchCalls := []rpc.BatchElem{ - { - Method: "eth_call", - Args: []any{ - map[string]interface{}{ - "from": common.Address{}, - "to": o.l1OracleAddress, - "data": hexutil.Bytes(o.l1BaseFeeCalldata), - }, - "latest", - }, - Result: new(string), - }, - { - Method: "eth_call", - Args: []any{ - map[string]interface{}{ - "from": common.Address{}, - "to": o.l1OracleAddress, - "data": hexutil.Bytes(o.tokenRatioCalldata), - }, - "latest", - }, - Result: new(string), - }, - } - - err := o.client.BatchCallContext(ctx, rpcBatchCalls) - if err != nil { - return nil, fmt.Errorf("fetch gas price parameters batch call failed: %w", err) - } - if rpcBatchCalls[0].Error != nil { - return nil, fmt.Errorf("%s call failed in a batch: %w", l1BaseFeeMethod, err) - } - if rpcBatchCalls[1].Error != nil { - return nil, fmt.Errorf("%s call failed in a batch: %w", tokenRatioMethod, err) - } - - // Extract values from responses - l1BaseFeeResult := *(rpcBatchCalls[0].Result.(*string)) - tokenRatioResult := *(rpcBatchCalls[1].Result.(*string)) - - // Decode the responses into bytes - l1BaseFeeBytes, err := hexutil.Decode(l1BaseFeeResult) - if err != nil { - return nil, fmt.Errorf("failed to decode %s rpc result: %w", l1BaseFeeMethod, err) - } - tokenRatioBytes, err := hexutil.Decode(tokenRatioResult) - if err != nil { - return nil, fmt.Errorf("failed to decode %s rpc result: %w", tokenRatioMethod, err) - } - - // Convert bytes to big int for calculations - l1BaseFee := new(big.Int).SetBytes(l1BaseFeeBytes) - tokenRatio := new(big.Int).SetBytes(tokenRatioBytes) - - // multiply l1BaseFee and tokenRatio and return - return new(big.Int).Mul(l1BaseFee, tokenRatio), nil -} - // Returns the scaled gas price using baseFeeScalar, l1BaseFee, blobBaseFeeScalar, and blobBaseFee fields from the oracle // Confirmed the same calculation is used to determine gas price for both Ecotone and Fjord func (o *optimismL1Oracle) getEcotoneFjordGasPrice(ctx context.Context) (*big.Int, error) { diff --git a/core/chains/evm/gas/rollups/op_l1_oracle_test.go b/core/chains/evm/gas/rollups/op_l1_oracle_test.go index 88bb96534d3..f5f009f1ea6 100644 --- a/core/chains/evm/gas/rollups/op_l1_oracle_test.go +++ b/core/chains/evm/gas/rollups/op_l1_oracle_test.go @@ -111,94 +111,6 @@ func TestOPL1Oracle_ReadV1GasPrice(t *testing.T) { } } -func TestOPL1Oracle_ReadMantleGasPrice(t *testing.T) { - l1BaseFee := big.NewInt(100) - tokenRatio := big.NewInt(40) - oracleAddress := common.HexToAddress("0x1234").String() - - t.Parallel() - t.Run("correctly fetches gas price if chain is Mantle", func(t *testing.T) { - // Encode calldata for l1BaseFee method - l1BaseFeeMethodAbi, err := abi.JSON(strings.NewReader(L1BaseFeeAbiString)) - require.NoError(t, err) - l1BaseFeeCalldata, err := l1BaseFeeMethodAbi.Pack(l1BaseFeeMethod) - require.NoError(t, err) - - // Encode calldata for tokenRatio method - tokenRatioMethodAbi, err := abi.JSON(strings.NewReader(MantleTokenRatioAbiString)) - require.NoError(t, err) - tokenRatioCalldata, err := tokenRatioMethodAbi.Pack(tokenRatioMethod) - require.NoError(t, err) - - ethClient := mocks.NewL1OracleClient(t) - ethClient.On("BatchCallContext", mock.Anything, mock.IsType([]rpc.BatchElem{})).Run(func(args mock.Arguments) { - rpcElements := args.Get(1).([]rpc.BatchElem) - require.Equal(t, 2, len(rpcElements)) - for _, rE := range rpcElements { - require.Equal(t, "eth_call", rE.Method) - require.Equal(t, oracleAddress, rE.Args[0].(map[string]interface{})["to"]) - require.Equal(t, "latest", rE.Args[1]) - } - require.Equal(t, hexutil.Bytes(l1BaseFeeCalldata), rpcElements[0].Args[0].(map[string]interface{})["data"]) - require.Equal(t, hexutil.Bytes(tokenRatioCalldata), rpcElements[1].Args[0].(map[string]interface{})["data"]) - - res1 := common.BigToHash(l1BaseFee).Hex() - res2 := common.BigToHash(tokenRatio).Hex() - - rpcElements[0].Result = &res1 - rpcElements[1].Result = &res2 - }).Return(nil).Once() - - oracle, err := newOpStackL1GasOracle(logger.Test(t), ethClient, chaintype.ChainMantle, oracleAddress) - require.NoError(t, err) - - gasPrice, err := oracle.GetDAGasPrice(tests.Context(t)) - require.NoError(t, err) - - assert.Equal(t, new(big.Int).Mul(l1BaseFee, tokenRatio), gasPrice) - }) - - t.Run("fetching Mantle price but rpc returns bad data", func(t *testing.T) { - ethClient := mocks.NewL1OracleClient(t) - ethClient.On("BatchCallContext", mock.Anything, mock.IsType([]rpc.BatchElem{})).Run(func(args mock.Arguments) { - rpcElements := args.Get(1).([]rpc.BatchElem) - var badData = "zzz" - rpcElements[0].Result = &badData - rpcElements[1].Result = &badData - }).Return(nil).Once() - - oracle, err := newOpStackL1GasOracle(logger.Test(t), ethClient, chaintype.ChainMantle, oracleAddress) - require.NoError(t, err) - _, err = oracle.GetDAGasPrice(tests.Context(t)) - assert.Error(t, err) - }) - - t.Run("fetching Mantle price but rpc parent call errors", func(t *testing.T) { - ethClient := mocks.NewL1OracleClient(t) - ethClient.On("BatchCallContext", mock.Anything, mock.IsType([]rpc.BatchElem{})).Return(fmt.Errorf("revert")).Once() - - oracle, err := newOpStackL1GasOracle(logger.Test(t), ethClient, chaintype.ChainMantle, oracleAddress) - require.NoError(t, err) - _, err = oracle.GetDAGasPrice(tests.Context(t)) - assert.Error(t, err) - }) - - t.Run("fetching Mantle price but one of the sub rpc call errors", func(t *testing.T) { - ethClient := mocks.NewL1OracleClient(t) - ethClient.On("BatchCallContext", mock.Anything, mock.IsType([]rpc.BatchElem{})).Run(func(args mock.Arguments) { - rpcElements := args.Get(1).([]rpc.BatchElem) - res := common.BigToHash(l1BaseFee).Hex() - rpcElements[0].Result = &res - rpcElements[1].Error = fmt.Errorf("revert") - }).Return(nil).Once() - - oracle, err := newOpStackL1GasOracle(logger.Test(t), ethClient, chaintype.ChainMantle, oracleAddress) - require.NoError(t, err) - _, err = oracle.GetDAGasPrice(tests.Context(t)) - assert.Error(t, err) - }) -} - func setupUpgradeCheck(t *testing.T, oracleAddress string, isFjord, isEcotone bool) *mocks.L1OracleClient { trueHex := "0x0000000000000000000000000000000000000000000000000000000000000001" falseHex := "0x0000000000000000000000000000000000000000000000000000000000000000" diff --git a/core/chains/evm/gas/rollups/zkSync_l1_oracle.go b/core/chains/evm/gas/rollups/zkSync_l1_oracle.go index 31d93bc587d..c2941233545 100644 --- a/core/chains/evm/gas/rollups/zkSync_l1_oracle.go +++ b/core/chains/evm/gas/rollups/zkSync_l1_oracle.go @@ -83,6 +83,10 @@ func (o *zkSyncL1Oracle) Name() string { return o.logger.Name() } +func (o *zkSyncL1Oracle) ChainType(_ context.Context) chaintype.ChainType { + return o.chainType +} + func (o *zkSyncL1Oracle) Start(ctx context.Context) error { return o.StartOnce(o.Name(), func() error { go o.run()