-
Notifications
You must be signed in to change notification settings - Fork 51
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'ccip-develop' into crib-ignore-contracts-ccip-tag-prefix
- Loading branch information
Showing
14 changed files
with
725 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
80 changes: 80 additions & 0 deletions
80
core/services/ocr2/plugins/ccip/estimatorconfig/interceptors/mantle/interceptor.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
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" | ||
"github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas/rollups" | ||
) | ||
|
||
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; %v", tokenRatioMethod, err) | ||
} | ||
tokenRatioCallData, err := tokenRatioMethodAbi.Pack(tokenRatioMethod) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to parse GasPriceOracle %s() calldata for Mantle; %v", 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) { | ||
precompile := common.HexToAddress(rollups.OPGasOracleAddress) | ||
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 | ||
} |
96 changes: 96 additions & 0 deletions
96
core/services/ocr2/plugins/ccip/estimatorconfig/interceptors/mantle/interceptor_test.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
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()) | ||
}) | ||
} | ||
} |
Oops, something went wrong.