From cf7b1b047442a703c917f5306cb7369fb9663ddc Mon Sep 17 00:00:00 2001 From: Mateusz Sekara Date: Mon, 13 Jan 2025 18:31:40 +0100 Subject: [PATCH] CCIP-4420 Mantle gas interceptor (#15900) * Fixing gas estimator interceptor for Mantle * Adding extra check for the address presence --- .../interceptors/mantle/interceptor.go | 82 ---------------- .../interceptors/mantle/interceptor_test.go | 96 ------------------- core/services/relay/evm/evm.go | 8 +- .../evm/interceptors/mantle/interceptor.go | 16 ++-- .../interceptors/mantle/interceptor_test.go | 11 ++- 5 files changed, 24 insertions(+), 189 deletions(-) delete mode 100644 core/services/ocr2/plugins/ccip/estimatorconfig/interceptors/mantle/interceptor.go delete mode 100644 core/services/ocr2/plugins/ccip/estimatorconfig/interceptors/mantle/interceptor_test.go diff --git a/core/services/ocr2/plugins/ccip/estimatorconfig/interceptors/mantle/interceptor.go b/core/services/ocr2/plugins/ccip/estimatorconfig/interceptors/mantle/interceptor.go deleted file mode 100644 index 359f32e13a5..00000000000 --- a/core/services/ocr2/plugins/ccip/estimatorconfig/interceptors/mantle/interceptor.go +++ /dev/null @@ -1,82 +0,0 @@ -package mantle - -import ( - "context" - "fmt" - "math/big" - "strings" - "time" - - "github.com/ethereum/go-ethereum" - "github.com/ethereum/go-ethereum/accounts/abi" - "github.com/ethereum/go-ethereum/common" - - evmClient "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" -) - -const ( - // tokenRatio is not volatile and can be requested not often. - tokenRatioUpdateInterval = 60 * time.Minute - // tokenRatio fetches the tokenRatio used for Mantle's gas price calculation - tokenRatioMethod = "tokenRatio" - mantleTokenRatioAbiString = `[{"inputs":[],"name":"tokenRatio","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}]` -) - -type Interceptor struct { - client evmClient.Client - tokenRatioCallData []byte - tokenRatio *big.Int - tokenRatioLastUpdate time.Time -} - -func NewInterceptor(_ context.Context, client evmClient.Client) (*Interceptor, error) { - // 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 Mantle; %w", tokenRatioMethod, err) - } - tokenRatioCallData, err := tokenRatioMethodAbi.Pack(tokenRatioMethod) - if err != nil { - return nil, fmt.Errorf("failed to parse GasPriceOracle %s() calldata for Mantle; %w", tokenRatioMethod, err) - } - - return &Interceptor{ - client: client, - tokenRatioCallData: tokenRatioCallData, - }, nil -} - -// ModifyGasPriceComponents returns modified gasPrice. -func (i *Interceptor) ModifyGasPriceComponents(ctx context.Context, execGasPrice, daGasPrice *big.Int) (*big.Int, *big.Int, error) { - if time.Since(i.tokenRatioLastUpdate) > tokenRatioUpdateInterval { - mantleTokenRatio, err := i.getMantleTokenRatio(ctx) - if err != nil { - return nil, nil, err - } - - i.tokenRatio, i.tokenRatioLastUpdate = mantleTokenRatio, time.Now() - } - - // multiply daGasPrice and execGas price by tokenRatio - newExecGasPrice := new(big.Int).Mul(execGasPrice, i.tokenRatio) - newDAGasPrice := new(big.Int).Mul(daGasPrice, i.tokenRatio) - return newExecGasPrice, newDAGasPrice, nil -} - -// getMantleTokenRatio Requests and returns a token ratio value for the Mantle chain. -func (i *Interceptor) getMantleTokenRatio(ctx context.Context) (*big.Int, error) { - // FIXME it's removed from chainlink repo - // precompile := common.HexToAddress(rollups.OPGasOracleAddress) - precompile := common.Address{} - - tokenRatio, err := i.client.CallContract(ctx, ethereum.CallMsg{ - To: &precompile, - Data: i.tokenRatioCallData, - }, nil) - - if err != nil { - return nil, fmt.Errorf("getMantleTokenRatio call failed: %w", err) - } - - return new(big.Int).SetBytes(tokenRatio), nil -} diff --git a/core/services/ocr2/plugins/ccip/estimatorconfig/interceptors/mantle/interceptor_test.go b/core/services/ocr2/plugins/ccip/estimatorconfig/interceptors/mantle/interceptor_test.go deleted file mode 100644 index 9134d996c27..00000000000 --- a/core/services/ocr2/plugins/ccip/estimatorconfig/interceptors/mantle/interceptor_test.go +++ /dev/null @@ -1,96 +0,0 @@ -package mantle - -import ( - "context" - "math/big" - "testing" - - "github.com/ethereum/go-ethereum" - "github.com/ethereum/go-ethereum/common" - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" - - "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client/mocks" -) - -func TestInterceptor(t *testing.T) { - ethClient := mocks.NewClient(t) - ctx := context.Background() - - tokenRatio := big.NewInt(10) - interceptor, err := NewInterceptor(ctx, ethClient) - require.NoError(t, err) - - // request token ratio - ethClient.On("CallContract", ctx, mock.IsType(ethereum.CallMsg{}), mock.IsType(&big.Int{})). - Return(common.BigToHash(tokenRatio).Bytes(), nil).Once() - - modExecGasPrice, modDAGasPrice, err := interceptor.ModifyGasPriceComponents(ctx, big.NewInt(1), big.NewInt(1)) - require.NoError(t, err) - require.Equal(t, int64(10), modExecGasPrice.Int64()) - require.Equal(t, int64(10), modDAGasPrice.Int64()) - - // second call won't invoke eth client - modExecGasPrice, modDAGasPrice, err = interceptor.ModifyGasPriceComponents(ctx, big.NewInt(2), big.NewInt(1)) - require.NoError(t, err) - require.Equal(t, int64(20), modExecGasPrice.Int64()) - require.Equal(t, int64(10), modDAGasPrice.Int64()) -} - -func TestModifyGasPriceComponents(t *testing.T) { - testCases := map[string]struct { - execGasPrice *big.Int - daGasPrice *big.Int - tokenRatio *big.Int - resultExecGasPrice *big.Int - resultDAGasPrice *big.Int - }{ - "regular": { - execGasPrice: big.NewInt(1000), - daGasPrice: big.NewInt(100), - resultExecGasPrice: big.NewInt(2000), - resultDAGasPrice: big.NewInt(200), - tokenRatio: big.NewInt(2), - }, - "zero DAGasPrice": { - execGasPrice: big.NewInt(1000), - daGasPrice: big.NewInt(0), - resultExecGasPrice: big.NewInt(5000), - resultDAGasPrice: big.NewInt(0), - tokenRatio: big.NewInt(5), - }, - "zero ExecGasPrice": { - execGasPrice: big.NewInt(0), - daGasPrice: big.NewInt(10), - resultExecGasPrice: big.NewInt(0), - resultDAGasPrice: big.NewInt(50), - tokenRatio: big.NewInt(5), - }, - "zero token ratio": { - execGasPrice: big.NewInt(15), - daGasPrice: big.NewInt(10), - resultExecGasPrice: big.NewInt(0), - resultDAGasPrice: big.NewInt(0), - tokenRatio: big.NewInt(0), - }, - } - - for tcName, tc := range testCases { - t.Run(tcName, func(t *testing.T) { - ethClient := mocks.NewClient(t) - ctx := context.Background() - - interceptor, err := NewInterceptor(ctx, ethClient) - require.NoError(t, err) - - // request token ratio - ethClient.On("CallContract", ctx, mock.IsType(ethereum.CallMsg{}), mock.IsType(&big.Int{})). - Return(common.BigToHash(tc.tokenRatio).Bytes(), nil).Once() - - modExecGasPrice, modDAGasPrice, err := interceptor.ModifyGasPriceComponents(ctx, tc.execGasPrice, tc.daGasPrice) - require.NoError(t, err) - require.Equal(t, tc.resultExecGasPrice.Int64(), modExecGasPrice.Int64()) - require.Equal(t, tc.resultDAGasPrice.Int64(), modDAGasPrice.Int64()) - }) - } -} diff --git a/core/services/relay/evm/evm.go b/core/services/relay/evm/evm.go index 0a418262988..ae77ce61e10 100644 --- a/core/services/relay/evm/evm.go +++ b/core/services/relay/evm/evm.go @@ -51,13 +51,13 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/ccipexec" ccipconfig "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/config" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/estimatorconfig" - "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/estimatorconfig/interceptors/mantle" cciptransmitter "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/transmitter" lloconfig "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/llo/config" mercuryconfig "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/mercury/config" "github.com/smartcontractkit/chainlink/v2/core/services/ocrcommon" "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/codec" "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/functions" + "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/interceptors/mantle" "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury" mercuryutils "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/utils" reportcodecv1 "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/v1/reportcodec" @@ -541,7 +541,8 @@ func (r *Relayer) NewCCIPCommitProvider(ctx context.Context, rargs commontypes.R // to minimize misconfigure risk, might make sense to wire Mantle only when Commit + Mantle + IsSourceProvider if r.chain.Config().EVM().ChainID().Uint64() == 5003 || r.chain.Config().EVM().ChainID().Uint64() == 5000 { if commitPluginConfig.IsSourceProvider { - mantleInterceptor, iErr := mantle.NewInterceptor(ctx, r.chain.Client()) + oracleAddress := r.chain.Config().EVM().GasEstimator().DAOracle().OracleAddress() + mantleInterceptor, iErr := mantle.NewInterceptor(ctx, r.chain.Client(), oracleAddress) if iErr != nil { return nil, iErr } @@ -619,7 +620,8 @@ func (r *Relayer) NewCCIPExecProvider(ctx context.Context, rargs commontypes.Rel // to minimize misconfigure risk, make sense to wire Mantle only when Exec + Mantle + !IsSourceProvider if r.chain.Config().EVM().ChainID().Uint64() == 5003 || r.chain.Config().EVM().ChainID().Uint64() == 5000 { if !execPluginConfig.IsSourceProvider { - mantleInterceptor, iErr := mantle.NewInterceptor(ctx, r.chain.Client()) + oracleAddress := r.chain.Config().EVM().GasEstimator().DAOracle().OracleAddress() + mantleInterceptor, iErr := mantle.NewInterceptor(ctx, r.chain.Client(), oracleAddress) if iErr != nil { return nil, iErr } diff --git a/core/services/relay/evm/interceptors/mantle/interceptor.go b/core/services/relay/evm/interceptors/mantle/interceptor.go index c1520652d67..c6ace3e6cbc 100644 --- a/core/services/relay/evm/interceptors/mantle/interceptor.go +++ b/core/services/relay/evm/interceptors/mantle/interceptor.go @@ -2,6 +2,7 @@ package mantle import ( "context" + "errors" "fmt" "math/big" "strings" @@ -9,9 +10,10 @@ import ( "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" evmClient "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils" + evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" ) const ( @@ -27,9 +29,13 @@ type Interceptor struct { tokenRatioCallData []byte tokenRatio *big.Int tokenRatioLastUpdate time.Time + oracleAddress common.Address } -func NewInterceptor(_ context.Context, client evmClient.Client) (*Interceptor, error) { +func NewInterceptor(_ context.Context, client evmClient.Client, address *evmtypes.EIP55Address) (*Interceptor, error) { + if address == nil { + return nil, errors.New("oracle address is missing") + } // Encode calldata for tokenRatio method tokenRatioMethodAbi, err := abi.JSON(strings.NewReader(mantleTokenRatioAbiString)) if err != nil { @@ -43,6 +49,7 @@ func NewInterceptor(_ context.Context, client evmClient.Client) (*Interceptor, e return &Interceptor{ client: client, tokenRatioCallData: tokenRatioCallData, + oracleAddress: address.Address(), }, nil } @@ -65,11 +72,8 @@ func (i *Interceptor) ModifyGasPriceComponents(ctx context.Context, execGasPrice // getMantleTokenRatio Requests and returns a token ratio value for the Mantle chain. func (i *Interceptor) getMantleTokenRatio(ctx context.Context) (*big.Int, error) { - // FIXME it's removed from chainlink repo - // precompile := common.HexToAddress(rollups.OPGasOracleAddress) - precompile := utils.RandomAddress() tokenRatio, err := i.client.CallContract(ctx, ethereum.CallMsg{ - To: &precompile, + To: &i.oracleAddress, Data: i.tokenRatioCallData, }, nil) diff --git a/core/services/relay/evm/interceptors/mantle/interceptor_test.go b/core/services/relay/evm/interceptors/mantle/interceptor_test.go index 9134d996c27..6fa485de487 100644 --- a/core/services/relay/evm/interceptors/mantle/interceptor_test.go +++ b/core/services/relay/evm/interceptors/mantle/interceptor_test.go @@ -11,14 +11,21 @@ import ( "github.com/stretchr/testify/require" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client/mocks" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils" ) +var randomAddress = types.MustEIP55Address(utils.RandomAddress().String()) + func TestInterceptor(t *testing.T) { ethClient := mocks.NewClient(t) ctx := context.Background() + _, err := NewInterceptor(ctx, ethClient, nil) + require.Error(t, err) + tokenRatio := big.NewInt(10) - interceptor, err := NewInterceptor(ctx, ethClient) + interceptor, err := NewInterceptor(ctx, ethClient, &randomAddress) require.NoError(t, err) // request token ratio @@ -80,7 +87,7 @@ func TestModifyGasPriceComponents(t *testing.T) { ethClient := mocks.NewClient(t) ctx := context.Background() - interceptor, err := NewInterceptor(ctx, ethClient) + interceptor, err := NewInterceptor(ctx, ethClient, &randomAddress) require.NoError(t, err) // request token ratio