From 6b0b72bf73c8911307edab3b76d27aa8936b8eba Mon Sep 17 00:00:00 2001 From: Matthew Romage Date: Tue, 20 Aug 2024 08:27:46 -0400 Subject: [PATCH 01/14] edited op stack oracle to include tokenRatio for Mantle --- core/chains/evm/config/chaintype/chaintype.go | 6 ++- core/chains/evm/gas/rollups/l1_oracle.go | 4 +- core/chains/evm/gas/rollups/op_l1_oracle.go | 54 +++++++++++++++++-- 3 files changed, 58 insertions(+), 6 deletions(-) diff --git a/core/chains/evm/config/chaintype/chaintype.go b/core/chains/evm/config/chaintype/chaintype.go index 07ea6206241..d7a5ccd99ab 100644 --- a/core/chains/evm/config/chaintype/chaintype.go +++ b/core/chains/evm/config/chaintype/chaintype.go @@ -21,6 +21,7 @@ const ( ChainXLayer ChainType = "xlayer" ChainZkEvm ChainType = "zkevm" ChainZkSync ChainType = "zksync" + ChainMantle ChainType = "mantle" ) // IsL2 returns true if this chain is a Layer 2 chain. Notably: @@ -37,7 +38,7 @@ func (c ChainType) IsL2() bool { func (c ChainType) IsValid() bool { switch c { - case "", ChainArbitrum, ChainAstar, ChainCelo, ChainGnosis, ChainHedera, ChainKroma, ChainMetis, ChainOptimismBedrock, ChainScroll, ChainWeMix, ChainXLayer, ChainZkEvm, ChainZkSync: + case "", ChainArbitrum, ChainAstar, ChainCelo, ChainGnosis, ChainHedera, ChainKroma, ChainMetis, ChainOptimismBedrock, ChainScroll, ChainWeMix, ChainXLayer, ChainZkEvm, ChainZkSync, ChainMantle: return true } return false @@ -71,6 +72,8 @@ func ChainTypeFromSlug(slug string) ChainType { return ChainZkEvm case "zksync": return ChainZkSync + case "mantle": + return ChainMantle default: return ChainType(slug) } @@ -136,4 +139,5 @@ var ErrInvalidChainType = fmt.Errorf("must be one of %s or omitted", strings.Joi string(ChainXLayer), string(ChainZkEvm), string(ChainZkSync), + string(ChainMantle), }, ", ")) diff --git a/core/chains/evm/gas/rollups/l1_oracle.go b/core/chains/evm/gas/rollups/l1_oracle.go index f707fab6841..2ad1cc0a490 100644 --- a/core/chains/evm/gas/rollups/l1_oracle.go +++ b/core/chains/evm/gas/rollups/l1_oracle.go @@ -43,7 +43,7 @@ const ( PollPeriod = 6 * time.Second ) -var supportedChainTypes = []chaintype.ChainType{chaintype.ChainArbitrum, chaintype.ChainOptimismBedrock, chaintype.ChainKroma, chaintype.ChainScroll, chaintype.ChainZkSync} +var supportedChainTypes = []chaintype.ChainType{chaintype.ChainArbitrum, chaintype.ChainOptimismBedrock, chaintype.ChainKroma, chaintype.ChainScroll, chaintype.ChainZkSync, chainType.ChainMantle} func IsRollupWithL1Support(chainType chaintype.ChainType) bool { return slices.Contains(supportedChainTypes, chainType) @@ -56,7 +56,7 @@ func NewL1GasOracle(lggr logger.Logger, ethClient l1OracleClient, chainType chai var l1Oracle L1Oracle var err error switch chainType { - case chaintype.ChainOptimismBedrock, chaintype.ChainKroma, chaintype.ChainScroll: + case chaintype.ChainOptimismBedrock, chaintype.ChainKroma, chaintype.ChainScroll, chaintype.ChainMantle: l1Oracle, err = NewOpStackL1GasOracle(lggr, ethClient, chainType) case chaintype.ChainArbitrum: l1Oracle, err = NewArbitrumL1GasOracle(lggr, ethClient) diff --git a/core/chains/evm/gas/rollups/op_l1_oracle.go b/core/chains/evm/gas/rollups/op_l1_oracle.go index 6805cd7095b..53621a4ab2e 100644 --- a/core/chains/evm/gas/rollups/op_l1_oracle.go +++ b/core/chains/evm/gas/rollups/op_l1_oracle.go @@ -38,6 +38,7 @@ type optimismL1Oracle struct { l1GasPrice priceEntry isEcotone bool isFjord bool + isMantle bool upgradeCheckTs time.Time chInitialised chan struct{} @@ -50,6 +51,7 @@ type optimismL1Oracle struct { blobBaseFeeCalldata []byte blobBaseFeeScalarCalldata []byte decimalsCalldata []byte + tokenRatioMethodCalldata []byte isEcotoneCalldata []byte isEcotoneMethodAbi abi.ABI isFjordCalldata []byte @@ -87,7 +89,11 @@ 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 and Base. + // 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. KromaGasOracleAddress = "0x4200000000000000000000000000000000000005" @@ -97,6 +103,7 @@ const ( func NewOpStackL1GasOracle(lggr logger.Logger, ethClient l1OracleClient, chainType chaintype.ChainType) (*optimismL1Oracle, error) { var precompileAddress string + isMantle := false switch chainType { case chaintype.ChainOptimismBedrock: precompileAddress = OPGasOracleAddress @@ -104,13 +111,16 @@ func NewOpStackL1GasOracle(lggr logger.Logger, ethClient l1OracleClient, chainTy precompileAddress = KromaGasOracleAddress case chaintype.ChainScroll: precompileAddress = ScrollGasOracleAddress + case chaintype.ChainMantle: + precompileAddress = OPGasOracleAddress + isMantle = true default: return nil, fmt.Errorf("received unsupported chaintype %s", chainType) } - return newOpStackL1GasOracle(lggr, ethClient, chainType, precompileAddress) + return newOpStackL1GasOracle(lggr, ethClient, chainType, precompileAddress, isMantle) } -func newOpStackL1GasOracle(lggr logger.Logger, ethClient l1OracleClient, chainType chaintype.ChainType, precompileAddress string) (*optimismL1Oracle, error) { +func newOpStackL1GasOracle(lggr logger.Logger, ethClient l1OracleClient, chainType chaintype.ChainType, precompileAddress string, isMantle bool) (*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) @@ -187,6 +197,16 @@ 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", decimalsMethod, chainType, err) + } + tokenRatioCalldata, err := decimalsMethodAbi.Pack(tokenRatioMethod) + if err != nil { + return nil, fmt.Errorf("failed to parse GasPriceOracle %s() calldata for chain: %s; %w", decimalsMethod, chainType, err) + } + return &optimismL1Oracle{ client: ethClient, pollPeriod: PollPeriod, @@ -196,6 +216,7 @@ func newOpStackL1GasOracle(lggr logger.Logger, ethClient l1OracleClient, chainTy l1OracleAddress: precompileAddress, isEcotone: false, isFjord: false, + isMantle: isMantle, upgradeCheckTs: time.Time{}, chInitialised: make(chan struct{}), @@ -353,6 +374,9 @@ func (o *optimismL1Oracle) GetDAGasPrice(ctx context.Context) (*big.Int, error) if o.isFjord || o.isEcotone { return o.getEcotoneFjordGasPrice(ctx) } + if o.isMantle { + return o.getMantleGasPrice(ctx) + } return o.getV1GasPrice(ctx) } @@ -443,6 +467,30 @@ 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) { + // set oracle address + l1OracleAddress := common.HexToAddress(o.l1OracleAddress) + + // call oracle to get tokenRatio + b, err := o.client.CallContract(ctx, ethereum.CallMsg{ + To: &l1OracleAddress, + Data: o.tokenRatioCalldata + }, nil) + if err != nil { + return nil, fmt.Errorf("tokenRatio() call failed: %w", err) + } + + // create bigInt from returned bytes + tokenRatio := new(big.Int).SetBytes(b) + + // call optimism bedrock gas price function + classicEvmGasPrice := o.getV1GasPrice(ctx) + + // multiply return gas price by tokenRatio + return classicEvmGasPrice * tokenRatio +} + // 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) { From cde83ea1d485dc91f2324b22d533286078575374 Mon Sep 17 00:00:00 2001 From: Matthew Romage Date: Tue, 20 Aug 2024 09:32:14 -0400 Subject: [PATCH 02/14] typo fix --- core/chains/evm/gas/rollups/op_l1_oracle.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/chains/evm/gas/rollups/op_l1_oracle.go b/core/chains/evm/gas/rollups/op_l1_oracle.go index 53621a4ab2e..56069e3f831 100644 --- a/core/chains/evm/gas/rollups/op_l1_oracle.go +++ b/core/chains/evm/gas/rollups/op_l1_oracle.go @@ -475,7 +475,7 @@ func (o *optimismL1Oracle) getMantleGasPrice(ctx context.Context) (*big.Int, err // call oracle to get tokenRatio b, err := o.client.CallContract(ctx, ethereum.CallMsg{ To: &l1OracleAddress, - Data: o.tokenRatioCalldata + Data: o.tokenRatioCalldata, }, nil) if err != nil { return nil, fmt.Errorf("tokenRatio() call failed: %w", err) From ef3f4c5a738c751c66f8a33d2482e40bc16cb787 Mon Sep 17 00:00:00 2001 From: Matthew Romage Date: Tue, 20 Aug 2024 09:51:16 -0400 Subject: [PATCH 03/14] fixed syntax errors --- core/chains/evm/gas/rollups/l1_oracle.go | 2 +- core/chains/evm/gas/rollups/l1_oracle_abi.go | 1 + core/chains/evm/gas/rollups/op_l1_oracle.go | 12 ++++++++---- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/core/chains/evm/gas/rollups/l1_oracle.go b/core/chains/evm/gas/rollups/l1_oracle.go index 2ad1cc0a490..e1249fdb7e9 100644 --- a/core/chains/evm/gas/rollups/l1_oracle.go +++ b/core/chains/evm/gas/rollups/l1_oracle.go @@ -43,7 +43,7 @@ const ( PollPeriod = 6 * time.Second ) -var supportedChainTypes = []chaintype.ChainType{chaintype.ChainArbitrum, chaintype.ChainOptimismBedrock, chaintype.ChainKroma, chaintype.ChainScroll, chaintype.ChainZkSync, chainType.ChainMantle} +var supportedChainTypes = []chaintype.ChainType{chaintype.ChainArbitrum, chaintype.ChainOptimismBedrock, chaintype.ChainKroma, chaintype.ChainScroll, chaintype.ChainZkSync, chaintype.ChainMantle} func IsRollupWithL1Support(chainType chaintype.ChainType) bool { return slices.Contains(supportedChainTypes, chainType) diff --git a/core/chains/evm/gas/rollups/l1_oracle_abi.go b/core/chains/evm/gas/rollups/l1_oracle_abi.go index fa5d9c85391..bd73b6e7516 100644 --- a/core/chains/evm/gas/rollups/l1_oracle_abi.go +++ b/core/chains/evm/gas/rollups/l1_oracle_abi.go @@ -19,3 +19,4 @@ 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":"pure","type":"function"}]` diff --git a/core/chains/evm/gas/rollups/op_l1_oracle.go b/core/chains/evm/gas/rollups/op_l1_oracle.go index 56069e3f831..8e20c79dd8c 100644 --- a/core/chains/evm/gas/rollups/op_l1_oracle.go +++ b/core/chains/evm/gas/rollups/op_l1_oracle.go @@ -51,7 +51,7 @@ type optimismL1Oracle struct { blobBaseFeeCalldata []byte blobBaseFeeScalarCalldata []byte decimalsCalldata []byte - tokenRatioMethodCalldata []byte + tokenRatioCalldata []byte isEcotoneCalldata []byte isEcotoneMethodAbi abi.ABI isFjordCalldata []byte @@ -202,7 +202,7 @@ func newOpStackL1GasOracle(lggr logger.Logger, ethClient l1OracleClient, chainTy if err != nil { return nil, fmt.Errorf("failed to parse GasPriceOracle %s() method ABI for chain: %s; %w", decimalsMethod, chainType, err) } - tokenRatioCalldata, err := decimalsMethodAbi.Pack(tokenRatioMethod) + tokenRatioCalldata, err := tokenRatioMethodAbi.Pack(tokenRatioMethod) if err != nil { return nil, fmt.Errorf("failed to parse GasPriceOracle %s() calldata for chain: %s; %w", decimalsMethod, chainType, err) } @@ -229,6 +229,7 @@ func newOpStackL1GasOracle(lggr logger.Logger, ethClient l1OracleClient, chainTy blobBaseFeeCalldata: blobBaseFeeCalldata, blobBaseFeeScalarCalldata: blobBaseFeeScalarCalldata, decimalsCalldata: decimalsCalldata, + tokenRatioCalldata: tokenRatioCalldata, isEcotoneCalldata: isEcotoneCalldata, isEcotoneMethodAbi: isEcotoneMethodAbi, isFjordCalldata: isFjordCalldata, @@ -485,10 +486,13 @@ func (o *optimismL1Oracle) getMantleGasPrice(ctx context.Context) (*big.Int, err tokenRatio := new(big.Int).SetBytes(b) // call optimism bedrock gas price function - classicEvmGasPrice := o.getV1GasPrice(ctx) + classicEvmGasPrice, err := o.getV1GasPrice(ctx) + if err != nil { + return nil, fmt.Errorf("getting gas price from pptimism bedrock formula failed: %w", err) + } // multiply return gas price by tokenRatio - return classicEvmGasPrice * tokenRatio + return new(big.Int).Mul(classicEvmGasPrice, tokenRatio), nil } // Returns the scaled gas price using baseFeeScalar, l1BaseFee, blobBaseFeeScalar, and blobBaseFee fields from the oracle From e520d2ca2c850d110a0967b6c5935402239a8281 Mon Sep 17 00:00:00 2001 From: Matthew Romage Date: Tue, 20 Aug 2024 13:22:21 -0400 Subject: [PATCH 04/14] fixed compilation errors in test --- .../evm/gas/rollups/op_l1_oracle_test.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) 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..55e7505a7e7 100644 --- a/core/chains/evm/gas/rollups/op_l1_oracle_test.go +++ b/core/chains/evm/gas/rollups/op_l1_oracle_test.go @@ -101,7 +101,7 @@ 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) + oracle, err := newOpStackL1GasOracle(logger.Test(t), ethClient, chaintype.ChainOptimismBedrock, oracleAddress, false) require.NoError(t, err) gasPrice, err := oracle.GetDAGasPrice(tests.Context(t)) @@ -223,7 +223,7 @@ 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) + oracle, err := newOpStackL1GasOracle(logger.Test(t), ethClient, chaintype.ChainOptimismBedrock, oracleAddress, false) require.NoError(t, err) gasPrice, err := oracle.GetDAGasPrice(tests.Context(t)) require.NoError(t, err) @@ -242,7 +242,7 @@ func TestOPL1Oracle_CalculateEcotoneGasPrice(t *testing.T) { rpcElements[1].Result = &badData }).Return(nil).Once() - oracle, err := newOpStackL1GasOracle(logger.Test(t), ethClient, chaintype.ChainOptimismBedrock, oracleAddress) + oracle, err := newOpStackL1GasOracle(logger.Test(t), ethClient, chaintype.ChainOptimismBedrock, oracleAddress, false) require.NoError(t, err) _, err = oracle.GetDAGasPrice(tests.Context(t)) assert.Error(t, err) @@ -252,7 +252,7 @@ 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) + oracle, err := newOpStackL1GasOracle(logger.Test(t), ethClient, chaintype.ChainOptimismBedrock, oracleAddress, false) require.NoError(t, err) _, err = oracle.GetDAGasPrice(tests.Context(t)) assert.Error(t, err) @@ -267,7 +267,7 @@ 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) + oracle, err := newOpStackL1GasOracle(logger.Test(t), ethClient, chaintype.ChainOptimismBedrock, oracleAddress, false) require.NoError(t, err) _, err = oracle.GetDAGasPrice(tests.Context(t)) assert.Error(t, err) @@ -288,7 +288,7 @@ 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) + oracle, err := newOpStackL1GasOracle(logger.Test(t), ethClient, chaintype.ChainOptimismBedrock, oracleAddress, false) require.NoError(t, err) gasPrice, err := oracle.GetDAGasPrice(tests.Context(t)) require.NoError(t, err) @@ -307,7 +307,7 @@ func TestOPL1Oracle_CalculateFjordGasPrice(t *testing.T) { rpcElements[1].Result = &badData }).Return(nil).Once() - oracle, err := newOpStackL1GasOracle(logger.Test(t), ethClient, chaintype.ChainOptimismBedrock, oracleAddress) + oracle, err := newOpStackL1GasOracle(logger.Test(t), ethClient, chaintype.ChainOptimismBedrock, oracleAddress, false) require.NoError(t, err) _, err = oracle.GetDAGasPrice(tests.Context(t)) assert.Error(t, err) @@ -317,7 +317,7 @@ 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) + oracle, err := newOpStackL1GasOracle(logger.Test(t), ethClient, chaintype.ChainOptimismBedrock, oracleAddress, false) require.NoError(t, err) _, err = oracle.GetDAGasPrice(tests.Context(t)) assert.Error(t, err) @@ -332,7 +332,7 @@ 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) + oracle, err := newOpStackL1GasOracle(logger.Test(t), ethClient, chaintype.ChainOptimismBedrock, oracleAddress, false) require.NoError(t, err) _, err = oracle.GetDAGasPrice(tests.Context(t)) assert.Error(t, err) From 5676e395d706bf5e2812b309cdcaace072fe9ed7 Mon Sep 17 00:00:00 2001 From: Matthew Romage Date: Tue, 20 Aug 2024 19:21:10 -0400 Subject: [PATCH 05/14] added unit tests for Mantle oracle changes --- core/chains/evm/config/chaintype/chaintype.go | 8 +-- core/chains/evm/gas/rollups/l1_oracle_abi.go | 2 +- core/chains/evm/gas/rollups/op_l1_oracle.go | 25 +++----- .../evm/gas/rollups/op_l1_oracle_test.go | 62 ++++++++++++++++--- 4 files changed, 68 insertions(+), 29 deletions(-) diff --git a/core/chains/evm/config/chaintype/chaintype.go b/core/chains/evm/config/chaintype/chaintype.go index d7a5ccd99ab..da7f6f54d76 100644 --- a/core/chains/evm/config/chaintype/chaintype.go +++ b/core/chains/evm/config/chaintype/chaintype.go @@ -38,7 +38,7 @@ func (c ChainType) IsL2() bool { func (c ChainType) IsValid() bool { switch c { - case "", ChainArbitrum, ChainAstar, ChainCelo, ChainGnosis, ChainHedera, ChainKroma, ChainMetis, ChainOptimismBedrock, ChainScroll, ChainWeMix, ChainXLayer, ChainZkEvm, ChainZkSync, ChainMantle: + case "", ChainArbitrum, ChainAstar, ChainCelo, ChainGnosis, ChainHedera, ChainKroma, ChainMantle, ChainMetis, ChainOptimismBedrock, ChainScroll, ChainWeMix, ChainXLayer, ChainZkEvm, ChainZkSync: return true } return false @@ -58,6 +58,8 @@ func ChainTypeFromSlug(slug string) ChainType { return ChainHedera case "kroma": return ChainKroma + case "mantle": + return ChainMantle case "metis": return ChainMetis case "optimismBedrock": @@ -72,8 +74,6 @@ func ChainTypeFromSlug(slug string) ChainType { return ChainZkEvm case "zksync": return ChainZkSync - case "mantle": - return ChainMantle default: return ChainType(slug) } @@ -132,6 +132,7 @@ var ErrInvalidChainType = fmt.Errorf("must be one of %s or omitted", strings.Joi string(ChainGnosis), string(ChainHedera), string(ChainKroma), + string(ChainMantle), string(ChainMetis), string(ChainOptimismBedrock), string(ChainScroll), @@ -139,5 +140,4 @@ var ErrInvalidChainType = fmt.Errorf("must be one of %s or omitted", strings.Joi string(ChainXLayer), string(ChainZkEvm), string(ChainZkSync), - string(ChainMantle), }, ", ")) diff --git a/core/chains/evm/gas/rollups/l1_oracle_abi.go b/core/chains/evm/gas/rollups/l1_oracle_abi.go index bd73b6e7516..848957ce53a 100644 --- a/core/chains/evm/gas/rollups/l1_oracle_abi.go +++ b/core/chains/evm/gas/rollups/l1_oracle_abi.go @@ -19,4 +19,4 @@ 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":"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/op_l1_oracle.go b/core/chains/evm/gas/rollups/op_l1_oracle.go index 8e20c79dd8c..b20aaed9081 100644 --- a/core/chains/evm/gas/rollups/op_l1_oracle.go +++ b/core/chains/evm/gas/rollups/op_l1_oracle.go @@ -38,7 +38,6 @@ type optimismL1Oracle struct { l1GasPrice priceEntry isEcotone bool isFjord bool - isMantle bool upgradeCheckTs time.Time chInitialised chan struct{} @@ -103,24 +102,20 @@ const ( func NewOpStackL1GasOracle(lggr logger.Logger, ethClient l1OracleClient, chainType chaintype.ChainType) (*optimismL1Oracle, error) { var precompileAddress string - isMantle := false switch chainType { - case chaintype.ChainOptimismBedrock: + case chaintype.ChainOptimismBedrock, chaintype.ChainMantle: precompileAddress = OPGasOracleAddress case chaintype.ChainKroma: precompileAddress = KromaGasOracleAddress case chaintype.ChainScroll: precompileAddress = ScrollGasOracleAddress - case chaintype.ChainMantle: - precompileAddress = OPGasOracleAddress - isMantle = true default: return nil, fmt.Errorf("received unsupported chaintype %s", chainType) } - return newOpStackL1GasOracle(lggr, ethClient, chainType, precompileAddress, isMantle) + return newOpStackL1GasOracle(lggr, ethClient, chainType, precompileAddress) } -func newOpStackL1GasOracle(lggr logger.Logger, ethClient l1OracleClient, chainType chaintype.ChainType, precompileAddress string, isMantle bool) (*optimismL1Oracle, error) { +func newOpStackL1GasOracle(lggr logger.Logger, ethClient l1OracleClient, chainType chaintype.ChainType, precompileAddress string) (*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) @@ -216,7 +211,6 @@ func newOpStackL1GasOracle(lggr logger.Logger, ethClient l1OracleClient, chainTy l1OracleAddress: precompileAddress, isEcotone: false, isFjord: false, - isMantle: isMantle, upgradeCheckTs: time.Time{}, chInitialised: make(chan struct{}), @@ -368,6 +362,10 @@ 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 @@ -375,9 +373,6 @@ func (o *optimismL1Oracle) GetDAGasPrice(ctx context.Context) (*big.Int, error) if o.isFjord || o.isEcotone { return o.getEcotoneFjordGasPrice(ctx) } - if o.isMantle { - return o.getMantleGasPrice(ctx) - } return o.getV1GasPrice(ctx) } @@ -486,13 +481,13 @@ func (o *optimismL1Oracle) getMantleGasPrice(ctx context.Context) (*big.Int, err tokenRatio := new(big.Int).SetBytes(b) // call optimism bedrock gas price function - classicEvmGasPrice, err := o.getV1GasPrice(ctx) + v1GasPrice, err := o.getV1GasPrice(ctx) if err != nil { - return nil, fmt.Errorf("getting gas price from pptimism bedrock formula failed: %w", err) + return nil, fmt.Errorf("getting gas price from optimism bedrock formula failed: %w", err) } // multiply return gas price by tokenRatio - return new(big.Int).Mul(classicEvmGasPrice, tokenRatio), nil + return new(big.Int).Mul(v1GasPrice, tokenRatio), nil } // Returns the scaled gas price using baseFeeScalar, l1BaseFee, blobBaseFeeScalar, and blobBaseFee fields from the oracle 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 55e7505a7e7..6fd61fe964e 100644 --- a/core/chains/evm/gas/rollups/op_l1_oracle_test.go +++ b/core/chains/evm/gas/rollups/op_l1_oracle_test.go @@ -101,7 +101,7 @@ 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, false) + oracle, err := newOpStackL1GasOracle(logger.Test(t), ethClient, chaintype.ChainOptimismBedrock, oracleAddress) require.NoError(t, err) gasPrice, err := oracle.GetDAGasPrice(tests.Context(t)) @@ -111,6 +111,50 @@ 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 weighted gas price if chain is mantle", func(t *testing.T) { + 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("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) + require.Equal(t, tokenRatioCalldata, callMsg.Data) + require.Equal(t, oracleAddress, callMsg.To.String()) + assert.Nil(t, blockNumber) + }).Return(common.BigToHash(tokenRatio).Bytes(), nil).Once() + + 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) + require.Equal(t, l1BaseFeeCalldata, callMsg.Data) + require.Equal(t, oracleAddress, callMsg.To.String()) + assert.Nil(t, blockNumber) + }).Return(common.BigToHash(l1BaseFee).Bytes(), 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) + }) +} + func setupUpgradeCheck(t *testing.T, oracleAddress string, isFjord, isEcotone bool) *mocks.L1OracleClient { trueHex := "0x0000000000000000000000000000000000000000000000000000000000000001" falseHex := "0x0000000000000000000000000000000000000000000000000000000000000000" @@ -223,7 +267,7 @@ 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, false) + oracle, err := newOpStackL1GasOracle(logger.Test(t), ethClient, chaintype.ChainOptimismBedrock, oracleAddress) require.NoError(t, err) gasPrice, err := oracle.GetDAGasPrice(tests.Context(t)) require.NoError(t, err) @@ -242,7 +286,7 @@ func TestOPL1Oracle_CalculateEcotoneGasPrice(t *testing.T) { rpcElements[1].Result = &badData }).Return(nil).Once() - oracle, err := newOpStackL1GasOracle(logger.Test(t), ethClient, chaintype.ChainOptimismBedrock, oracleAddress, false) + oracle, err := newOpStackL1GasOracle(logger.Test(t), ethClient, chaintype.ChainOptimismBedrock, oracleAddress) require.NoError(t, err) _, err = oracle.GetDAGasPrice(tests.Context(t)) assert.Error(t, err) @@ -252,7 +296,7 @@ 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, false) + oracle, err := newOpStackL1GasOracle(logger.Test(t), ethClient, chaintype.ChainOptimismBedrock, oracleAddress) require.NoError(t, err) _, err = oracle.GetDAGasPrice(tests.Context(t)) assert.Error(t, err) @@ -267,7 +311,7 @@ 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, false) + oracle, err := newOpStackL1GasOracle(logger.Test(t), ethClient, chaintype.ChainOptimismBedrock, oracleAddress) require.NoError(t, err) _, err = oracle.GetDAGasPrice(tests.Context(t)) assert.Error(t, err) @@ -288,7 +332,7 @@ 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, false) + oracle, err := newOpStackL1GasOracle(logger.Test(t), ethClient, chaintype.ChainOptimismBedrock, oracleAddress) require.NoError(t, err) gasPrice, err := oracle.GetDAGasPrice(tests.Context(t)) require.NoError(t, err) @@ -307,7 +351,7 @@ func TestOPL1Oracle_CalculateFjordGasPrice(t *testing.T) { rpcElements[1].Result = &badData }).Return(nil).Once() - oracle, err := newOpStackL1GasOracle(logger.Test(t), ethClient, chaintype.ChainOptimismBedrock, oracleAddress, false) + oracle, err := newOpStackL1GasOracle(logger.Test(t), ethClient, chaintype.ChainOptimismBedrock, oracleAddress) require.NoError(t, err) _, err = oracle.GetDAGasPrice(tests.Context(t)) assert.Error(t, err) @@ -317,7 +361,7 @@ 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, false) + oracle, err := newOpStackL1GasOracle(logger.Test(t), ethClient, chaintype.ChainOptimismBedrock, oracleAddress) require.NoError(t, err) _, err = oracle.GetDAGasPrice(tests.Context(t)) assert.Error(t, err) @@ -332,7 +376,7 @@ 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, false) + oracle, err := newOpStackL1GasOracle(logger.Test(t), ethClient, chaintype.ChainOptimismBedrock, oracleAddress) require.NoError(t, err) _, err = oracle.GetDAGasPrice(tests.Context(t)) assert.Error(t, err) From 88ea6c343d1d61d0b0de4bfe5327d40282360580 Mon Sep 17 00:00:00 2001 From: Matthew Romage Date: Tue, 20 Aug 2024 19:22:04 -0400 Subject: [PATCH 06/14] typo fix --- core/chains/evm/gas/rollups/op_l1_oracle_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 6fd61fe964e..43ee1a2910b 100644 --- a/core/chains/evm/gas/rollups/op_l1_oracle_test.go +++ b/core/chains/evm/gas/rollups/op_l1_oracle_test.go @@ -117,7 +117,7 @@ func TestOPL1Oracle_ReadMantleGasPrice(t *testing.T) { oracleAddress := common.HexToAddress("0x1234").String() t.Parallel() - t.Run("correctly fetches weighted gas price if chain is mantle", func(t *testing.T) { + t.Run("correctly fetches gas price if chain is Mantle", func(t *testing.T) { l1BaseFeeMethodAbi, err := abi.JSON(strings.NewReader(L1BaseFeeAbiString)) require.NoError(t, err) l1BaseFeeCalldata, err := l1BaseFeeMethodAbi.Pack(l1BaseFeeMethod) From 3076e6aa25832bf45438e57835a2c8f29e5581ba Mon Sep 17 00:00:00 2001 From: Matthew Romage Date: Tue, 20 Aug 2024 19:35:24 -0400 Subject: [PATCH 07/14] added changeset file --- .changeset/two-mugs-complain.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/two-mugs-complain.md diff --git a/.changeset/two-mugs-complain.md b/.changeset/two-mugs-complain.md new file mode 100644 index 00000000000..7c5671de20f --- /dev/null +++ b/.changeset/two-mugs-complain.md @@ -0,0 +1,5 @@ +--- +"chainlink": minor +--- + +Edited the Optimism Stack L1 Oracle to add support for Mantle From ccfbaf48f1478ec5a5d2af9b57db1e6ba2f5032e Mon Sep 17 00:00:00 2001 From: Matthew Romage Date: Tue, 20 Aug 2024 19:37:13 -0400 Subject: [PATCH 08/14] added hashtag to changeset --- .changeset/two-mugs-complain.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/two-mugs-complain.md b/.changeset/two-mugs-complain.md index 7c5671de20f..77cdcbfe9ec 100644 --- a/.changeset/two-mugs-complain.md +++ b/.changeset/two-mugs-complain.md @@ -2,4 +2,4 @@ "chainlink": minor --- -Edited the Optimism Stack L1 Oracle to add support for Mantle +Edited the Optimism Stack L1 Oracle to add support for Mantle #added From 6d6f8fec183abca55ddf1e1e2479518f297faff7 Mon Sep 17 00:00:00 2001 From: Matthew Romage Date: Tue, 20 Aug 2024 20:02:27 -0400 Subject: [PATCH 09/14] changed unit test to include new chaintype --- core/services/chainlink/config_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/services/chainlink/config_test.go b/core/services/chainlink/config_test.go index 5fc9babe77b..11e68a9a148 100644 --- a/core/services/chainlink/config_test.go +++ b/core/services/chainlink/config_test.go @@ -1317,7 +1317,7 @@ func TestConfig_Validate(t *testing.T) { - 2: 5 errors: - ChainType: invalid value (Arbitrum): only "optimismBedrock" can be used with this chain id - Nodes: missing: must have at least one node - - ChainType: invalid value (Arbitrum): must be one of arbitrum, astar, celo, gnosis, hedera, kroma, metis, optimismBedrock, scroll, wemix, xlayer, zkevm, zksync or omitted + - ChainType: invalid value (Arbitrum): must be one of arbitrum, astar, celo, gnosis, hedera, kroma, metis, mantle, optimismBedrock, scroll, wemix, xlayer, zkevm, zksync or omitted - FinalityDepth: invalid value (0): must be greater than or equal to 1 - MinIncomingConfirmations: invalid value (0): must be greater than or equal to 1 - 3.Nodes: 5 errors: From b4cbe2e8f1993be2273b852b996dde0caa78ee0e Mon Sep 17 00:00:00 2001 From: Matthew Romage Date: Tue, 20 Aug 2024 20:19:52 -0400 Subject: [PATCH 10/14] changed test to include new chaintype --- core/services/chainlink/config_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/services/chainlink/config_test.go b/core/services/chainlink/config_test.go index 11e68a9a148..a510a4f9f5d 100644 --- a/core/services/chainlink/config_test.go +++ b/core/services/chainlink/config_test.go @@ -1304,7 +1304,7 @@ func TestConfig_Validate(t *testing.T) { - 1: 10 errors: - ChainType: invalid value (Foo): must not be set with this chain id - Nodes: missing: must have at least one node - - ChainType: invalid value (Foo): must be one of arbitrum, astar, celo, gnosis, hedera, kroma, metis, optimismBedrock, scroll, wemix, xlayer, zkevm, zksync or omitted + - ChainType: invalid value (Foo): must be one of arbitrum, astar, celo, gnosis, hedera, kroma, metis, mantle, optimismBedrock, scroll, wemix, xlayer, zkevm, zksync or omitted - HeadTracker.HistoryDepth: invalid value (30): must be greater than or equal to FinalizedBlockOffset - GasEstimator.BumpThreshold: invalid value (0): cannot be 0 if auto-purge feature is enabled for Foo - Transactions.AutoPurge.Threshold: missing: needs to be set if auto-purge feature is enabled for Foo From 7e804127c18c497e93fe2383089085385213199a Mon Sep 17 00:00:00 2001 From: Matthew Romage Date: Tue, 20 Aug 2024 20:40:53 -0400 Subject: [PATCH 11/14] typo fix --- core/services/chainlink/config_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/services/chainlink/config_test.go b/core/services/chainlink/config_test.go index a510a4f9f5d..b9d7921d783 100644 --- a/core/services/chainlink/config_test.go +++ b/core/services/chainlink/config_test.go @@ -1304,7 +1304,7 @@ func TestConfig_Validate(t *testing.T) { - 1: 10 errors: - ChainType: invalid value (Foo): must not be set with this chain id - Nodes: missing: must have at least one node - - ChainType: invalid value (Foo): must be one of arbitrum, astar, celo, gnosis, hedera, kroma, metis, mantle, optimismBedrock, scroll, wemix, xlayer, zkevm, zksync or omitted + - ChainType: invalid value (Foo): must be one of arbitrum, astar, celo, gnosis, hedera, kroma, mantle, metis, optimismBedrock, scroll, wemix, xlayer, zkevm, zksync or omitted - HeadTracker.HistoryDepth: invalid value (30): must be greater than or equal to FinalizedBlockOffset - GasEstimator.BumpThreshold: invalid value (0): cannot be 0 if auto-purge feature is enabled for Foo - Transactions.AutoPurge.Threshold: missing: needs to be set if auto-purge feature is enabled for Foo @@ -1317,7 +1317,7 @@ func TestConfig_Validate(t *testing.T) { - 2: 5 errors: - ChainType: invalid value (Arbitrum): only "optimismBedrock" can be used with this chain id - Nodes: missing: must have at least one node - - ChainType: invalid value (Arbitrum): must be one of arbitrum, astar, celo, gnosis, hedera, kroma, metis, mantle, optimismBedrock, scroll, wemix, xlayer, zkevm, zksync or omitted + - ChainType: invalid value (Arbitrum): must be one of arbitrum, astar, celo, gnosis, hedera, kroma, mantle, metis, optimismBedrock, scroll, wemix, xlayer, zkevm, zksync or omitted - FinalityDepth: invalid value (0): must be greater than or equal to 1 - MinIncomingConfirmations: invalid value (0): must be greater than or equal to 1 - 3.Nodes: 5 errors: From d9185ccb2a7500da30f69f637977f111b3d02b2b Mon Sep 17 00:00:00 2001 From: Matthew Romage Date: Wed, 21 Aug 2024 12:38:31 -0400 Subject: [PATCH 12/14] addressed alphabetical order and error issues --- core/chains/evm/config/chaintype/chaintype.go | 2 +- core/chains/evm/gas/rollups/op_l1_oracle.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/core/chains/evm/config/chaintype/chaintype.go b/core/chains/evm/config/chaintype/chaintype.go index da7f6f54d76..35dd214b1f5 100644 --- a/core/chains/evm/config/chaintype/chaintype.go +++ b/core/chains/evm/config/chaintype/chaintype.go @@ -14,6 +14,7 @@ const ( ChainGnosis ChainType = "gnosis" ChainHedera ChainType = "hedera" ChainKroma ChainType = "kroma" + ChainMantle ChainType = "mantle" ChainMetis ChainType = "metis" ChainOptimismBedrock ChainType = "optimismBedrock" ChainScroll ChainType = "scroll" @@ -21,7 +22,6 @@ const ( ChainXLayer ChainType = "xlayer" ChainZkEvm ChainType = "zkevm" ChainZkSync ChainType = "zksync" - ChainMantle ChainType = "mantle" ) // IsL2 returns true if this chain is a Layer 2 chain. Notably: diff --git a/core/chains/evm/gas/rollups/op_l1_oracle.go b/core/chains/evm/gas/rollups/op_l1_oracle.go index b20aaed9081..c7d0a6fa2b9 100644 --- a/core/chains/evm/gas/rollups/op_l1_oracle.go +++ b/core/chains/evm/gas/rollups/op_l1_oracle.go @@ -195,11 +195,11 @@ func newOpStackL1GasOracle(lggr logger.Logger, ethClient l1OracleClient, chainTy // 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", decimalsMethod, chainType, err) + 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", decimalsMethod, chainType, err) + return nil, fmt.Errorf("failed to parse GasPriceOracle %s() calldata for chain: %s; %w", tokenRatioMethod, chainType, err) } return &optimismL1Oracle{ From 7cf35b2a6a321c1860e2d40477807fca62eb9109 Mon Sep 17 00:00:00 2001 From: Matthew Romage Date: Wed, 21 Aug 2024 13:17:30 -0400 Subject: [PATCH 13/14] used batch call instead of separate calls for L1 Base Fee --- core/chains/evm/gas/rollups/op_l1_oracle.go | 64 +++++++++++++++---- .../evm/gas/rollups/op_l1_oracle_test.go | 36 ++++++----- 2 files changed, 70 insertions(+), 30 deletions(-) diff --git a/core/chains/evm/gas/rollups/op_l1_oracle.go b/core/chains/evm/gas/rollups/op_l1_oracle.go index c7d0a6fa2b9..b91e5554bdb 100644 --- a/core/chains/evm/gas/rollups/op_l1_oracle.go +++ b/core/chains/evm/gas/rollups/op_l1_oracle.go @@ -465,29 +465,65 @@ func (o *optimismL1Oracle) getV1GasPrice(ctx context.Context) (*big.Int, error) // 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) { - // set oracle address - l1OracleAddress := common.HexToAddress(o.l1OracleAddress) + // 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), + }, + } - // call oracle to get tokenRatio - b, err := o.client.CallContract(ctx, ethereum.CallMsg{ - To: &l1OracleAddress, - Data: o.tokenRatioCalldata, - }, nil) + err := o.client.BatchCallContext(ctx, rpcBatchCalls) if err != nil { - return nil, fmt.Errorf("tokenRatio() call failed: %w", err) + 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) } - // create bigInt from returned bytes - tokenRatio := new(big.Int).SetBytes(b) + // Extract values from responses + l1BaseFeeResult := *(rpcBatchCalls[0].Result.(*string)) + tokenRatioResult := *(rpcBatchCalls[1].Result.(*string)) - // call optimism bedrock gas price function - v1GasPrice, err := o.getV1GasPrice(ctx) + // Decode the responses into bytes + l1BaseFeeBytes, err := hexutil.Decode(l1BaseFeeResult) if err != nil { - return nil, fmt.Errorf("getting gas price from optimism bedrock formula failed: %w", err) + 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 return gas price by tokenRatio - return new(big.Int).Mul(v1GasPrice, tokenRatio), nil + return new(big.Int).Mul(l1BaseFee, tokenRatio), nil } // Returns the scaled gas price using baseFeeScalar, l1BaseFee, blobBaseFeeScalar, and blobBaseFee fields from the oracle 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 43ee1a2910b..e1b3cd57797 100644 --- a/core/chains/evm/gas/rollups/op_l1_oracle_test.go +++ b/core/chains/evm/gas/rollups/op_l1_oracle_test.go @@ -118,6 +118,7 @@ func TestOPL1Oracle_ReadMantleGasPrice(t *testing.T) { 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) @@ -130,27 +131,30 @@ func TestOPL1Oracle_ReadMantleGasPrice(t *testing.T) { require.NoError(t, err) ethClient := mocks.NewL1OracleClient(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) - require.Equal(t, tokenRatioCalldata, callMsg.Data) - require.Equal(t, oracleAddress, callMsg.To.String()) - assert.Nil(t, blockNumber) - }).Return(common.BigToHash(tokenRatio).Bytes(), nil).Once() - - 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) - require.Equal(t, l1BaseFeeCalldata, callMsg.Data) - require.Equal(t, oracleAddress, callMsg.To.String()) - assert.Nil(t, blockNumber) - }).Return(common.BigToHash(l1BaseFee).Bytes(), nil).Once() + 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)) + gasPrice, err := oracle.GetDAGasPrice(tests.Context(t)) require.NoError(t, err) + assert.Equal(t, new(big.Int).Mul(l1BaseFee, tokenRatio), gasPrice) }) } From ed8db2f45e77f33df72d01430b5cebb3c601e5ce Mon Sep 17 00:00:00 2001 From: Matthew Romage Date: Wed, 21 Aug 2024 14:41:15 -0400 Subject: [PATCH 14/14] added test cases for RPC errors to Mantle --- core/chains/evm/gas/rollups/op_l1_oracle.go | 2 +- .../evm/gas/rollups/op_l1_oracle_test.go | 40 +++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/core/chains/evm/gas/rollups/op_l1_oracle.go b/core/chains/evm/gas/rollups/op_l1_oracle.go index b91e5554bdb..1b93f8fc3f9 100644 --- a/core/chains/evm/gas/rollups/op_l1_oracle.go +++ b/core/chains/evm/gas/rollups/op_l1_oracle.go @@ -522,7 +522,7 @@ func (o *optimismL1Oracle) getMantleGasPrice(ctx context.Context) (*big.Int, err l1BaseFee := new(big.Int).SetBytes(l1BaseFeeBytes) tokenRatio := new(big.Int).SetBytes(tokenRatioBytes) - // multiply return gas price by tokenRatio + // multiply l1BaseFee and tokenRatio and return return new(big.Int).Mul(l1BaseFee, tokenRatio), 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 e1b3cd57797..88bb96534d3 100644 --- a/core/chains/evm/gas/rollups/op_l1_oracle_test.go +++ b/core/chains/evm/gas/rollups/op_l1_oracle_test.go @@ -157,6 +157,46 @@ func TestOPL1Oracle_ReadMantleGasPrice(t *testing.T) { 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 {