From 4ac405a6d5e3bc668ee96eac6bdbecc524467fd6 Mon Sep 17 00:00:00 2001 From: Oliver Townsend <133903322+ogtownsend@users.noreply.github.com> Date: Fri, 11 Oct 2024 11:37:12 -0700 Subject: [PATCH] CCIP-3552 refactor OP oracle to accept generic DA oracle config (#14599) * Add DA oracle interface * run mockery * changeset * Comment out DA oracle validations for now * adding tag to changeset * fix tests * fix tests and some more refactoring * add tests for custom gas API * Rebase * feedback * Fall back to check chainType when creating the L1 oracle * update default tomls with DA config * renamings * remove custom calldata option from op oracle * update namings again * change OP oracle type to opstack * feedback * use random address for tests --- .changeset/late-pillows-shake.md | 5 + .../ocrimpls/contract_transmitter_test.go | 20 +++ .../evm/config/chain_scoped_gas_estimator.go | 24 ++++ core/chains/evm/config/config.go | 8 ++ core/chains/evm/config/mocks/gas_estimator.go | 47 +++++++ core/chains/evm/config/toml/config.go | 24 ++++ .../evm/config/toml/defaults/Base_Goerli.toml | 4 + .../config/toml/defaults/Base_Mainnet.toml | 4 + .../config/toml/defaults/Base_Sepolia.toml | 4 + .../config/toml/defaults/Kroma_Mainnet.toml | 4 + .../config/toml/defaults/Kroma_Sepolia.toml | 4 + .../config/toml/defaults/Optimism_Goerli.toml | 4 + .../toml/defaults/Optimism_Mainnet.toml | 4 + .../toml/defaults/Optimism_Sepolia.toml | 4 + .../config/toml/defaults/Scroll_Mainnet.toml | 4 + .../config/toml/defaults/Scroll_Sepolia.toml | 4 + .../config/toml/defaults/Soneium_Sepolia.toml | 4 + .../config/toml/defaults/Zircuit_Mainnet.toml | 4 + .../config/toml/defaults/Zircuit_Sepolia.toml | 4 + core/chains/evm/gas/models.go | 3 +- core/chains/evm/gas/models_test.go | 4 +- .../evm/gas/rollups/da_oracle_test_helper.go | 39 ++++++ core/chains/evm/gas/rollups/l1_oracle.go | 24 +++- core/chains/evm/gas/rollups/l1_oracle_test.go | 32 +++-- core/chains/evm/gas/rollups/op_l1_oracle.go | 115 ++++++------------ .../evm/gas/rollups/op_l1_oracle_test.go | 33 +++-- core/chains/evm/txmgr/test_helpers.go | 20 +++ core/config/docs/chains-evm.toml | 8 ++ core/config/docs/docs_test.go | 4 + core/services/chainlink/config_test.go | 3 + docs/CONFIG.md | 92 ++++++++++++++ 31 files changed, 456 insertions(+), 101 deletions(-) create mode 100644 .changeset/late-pillows-shake.md create mode 100644 core/chains/evm/gas/rollups/da_oracle_test_helper.go diff --git a/.changeset/late-pillows-shake.md b/.changeset/late-pillows-shake.md new file mode 100644 index 00000000000..f27f09519db --- /dev/null +++ b/.changeset/late-pillows-shake.md @@ -0,0 +1,5 @@ +--- +"chainlink": patch +--- + +Refactor OP oracle to accept generic DA oracle config #wip diff --git a/core/capabilities/ccip/ocrimpls/contract_transmitter_test.go b/core/capabilities/ccip/ocrimpls/contract_transmitter_test.go index 6440e51ff4a..521ff08ed15 100644 --- a/core/capabilities/ccip/ocrimpls/contract_transmitter_test.go +++ b/core/capabilities/ccip/ocrimpls/contract_transmitter_test.go @@ -9,6 +9,8 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/ocrimpls" cctypes "github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/types" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/toml" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/accounts/abi/bind/backends" @@ -588,6 +590,24 @@ type TestGasEstimatorConfig struct { bumpThreshold uint64 } +func (g *TestGasEstimatorConfig) DAOracle() evmconfig.DAOracle { + return &TestDAOracleConfig{} +} + +type TestDAOracleConfig struct { + evmconfig.DAOracle +} + +func (d *TestDAOracleConfig) OracleType() toml.OracleType { return toml.OPStack } +func (d *TestDAOracleConfig) OracleAddress() *types.EIP55Address { + a, err := types.NewEIP55Address("0x420000000000000000000000000000000000000F") + if err != nil { + panic(err) + } + return &a +} +func (d *TestDAOracleConfig) CustomGasPriceCalldata() string { return "" } + func (g *TestGasEstimatorConfig) BlockHistory() evmconfig.BlockHistory { return &TestBlockHistoryConfig{} } diff --git a/core/chains/evm/config/chain_scoped_gas_estimator.go b/core/chains/evm/config/chain_scoped_gas_estimator.go index 54c7c360639..49e438fc67c 100644 --- a/core/chains/evm/config/chain_scoped_gas_estimator.go +++ b/core/chains/evm/config/chain_scoped_gas_estimator.go @@ -7,6 +7,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/toml" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" ) type gasEstimatorConfig struct { @@ -42,6 +43,10 @@ func (g *gasEstimatorConfig) FeeHistory() FeeHistory { return &feeHistoryConfig{c: g.c.FeeHistory} } +func (g *gasEstimatorConfig) DAOracle() DAOracle { + return &daOracleConfig{c: g.c.DAOracle} +} + func (g *gasEstimatorConfig) EIP1559DynamicFees() bool { return *g.c.EIP1559DynamicFees } @@ -118,6 +123,25 @@ func (g *gasEstimatorConfig) EstimateLimit() bool { return *g.c.EstimateLimit } +type daOracleConfig struct { + c toml.DAOracle +} + +func (d *daOracleConfig) OracleType() toml.OracleType { + return d.c.OracleType +} + +// OracleAddress returns the address of the oracle contract and is only supported on the OP stack for now. +func (d *daOracleConfig) OracleAddress() *types.EIP55Address { + return d.c.OracleAddress +} + +// CustomGasPriceCalldata returns the calldata for a custom gas price API. +func (d *daOracleConfig) CustomGasPriceCalldata() string { + // TODO: CCIP-3710 update once custom calldata oracle is added + return "" +} + type limitJobTypeConfig struct { c toml.GasLimitJobType } diff --git a/core/chains/evm/config/config.go b/core/chains/evm/config/config.go index c886f9fb616..db08512a146 100644 --- a/core/chains/evm/config/config.go +++ b/core/chains/evm/config/config.go @@ -11,6 +11,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/chaintype" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/toml" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" ) @@ -141,6 +142,7 @@ type GasEstimator interface { Mode() string PriceMaxKey(gethcommon.Address) *assets.Wei EstimateLimit() bool + DAOracle() DAOracle } type LimitJobType interface { @@ -162,6 +164,12 @@ type BlockHistory interface { TransactionPercentile() uint16 } +type DAOracle interface { + OracleType() toml.OracleType + OracleAddress() *types.EIP55Address + CustomGasPriceCalldata() string +} + type FeeHistory interface { CacheTimeout() time.Duration } diff --git a/core/chains/evm/config/mocks/gas_estimator.go b/core/chains/evm/config/mocks/gas_estimator.go index 70b9c18d0bf..44013768156 100644 --- a/core/chains/evm/config/mocks/gas_estimator.go +++ b/core/chains/evm/config/mocks/gas_estimator.go @@ -253,6 +253,53 @@ func (_c *GasEstimator_BumpTxDepth_Call) RunAndReturn(run func() uint32) *GasEst return _c } +// DAOracle provides a mock function with given fields: +func (_m *GasEstimator) DAOracle() config.DAOracle { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for DAOracle") + } + + var r0 config.DAOracle + if rf, ok := ret.Get(0).(func() config.DAOracle); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(config.DAOracle) + } + } + + return r0 +} + +// GasEstimator_DAOracle_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DAOracle' +type GasEstimator_DAOracle_Call struct { + *mock.Call +} + +// DAOracle is a helper method to define mock.On call +func (_e *GasEstimator_Expecter) DAOracle() *GasEstimator_DAOracle_Call { + return &GasEstimator_DAOracle_Call{Call: _e.mock.On("DAOracle")} +} + +func (_c *GasEstimator_DAOracle_Call) Run(run func()) *GasEstimator_DAOracle_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *GasEstimator_DAOracle_Call) Return(_a0 config.DAOracle) *GasEstimator_DAOracle_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *GasEstimator_DAOracle_Call) RunAndReturn(run func() config.DAOracle) *GasEstimator_DAOracle_Call { + _c.Call.Return(run) + return _c +} + // EIP1559DynamicFees provides a mock function with given fields: func (_m *GasEstimator) EIP1559DynamicFees() bool { ret := _m.Called() diff --git a/core/chains/evm/config/toml/config.go b/core/chains/evm/config/toml/config.go index b6b6a732363..27a279f49fc 100644 --- a/core/chains/evm/config/toml/config.go +++ b/core/chains/evm/config/toml/config.go @@ -588,6 +588,7 @@ type GasEstimator struct { BlockHistory BlockHistoryEstimator `toml:",omitempty"` FeeHistory FeeHistoryEstimator `toml:",omitempty"` + DAOracle DAOracle `toml:",omitempty"` } func (e *GasEstimator) ValidateConfig() (err error) { @@ -683,6 +684,7 @@ func (e *GasEstimator) setFrom(f *GasEstimator) { e.LimitJobType.setFrom(&f.LimitJobType) e.BlockHistory.setFrom(&f.BlockHistory) e.FeeHistory.setFrom(&f.FeeHistory) + e.DAOracle.setFrom(&f.DAOracle) } type GasLimitJobType struct { @@ -755,6 +757,28 @@ func (u *FeeHistoryEstimator) setFrom(f *FeeHistoryEstimator) { } } +type DAOracle struct { + OracleType OracleType + OracleAddress *types.EIP55Address + CustomGasPriceCalldata string +} + +type OracleType string + +const ( + OPStack = OracleType("opstack") + Arbitrum = OracleType("arbitrum") + ZKSync = OracleType("zksync") +) + +func (d *DAOracle) setFrom(f *DAOracle) { + d.OracleType = f.OracleType + if v := f.OracleAddress; v != nil { + d.OracleAddress = v + } + d.CustomGasPriceCalldata = f.CustomGasPriceCalldata +} + type KeySpecificConfig []KeySpecific func (ks KeySpecificConfig) ValidateConfig() (err error) { diff --git a/core/chains/evm/config/toml/defaults/Base_Goerli.toml b/core/chains/evm/config/toml/defaults/Base_Goerli.toml index 5ecfd036f46..2dd4f4033c6 100644 --- a/core/chains/evm/config/toml/defaults/Base_Goerli.toml +++ b/core/chains/evm/config/toml/defaults/Base_Goerli.toml @@ -13,6 +13,10 @@ BumpMin = '100 wei' [GasEstimator.BlockHistory] BlockHistorySize = 60 +[GasEstimator.DAOracle] +OracleType = 'opstack' +OracleAddress = '0x420000000000000000000000000000000000000F' + [Transactions] ResendAfterThreshold = '30s' diff --git a/core/chains/evm/config/toml/defaults/Base_Mainnet.toml b/core/chains/evm/config/toml/defaults/Base_Mainnet.toml index da38182b194..b683aff9840 100644 --- a/core/chains/evm/config/toml/defaults/Base_Mainnet.toml +++ b/core/chains/evm/config/toml/defaults/Base_Mainnet.toml @@ -15,6 +15,10 @@ BumpMin = '100 wei' [GasEstimator.BlockHistory] BlockHistorySize = 24 +[GasEstimator.DAOracle] +OracleType = 'opstack' +OracleAddress = '0x420000000000000000000000000000000000000F' + [Transactions] ResendAfterThreshold = '30s' diff --git a/core/chains/evm/config/toml/defaults/Base_Sepolia.toml b/core/chains/evm/config/toml/defaults/Base_Sepolia.toml index b4a7eb680ab..3e6aee20e29 100644 --- a/core/chains/evm/config/toml/defaults/Base_Sepolia.toml +++ b/core/chains/evm/config/toml/defaults/Base_Sepolia.toml @@ -15,6 +15,10 @@ BumpMin = '100 wei' [GasEstimator.BlockHistory] BlockHistorySize = 60 +[GasEstimator.DAOracle] +OracleType = 'opstack' +OracleAddress = '0x420000000000000000000000000000000000000F' + [Transactions] ResendAfterThreshold = '30s' diff --git a/core/chains/evm/config/toml/defaults/Kroma_Mainnet.toml b/core/chains/evm/config/toml/defaults/Kroma_Mainnet.toml index 3a48aa8ae1b..327c8b0975c 100644 --- a/core/chains/evm/config/toml/defaults/Kroma_Mainnet.toml +++ b/core/chains/evm/config/toml/defaults/Kroma_Mainnet.toml @@ -14,6 +14,10 @@ BumpMin = '100 wei' [GasEstimator.BlockHistory] BlockHistorySize = 24 +[GasEstimator.DAOracle] +OracleType = 'opstack' +OracleAddress = '0x4200000000000000000000000000000000000005' + [Transactions] ResendAfterThreshold = '30s' diff --git a/core/chains/evm/config/toml/defaults/Kroma_Sepolia.toml b/core/chains/evm/config/toml/defaults/Kroma_Sepolia.toml index 9609a09e076..da0dc36541c 100644 --- a/core/chains/evm/config/toml/defaults/Kroma_Sepolia.toml +++ b/core/chains/evm/config/toml/defaults/Kroma_Sepolia.toml @@ -14,6 +14,10 @@ BumpMin = '100 wei' [GasEstimator.BlockHistory] BlockHistorySize = 24 +[GasEstimator.DAOracle] +OracleType = 'opstack' +OracleAddress = '0x4200000000000000000000000000000000000005' + [Transactions] ResendAfterThreshold = '30s' diff --git a/core/chains/evm/config/toml/defaults/Optimism_Goerli.toml b/core/chains/evm/config/toml/defaults/Optimism_Goerli.toml index 458b3b08123..76c976a9114 100644 --- a/core/chains/evm/config/toml/defaults/Optimism_Goerli.toml +++ b/core/chains/evm/config/toml/defaults/Optimism_Goerli.toml @@ -14,6 +14,10 @@ BumpMin = '100 wei' [GasEstimator.BlockHistory] BlockHistorySize = 60 +[GasEstimator.DAOracle] +OracleType = 'opstack' +OracleAddress = '0x420000000000000000000000000000000000000F' + [Transactions] ResendAfterThreshold = '30s' diff --git a/core/chains/evm/config/toml/defaults/Optimism_Mainnet.toml b/core/chains/evm/config/toml/defaults/Optimism_Mainnet.toml index b0f56a49d90..cd09ad4a60f 100644 --- a/core/chains/evm/config/toml/defaults/Optimism_Mainnet.toml +++ b/core/chains/evm/config/toml/defaults/Optimism_Mainnet.toml @@ -16,6 +16,10 @@ BumpMin = '100 wei' [GasEstimator.BlockHistory] BlockHistorySize = 24 +[GasEstimator.DAOracle] +OracleType = 'opstack' +OracleAddress = '0x420000000000000000000000000000000000000F' + [Transactions] ResendAfterThreshold = '30s' diff --git a/core/chains/evm/config/toml/defaults/Optimism_Sepolia.toml b/core/chains/evm/config/toml/defaults/Optimism_Sepolia.toml index 1c71aa5dd83..ff367df0feb 100644 --- a/core/chains/evm/config/toml/defaults/Optimism_Sepolia.toml +++ b/core/chains/evm/config/toml/defaults/Optimism_Sepolia.toml @@ -15,6 +15,10 @@ BumpMin = '100 wei' [GasEstimator.BlockHistory] BlockHistorySize = 60 +[GasEstimator.DAOracle] +OracleType = 'opstack' +OracleAddress = '0x420000000000000000000000000000000000000F' + [Transactions] ResendAfterThreshold = '30s' diff --git a/core/chains/evm/config/toml/defaults/Scroll_Mainnet.toml b/core/chains/evm/config/toml/defaults/Scroll_Mainnet.toml index 4a887b504df..5a5e0459512 100644 --- a/core/chains/evm/config/toml/defaults/Scroll_Mainnet.toml +++ b/core/chains/evm/config/toml/defaults/Scroll_Mainnet.toml @@ -15,6 +15,10 @@ BumpMin = '1 gwei' [GasEstimator.BlockHistory] BlockHistorySize = 24 +[GasEstimator.DAOracle] +OracleType = 'opstack' +OracleAddress = '0x5300000000000000000000000000000000000002' + [HeadTracker] HistoryDepth = 50 diff --git a/core/chains/evm/config/toml/defaults/Scroll_Sepolia.toml b/core/chains/evm/config/toml/defaults/Scroll_Sepolia.toml index b2e1cfbd733..a9c6a979e64 100644 --- a/core/chains/evm/config/toml/defaults/Scroll_Sepolia.toml +++ b/core/chains/evm/config/toml/defaults/Scroll_Sepolia.toml @@ -15,6 +15,10 @@ BumpMin = '1 gwei' [GasEstimator.BlockHistory] BlockHistorySize = 24 +[GasEstimator.DAOracle] +OracleType = 'opstack' +OracleAddress = '0x5300000000000000000000000000000000000002' + [HeadTracker] HistoryDepth = 50 diff --git a/core/chains/evm/config/toml/defaults/Soneium_Sepolia.toml b/core/chains/evm/config/toml/defaults/Soneium_Sepolia.toml index 9f4772dd9a0..7ab92f04e5b 100755 --- a/core/chains/evm/config/toml/defaults/Soneium_Sepolia.toml +++ b/core/chains/evm/config/toml/defaults/Soneium_Sepolia.toml @@ -16,6 +16,10 @@ BumpMin = '1 mwei' [GasEstimator.BlockHistory] BlockHistorySize = 60 +[GasEstimator.DAOracle] +OracleType = 'opstack' +OracleAddress = '0x420000000000000000000000000000000000000F' + [Transactions] ResendAfterThreshold = '30s' diff --git a/core/chains/evm/config/toml/defaults/Zircuit_Mainnet.toml b/core/chains/evm/config/toml/defaults/Zircuit_Mainnet.toml index 885166fe8eb..b2c1411726d 100644 --- a/core/chains/evm/config/toml/defaults/Zircuit_Mainnet.toml +++ b/core/chains/evm/config/toml/defaults/Zircuit_Mainnet.toml @@ -15,6 +15,10 @@ BumpMin = '100 wei' [GasEstimator.BlockHistory] BlockHistorySize = 24 +[GasEstimator.DAOracle] +OracleType = 'opstack' +OracleAddress = '0x420000000000000000000000000000000000000F' + [Transactions] ResendAfterThreshold = '30s' diff --git a/core/chains/evm/config/toml/defaults/Zircuit_Sepolia.toml b/core/chains/evm/config/toml/defaults/Zircuit_Sepolia.toml index 40493a9dab3..3a82bb9623d 100644 --- a/core/chains/evm/config/toml/defaults/Zircuit_Sepolia.toml +++ b/core/chains/evm/config/toml/defaults/Zircuit_Sepolia.toml @@ -15,6 +15,10 @@ BumpMin = '100 wei' [GasEstimator.BlockHistory] BlockHistorySize = 60 +[GasEstimator.DAOracle] +OracleType = 'opstack' +OracleAddress = '0x420000000000000000000000000000000000000F' + [Transactions] ResendAfterThreshold = '30s' diff --git a/core/chains/evm/gas/models.go b/core/chains/evm/gas/models.go index 6c88edb02f4..cac6efb7271 100644 --- a/core/chains/evm/gas/models.go +++ b/core/chains/evm/gas/models.go @@ -76,6 +76,7 @@ func NewEstimator(lggr logger.Logger, ethClient feeEstimatorClient, chaintype ch "priceMax", geCfg.PriceMax(), "priceMin", geCfg.PriceMin(), "estimateLimit", geCfg.EstimateLimit(), + "daOracleAddress", geCfg.DAOracle().OracleAddress(), ) df := geCfg.EIP1559DynamicFees() @@ -83,7 +84,7 @@ func NewEstimator(lggr logger.Logger, ethClient feeEstimatorClient, chaintype ch var l1Oracle rollups.L1Oracle if rollups.IsRollupWithL1Support(chaintype) { var err error - l1Oracle, err = rollups.NewL1GasOracle(lggr, ethClient, chaintype) + l1Oracle, err = rollups.NewL1GasOracle(lggr, ethClient, chaintype, geCfg.DAOracle()) if err != nil { return nil, fmt.Errorf("failed to initialize L1 oracle: %w", err) } diff --git a/core/chains/evm/gas/models_test.go b/core/chains/evm/gas/models_test.go index 29f287f2a00..28ad5260d8a 100644 --- a/core/chains/evm/gas/models_test.go +++ b/core/chains/evm/gas/models_test.go @@ -16,6 +16,7 @@ import ( commonfee "github.com/smartcontractkit/chainlink/v2/common/fee" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/chaintype" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/toml" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas/mocks" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas/rollups" @@ -71,7 +72,8 @@ func TestWrappedEvmEstimator(t *testing.T) { assert.Nil(t, l1Oracle) // expect l1Oracle - oracle, err := rollups.NewL1GasOracle(lggr, nil, chaintype.ChainOptimismBedrock) + daOracle := rollups.CreateTestDAOracle(t, toml.OPStack, "0x420000000000000000000000000000000000000F", "") + oracle, err := rollups.NewL1GasOracle(lggr, nil, chaintype.ChainOptimismBedrock, daOracle) require.NoError(t, err) // cast oracle to L1Oracle interface estimator = gas.NewEvmFeeEstimator(lggr, getEst, false, geCfg, nil) diff --git a/core/chains/evm/gas/rollups/da_oracle_test_helper.go b/core/chains/evm/gas/rollups/da_oracle_test_helper.go new file mode 100644 index 00000000000..57c81c007c0 --- /dev/null +++ b/core/chains/evm/gas/rollups/da_oracle_test_helper.go @@ -0,0 +1,39 @@ +package rollups + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/toml" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" +) + +type TestDAOracle struct { + toml.DAOracle +} + +func (d *TestDAOracle) OracleType() toml.OracleType { + return d.DAOracle.OracleType +} + +func (d *TestDAOracle) OracleAddress() *types.EIP55Address { + return d.DAOracle.OracleAddress +} + +func (d *TestDAOracle) CustomGasPriceCalldata() string { + return d.DAOracle.CustomGasPriceCalldata +} + +func CreateTestDAOracle(t *testing.T, oracleType toml.OracleType, oracleAddress string, customGasPriceCalldata string) *TestDAOracle { + oracleAddr, err := types.NewEIP55Address(oracleAddress) + require.NoError(t, err) + + return &TestDAOracle{ + DAOracle: toml.DAOracle{ + OracleType: oracleType, + OracleAddress: &oracleAddr, + CustomGasPriceCalldata: customGasPriceCalldata, + }, + } +} diff --git a/core/chains/evm/gas/rollups/l1_oracle.go b/core/chains/evm/gas/rollups/l1_oracle.go index 07cecac3cb9..e03b27b3176 100644 --- a/core/chains/evm/gas/rollups/l1_oracle.go +++ b/core/chains/evm/gas/rollups/l1_oracle.go @@ -15,7 +15,9 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" + evmconfig "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/chaintype" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/toml" ) // L1Oracle provides interface for fetching L1-specific fee components if the chain is an L2. @@ -47,15 +49,31 @@ func IsRollupWithL1Support(chainType chaintype.ChainType) bool { return slices.Contains(supportedChainTypes, chainType) } -func NewL1GasOracle(lggr logger.Logger, ethClient l1OracleClient, chainType chaintype.ChainType) (L1Oracle, error) { +func NewL1GasOracle(lggr logger.Logger, ethClient l1OracleClient, chainType chaintype.ChainType, daOracle evmconfig.DAOracle) (L1Oracle, error) { if !IsRollupWithL1Support(chainType) { return nil, nil } var l1Oracle L1Oracle var err error + if daOracle != nil { + switch daOracle.OracleType() { + case toml.OPStack: + l1Oracle, err = NewOpStackL1GasOracle(lggr, ethClient, chainType, daOracle) + case toml.Arbitrum: + l1Oracle, err = NewArbitrumL1GasOracle(lggr, ethClient) + case toml.ZKSync: + l1Oracle = NewZkSyncL1GasOracle(lggr, ethClient) + default: + err = fmt.Errorf("unsupported DA oracle type %s. Going forward all chain configs should specify an oracle type", daOracle.OracleType()) + } + if err != nil { + return nil, fmt.Errorf("failed to initialize L1 oracle for chaintype %s: %w", chainType, err) + } + return l1Oracle, nil + } + + // Going forward all configs should specify a DAOracle config. This is a fall back to maintain backwards compat. switch chainType { - case chaintype.ChainOptimismBedrock, chaintype.ChainKroma, chaintype.ChainScroll, chaintype.ChainMantle, chaintype.ChainZircuit: - l1Oracle, err = NewOpStackL1GasOracle(lggr, ethClient, chainType) case chaintype.ChainArbitrum: l1Oracle, err = NewArbitrumL1GasOracle(lggr, ethClient) case chaintype.ChainZkSync: diff --git a/core/chains/evm/gas/rollups/l1_oracle_test.go b/core/chains/evm/gas/rollups/l1_oracle_test.go index 3432723c144..82dc7925293 100644 --- a/core/chains/evm/gas/rollups/l1_oracle_test.go +++ b/core/chains/evm/gas/rollups/l1_oracle_test.go @@ -19,7 +19,9 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/chaintype" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/toml" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas/rollups/mocks" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils" ) func TestL1Oracle(t *testing.T) { @@ -28,7 +30,8 @@ func TestL1Oracle(t *testing.T) { t.Run("Unsupported ChainType returns nil", func(t *testing.T) { ethClient := mocks.NewL1OracleClient(t) - oracle, err := NewL1GasOracle(logger.Test(t), ethClient, chaintype.ChainCelo) + daOracle := CreateTestDAOracle(t, toml.OPStack, utils.RandomAddress().String(), "") + oracle, err := NewL1GasOracle(logger.Test(t), ethClient, chaintype.ChainCelo, daOracle) require.NoError(t, err) assert.Nil(t, oracle) }) @@ -40,7 +43,8 @@ func TestL1Oracle_GasPrice(t *testing.T) { t.Run("Calling GasPrice on unstarted L1Oracle returns error", func(t *testing.T) { ethClient := mocks.NewL1OracleClient(t) - oracle, err := NewL1GasOracle(logger.Test(t), ethClient, chaintype.ChainOptimismBedrock) + daOracle := CreateTestDAOracle(t, toml.OPStack, utils.RandomAddress().String(), "") + oracle, err := NewL1GasOracle(logger.Test(t), ethClient, chaintype.ChainOptimismBedrock, daOracle) require.NoError(t, err) _, err = oracle.GasPrice(tests.Context(t)) @@ -63,7 +67,8 @@ func TestL1Oracle_GasPrice(t *testing.T) { assert.Nil(t, blockNumber) }).Return(common.BigToHash(l1BaseFee).Bytes(), nil) - oracle, err := NewL1GasOracle(logger.Test(t), ethClient, chaintype.ChainArbitrum) + daOracle := CreateTestDAOracle(t, toml.Arbitrum, "0x0000000000000000000000000000000000000000", "") + oracle, err := NewL1GasOracle(logger.Test(t), ethClient, chaintype.ChainArbitrum, daOracle) require.NoError(t, err) servicetest.RunHealthy(t, oracle) @@ -79,7 +84,8 @@ func TestL1Oracle_GasPrice(t *testing.T) { l1GasPriceMethodAbi, err := abi.JSON(strings.NewReader(L1BaseFeeAbiString)) require.NoError(t, err) - ethClient := setupUpgradeCheck(t, KromaGasOracleAddress, false, false) // Ecotone, Fjord disabled + oracleAddress := utils.RandomAddress().String() + ethClient := setupUpgradeCheck(t, oracleAddress, false, false) // Ecotone, Fjord disabled ethClient.On("CallContract", mock.Anything, mock.IsType(ethereum.CallMsg{}), mock.IsType(&big.Int{})).Run(func(args mock.Arguments) { callMsg := args.Get(1).(ethereum.CallMsg) @@ -91,7 +97,8 @@ func TestL1Oracle_GasPrice(t *testing.T) { assert.Nil(t, blockNumber) }).Return(common.BigToHash(l1BaseFee).Bytes(), nil) - oracle, err := NewL1GasOracle(logger.Test(t), ethClient, chaintype.ChainKroma) + daOracle := CreateTestDAOracle(t, toml.OPStack, oracleAddress, "") + oracle, err := NewL1GasOracle(logger.Test(t), ethClient, chaintype.ChainKroma, daOracle) require.NoError(t, err) servicetest.RunHealthy(t, oracle) @@ -107,7 +114,8 @@ func TestL1Oracle_GasPrice(t *testing.T) { l1GasPriceMethodAbi, err := abi.JSON(strings.NewReader(L1BaseFeeAbiString)) require.NoError(t, err) - ethClient := setupUpgradeCheck(t, OPGasOracleAddress, false, false) // Ecotone, Fjord disabled + oracleAddress := utils.RandomAddress().String() + ethClient := setupUpgradeCheck(t, oracleAddress, false, false) // Ecotone, Fjord disabled ethClient.On("CallContract", mock.Anything, mock.IsType(ethereum.CallMsg{}), mock.IsType(&big.Int{})).Run(func(args mock.Arguments) { callMsg := args.Get(1).(ethereum.CallMsg) @@ -119,7 +127,8 @@ func TestL1Oracle_GasPrice(t *testing.T) { assert.Nil(t, blockNumber) }).Return(common.BigToHash(l1BaseFee).Bytes(), nil) - oracle, err := NewL1GasOracle(logger.Test(t), ethClient, chaintype.ChainOptimismBedrock) + daOracle := CreateTestDAOracle(t, toml.OPStack, oracleAddress, "") + oracle, err := NewL1GasOracle(logger.Test(t), ethClient, chaintype.ChainOptimismBedrock, daOracle) require.NoError(t, err) servicetest.RunHealthy(t, oracle) @@ -134,7 +143,8 @@ func TestL1Oracle_GasPrice(t *testing.T) { l1GasPriceMethodAbi, err := abi.JSON(strings.NewReader(L1BaseFeeAbiString)) require.NoError(t, err) - ethClient := setupUpgradeCheck(t, ScrollGasOracleAddress, false, false) // Ecotone, Fjord disabled + oracleAddress := utils.RandomAddress().String() + ethClient := setupUpgradeCheck(t, oracleAddress, false, false) // Ecotone, Fjord disabled ethClient.On("CallContract", mock.Anything, mock.IsType(ethereum.CallMsg{}), mock.IsType(&big.Int{})).Run(func(args mock.Arguments) { callMsg := args.Get(1).(ethereum.CallMsg) @@ -146,7 +156,8 @@ func TestL1Oracle_GasPrice(t *testing.T) { assert.Nil(t, blockNumber) }).Return(common.BigToHash(l1BaseFee).Bytes(), nil) - oracle, err := NewL1GasOracle(logger.Test(t), ethClient, chaintype.ChainScroll) + daOracle := CreateTestDAOracle(t, toml.OPStack, oracleAddress, "") + oracle, err := NewL1GasOracle(logger.Test(t), ethClient, chaintype.ChainScroll, daOracle) require.NoError(t, err) servicetest.RunHealthy(t, oracle) @@ -183,7 +194,8 @@ func TestL1Oracle_GasPrice(t *testing.T) { assert.Nil(t, blockNumber) }).Return(common.BigToHash(gasPerPubByteL2).Bytes(), nil) - oracle, err := NewL1GasOracle(logger.Test(t), ethClient, chaintype.ChainZkSync) + daOracle := CreateTestDAOracle(t, toml.ZKSync, "0x0000000000000000000000000000000000000000", "") + oracle, err := NewL1GasOracle(logger.Test(t), ethClient, chaintype.ChainZkSync, daOracle) require.NoError(t, err) servicetest.RunHealthy(t, oracle) diff --git a/core/chains/evm/gas/rollups/op_l1_oracle.go b/core/chains/evm/gas/rollups/op_l1_oracle.go index 6984fe1cd60..4f1e8c67cb7 100644 --- a/core/chains/evm/gas/rollups/op_l1_oracle.go +++ b/core/chains/evm/gas/rollups/op_l1_oracle.go @@ -19,6 +19,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" evmclient "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" + evmconfig "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/chaintype" ) @@ -29,12 +30,12 @@ type optimismL1Oracle struct { pollPeriod time.Duration logger logger.SugaredLogger - l1OracleAddress string - l1GasPriceMu sync.RWMutex - l1GasPrice priceEntry - isEcotone bool - isFjord bool - upgradeCheckTs time.Time + daOracleConfig evmconfig.DAOracle + l1GasPriceMu sync.RWMutex + l1GasPrice priceEntry + isEcotone bool + isFjord bool + upgradeCheckTs time.Time chInitialised chan struct{} chStop services.StopChan @@ -84,30 +85,9 @@ const ( // decimals is a hex encoded call to: // `function decimals() public pure returns (uint256);` decimalsMethod = "decimals" - // 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. - KromaGasOracleAddress = "0x4200000000000000000000000000000000000005" - // ScrollGasOracleAddress is the address of the precompiled contract that exists on Scroll. - ScrollGasOracleAddress = "0x5300000000000000000000000000000000000002" ) -func NewOpStackL1GasOracle(lggr logger.Logger, ethClient l1OracleClient, chainType chaintype.ChainType) (*optimismL1Oracle, error) { - var precompileAddress string - switch chainType { - case chaintype.ChainOptimismBedrock, chaintype.ChainMantle, chaintype.ChainZircuit: - precompileAddress = OPGasOracleAddress - case chaintype.ChainKroma: - precompileAddress = KromaGasOracleAddress - case chaintype.ChainScroll: - precompileAddress = ScrollGasOracleAddress - default: - return nil, fmt.Errorf("received unsupported chaintype %s", chainType) - } - return newOpStackL1GasOracle(lggr, ethClient, chainType, precompileAddress) -} - -func newOpStackL1GasOracle(lggr logger.Logger, ethClient l1OracleClient, chainType chaintype.ChainType, precompileAddress string) (*optimismL1Oracle, error) { +func NewOpStackL1GasOracle(lggr logger.Logger, ethClient l1OracleClient, chainType chaintype.ChainType, daOracle evmconfig.DAOracle) (*optimismL1Oracle, error) { getL1FeeMethodAbi, err := abi.JSON(strings.NewReader(GetL1FeeAbiString)) if err != nil { return nil, fmt.Errorf("failed to parse L1 gas cost method ABI for chain: %s", chainType) @@ -115,71 +95,43 @@ func newOpStackL1GasOracle(lggr logger.Logger, ethClient l1OracleClient, chainTy // encode calldata for each method; these calldata will remain the same for each call, we can encode them just once // Encode calldata for l1BaseFee method - l1BaseFeeMethodAbi, err := abi.JSON(strings.NewReader(L1BaseFeeAbiString)) - if err != nil { - return nil, fmt.Errorf("failed to parse GasPriceOracle %s() method ABI for chain: %s; %w", l1BaseFeeMethod, chainType, err) - } - l1BaseFeeCalldata, err := l1BaseFeeMethodAbi.Pack(l1BaseFeeMethod) + l1BaseFeeCalldata, _, err := encodeCalldata(L1BaseFeeAbiString, l1BaseFeeMethod) if err != nil { return nil, fmt.Errorf("failed to parse GasPriceOracle %s() calldata for chain: %s; %w", l1BaseFeeMethod, chainType, err) } // Encode calldata for isEcotone method - isEcotoneMethodAbi, err := abi.JSON(strings.NewReader(OPIsEcotoneAbiString)) - if err != nil { - return nil, fmt.Errorf("failed to parse GasPriceOracle %s() method ABI for chain: %s; %w", isEcotoneMethod, chainType, err) - } - isEcotoneCalldata, err := isEcotoneMethodAbi.Pack(isEcotoneMethod) + isEcotoneCalldata, isEcotoneMethodAbi, err := encodeCalldata(OPIsEcotoneAbiString, isEcotoneMethod) if err != nil { return nil, fmt.Errorf("failed to parse GasPriceOracle %s() calldata for chain: %s; %w", isEcotoneMethod, chainType, err) } // Encode calldata for isFjord method - isFjordMethodAbi, err := abi.JSON(strings.NewReader(OPIsFjordAbiString)) - if err != nil { - return nil, fmt.Errorf("failed to parse GasPriceOracle %s() method ABI for chain: %s; %w", isFjordMethod, chainType, err) - } - isFjordCalldata, err := isFjordMethodAbi.Pack(isFjordMethod) + isFjordCalldata, isFjordMethodAbi, err := encodeCalldata(OPIsFjordAbiString, isFjordMethod) if err != nil { return nil, fmt.Errorf("failed to parse GasPriceOracle %s() calldata for chain: %s; %w", isFjordMethod, chainType, err) } // Encode calldata for baseFeeScalar method - baseFeeScalarMethodAbi, err := abi.JSON(strings.NewReader(OPBaseFeeScalarAbiString)) - if err != nil { - return nil, fmt.Errorf("failed to parse GasPriceOracle %s() method ABI for chain: %s; %w", baseFeeScalarMethod, chainType, err) - } - baseFeeScalarCalldata, err := baseFeeScalarMethodAbi.Pack(baseFeeScalarMethod) + baseFeeScalarCalldata, _, err := encodeCalldata(OPBaseFeeScalarAbiString, baseFeeScalarMethod) if err != nil { return nil, fmt.Errorf("failed to parse GasPriceOracle %s() calldata for chain: %s; %w", baseFeeScalarMethod, chainType, err) } // Encode calldata for blobBaseFee method - blobBaseFeeMethodAbi, err := abi.JSON(strings.NewReader(OPBlobBaseFeeAbiString)) - if err != nil { - return nil, fmt.Errorf("failed to parse GasPriceOracle %s() method ABI for chain: %s; %w", blobBaseFeeMethod, chainType, err) - } - blobBaseFeeCalldata, err := blobBaseFeeMethodAbi.Pack(blobBaseFeeMethod) + blobBaseFeeCalldata, _, err := encodeCalldata(OPBlobBaseFeeAbiString, blobBaseFeeMethod) if err != nil { return nil, fmt.Errorf("failed to parse GasPriceOracle %s() calldata for chain: %s; %w", blobBaseFeeMethod, chainType, err) } // Encode calldata for blobBaseFeeScalar method - blobBaseFeeScalarMethodAbi, err := abi.JSON(strings.NewReader(OPBlobBaseFeeScalarAbiString)) - if err != nil { - return nil, fmt.Errorf("failed to parse GasPriceOracle %s() method ABI for chain: %s; %w", blobBaseFeeScalarMethod, chainType, err) - } - blobBaseFeeScalarCalldata, err := blobBaseFeeScalarMethodAbi.Pack(blobBaseFeeScalarMethod) + blobBaseFeeScalarCalldata, _, err := encodeCalldata(OPBlobBaseFeeScalarAbiString, blobBaseFeeScalarMethod) if err != nil { return nil, fmt.Errorf("failed to parse GasPriceOracle %s() calldata for chain: %s; %w", blobBaseFeeScalarMethod, chainType, err) } // Encode calldata for decimals method - decimalsMethodAbi, err := abi.JSON(strings.NewReader(OPDecimalsAbiString)) - if err != nil { - return nil, fmt.Errorf("failed to parse GasPriceOracle %s() method ABI for chain: %s; %w", decimalsMethod, chainType, err) - } - decimalsCalldata, err := decimalsMethodAbi.Pack(decimalsMethod) + decimalsCalldata, _, err := encodeCalldata(OPDecimalsAbiString, decimalsMethod) if err != nil { return nil, fmt.Errorf("failed to parse GasPriceOracle %s() calldata for chain: %s; %w", decimalsMethod, chainType, err) } @@ -189,10 +141,10 @@ func newOpStackL1GasOracle(lggr logger.Logger, ethClient l1OracleClient, chainTy pollPeriod: PollPeriod, logger: logger.Sugared(logger.Named(lggr, fmt.Sprintf("L1GasOracle(%s)", chainType))), - l1OracleAddress: precompileAddress, - isEcotone: false, - isFjord: false, - upgradeCheckTs: time.Time{}, + daOracleConfig: daOracle, + isEcotone: false, + isFjord: false, + upgradeCheckTs: time.Time{}, chInitialised: make(chan struct{}), chStop: make(chan struct{}), @@ -255,6 +207,7 @@ func (o *optimismL1Oracle) run() { } } } + func (o *optimismL1Oracle) refresh() { err := o.refreshWithError() if err != nil { @@ -330,7 +283,7 @@ func (o *optimismL1Oracle) checkForUpgrade(ctx context.Context) error { Args: []any{ map[string]interface{}{ "from": common.Address{}, - "to": o.l1OracleAddress, + "to": o.daOracleConfig.OracleAddress().String(), "data": hexutil.Bytes(o.isFjordCalldata), }, "latest", @@ -342,7 +295,7 @@ func (o *optimismL1Oracle) checkForUpgrade(ctx context.Context) error { Args: []any{ map[string]interface{}{ "from": common.Address{}, - "to": o.l1OracleAddress, + "to": o.daOracleConfig.OracleAddress().String(), "data": hexutil.Bytes(o.isEcotoneCalldata), }, "latest", @@ -383,7 +336,7 @@ func (o *optimismL1Oracle) checkForUpgrade(ctx context.Context) error { } func (o *optimismL1Oracle) getV1GasPrice(ctx context.Context) (*big.Int, error) { - l1OracleAddress := common.HexToAddress(o.l1OracleAddress) + l1OracleAddress := o.daOracleConfig.OracleAddress().Address() b, err := o.client.CallContract(ctx, ethereum.CallMsg{ To: &l1OracleAddress, Data: o.l1BaseFeeCalldata, @@ -407,7 +360,7 @@ func (o *optimismL1Oracle) getEcotoneFjordGasPrice(ctx context.Context) (*big.In Args: []any{ map[string]interface{}{ "from": common.Address{}, - "to": o.l1OracleAddress, + "to": o.daOracleConfig.OracleAddress().String(), "data": hexutil.Bytes(o.l1BaseFeeCalldata), }, "latest", @@ -419,7 +372,7 @@ func (o *optimismL1Oracle) getEcotoneFjordGasPrice(ctx context.Context) (*big.In Args: []any{ map[string]interface{}{ "from": common.Address{}, - "to": o.l1OracleAddress, + "to": o.daOracleConfig.OracleAddress().String(), "data": hexutil.Bytes(o.baseFeeScalarCalldata), }, "latest", @@ -431,7 +384,7 @@ func (o *optimismL1Oracle) getEcotoneFjordGasPrice(ctx context.Context) (*big.In Args: []any{ map[string]interface{}{ "from": common.Address{}, - "to": o.l1OracleAddress, + "to": o.daOracleConfig.OracleAddress().String(), "data": hexutil.Bytes(o.blobBaseFeeCalldata), }, "latest", @@ -443,7 +396,7 @@ func (o *optimismL1Oracle) getEcotoneFjordGasPrice(ctx context.Context) (*big.In Args: []any{ map[string]interface{}{ "from": common.Address{}, - "to": o.l1OracleAddress, + "to": o.daOracleConfig.OracleAddress().String(), "data": hexutil.Bytes(o.blobBaseFeeScalarCalldata), }, "latest", @@ -455,7 +408,7 @@ func (o *optimismL1Oracle) getEcotoneFjordGasPrice(ctx context.Context) (*big.In Args: []any{ map[string]interface{}{ "from": common.Address{}, - "to": o.l1OracleAddress, + "to": o.daOracleConfig.OracleAddress().String(), "data": hexutil.Bytes(o.decimalsCalldata), }, "latest", @@ -538,3 +491,15 @@ func (o *optimismL1Oracle) getEcotoneFjordGasPrice(ctx context.Context) (*big.In return new(big.Int).Div(scaledGasPrice, scale), nil } + +func encodeCalldata(abiString, methodName string) ([]byte, abi.ABI, error) { + methodAbi, err := abi.JSON(strings.NewReader(abiString)) + if err != nil { + return nil, abi.ABI{}, fmt.Errorf("failed to parse ABI: %w", err) + } + calldata, err := methodAbi.Pack(methodName) + if err != nil { + return nil, abi.ABI{}, fmt.Errorf("failed to pack calldata for %s: %w", methodName, err) + } + return calldata, methodAbi, nil +} 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 f5f009f1ea6..8d5fd357baf 100644 --- a/core/chains/evm/gas/rollups/op_l1_oracle_test.go +++ b/core/chains/evm/gas/rollups/op_l1_oracle_test.go @@ -19,7 +19,9 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/chaintype" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/toml" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas/rollups/mocks" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils" ) func TestOPL1Oracle_ReadV1GasPrice(t *testing.T) { @@ -101,7 +103,8 @@ func TestOPL1Oracle_ReadV1GasPrice(t *testing.T) { assert.Nil(t, blockNumber) }).Return(common.BigToHash(l1BaseFee).Bytes(), nil).Once() - oracle, err := newOpStackL1GasOracle(logger.Test(t), ethClient, chaintype.ChainOptimismBedrock, oracleAddress) + daOracle := CreateTestDAOracle(t, toml.OPStack, "0x0000000000000000000000000000000000001234", "") + oracle, err := NewOpStackL1GasOracle(logger.Test(t), ethClient, chaintype.ChainOptimismBedrock, daOracle) require.NoError(t, err) gasPrice, err := oracle.GetDAGasPrice(tests.Context(t)) @@ -215,7 +218,7 @@ func TestOPL1Oracle_CalculateEcotoneGasPrice(t *testing.T) { baseFeeScalar := big.NewInt(10) blobBaseFeeScalar := big.NewInt(5) decimals := big.NewInt(6) - oracleAddress := common.HexToAddress("0x1234").String() + oracleAddress := utils.RandomAddress().String() t.Parallel() @@ -223,7 +226,8 @@ func TestOPL1Oracle_CalculateEcotoneGasPrice(t *testing.T) { ethClient := setupUpgradeCheck(t, oracleAddress, false, true) mockBatchContractCall(t, ethClient, oracleAddress, baseFee, baseFeeScalar, blobBaseFee, blobBaseFeeScalar, decimals) - oracle, err := newOpStackL1GasOracle(logger.Test(t), ethClient, chaintype.ChainOptimismBedrock, oracleAddress) + daOracle := CreateTestDAOracle(t, toml.OPStack, oracleAddress, "") + oracle, err := NewOpStackL1GasOracle(logger.Test(t), ethClient, chaintype.ChainOptimismBedrock, daOracle) require.NoError(t, err) gasPrice, err := oracle.GetDAGasPrice(tests.Context(t)) require.NoError(t, err) @@ -242,7 +246,8 @@ func TestOPL1Oracle_CalculateEcotoneGasPrice(t *testing.T) { rpcElements[1].Result = &badData }).Return(nil).Once() - oracle, err := newOpStackL1GasOracle(logger.Test(t), ethClient, chaintype.ChainOptimismBedrock, oracleAddress) + daOracle := CreateTestDAOracle(t, toml.OPStack, oracleAddress, "") + oracle, err := NewOpStackL1GasOracle(logger.Test(t), ethClient, chaintype.ChainOptimismBedrock, daOracle) require.NoError(t, err) _, err = oracle.GetDAGasPrice(tests.Context(t)) assert.Error(t, err) @@ -252,7 +257,8 @@ func TestOPL1Oracle_CalculateEcotoneGasPrice(t *testing.T) { ethClient := setupUpgradeCheck(t, oracleAddress, false, true) ethClient.On("BatchCallContext", mock.Anything, mock.IsType([]rpc.BatchElem{})).Return(fmt.Errorf("revert")).Once() - oracle, err := newOpStackL1GasOracle(logger.Test(t), ethClient, chaintype.ChainOptimismBedrock, oracleAddress) + daOracle := CreateTestDAOracle(t, toml.OPStack, oracleAddress, "") + oracle, err := NewOpStackL1GasOracle(logger.Test(t), ethClient, chaintype.ChainOptimismBedrock, daOracle) require.NoError(t, err) _, err = oracle.GetDAGasPrice(tests.Context(t)) assert.Error(t, err) @@ -267,7 +273,8 @@ func TestOPL1Oracle_CalculateEcotoneGasPrice(t *testing.T) { rpcElements[1].Error = fmt.Errorf("revert") }).Return(nil).Once() - oracle, err := newOpStackL1GasOracle(logger.Test(t), ethClient, chaintype.ChainOptimismBedrock, oracleAddress) + daOracle := CreateTestDAOracle(t, toml.OPStack, oracleAddress, "") + oracle, err := NewOpStackL1GasOracle(logger.Test(t), ethClient, chaintype.ChainOptimismBedrock, daOracle) require.NoError(t, err) _, err = oracle.GetDAGasPrice(tests.Context(t)) assert.Error(t, err) @@ -280,7 +287,7 @@ func TestOPL1Oracle_CalculateFjordGasPrice(t *testing.T) { baseFeeScalar := big.NewInt(10) blobBaseFeeScalar := big.NewInt(5) decimals := big.NewInt(6) - oracleAddress := common.HexToAddress("0x1234").String() + oracleAddress := utils.RandomAddress().String() t.Parallel() @@ -288,7 +295,8 @@ func TestOPL1Oracle_CalculateFjordGasPrice(t *testing.T) { ethClient := setupUpgradeCheck(t, oracleAddress, true, true) mockBatchContractCall(t, ethClient, oracleAddress, baseFee, baseFeeScalar, blobBaseFee, blobBaseFeeScalar, decimals) - oracle, err := newOpStackL1GasOracle(logger.Test(t), ethClient, chaintype.ChainOptimismBedrock, oracleAddress) + daOracle := CreateTestDAOracle(t, toml.OPStack, oracleAddress, "") + oracle, err := NewOpStackL1GasOracle(logger.Test(t), ethClient, chaintype.ChainOptimismBedrock, daOracle) require.NoError(t, err) gasPrice, err := oracle.GetDAGasPrice(tests.Context(t)) require.NoError(t, err) @@ -307,7 +315,8 @@ func TestOPL1Oracle_CalculateFjordGasPrice(t *testing.T) { rpcElements[1].Result = &badData }).Return(nil).Once() - oracle, err := newOpStackL1GasOracle(logger.Test(t), ethClient, chaintype.ChainOptimismBedrock, oracleAddress) + daOracle := CreateTestDAOracle(t, toml.OPStack, oracleAddress, "") + oracle, err := NewOpStackL1GasOracle(logger.Test(t), ethClient, chaintype.ChainOptimismBedrock, daOracle) require.NoError(t, err) _, err = oracle.GetDAGasPrice(tests.Context(t)) assert.Error(t, err) @@ -317,7 +326,8 @@ func TestOPL1Oracle_CalculateFjordGasPrice(t *testing.T) { ethClient := setupUpgradeCheck(t, oracleAddress, true, true) ethClient.On("BatchCallContext", mock.Anything, mock.IsType([]rpc.BatchElem{})).Return(fmt.Errorf("revert")).Once() - oracle, err := newOpStackL1GasOracle(logger.Test(t), ethClient, chaintype.ChainOptimismBedrock, oracleAddress) + daOracle := CreateTestDAOracle(t, toml.OPStack, oracleAddress, "") + oracle, err := NewOpStackL1GasOracle(logger.Test(t), ethClient, chaintype.ChainOptimismBedrock, daOracle) require.NoError(t, err) _, err = oracle.GetDAGasPrice(tests.Context(t)) assert.Error(t, err) @@ -332,7 +342,8 @@ func TestOPL1Oracle_CalculateFjordGasPrice(t *testing.T) { rpcElements[1].Error = fmt.Errorf("revert") }).Return(nil).Once() - oracle, err := newOpStackL1GasOracle(logger.Test(t), ethClient, chaintype.ChainOptimismBedrock, oracleAddress) + daOracle := CreateTestDAOracle(t, toml.OPStack, oracleAddress, "") + oracle, err := NewOpStackL1GasOracle(logger.Test(t), ethClient, chaintype.ChainOptimismBedrock, daOracle) require.NoError(t, err) _, err = oracle.GetDAGasPrice(tests.Context(t)) assert.Error(t, err) diff --git a/core/chains/evm/txmgr/test_helpers.go b/core/chains/evm/txmgr/test_helpers.go index a66727ce133..5b5295432da 100644 --- a/core/chains/evm/txmgr/test_helpers.go +++ b/core/chains/evm/txmgr/test_helpers.go @@ -10,6 +10,8 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" evmconfig "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/chaintype" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/toml" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils" "github.com/smartcontractkit/chainlink/v2/core/config" ) @@ -72,6 +74,24 @@ type TestGasEstimatorConfig struct { bumpThreshold uint64 } +func (g *TestGasEstimatorConfig) DAOracle() evmconfig.DAOracle { + return &TestDAOracleConfig{} +} + +type TestDAOracleConfig struct { + evmconfig.DAOracle +} + +func (d *TestDAOracleConfig) OracleType() toml.OracleType { return toml.OPStack } +func (d *TestDAOracleConfig) OracleAddress() *types.EIP55Address { + a, err := types.NewEIP55Address("0x420000000000000000000000000000000000000F") + if err != nil { + panic(err) + } + return &a +} +func (d *TestDAOracleConfig) CustomGasPriceCalldata() string { return "" } + func (g *TestGasEstimatorConfig) BlockHistory() evmconfig.BlockHistory { return &TestBlockHistoryConfig{} } diff --git a/core/config/docs/chains-evm.toml b/core/config/docs/chains-evm.toml index c237de5826d..683e59d74b8 100644 --- a/core/config/docs/chains-evm.toml +++ b/core/config/docs/chains-evm.toml @@ -263,6 +263,14 @@ TipCapDefault = '1 wei' # Default # (Only applies to EIP-1559 transactions) TipCapMin = '1 wei' # Default +[EVM.GasEstimator.DAOracle] +# OracleType refers to the oracle family this config belongs to. Currently the available oracle types are: 'opstack', 'arbitrum', and 'zksync'. +OracleType = 'opstack' # Example +# OracleAddress is the address of the oracle contract. +OracleAddress = '0x420000000000000000000000000000000000000F' # Example +# CustomGasPriceCalldata is optional and can be set to call a custom gas price function at the given OracleAddress. +CustomGasPriceCalldata = '' # Default + [EVM.GasEstimator.LimitJobType] # OCR overrides LimitDefault for OCR jobs. OCR = 100_000 # Example diff --git a/core/config/docs/docs_test.go b/core/config/docs/docs_test.go index 13256a5764d..bb572773c4a 100644 --- a/core/config/docs/docs_test.go +++ b/core/config/docs/docs_test.go @@ -15,6 +15,7 @@ import ( stkcfg "github.com/smartcontractkit/chainlink-starknet/relayer/pkg/chainlink/config" "github.com/smartcontractkit/chainlink-common/pkg/config" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/chaintype" evmcfg "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/toml" @@ -96,6 +97,9 @@ func TestDoc(t *testing.T) { docDefaults.Transactions.AutoPurge.Threshold = nil docDefaults.Transactions.AutoPurge.MinAttempts = nil + // GasEstimator.DAOracle.OracleAddress is only set if DA oracle config is used + docDefaults.GasEstimator.DAOracle.OracleAddress = nil + assertTOML(t, fallbackDefaults, docDefaults) }) diff --git a/core/services/chainlink/config_test.go b/core/services/chainlink/config_test.go index 9443fc1d060..be7c0a08530 100644 --- a/core/services/chainlink/config_test.go +++ b/core/services/chainlink/config_test.go @@ -1396,6 +1396,9 @@ func TestConfig_full(t *testing.T) { if got.EVM[c].Transactions.AutoPurge.DetectionApiUrl == nil { got.EVM[c].Transactions.AutoPurge.DetectionApiUrl = new(commoncfg.URL) } + if got.EVM[c].GasEstimator.DAOracle.OracleAddress == nil { + got.EVM[c].GasEstimator.DAOracle.OracleAddress = new(types.EIP55Address) + } } cfgtest.AssertFieldsNotNil(t, got) diff --git a/docs/CONFIG.md b/docs/CONFIG.md index 59192906fc1..67818325b37 100644 --- a/docs/CONFIG.md +++ b/docs/CONFIG.md @@ -2442,6 +2442,11 @@ TransactionPercentile = 60 [GasEstimator.FeeHistory] CacheTimeout = '10s' +[GasEstimator.DAOracle] +OracleType = 'opstack' +OracleAddress = '0x420000000000000000000000000000000000000F' +CustomGasPriceCalldata = '' + [HeadTracker] HistoryDepth = 300 MaxBufferSize = 3 @@ -3898,6 +3903,11 @@ TransactionPercentile = 60 [GasEstimator.FeeHistory] CacheTimeout = '10s' +[GasEstimator.DAOracle] +OracleType = 'opstack' +OracleAddress = '0x4200000000000000000000000000000000000005' +CustomGasPriceCalldata = '' + [HeadTracker] HistoryDepth = 400 MaxBufferSize = 3 @@ -4523,6 +4533,11 @@ TransactionPercentile = 60 [GasEstimator.FeeHistory] CacheTimeout = '10s' +[GasEstimator.DAOracle] +OracleType = 'opstack' +OracleAddress = '0x420000000000000000000000000000000000000F' +CustomGasPriceCalldata = '' + [HeadTracker] HistoryDepth = 300 MaxBufferSize = 3 @@ -5354,6 +5369,11 @@ TransactionPercentile = 60 [GasEstimator.FeeHistory] CacheTimeout = '10s' +[GasEstimator.DAOracle] +OracleType = 'opstack' +OracleAddress = '0x420000000000000000000000000000000000000F' +CustomGasPriceCalldata = '' + [HeadTracker] HistoryDepth = 300 MaxBufferSize = 3 @@ -5458,6 +5478,11 @@ TransactionPercentile = 60 [GasEstimator.FeeHistory] CacheTimeout = '10s' +[GasEstimator.DAOracle] +OracleType = 'opstack' +OracleAddress = '0x4200000000000000000000000000000000000005' +CustomGasPriceCalldata = '' + [HeadTracker] HistoryDepth = 400 MaxBufferSize = 3 @@ -5873,6 +5898,11 @@ TransactionPercentile = 60 [GasEstimator.FeeHistory] CacheTimeout = '10s' +[GasEstimator.DAOracle] +OracleType = 'opstack' +OracleAddress = '0x420000000000000000000000000000000000000F' +CustomGasPriceCalldata = '' + [HeadTracker] HistoryDepth = 300 MaxBufferSize = 3 @@ -6815,6 +6845,11 @@ TransactionPercentile = 60 [GasEstimator.FeeHistory] CacheTimeout = '10s' +[GasEstimator.DAOracle] +OracleType = 'opstack' +OracleAddress = '0x420000000000000000000000000000000000000F' +CustomGasPriceCalldata = '' + [HeadTracker] HistoryDepth = 2000 MaxBufferSize = 3 @@ -6922,6 +6957,11 @@ TransactionPercentile = 60 [GasEstimator.FeeHistory] CacheTimeout = '10s' +[GasEstimator.DAOracle] +OracleType = 'opstack' +OracleAddress = '0x420000000000000000000000000000000000000F' +CustomGasPriceCalldata = '' + [HeadTracker] HistoryDepth = 2000 MaxBufferSize = 3 @@ -7646,6 +7686,11 @@ TransactionPercentile = 60 [GasEstimator.FeeHistory] CacheTimeout = '10s' +[GasEstimator.DAOracle] +OracleType = 'opstack' +OracleAddress = '0x420000000000000000000000000000000000000F' +CustomGasPriceCalldata = '' + [HeadTracker] HistoryDepth = 300 MaxBufferSize = 3 @@ -7750,6 +7795,11 @@ TransactionPercentile = 60 [GasEstimator.FeeHistory] CacheTimeout = '10s' +[GasEstimator.DAOracle] +OracleType = 'opstack' +OracleAddress = '0x420000000000000000000000000000000000000F' +CustomGasPriceCalldata = '' + [HeadTracker] HistoryDepth = 300 MaxBufferSize = 3 @@ -8168,6 +8218,11 @@ TransactionPercentile = 60 [GasEstimator.FeeHistory] CacheTimeout = '10s' +[GasEstimator.DAOracle] +OracleType = 'opstack' +OracleAddress = '0x5300000000000000000000000000000000000002' +CustomGasPriceCalldata = '' + [HeadTracker] HistoryDepth = 50 MaxBufferSize = 3 @@ -8272,6 +8327,11 @@ TransactionPercentile = 60 [GasEstimator.FeeHistory] CacheTimeout = '10s' +[GasEstimator.DAOracle] +OracleType = 'opstack' +OracleAddress = '0x5300000000000000000000000000000000000002' +CustomGasPriceCalldata = '' + [HeadTracker] HistoryDepth = 50 MaxBufferSize = 3 @@ -8480,6 +8540,11 @@ TransactionPercentile = 60 [GasEstimator.FeeHistory] CacheTimeout = '10s' +[GasEstimator.DAOracle] +OracleType = 'opstack' +OracleAddress = '0x420000000000000000000000000000000000000F' +CustomGasPriceCalldata = '' + [HeadTracker] HistoryDepth = 300 MaxBufferSize = 3 @@ -9243,6 +9308,33 @@ TipCapMinimum is the minimum gas tip to use when submitting transactions to the (Only applies to EIP-1559 transactions) +## EVM.GasEstimator.DAOracle +```toml +[EVM.GasEstimator.DAOracle] +OracleType = 'opstack' # Example +OracleAddress = '0x420000000000000000000000000000000000000F' # Example +CustomGasPriceCalldata = '' # Default +``` + + +### OracleType +```toml +OracleType = 'opstack' # Example +``` +OracleType refers to the oracle family this config belongs to. Currently the available oracle types are: 'opstack', 'arbitrum', and 'zksync'. + +### OracleAddress +```toml +OracleAddress = '0x420000000000000000000000000000000000000F' # Example +``` +OracleAddress is the address of the oracle contract. + +### CustomGasPriceCalldata +```toml +CustomGasPriceCalldata = '' # Default +``` +CustomGasPriceCalldata is optional and can be set to call a custom gas price function at the given OracleAddress. + ## EVM.GasEstimator.LimitJobType ```toml [EVM.GasEstimator.LimitJobType]