Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Changed Optimism L1 Oracle to support Mantle #14160

Merged
merged 25 commits into from
Aug 22, 2024
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
6b0b72b
edited op stack oracle to include tokenRatio for Mantle
ma33r Aug 20, 2024
cde83ea
typo fix
ma33r Aug 20, 2024
eff9ea7
Merge branch 'develop' into bci-3964-mantle-gas-oracle
ma33r Aug 20, 2024
ef3f4c5
fixed syntax errors
ma33r Aug 20, 2024
e520d2c
fixed compilation errors in test
ma33r Aug 20, 2024
5676e39
added unit tests for Mantle oracle changes
ma33r Aug 20, 2024
88ea6c3
typo fix
ma33r Aug 20, 2024
b607511
Merge branch 'develop' into bci-3964-mantle-gas-oracle
ma33r Aug 20, 2024
3076e6a
added changeset file
ma33r Aug 20, 2024
ccfbaf4
added hashtag to changeset
ma33r Aug 20, 2024
6d6f8fe
changed unit test to include new chaintype
ma33r Aug 21, 2024
b4cbe2e
changed test to include new chaintype
ma33r Aug 21, 2024
7e80412
typo fix
ma33r Aug 21, 2024
3ddcec7
Merge branch 'develop' into bci-3964-mantle-gas-oracle
ma33r Aug 21, 2024
044c87d
Merge branch 'develop' into bci-3964-mantle-gas-oracle
ma33r Aug 21, 2024
6a83c3a
Merge branch 'develop' into bci-3964-mantle-gas-oracle
ma33r Aug 21, 2024
d9185cc
addressed alphabetical order and error issues
ma33r Aug 21, 2024
7cf35b2
used batch call instead of separate calls for L1 Base Fee
ma33r Aug 21, 2024
10415c7
Merge branch 'develop' into bci-3964-mantle-gas-oracle
ma33r Aug 21, 2024
ed8db2f
added test cases for RPC errors to Mantle
ma33r Aug 21, 2024
cd051f4
Merge branch 'develop' into bci-3964-mantle-gas-oracle
ma33r Aug 21, 2024
74c62bc
Merge branch 'develop' into bci-3964-mantle-gas-oracle
ma33r Aug 22, 2024
b158efb
Merge branch 'develop' into bci-3964-mantle-gas-oracle
ma33r Aug 22, 2024
1e34607
Merge branch 'develop' into bci-3964-mantle-gas-oracle
ma33r Aug 22, 2024
5692a55
Merge branch 'develop' into bci-3964-mantle-gas-oracle
ma33r Aug 22, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/two-mugs-complain.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"chainlink": minor
---

Edited the Optimism Stack L1 Oracle to add support for Mantle #added
6 changes: 5 additions & 1 deletion core/chains/evm/config/chaintype/chaintype.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const (
ChainXLayer ChainType = "xlayer"
ChainZkEvm ChainType = "zkevm"
ChainZkSync ChainType = "zksync"
ChainMantle ChainType = "mantle"
amit-momin marked this conversation as resolved.
Show resolved Hide resolved
)

// IsL2 returns true if this chain is a Layer 2 chain. Notably:
Expand All @@ -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, ChainMantle, ChainMetis, ChainOptimismBedrock, ChainScroll, ChainWeMix, ChainXLayer, ChainZkEvm, ChainZkSync:
return true
}
return false
Expand All @@ -57,6 +58,8 @@ func ChainTypeFromSlug(slug string) ChainType {
return ChainHedera
case "kroma":
return ChainKroma
case "mantle":
return ChainMantle
case "metis":
return ChainMetis
case "optimismBedrock":
Expand Down Expand Up @@ -129,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),
Expand Down
4 changes: 2 additions & 2 deletions core/chains/evm/gas/rollups/l1_oracle.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand Down
1 change: 1 addition & 0 deletions core/chains/evm/gas/rollups/l1_oracle_abi.go
Original file line number Diff line number Diff line change
Expand Up @@ -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":"view","type":"function"}]`
51 changes: 49 additions & 2 deletions core/chains/evm/gas/rollups/op_l1_oracle.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ type optimismL1Oracle struct {
blobBaseFeeCalldata []byte
blobBaseFeeScalarCalldata []byte
decimalsCalldata []byte
tokenRatioCalldata []byte
isEcotoneCalldata []byte
isEcotoneMethodAbi abi.ABI
isFjordCalldata []byte
Expand Down Expand Up @@ -87,7 +88,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"
Expand All @@ -98,7 +103,7 @@ const (
func NewOpStackL1GasOracle(lggr logger.Logger, ethClient l1OracleClient, chainType chaintype.ChainType) (*optimismL1Oracle, error) {
var precompileAddress string
switch chainType {
case chaintype.ChainOptimismBedrock:
case chaintype.ChainOptimismBedrock, chaintype.ChainMantle:
precompileAddress = OPGasOracleAddress
case chaintype.ChainKroma:
precompileAddress = KromaGasOracleAddress
Expand Down Expand Up @@ -187,6 +192,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 := tokenRatioMethodAbi.Pack(tokenRatioMethod)
if err != nil {
return nil, fmt.Errorf("failed to parse GasPriceOracle %s() calldata for chain: %s; %w", decimalsMethod, chainType, err)
amit-momin marked this conversation as resolved.
Show resolved Hide resolved
}

return &optimismL1Oracle{
client: ethClient,
pollPeriod: PollPeriod,
Expand All @@ -208,6 +223,7 @@ func newOpStackL1GasOracle(lggr logger.Logger, ethClient l1OracleClient, chainTy
blobBaseFeeCalldata: blobBaseFeeCalldata,
blobBaseFeeScalarCalldata: blobBaseFeeScalarCalldata,
decimalsCalldata: decimalsCalldata,
tokenRatioCalldata: tokenRatioCalldata,
isEcotoneCalldata: isEcotoneCalldata,
isEcotoneMethodAbi: isEcotoneMethodAbi,
isFjordCalldata: isFjordCalldata,
Expand Down Expand Up @@ -346,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
Expand Down Expand Up @@ -443,6 +463,33 @@ 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
v1GasPrice, err := o.getV1GasPrice(ctx)
ma33r marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
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(v1GasPrice, tokenRatio), nil
}

// Returns the scaled gas price using baseFeeScalar, l1BaseFee, blobBaseFeeScalar, and blobBaseFee fields from the oracle
// Confirmed the same calculation is used to determine gas price for both Ecotone and Fjord
func (o *optimismL1Oracle) getEcotoneFjordGasPrice(ctx context.Context) (*big.Int, error) {
Expand Down
44 changes: 44 additions & 0 deletions core/chains/evm/gas/rollups/op_l1_oracle_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 gas price if chain is Mantle", func(t *testing.T) {
ma33r marked this conversation as resolved.
Show resolved Hide resolved
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"
Expand Down
4 changes: 2 additions & 2 deletions core/services/chainlink/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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, 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
Expand All @@ -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, 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:
Expand Down
Loading