Skip to content

Commit

Permalink
[CCIP-3376] tests and refactoring
Browse files Browse the repository at this point in the history
  • Loading branch information
valerii-kabisov-cll committed Sep 20, 2024
1 parent 24d05c4 commit 5fe2674
Show file tree
Hide file tree
Showing 11 changed files with 341 additions and 99 deletions.
7 changes: 6 additions & 1 deletion .mockery.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -467,7 +467,7 @@ packages:
filename: optimism_portal2_interface.go
outpkg: mock_optimism_portal_2
interfaces:
OptimismPortal2Interface:
OptimismPortal2Interface:
github.com/smartcontractkit/chainlink/v2/core/gethwrappers/liquiditymanager/generated/optimism_dispute_game_factory:
config:
dir: core/gethwrappers/liquiditymanager/mocks/mock_optimism_dispute_game_factory/
Expand Down Expand Up @@ -503,6 +503,11 @@ packages:
USDCReader:
config:
filename: usdc_reader_mock.go
github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/estimatorconfig:
interfaces:
GasPriceInterceptor:
config:
filename: gas_price_interceptor_mock.go
github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/ccipdata/batchreader:
config:
filename: token_pool_batched_reader_mock.go
Expand Down
12 changes: 6 additions & 6 deletions core/services/ocr2/plugins/ccip/estimatorconfig/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,18 @@ import (
// fields for the daGasEstimator from the encapsulated onRampReader.
type FeeEstimatorConfigProvider interface {
SetOnRampReader(reader ccip.OnRampReader)
AddGasPriceInterceptor(gasPriceInterceptor)
ModifyGasPriceComponents(ctx context.Context, gasPrice, daGasPrice *big.Int) (*big.Int, *big.Int, error)
AddGasPriceInterceptor(GasPriceInterceptor)
ModifyGasPriceComponents(ctx context.Context, execGasPrice, daGasPrice *big.Int) (modExecGasPrice, modDAGasPrice *big.Int, err error)
GetDataAvailabilityConfig(ctx context.Context) (destDataAvailabilityOverheadGas, destGasPerDataAvailabilityByte, destDataAvailabilityMultiplierBps int64, err error)
}

type gasPriceInterceptor interface {
ModifyGasPriceComponents(ctx context.Context, gasPrice, daGasPrice *big.Int) (modGasPrice, modDAGasPrice *big.Int, err error)
type GasPriceInterceptor interface {
ModifyGasPriceComponents(ctx context.Context, execGasPrice, daGasPrice *big.Int) (modExecGasPrice, modDAGasPrice *big.Int, err error)
}

type FeeEstimatorConfigService struct {
onRampReader ccip.OnRampReader
gasPriceInterceptors []gasPriceInterceptor
gasPriceInterceptors []GasPriceInterceptor
}

func NewFeeEstimatorConfigService() *FeeEstimatorConfigService {
Expand Down Expand Up @@ -57,7 +57,7 @@ func (c *FeeEstimatorConfigService) GetDataAvailabilityConfig(ctx context.Contex
}

// AddGasPriceInterceptor adds price interceptors that can modify gas price.
func (c *FeeEstimatorConfigService) AddGasPriceInterceptor(gpi gasPriceInterceptor) {
func (c *FeeEstimatorConfigService) AddGasPriceInterceptor(gpi GasPriceInterceptor) {
if gpi != nil {
c.gasPriceInterceptors = append(c.gasPriceInterceptors, gpi)
}
Expand Down
48 changes: 48 additions & 0 deletions core/services/ocr2/plugins/ccip/estimatorconfig/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ package estimatorconfig_test
import (
"context"
"errors"
"math/big"
"testing"

mocks2 "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/estimatorconfig/mocks"
"github.com/stretchr/testify/require"

"github.com/smartcontractkit/chainlink-common/pkg/types/ccip"
Expand Down Expand Up @@ -43,3 +45,49 @@ func TestFeeEstimatorConfigService(t *testing.T) {
_, _, _, err = svc.GetDataAvailabilityConfig(ctx)
require.Error(t, err)
}

func TestModifyGasPriceComponents(t *testing.T) {
t.Run("success modification", func(t *testing.T) {
svc := estimatorconfig.NewFeeEstimatorConfigService()
ctx := context.Background()

initialExecGasPrice, initialDaGasPrice := big.NewInt(10), big.NewInt(1)

gpi1 := mocks2.NewGasPriceInterceptor(t)
svc.AddGasPriceInterceptor(gpi1)

// change in first interceptor
firstModExecGasPrice, firstModDaGasPrice := big.NewInt(5), big.NewInt(2)
gpi1.On("ModifyGasPriceComponents", ctx, initialExecGasPrice, initialDaGasPrice).
Return(firstModExecGasPrice, firstModDaGasPrice, nil)

gpi2 := mocks2.NewGasPriceInterceptor(t)
svc.AddGasPriceInterceptor(gpi2)

// change in second iterceptor
secondModExecGasPrice, secondModDaGasPrice := big.NewInt(50), big.NewInt(20)
gpi2.On("ModifyGasPriceComponents", ctx, firstModExecGasPrice, firstModDaGasPrice).
Return(secondModExecGasPrice, secondModDaGasPrice, nil)

// has to return second interceptor values
resGasPrice, resDAGasPrice, err := svc.ModifyGasPriceComponents(ctx, initialExecGasPrice, initialDaGasPrice)
require.NoError(t, err)
require.Equal(t, secondModExecGasPrice.Int64(), resGasPrice.Int64())
require.Equal(t, secondModDaGasPrice.Int64(), resDAGasPrice.Int64())
})

t.Run("error modification", func(t *testing.T) {
svc := estimatorconfig.NewFeeEstimatorConfigService()
ctx := context.Background()

initialExecGasPrice, initialDaGasPrice := big.NewInt(10), big.NewInt(1)
gpi1 := mocks2.NewGasPriceInterceptor(t)
svc.AddGasPriceInterceptor(gpi1)
gpi1.On("ModifyGasPriceComponents", ctx, initialExecGasPrice, initialDaGasPrice).
Return(nil, nil, errors.New("test"))

// has to return second interceptor values
_, _, err := svc.ModifyGasPriceComponents(ctx, initialExecGasPrice, initialDaGasPrice)
require.Error(t, err)
})
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,14 @@ package mantle
import (
"context"
"fmt"
"log"
"math/big"
"strings"
"time"

"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/rpc"

evmClient "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client"
"github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas/rollups"
)
Expand All @@ -32,27 +31,25 @@ type Interceptor struct {
tokenRatioLastUpdate time.Time
}

func NewInterceptor(ctx context.Context, client evmClient.Client) (*Interceptor, error) {
func NewInterceptor(ctx context.Context, client evmClient.Client) *Interceptor {
// 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)
log.Panicf("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; %w", tokenRatioMethod, err)
log.Panicf("failed to parse GasPriceOracle %s() calldata for Mantle; %v", tokenRatioMethod, err)
}

interceptor := &Interceptor{
return &Interceptor{
client: client,
callData: tokenRatioCallData,
}

return interceptor, nil
}

// ModifyGasPriceComponents returns modified gasPrice.
func (i *Interceptor) ModifyGasPriceComponents(ctx context.Context, gasPrice, daGasPrice *big.Int) (*big.Int, *big.Int, error) {
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.getMantleGasPrice(ctx)
if err != nil {
Expand All @@ -62,49 +59,24 @@ func (i *Interceptor) ModifyGasPriceComponents(ctx context.Context, gasPrice, da
i.tokenRatio, i.tokenRatioLastUpdate = mantleTokenRatio, time.Now()
}

newGasPrice := new(big.Int).Add(gasPrice, daGasPrice)

// returns result of (execGasPrice+daGasPrice)*i.tokenRatio as a execGasPrice, daGasPrice not modified.
newGasPrice := new(big.Int).Add(execGasPrice, daGasPrice)
return new(big.Int).Mul(newGasPrice, i.tokenRatio), daGasPrice, nil
}

// getMantleGasPrice Requests and returns the token ratio for Mantle.
func (i *Interceptor) getMantleGasPrice(ctx context.Context) (*big.Int, error) {
// call oracle to get l1BaseFee and tokenRatio
rpcBatchCalls := []rpc.BatchElem{
{
Method: "eth_call",
Args: []any{
map[string]interface{}{
"from": common.Address{},
"to": rollups.OPGasOracleAddress,
"data": hexutil.Bytes(i.callData),
},
"latest",
},
Result: new(string),
},
}

err := i.client.BatchCallContext(ctx, rpcBatchCalls)
if err != nil {
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", tokenRatioMethod, err)
}
precompile := common.HexToAddress(rollups.OPGasOracleAddress)
tokenRatio, err := i.client.CallContract(ctx, ethereum.CallMsg{
To: &precompile,
Data: i.callData,
}, nil)

// Extract values from responses
tokenRatioResult := *(rpcBatchCalls[0].Result.(*string))

// Decode the responses into bytes
tokenRatioBytes, err := hexutil.Decode(tokenRatioResult)
if err != nil {
return nil, fmt.Errorf("failed to decode %s rpc result: %w", tokenRatioMethod, err)
return nil, fmt.Errorf("fetch gas price parameters call failed: %w", err)
}

// Convert bytes to big int for calculations
tokenRatio := new(big.Int).SetBytes(tokenRatioBytes)

// multiply l1BaseFee and tokenRatio and return
return tokenRatio, nil
// Convert bytes to big int for calculations and return
return new(big.Int).SetBytes(tokenRatio), nil
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package mantle

import (
"context"
"math/big"
"testing"

"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common"
"github.com/smartcontractkit/chainlink/v2/core/chains/evm/client/mocks"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
)

func TestInterceptor(t *testing.T) {
ethClient := mocks.NewClient(t)
ctx := context.Background()

tokenRatio := big.NewInt(10)
interceptor := NewInterceptor(ctx, ethClient)

ethClient.On("CallContract", ctx, mock.IsType(ethereum.CallMsg{}), mock.IsType(&big.Int{})).
Return(common.BigToHash(tokenRatio).Bytes(), nil)

modExecGasPrice, modDAGasPrice, err := interceptor.ModifyGasPriceComponents(ctx, big.NewInt(1), big.NewInt(1))
require.NoError(t, err)
require.Equal(t, modExecGasPrice.Int64(), int64(20))
require.Equal(t, modDAGasPrice.Int64(), int64(1))

// 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, modExecGasPrice.Int64(), int64(30))
require.Equal(t, modDAGasPrice.Int64(), int64(1))
}

func TestModifyGasPriceComponents(t *testing.T) {
testCases := map[string]struct {
execGasPrice *big.Int
daGasPrice *big.Int
tokenRatio *big.Int
resultExecGasPrice *big.Int
}{
"regular": {
execGasPrice: big.NewInt(1),
daGasPrice: big.NewInt(1),
tokenRatio: big.NewInt(10),
resultExecGasPrice: big.NewInt(20),
},
"zero DAGasPrice": {
execGasPrice: big.NewInt(1),
daGasPrice: big.NewInt(0),
tokenRatio: big.NewInt(1),
resultExecGasPrice: big.NewInt(1),
},
"zero token ratio": {
execGasPrice: big.NewInt(15),
daGasPrice: big.NewInt(10),
tokenRatio: big.NewInt(0),
resultExecGasPrice: big.NewInt(0),
},
}

for tcName, tc := range testCases {
t.Run(tcName, func(t *testing.T) {
ethClient := mocks.NewClient(t)
ctx := context.Background()

interceptor := NewInterceptor(ctx, ethClient)

ethClient.On("CallContract", ctx, mock.IsType(ethereum.CallMsg{}), mock.IsType(&big.Int{})).
Return(common.BigToHash(tc.tokenRatio).Bytes(), nil)

modExecGasPrice, modDAGasPrice, err := interceptor.ModifyGasPriceComponents(ctx, tc.execGasPrice, tc.daGasPrice)
require.NoError(t, err)
require.Equal(t, modExecGasPrice, tc.resultExecGasPrice)
require.Equal(t, modDAGasPrice, tc.daGasPrice)
})
}
}
Loading

0 comments on commit 5fe2674

Please sign in to comment.