diff --git a/.changeset/pink-papayas-swim.md b/.changeset/pink-papayas-swim.md new file mode 100644 index 00000000000..1e6a2cacd06 --- /dev/null +++ b/.changeset/pink-papayas-swim.md @@ -0,0 +1,5 @@ +--- +"chainlink": minor +--- + +#internal Add BatchGetLatestValues to ChainReader diff --git a/core/capabilities/targets/mocks/chain_reader.go b/core/capabilities/targets/mocks/chain_reader.go index ea5ba8186e7..0f88da75bcf 100644 --- a/core/capabilities/targets/mocks/chain_reader.go +++ b/core/capabilities/targets/mocks/chain_reader.go @@ -17,6 +17,36 @@ type ChainReader struct { mock.Mock } +// BatchGetLatestValues provides a mock function with given fields: ctx, request +func (_m *ChainReader) BatchGetLatestValues(ctx context.Context, request types.BatchGetLatestValuesRequest) (types.BatchGetLatestValuesResult, error) { + ret := _m.Called(ctx, request) + + if len(ret) == 0 { + panic("no return value specified for BatchGetLatestValues") + } + + var r0 types.BatchGetLatestValuesResult + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, types.BatchGetLatestValuesRequest) (types.BatchGetLatestValuesResult, error)); ok { + return rf(ctx, request) + } + if rf, ok := ret.Get(0).(func(context.Context, types.BatchGetLatestValuesRequest) types.BatchGetLatestValuesResult); ok { + r0 = rf(ctx, request) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(types.BatchGetLatestValuesResult) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, types.BatchGetLatestValuesRequest) error); ok { + r1 = rf(ctx, request) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // Bind provides a mock function with given fields: ctx, bindings func (_m *ChainReader) Bind(ctx context.Context, bindings []types.BoundContract) error { ret := _m.Called(ctx, bindings) diff --git a/core/scripts/go.mod b/core/scripts/go.mod index 43d3909cd51..e382089d622 100644 --- a/core/scripts/go.mod +++ b/core/scripts/go.mod @@ -22,7 +22,7 @@ require ( github.com/prometheus/client_golang v1.17.0 github.com/shopspring/decimal v1.3.1 github.com/smartcontractkit/chainlink-automation v1.0.4 - github.com/smartcontractkit/chainlink-common v0.1.7-0.20240710165532-ade916a95858 + github.com/smartcontractkit/chainlink-common v0.1.7-0.20240712101200-5b11e6cc6e86 github.com/smartcontractkit/chainlink/v2 v2.0.0-00010101000000-000000000000 github.com/smartcontractkit/libocr v0.0.0-20240702141926-063ceef8c42e github.com/spf13/cobra v1.8.0 @@ -273,7 +273,7 @@ require ( github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240710121324-3ed288aa9b45 // indirect github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240702145022-37a2c3a742d1 // indirect github.com/smartcontractkit/chainlink-feeds v0.0.0-20240710170203-5b41615da827 // indirect - github.com/smartcontractkit/chainlink-solana v1.0.3-0.20240710170818-eccca28888e5 // indirect + github.com/smartcontractkit/chainlink-solana v1.0.3-0.20240712132946-267a37c5ac6e // indirect github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20240709043547-03612098f799 // indirect github.com/smartcontractkit/tdh2/go/ocr2/decryptionplugin v0.0.0-20230906073235-9e478e5e19f1 // indirect github.com/smartcontractkit/tdh2/go/tdh2 v0.0.0-20230906073235-9e478e5e19f1 // indirect diff --git a/core/scripts/go.sum b/core/scripts/go.sum index 7a8985269d3..40474b4de5b 100644 --- a/core/scripts/go.sum +++ b/core/scripts/go.sum @@ -1178,16 +1178,16 @@ github.com/smartcontractkit/chain-selectors v1.0.10 h1:t9kJeE6B6G+hKD0GYR4kGJSCq github.com/smartcontractkit/chain-selectors v1.0.10/go.mod h1:d4Hi+E1zqjy9HqMkjBE5q1vcG9VGgxf5VxiRHfzi2kE= github.com/smartcontractkit/chainlink-automation v1.0.4 h1:iyW181JjKHLNMnDleI8umfIfVVlwC7+n5izbLSFgjw8= github.com/smartcontractkit/chainlink-automation v1.0.4/go.mod h1:u4NbPZKJ5XiayfKHD/v3z3iflQWqvtdhj13jVZXj/cM= -github.com/smartcontractkit/chainlink-common v0.1.7-0.20240710165532-ade916a95858 h1:nwAe0iA4JN7/oEFz/N2lkTpNh6rxlzbK7g8Els/dDew= -github.com/smartcontractkit/chainlink-common v0.1.7-0.20240710165532-ade916a95858/go.mod h1:fh9eBbrReCmv31bfz52ENCAMa7nTKQbdhb2B3+S2VGo= +github.com/smartcontractkit/chainlink-common v0.1.7-0.20240712101200-5b11e6cc6e86 h1:TYALsn6Jue7xCIcXMel+Ow0SuudVfOUAz6iups946Yw= +github.com/smartcontractkit/chainlink-common v0.1.7-0.20240712101200-5b11e6cc6e86/go.mod h1:fh9eBbrReCmv31bfz52ENCAMa7nTKQbdhb2B3+S2VGo= github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240710121324-3ed288aa9b45 h1:NBQLtqk8zsyY4qTJs+NElI3aDFTcAo83JHvqD04EvB0= github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240710121324-3ed288aa9b45/go.mod h1:LV0h7QBQUpoC2UUi6TcUvcIFm1xjP/DtEcqV8+qeLUs= github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240702145022-37a2c3a742d1 h1:dsTmitRaVizHxoYFoGz4+y/zVa8XnvKUiTaZdx+6t9M= github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240702145022-37a2c3a742d1/go.mod h1:6DgCnHMGdBaIh0bLs1dK0MtdeMZfeNhc/nvBUN6KIUg= github.com/smartcontractkit/chainlink-feeds v0.0.0-20240710170203-5b41615da827 h1:BCHu4pNP6arrcHLEWx61XjLaonOd2coQNyL0NTUcaMc= github.com/smartcontractkit/chainlink-feeds v0.0.0-20240710170203-5b41615da827/go.mod h1:OPX+wC2TWQsyLNpR7daMt2vMpmsNcoBxbZyGTHr6tiA= -github.com/smartcontractkit/chainlink-solana v1.0.3-0.20240710170818-eccca28888e5 h1:gktRCdvNp0tczyqb79JaQOloa/elDS6t33qjAS9SrEU= -github.com/smartcontractkit/chainlink-solana v1.0.3-0.20240710170818-eccca28888e5/go.mod h1:aJUY4hdo1g942mhlPX9Z4FWe5ldEyWvsWSNf7frh7yU= +github.com/smartcontractkit/chainlink-solana v1.0.3-0.20240712132946-267a37c5ac6e h1:PzwzlHNv1YbJ6ZIdl/pIFRoOuOS4V4WLvjZvFUnZFL4= +github.com/smartcontractkit/chainlink-solana v1.0.3-0.20240712132946-267a37c5ac6e/go.mod h1:hsFhop+SlQHKD+DEFjZrMJmbauT1A/wvtZIeeo4PxFU= github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20240709043547-03612098f799 h1:HyLTySm7BR+oNfZqDTkVJ25wnmcTtxBBD31UkFL+kEM= github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20240709043547-03612098f799/go.mod h1:UVFRacRkP7O7TQAzFmR52v5mUlxf+G1ovMlCQAB/cHU= github.com/smartcontractkit/go-plugin v0.0.0-20240208201424-b3b91517de16 h1:TFe+FvzxClblt6qRfqEhUfa4kFQx5UobuoFGO2W4mMo= diff --git a/core/services/relay/evm/batch_caller.go b/core/services/relay/evm/batch_caller.go new file mode 100644 index 00000000000..7288a33894b --- /dev/null +++ b/core/services/relay/evm/batch_caller.go @@ -0,0 +1,312 @@ +package evm + +import ( + "context" + "fmt" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/rpc" + "github.com/pkg/errors" + "golang.org/x/sync/errgroup" + + "github.com/smartcontractkit/chainlink-common/pkg/types" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" + "github.com/smartcontractkit/chainlink/v2/core/logger" +) + +var errEmptyOutput = errors.New("rpc call output is empty (make sure that the contract method exists and rpc is healthy)") + +const ( + // DefaultRpcBatchSizeLimit defines the maximum number of rpc requests to be included in a batch. + DefaultRpcBatchSizeLimit = 100 + + // DefaultRpcBatchBackOffMultiplier defines the rate of reducing the batch size limit for retried calls. + // For example if limit is 20 and multiplier is 4: + // 1. 20 + // 2. 20/4 = 5 + // 3. 5/4 = 1 + DefaultRpcBatchBackOffMultiplier = 5 + + // DefaultMaxParallelRpcCalls defines the default maximum number of individual in-parallel rpc calls. + DefaultMaxParallelRpcCalls = 10 +) + +// BatchResult is organised by contracts names, key is contract name. +type BatchResult map[string]ContractResults +type ContractResults []MethodCallResult +type MethodCallResult struct { + MethodName string + ReturnValue any + Err error +} + +type BatchCall []Call +type Call struct { + ContractAddress common.Address + ContractName, MethodName string + Params, ReturnVal any +} + +func (c BatchCall) String() string { + callString := "" + for _, call := range c { + callString += fmt.Sprintf("%s\n", call.String()) + } + return callString +} + +// Implement the String method for the Call struct +func (c Call) String() string { + return fmt.Sprintf("contractAddress: %s, contractName: %s, method: %s, params: %+v returnValType: %T", + c.ContractAddress.Hex(), c.ContractName, c.MethodName, c.Params, c.ReturnVal) +} + +//go:generate mockery --quiet --name BatchCaller --output ./rpclibmocks --outpkg rpclibmocks --filename batch_caller.go --case=underscore +type BatchCaller interface { + // BatchCall executes all the provided BatchRequest and returns the results in the same order + // of the calls. Pass blockNumber=0 to use the latest block. + BatchCall(ctx context.Context, blockNumber uint64, batchRequests BatchCall) (BatchResult, error) +} + +// dynamicLimitedBatchCaller makes batched rpc calls and perform retries by reducing the batch size on each retry. +type dynamicLimitedBatchCaller struct { + bc *defaultEvmBatchCaller +} + +func NewDynamicLimitedBatchCaller(lggr logger.Logger, codec types.Codec, evmClient client.Client, batchSizeLimit, backOffMultiplier, parallelRpcCallsLimit uint) BatchCaller { + return &dynamicLimitedBatchCaller{ + bc: newDefaultEvmBatchCaller(lggr, evmClient, codec, batchSizeLimit, backOffMultiplier, parallelRpcCallsLimit), + } +} + +func (c *dynamicLimitedBatchCaller) BatchCall(ctx context.Context, blockNumber uint64, reqs BatchCall) (BatchResult, error) { + return c.bc.batchCallDynamicLimitRetries(ctx, blockNumber, reqs) +} + +type defaultEvmBatchCaller struct { + lggr logger.Logger + evmClient client.Client + codec types.Codec + batchSizeLimit uint + parallelRpcCallsLimit uint + backOffMultiplier uint +} + +// NewDefaultEvmBatchCaller returns a new batch caller instance. +// batchCallLimit defines the maximum number of calls for BatchCallLimit method, pass 0 to keep the default. +// backOffMultiplier defines the back-off strategy for retries on BatchCallDynamicLimitRetries method, pass 0 to keep the default. +func newDefaultEvmBatchCaller( + lggr logger.Logger, evmClient client.Client, codec types.Codec, batchSizeLimit, backOffMultiplier, parallelRpcCallsLimit uint, +) *defaultEvmBatchCaller { + batchSize := uint(DefaultRpcBatchSizeLimit) + if batchSizeLimit > 0 { + batchSize = batchSizeLimit + } + + multiplier := uint(DefaultRpcBatchBackOffMultiplier) + if backOffMultiplier > 0 { + multiplier = backOffMultiplier + } + + parallelRpcCalls := uint(DefaultMaxParallelRpcCalls) + if parallelRpcCallsLimit > 0 { + parallelRpcCalls = parallelRpcCallsLimit + } + + return &defaultEvmBatchCaller{ + lggr: lggr, + evmClient: evmClient, + codec: codec, + batchSizeLimit: batchSize, + parallelRpcCallsLimit: parallelRpcCalls, + backOffMultiplier: multiplier, + } +} + +func (c *defaultEvmBatchCaller) batchCall(ctx context.Context, blockNumber uint64, batchCall BatchCall) ([]dataAndErr, error) { + if len(batchCall) == 0 { + return nil, nil + } + + packedOutputs := make([]string, len(batchCall)) + rpcBatchCalls := make([]rpc.BatchElem, len(batchCall)) + for i, call := range batchCall { + data, err := c.codec.Encode(ctx, call.Params, WrapItemType(call.ContractName, call.MethodName, true)) + if err != nil { + return nil, err + } + + blockNumStr := "latest" + if blockNumber > 0 { + blockNumStr = hexutil.EncodeBig(big.NewInt(0).SetUint64(blockNumber)) + } + + rpcBatchCalls[i] = rpc.BatchElem{ + Method: "eth_call", + Args: []any{ + map[string]interface{}{ + "from": common.Address{}, + "to": call.ContractAddress, + "data": data, + }, + blockNumStr, + }, + Result: &packedOutputs[i], + } + } + + if err := c.evmClient.BatchCallContext(ctx, rpcBatchCalls); err != nil { + return nil, fmt.Errorf("batch call context: %w", err) + } + + results := make([]dataAndErr, len(batchCall)) + for i, call := range batchCall { + results[i] = dataAndErr{ + contractName: call.ContractName, + methodName: call.MethodName, + returnVal: call.ReturnVal, + } + + if rpcBatchCalls[i].Error != nil { + results[i].err = rpcBatchCalls[i].Error + continue + } + + if packedOutputs[i] == "" { + // Some RPCs instead of returning "0x" are returning an empty string. + // We are overriding this behaviour for consistent handling of this scenario. + packedOutputs[i] = "0x" + } + + b, err := hexutil.Decode(packedOutputs[i]) + if err != nil { + return nil, fmt.Errorf("decode result %s: packedOutputs %s: %w", call, packedOutputs[i], err) + } + + if err = c.codec.Decode(ctx, b, call.ReturnVal, WrapItemType(call.ContractName, call.MethodName, false)); err != nil { + if len(b) == 0 { + results[i].err = fmt.Errorf("unpack result %s: %s: %w", call, err.Error(), errEmptyOutput) + } else { + results[i].err = fmt.Errorf("unpack result %s: %w", call, err) + } + continue + } + results[i].returnVal = call.ReturnVal + } + + return results, nil +} + +func (c *defaultEvmBatchCaller) batchCallDynamicLimitRetries(ctx context.Context, blockNumber uint64, calls BatchCall) (BatchResult, error) { + lim := c.batchSizeLimit + // Limit the batch size to the number of calls + if uint(len(calls)) < lim { + lim = uint(len(calls)) + } + for { + results, err := c.batchCallLimit(ctx, blockNumber, calls, lim) + if err == nil { + return results, nil + } + + if lim <= 1 { + return nil, errors.Wrapf(err, "calls %+v", calls) + } + + newLim := lim / c.backOffMultiplier + if newLim == 0 || newLim == lim { + newLim = 1 + } + lim = newLim + c.lggr.Errorf("retrying batch call with %d calls and %d limit that failed with error=%s", + len(calls), lim, err) + } +} + +type dataAndErr struct { + contractName, methodName string + returnVal any + err error +} + +func (c *defaultEvmBatchCaller) batchCallLimit(ctx context.Context, blockNumber uint64, calls BatchCall, batchSizeLimit uint) (BatchResult, error) { + if batchSizeLimit <= 0 { + res, err := c.batchCall(ctx, blockNumber, calls) + return convertToBatchResult(res), err + } + + type job struct { + blockNumber uint64 + calls BatchCall + results []dataAndErr + } + + jobs := make([]job, 0) + for i := 0; i < len(calls); i += int(batchSizeLimit) { + idxFrom := i + idxTo := idxFrom + int(batchSizeLimit) + if idxTo > len(calls) { + idxTo = len(calls) + } + jobs = append(jobs, job{blockNumber: blockNumber, calls: calls[idxFrom:idxTo], results: nil}) + } + + if c.parallelRpcCallsLimit > 1 { + eg := new(errgroup.Group) + eg.SetLimit(int(c.parallelRpcCallsLimit)) + for jobIdx := range jobs { + jobIdx := jobIdx + eg.Go(func() error { + res, err := c.batchCall(ctx, jobs[jobIdx].blockNumber, jobs[jobIdx].calls) + if err != nil { + return err + } + jobs[jobIdx].results = res + return nil + }) + } + if err := eg.Wait(); err != nil { + return nil, err + } + } else { + var err error + for jobIdx := range jobs { + jobs[jobIdx].results, err = c.batchCall(ctx, jobs[jobIdx].blockNumber, jobs[jobIdx].calls) + if err != nil { + return nil, err + } + } + } + + var results []dataAndErr + for _, jb := range jobs { + results = append(results, jb.results...) + } + + return convertToBatchResult(results), nil +} + +func convertToBatchResult(data []dataAndErr) BatchResult { + if data == nil { + return nil + } + + batchResult := make(BatchResult) + for _, d := range data { + methodCall := MethodCallResult{ + MethodName: d.methodName, + ReturnValue: d.returnVal, + Err: d.err, + } + + if _, exists := batchResult[d.contractName]; !exists { + batchResult[d.contractName] = ContractResults{} + } + + batchResult[d.contractName] = append(batchResult[d.contractName], methodCall) + } + + return batchResult +} diff --git a/core/services/relay/evm/batch_caller_test.go b/core/services/relay/evm/batch_caller_test.go new file mode 100644 index 00000000000..94614aa4d64 --- /dev/null +++ b/core/services/relay/evm/batch_caller_test.go @@ -0,0 +1,193 @@ +package evm_test + +import ( + "encoding/hex" + "fmt" + "math/big" + "testing" + + "github.com/cometbft/cometbft/libs/rand" + "github.com/ethereum/go-ethereum/rpc" + "github.com/pkg/errors" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + + chainmocks "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client/mocks" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" + "github.com/smartcontractkit/chainlink/v2/core/logger" + "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm" + "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mocks" + evmtypes "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/types" +) + +//go:generate mockery --quiet --name Codec --srcpkg=github.com/smartcontractkit/chainlink-common/pkg/types --output ./mocks/ --case=underscore +func TestDefaultEvmBatchCaller_BatchCallDynamicLimit(t *testing.T) { + testCases := []struct { + name string + maxBatchSize uint + backOffMultiplier uint + numCalls int + expectedBatchSizesOnEachRetry []int + }{ + { + name: "defaults", + maxBatchSize: evm.DefaultRpcBatchSizeLimit, + backOffMultiplier: evm.DefaultRpcBatchBackOffMultiplier, + numCalls: 200, + expectedBatchSizesOnEachRetry: []int{100, 20, 4, 1}, + }, + { + name: "base simple scenario", + maxBatchSize: 20, + backOffMultiplier: 2, + numCalls: 100, + expectedBatchSizesOnEachRetry: []int{20, 10, 5, 2, 1}, + }, + { + name: "remainder", + maxBatchSize: 99, + backOffMultiplier: 5, + numCalls: 100, + expectedBatchSizesOnEachRetry: []int{99, 19, 3, 1}, + }, + { + name: "large back off multiplier", + maxBatchSize: 20, + backOffMultiplier: 18, + numCalls: 100, + expectedBatchSizesOnEachRetry: []int{20, 1}, + }, + { + name: "back off equal to batch size", + maxBatchSize: 20, + backOffMultiplier: 20, + numCalls: 100, + expectedBatchSizesOnEachRetry: []int{20, 1}, + }, + { + name: "back off larger than batch size", + maxBatchSize: 20, + backOffMultiplier: 220, + numCalls: 100, + expectedBatchSizesOnEachRetry: []int{20, 1}, + }, + { + name: "back off 1", + maxBatchSize: 20, + backOffMultiplier: 1, + numCalls: 100, + expectedBatchSizesOnEachRetry: []int{20, 1}, + }, + } + + mockCodec := mocks.NewCodec(t) + mockCodec.On("Encode", mock.Anything, mock.Anything, mock.Anything).Return([]byte{}, nil) + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + batchSizes := make([]int, 0) + ec := chainmocks.NewClient(t) + ec.On("BatchCallContext", mock.Anything, mock.Anything).Run(func(args mock.Arguments) { + evmCalls := args.Get(1).([]rpc.BatchElem) + batchSizes = append(batchSizes, len(evmCalls)) + }).Return(errors.New("some error")) + + calls := make(evm.BatchCall, tc.numCalls) + for i := range calls { + calls[i] = evm.Call{} + } + + bc := evm.NewDynamicLimitedBatchCaller(logger.TestLogger(t), mockCodec, ec, tc.maxBatchSize, tc.backOffMultiplier, 1) + _, _ = bc.BatchCall(testutils.Context(t), 123, calls) + assert.Equal(t, tc.expectedBatchSizesOnEachRetry, batchSizes) + }) + } +} + +func TestDefaultEvmBatchCaller_batchCallLimit(t *testing.T) { + ctx := testutils.Context(t) + testCases := []struct { + numCalls uint + batchSize uint + parallelRpcCallsLimit uint + }{ + {numCalls: 100, batchSize: 10, parallelRpcCallsLimit: 5}, + {numCalls: 10, batchSize: 100, parallelRpcCallsLimit: 10}, + {numCalls: 1, batchSize: 100, parallelRpcCallsLimit: 10}, + {numCalls: 1000, batchSize: 10, parallelRpcCallsLimit: 2}, + {numCalls: rand.Uint() % 1000, batchSize: rand.Uint() % 500, parallelRpcCallsLimit: rand.Uint() % 500}, + } + + type MethodParam struct { + A uint64 + } + type MethodReturn struct { + B uint64 + } + paramABI := `[{"type":"uint64","name":"A"}]` + returnABI := `[{"type":"uint64","name":"B"}]` + codecConfig := evmtypes.CodecConfig{Configs: map[string]evmtypes.ChainCodecConfig{}} + + for i, tc := range testCases { + t.Run(fmt.Sprintf("%v", tc), func(t *testing.T) { + ec := chainmocks.NewClient(t) + calls := make(evm.BatchCall, tc.numCalls) + for j := range calls { + contractName := fmt.Sprintf("testCase_%d", i) + methodName := fmt.Sprintf("method_%d", j) + codecConfig.Configs[fmt.Sprintf("params.%s.%s", contractName, methodName)] = evmtypes.ChainCodecConfig{TypeABI: paramABI} + codecConfig.Configs[fmt.Sprintf("return.%s.%s", contractName, methodName)] = evmtypes.ChainCodecConfig{TypeABI: returnABI} + + params := MethodParam{A: uint64(j)} + var returnVal MethodReturn + calls[j] = evm.Call{ + ContractName: contractName, + MethodName: methodName, + Params: ¶ms, + ReturnVal: &returnVal, + } + } + + ec.On("BatchCallContext", mock.Anything, mock.Anything). + Run(func(args mock.Arguments) { + evmCalls := args.Get(1).([]rpc.BatchElem) + for i := range evmCalls { + arg := evmCalls[i].Args[0].(map[string]interface{})["data"].([]uint8) + bytes, err := hex.DecodeString(fmt.Sprintf("%x", arg)) + require.NoError(t, err) + str, isOk := evmCalls[i].Result.(*string) + require.True(t, isOk) + *str = fmt.Sprintf("0x%064x", new(big.Int).SetBytes(bytes[24:]).Uint64()) + } + }).Return(nil) + + testCodec, err := evm.NewCodec(codecConfig) + require.NoError(t, err) + bc := evm.NewDynamicLimitedBatchCaller(logger.TestLogger(t), testCodec, ec, tc.batchSize, 99999, tc.parallelRpcCallsLimit) + + // make the call and make sure the results are there + results, err := bc.BatchCall(ctx, 0, calls) + require.NoError(t, err) + for _, call := range calls { + contractResults, ok := results[call.ContractName] + if !ok { + t.Errorf("missing contract name %s", call.ContractName) + } + hasResult := false + for j, result := range contractResults { + if hasResult = result.MethodName == call.MethodName; hasResult { + require.NoError(t, result.Err) + resNum, isOk := result.ReturnValue.(*MethodReturn) + require.True(t, isOk) + require.Equal(t, uint64(j), resNum.B) + break + } + } + if !hasResult { + t.Errorf("missing method name %s", call.MethodName) + } + } + }) + } +} diff --git a/core/services/relay/evm/bindings.go b/core/services/relay/evm/bindings.go index 9ad73c01926..4d4760db52c 100644 --- a/core/services/relay/evm/bindings.go +++ b/core/services/relay/evm/bindings.go @@ -9,11 +9,15 @@ import ( ) // bindings manage all contract bindings, key is contract name. -type bindings map[string]*contractBinding + +type bindings struct { + contractBindings map[string]*contractBinding + BatchCaller +} func (b bindings) GetReadBinding(contractName, readName string) (readBinding, error) { // GetReadBindings should only be called after Chain Reader init. - cb, cbExists := b[contractName] + cb, cbExists := b.contractBindings[contractName] if !cbExists { return nil, fmt.Errorf("%w: no contract named %s", commontypes.ErrInvalidType, contractName) } @@ -27,13 +31,13 @@ func (b bindings) GetReadBinding(contractName, readName string) (readBinding, er // AddReadBinding adds read bindings. Calling this outside of Chain Reader init is not thread safe. func (b bindings) AddReadBinding(contractName, readName string, rb readBinding) { - cb, cbExists := b[contractName] + cb, cbExists := b.contractBindings[contractName] if !cbExists { cb = &contractBinding{ name: contractName, readBindings: make(map[string]readBinding), } - b[contractName] = cb + b.contractBindings[contractName] = cb } cb.readBindings[readName] = rb } @@ -42,7 +46,7 @@ func (b bindings) AddReadBinding(contractName, readName string, rb readBinding) // Bind also registers the common contract polling filter and eventBindings polling filters. func (b bindings) Bind(ctx context.Context, lp logpoller.LogPoller, boundContracts []commontypes.BoundContract) error { for _, bc := range boundContracts { - cb, cbExists := b[bc.Name] + cb, cbExists := b.contractBindings[bc.Name] if !cbExists { return fmt.Errorf("%w: no contract named %s", commontypes.ErrInvalidConfig, bc.Name) } @@ -60,8 +64,54 @@ func (b bindings) Bind(ctx context.Context, lp logpoller.LogPoller, boundContrac return nil } +func (b bindings) BatchGetLatestValues(ctx context.Context, request commontypes.BatchGetLatestValuesRequest) (commontypes.BatchGetLatestValuesResult, error) { + var batchCall BatchCall + toChainAgnosticMethodName := make(map[string]string) + for contractName, contractBatch := range request { + cb := b.contractBindings[contractName] + for i := range contractBatch { + req := contractBatch[i] + switch rb := cb.readBindings[req.ReadName].(type) { + case *methodBinding: + toChainAgnosticMethodName[rb.method] = req.ReadName + batchCall = append(batchCall, Call{ + ContractAddress: rb.address, + ContractName: cb.name, + MethodName: rb.method, + Params: req.Params, + ReturnVal: req.ReturnVal, + }) + // results here will have chain specific method names. + case *eventBinding: + // TODO Use FilteredLogs to batch? This isn't a priority right now, but should get implemented at some point. + return nil, fmt.Errorf("%w: events are not yet supported in batch get latest values", commontypes.ErrInvalidType) + default: + return nil, fmt.Errorf("%w: missing read binding type for contract: %s read: %s", commontypes.ErrInvalidType, contractName, req.ReadName) + } + } + } + + results, err := b.BatchCall(ctx, 0, batchCall) + if err != nil { + return nil, err + } + + // reconstruct results from batchCall and filteredLogs into common type while maintaining order from request. + batchGetLatestValuesResults := make(commontypes.BatchGetLatestValuesResult) + for contractName, contractResult := range results { + batchGetLatestValuesResults[contractName] = commontypes.ContractBatchResults{} + for _, methodResult := range contractResult { + brr := commontypes.BatchReadResult{ReadName: toChainAgnosticMethodName[methodResult.MethodName]} + brr.SetResult(methodResult.ReturnValue, methodResult.Err) + batchGetLatestValuesResults[contractName] = append(batchGetLatestValuesResults[contractName], brr) + } + } + + return batchGetLatestValuesResults, err +} + func (b bindings) ForEach(ctx context.Context, fn func(context.Context, *contractBinding) error) error { - for _, cb := range b { + for _, cb := range b.contractBindings { if err := fn(ctx, cb); err != nil { return err } diff --git a/core/services/relay/evm/chain_reader.go b/core/services/relay/evm/chain_reader.go index 0576cc345d1..d84c2f00a9c 100644 --- a/core/services/relay/evm/chain_reader.go +++ b/core/services/relay/evm/chain_reader.go @@ -32,13 +32,13 @@ type ChainReaderService interface { } type chainReader struct { - lggr logger.Logger - ht logpoller.HeadTracker - lp logpoller.LogPoller - client evmclient.Client - contractBindings bindings - parsed *ParsedTypes - codec commontypes.RemoteCodec + lggr logger.Logger + ht logpoller.HeadTracker + lp logpoller.LogPoller + client evmclient.Client + parsed *ParsedTypes + bindings + codec commontypes.RemoteCodec commonservices.StateMachine } @@ -49,12 +49,12 @@ var _ commontypes.ContractTypeProvider = &chainReader{} // Note that the ChainReaderService returned does not support anonymous events. func NewChainReaderService(ctx context.Context, lggr logger.Logger, lp logpoller.LogPoller, ht logpoller.HeadTracker, client evmclient.Client, config types.ChainReaderConfig) (ChainReaderService, error) { cr := &chainReader{ - lggr: lggr.Named("ChainReader"), - ht: ht, - lp: lp, - client: client, - contractBindings: bindings{}, - parsed: &ParsedTypes{EncoderDefs: map[string]types.CodecEntry{}, DecoderDefs: map[string]types.CodecEntry{}}, + lggr: lggr.Named("ChainReader"), + ht: ht, + lp: lp, + client: client, + bindings: bindings{contractBindings: make(map[string]*contractBinding)}, + parsed: &ParsedTypes{EncoderDefs: map[string]types.CodecEntry{}, DecoderDefs: map[string]types.CodecEntry{}}, } var err error @@ -66,7 +66,16 @@ func NewChainReaderService(ctx context.Context, lggr logger.Logger, lp logpoller return nil, err } - err = cr.contractBindings.ForEach(ctx, func(c context.Context, cb *contractBinding) error { + cr.bindings.BatchCaller = NewDynamicLimitedBatchCaller( + cr.lggr, + cr.codec, + cr.client, + DefaultRpcBatchSizeLimit, + DefaultRpcBatchBackOffMultiplier, + DefaultMaxParallelRpcCalls, + ) + + err = cr.bindings.ForEach(ctx, func(c context.Context, cb *contractBinding) error { for _, rb := range cb.readBindings { rb.SetCodec(cr.codec) } @@ -119,7 +128,7 @@ func (cr *chainReader) init(chainContractReaders map[string]types.ChainContractR return err } } - cr.contractBindings[contractName].pollingFilter = chainContractReader.PollingFilter.ToLPFilter(eventSigsForContractFilter) + cr.bindings.contractBindings[contractName].pollingFilter = chainContractReader.PollingFilter.ToLPFilter(eventSigsForContractFilter) } return nil } @@ -129,7 +138,7 @@ func (cr *chainReader) Name() string { return cr.lggr.Name() } // Start registers polling filters if contracts are already bound. func (cr *chainReader) Start(ctx context.Context) error { return cr.StartOnce("ChainReader", func() error { - return cr.contractBindings.ForEach(ctx, func(c context.Context, cb *contractBinding) error { + return cr.bindings.ForEach(ctx, func(c context.Context, cb *contractBinding) error { for _, rb := range cb.readBindings { if err := rb.Register(ctx); err != nil { return err @@ -145,7 +154,7 @@ func (cr *chainReader) Close() error { return cr.StopOnce("ChainReader", func() error { ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() - return cr.contractBindings.ForEach(ctx, func(c context.Context, cb *contractBinding) error { + return cr.bindings.ForEach(ctx, func(c context.Context, cb *contractBinding) error { for _, rb := range cb.readBindings { if err := rb.Unregister(ctx); err != nil { return err @@ -163,7 +172,7 @@ func (cr *chainReader) HealthReport() map[string]error { } func (cr *chainReader) GetLatestValue(ctx context.Context, contractName, method string, confidenceLevel primitives.ConfidenceLevel, params, returnVal any) error { - b, err := cr.contractBindings.GetReadBinding(contractName, method) + b, err := cr.bindings.GetReadBinding(contractName, method) if err != nil { return err } @@ -171,12 +180,16 @@ func (cr *chainReader) GetLatestValue(ctx context.Context, contractName, method return b.GetLatestValue(ctx, confidenceLevel, params, returnVal) } +func (cr *chainReader) BatchGetLatestValues(ctx context.Context, request commontypes.BatchGetLatestValuesRequest) (commontypes.BatchGetLatestValuesResult, error) { + return cr.bindings.BatchGetLatestValues(ctx, request) +} + func (cr *chainReader) Bind(ctx context.Context, bindings []commontypes.BoundContract) error { - return cr.contractBindings.Bind(ctx, cr.lp, bindings) + return cr.bindings.Bind(ctx, cr.lp, bindings) } func (cr *chainReader) QueryKey(ctx context.Context, contractName string, filter query.KeyFilter, limitAndSort query.LimitAndSort, sequenceDataType any) ([]commontypes.Sequence, error) { - b, err := cr.contractBindings.GetReadBinding(contractName, filter.Key) + b, err := cr.bindings.GetReadBinding(contractName, filter.Key) if err != nil { return nil, err } @@ -210,7 +223,7 @@ func (cr *chainReader) addMethod( return err } - cr.contractBindings.AddReadBinding(contractName, methodName, &methodBinding{ + cr.bindings.AddReadBinding(contractName, methodName, &methodBinding{ lggr: cr.lggr, contractName: contractName, method: methodName, @@ -219,11 +232,11 @@ func (cr *chainReader) addMethod( confirmationsMapping: confirmations, }) - if err := cr.addEncoderDef(contractName, methodName, method.Inputs, method.ID, chainReaderDefinition); err != nil { + if err := cr.addEncoderDef(contractName, methodName, method.Inputs, method.ID, chainReaderDefinition.InputModifications); err != nil { return err } - return cr.addDecoderDef(contractName, methodName, method.Outputs, chainReaderDefinition) + return cr.addDecoderDef(contractName, methodName, method.Outputs, chainReaderDefinition.OutputModifications) } func (cr *chainReader) addEvent(contractName, eventName string, a abi.ABI, chainReaderDefinition types.ChainReaderDefinition) error { @@ -247,7 +260,7 @@ func (cr *chainReader) addEvent(contractName, eventName string, a abi.ABI, chain } // Encoder def's codec won't be used to encode, only for its type as input for GetLatestValue - if err := cr.addEncoderDef(contractName, eventName, filterArgs, nil, chainReaderDefinition); err != nil { + if err := cr.addEncoderDef(contractName, eventName, filterArgs, nil, chainReaderDefinition.InputModifications); err != nil { return err } @@ -289,9 +302,9 @@ func (cr *chainReader) addEvent(contractName, eventName string, a abi.ABI, chain cr.addQueryingReadBindings(contractName, eventDefinitions.GenericTopicNames, event.Inputs, eb) } - cr.contractBindings.AddReadBinding(contractName, eventName, eb) + cr.bindings.AddReadBinding(contractName, eventName, eb) - return cr.addDecoderDef(contractName, eventName, event.Inputs, chainReaderDefinition) + return cr.addDecoderDef(contractName, eventName, event.Inputs, chainReaderDefinition.OutputModifications) } // addQueryingReadBindings reuses the eventBinding and maps it to topic and dataWord keys used for QueryKey. @@ -305,12 +318,12 @@ func (cr *chainReader) addQueryingReadBindings(contractName string, genericTopic Index: uint64(topicIndex), } } - cr.contractBindings.AddReadBinding(contractName, genericTopicName, eb) + cr.bindings.AddReadBinding(contractName, genericTopicName, eb) } // add data word readBindings for QueryKey for genericDataWordName := range eb.eventDataWords { - cr.contractBindings.AddReadBinding(contractName, genericDataWordName, eb) + cr.bindings.AddReadBinding(contractName, genericDataWordName, eb) } } @@ -339,9 +352,9 @@ func verifyEventIndexedInputsUsed(eventName string, inputFields []string, indexA return nil } -func (cr *chainReader) addEncoderDef(contractName, itemType string, args abi.Arguments, prefix []byte, chainReaderDefinition types.ChainReaderDefinition) error { +func (cr *chainReader) addEncoderDef(contractName, itemType string, args abi.Arguments, prefix []byte, inputModifications codec.ModifiersConfig) error { // ABI.Pack prepends the method.ID to the encodings, we'll need the encoder to do the same. - inputMod, err := chainReaderDefinition.InputModifications.ToModifier(DecoderHooks...) + inputMod, err := inputModifications.ToModifier(DecoderHooks...) if err != nil { return err } @@ -355,8 +368,8 @@ func (cr *chainReader) addEncoderDef(contractName, itemType string, args abi.Arg return nil } -func (cr *chainReader) addDecoderDef(contractName, itemType string, outputs abi.Arguments, def types.ChainReaderDefinition) error { - mod, err := def.OutputModifications.ToModifier(DecoderHooks...) +func (cr *chainReader) addDecoderDef(contractName, itemType string, outputs abi.Arguments, outputModifications codec.ModifiersConfig) error { + mod, err := outputModifications.ToModifier(DecoderHooks...) if err != nil { return err } diff --git a/core/services/relay/evm/evmtesting/chain_reader_interface_tester.go b/core/services/relay/evm/evmtesting/chain_reader_interface_tester.go index ee688e2e88e..4474f054dbc 100644 --- a/core/services/relay/evm/evmtesting/chain_reader_interface_tester.go +++ b/core/services/relay/evm/evmtesting/chain_reader_interface_tester.go @@ -50,15 +50,15 @@ type EVMChainReaderInterfaceTesterHelper[T TestingT[T]] interface { } type EVMChainReaderInterfaceTester[T TestingT[T]] struct { - Helper EVMChainReaderInterfaceTesterHelper[T] - client client.Client - address string - address2 string - chainConfig types.ChainReaderConfig - auth *bind.TransactOpts - evmTest *chain_reader_tester.ChainReaderTester - cr evm.ChainReaderService - dirtyContracts bool + Helper EVMChainReaderInterfaceTesterHelper[T] + client client.Client + address string + address2 string + contractTesters map[string]*chain_reader_tester.ChainReaderTester + chainConfig types.ChainReaderConfig + auth *bind.TransactOpts + cr evm.ChainReaderService + dirtyContracts bool } func (it *EVMChainReaderInterfaceTester[T]) Setup(t T) { @@ -70,7 +70,7 @@ func (it *EVMChainReaderInterfaceTester[T]) Setup(t T) { it.cr = nil if it.dirtyContracts { - it.evmTest = nil + it.contractTesters = nil } }) @@ -84,6 +84,13 @@ func (it *EVMChainReaderInterfaceTester[T]) Setup(t T) { testStruct := CreateTestStruct[T](0, it) + methodTakingLatestParamsReturningTestStructConfig := types.ChainReaderDefinition{ + ChainSpecificName: "getElementAtIndex", + OutputModifications: codec.ModifiersConfig{ + &codec.RenameModifierConfig{Fields: map[string]string{"NestedStruct.Inner.IntVal": "I"}}, + }, + } + it.chainConfig = types.ChainReaderConfig{ Contracts: map[string]types.ChainContractReader{ AnyContractName: { @@ -92,12 +99,7 @@ func (it *EVMChainReaderInterfaceTester[T]) Setup(t T) { GenericEventNames: []string{EventName, EventWithFilterName}, }, Configs: map[string]*types.ChainReaderDefinition{ - MethodTakingLatestParamsReturningTestStruct: { - ChainSpecificName: "getElementAtIndex", - OutputModifications: codec.ModifiersConfig{ - &codec.RenameModifierConfig{Fields: map[string]string{"NestedStruct.Inner.IntVal": "I"}}, - }, - }, + MethodTakingLatestParamsReturningTestStruct: &methodTakingLatestParamsReturningTestStructConfig, MethodReturningAlterableUint64: { ChainSpecificName: "getAlterablePrimitiveValue", }, @@ -164,6 +166,7 @@ func (it *EVMChainReaderInterfaceTester[T]) Setup(t T) { AnySecondContractName: { ContractABI: chain_reader_tester.ChainReaderTesterMetaData.ABI, Configs: map[string]*types.ChainReaderDefinition{ + MethodTakingLatestParamsReturningTestStruct: &methodTakingLatestParamsReturningTestStructConfig, MethodReturningUint64: { ChainSpecificName: "getDifferentPrimitiveValue", }, @@ -226,7 +229,26 @@ func (it *EVMChainReaderInterfaceTester[T]) GetChainReader(t T) clcommontypes.Co } func (it *EVMChainReaderInterfaceTester[T]) SetTestStructLatestValue(t T, testStruct *TestStruct) { - it.sendTxWithTestStruct(t, testStruct, (*chain_reader_tester.ChainReaderTesterTransactor).AddTestStruct) + it.sendTxWithTestStruct(t, it.address, testStruct, (*chain_reader_tester.ChainReaderTesterTransactor).AddTestStruct) +} + +func (it *EVMChainReaderInterfaceTester[T]) SetBatchLatestValues(t T, batchCallEntry BatchCallEntry) { + nameToAddress := make(map[string]string) + boundContracts := it.GetBindings(t) + for _, bc := range boundContracts { + nameToAddress[bc.Name] = bc.Address + } + + for contractName, contractBatch := range batchCallEntry { + require.Contains(t, nameToAddress, contractName) + for _, readEntry := range contractBatch { + val, isOk := readEntry.ReturnValue.(*TestStruct) + if !isOk { + require.Fail(t, "expected *TestStruct for contract: %s read: %s, but received %T", contractName, readEntry.Name, readEntry.ReturnValue) + } + it.sendTxWithTestStruct(t, nameToAddress[contractName], val, (*chain_reader_tester.ChainReaderTesterTransactor).AddTestStruct) + } + } } // SetUintLatestValue is supposed to be used for testing confidence levels, but geth simulated backend doesn't support calling past state @@ -236,12 +258,12 @@ func (it *EVMChainReaderInterfaceTester[T]) SetUintLatestValue(t T, val uint64, require.True(t, ok, "SetUintLatestValue should always be used for tests involving finality") } - it.sendTxWithUintVal(t, val, (*chain_reader_tester.ChainReaderTesterTransactor).SetAlterablePrimitiveValue) + it.sendTxWithUintVal(t, it.address, val, (*chain_reader_tester.ChainReaderTesterTransactor).SetAlterablePrimitiveValue) require.NoError(t, cw.SetUintLatestValue(it.Helper.Context(t), val, forCall)) } func (it *EVMChainReaderInterfaceTester[T]) TriggerEvent(t T, testStruct *TestStruct) { - it.sendTxWithTestStruct(t, testStruct, (*chain_reader_tester.ChainReaderTesterTransactor).TriggerEvent) + it.sendTxWithTestStruct(t, it.address, testStruct, (*chain_reader_tester.ChainReaderTesterTransactor).TriggerEvent) } // GenerateBlocksTillConfidenceLevel is supposed to be used for testing confidence levels, but geth simulated backend doesn't support calling past state @@ -275,9 +297,9 @@ func (it *EVMChainReaderInterfaceTester[T]) GetBindings(_ T) []clcommontypes.Bou type uintFn = func(*chain_reader_tester.ChainReaderTesterTransactor, *bind.TransactOpts, uint64) (*gethtypes.Transaction, error) // sendTxWithUintVal is supposed to be used for testing confidence levels, but geth simulated backend doesn't support calling past state -func (it *EVMChainReaderInterfaceTester[T]) sendTxWithUintVal(t T, val uint64, fn uintFn) { +func (it *EVMChainReaderInterfaceTester[T]) sendTxWithUintVal(t T, contractAddress string, val uint64, fn uintFn) { tx, err := fn( - &it.evmTest.ChainReaderTesterTransactor, + &it.contractTesters[contractAddress].ChainReaderTesterTransactor, it.GetAuthWithGasSet(t), val, ) @@ -291,9 +313,9 @@ func (it *EVMChainReaderInterfaceTester[T]) sendTxWithUintVal(t T, val uint64, f type testStructFn = func(*chain_reader_tester.ChainReaderTesterTransactor, *bind.TransactOpts, int32, string, uint8, [32]uint8, common.Address, []common.Address, *big.Int, chain_reader_tester.MidLevelTestStruct) (*gethtypes.Transaction, error) -func (it *EVMChainReaderInterfaceTester[T]) sendTxWithTestStruct(t T, testStruct *TestStruct, fn testStructFn) { +func (it *EVMChainReaderInterfaceTester[T]) sendTxWithTestStruct(t T, contractAddress string, testStruct *TestStruct, fn testStructFn) { tx, err := fn( - &it.evmTest.ChainReaderTesterTransactor, + &it.contractTesters[contractAddress].ChainReaderTesterTransactor, it.GetAuthWithGasSet(t), *testStruct.Field, testStruct.DifferentField, @@ -337,16 +359,17 @@ func (it *EVMChainReaderInterfaceTester[T]) AwaitTx(t T, tx *gethtypes.Transacti func (it *EVMChainReaderInterfaceTester[T]) deployNewContracts(t T) { // First test deploy both contracts, otherwise only deploy contracts if cleanup decides that we need to. - if it.address == "" { - it.address = it.deployNewContract(t) - it.address2 = it.deployNewContract(t) - } else if it.evmTest == nil { - it.address = it.deployNewContract(t) - it.dirtyContracts = false + if it.address == "" || it.contractTesters == nil { + it.contractTesters = make(map[string]*chain_reader_tester.ChainReaderTester, 2) + address, ts1 := it.deployNewContract(t) + address2, ts2 := it.deployNewContract(t) + it.address, it.address2 = address, address2 + it.contractTesters[it.address] = ts1 + it.contractTesters[it.address2] = ts2 } } -func (it *EVMChainReaderInterfaceTester[T]) deployNewContract(t T) string { +func (it *EVMChainReaderInterfaceTester[T]) deployNewContract(t T) (string, *chain_reader_tester.ChainReaderTester) { // 105528 was in the error: gas too low: have 0, want 105528 // Not sure if there's a better way to get it. it.auth.GasLimit = 10552800 @@ -354,13 +377,10 @@ func (it *EVMChainReaderInterfaceTester[T]) deployNewContract(t T) string { address, tx, ts, err := chain_reader_tester.DeployChainReaderTester(it.GetAuthWithGasSet(t), it.Helper.Backend()) require.NoError(t, err) it.Helper.Commit() - if it.evmTest == nil { - it.evmTest = ts - } it.IncNonce() it.AwaitTx(t, tx) - return address.String() + return address.String(), ts } func (it *EVMChainReaderInterfaceTester[T]) MaxWaitTimeForEvents() time.Duration { diff --git a/core/services/relay/evm/evmtesting/run_tests.go b/core/services/relay/evm/evmtesting/run_tests.go index e2ce5385c8b..f958c055ca7 100644 --- a/core/services/relay/evm/evmtesting/run_tests.go +++ b/core/services/relay/evm/evmtesting/run_tests.go @@ -26,7 +26,7 @@ func RunChainReaderEvmTests[T TestingT[T]](t T, it *EVMChainReaderInterfaceTeste anyString := "foo" it.dirtyContracts = true - tx, err := it.evmTest.ChainReaderTesterTransactor.TriggerEventWithDynamicTopic(it.GetAuthWithGasSet(t), anyString) + tx, err := it.contractTesters[it.address].ChainReaderTesterTransactor.TriggerEventWithDynamicTopic(it.GetAuthWithGasSet(t), anyString) require.NoError(t, err) it.Helper.Commit() it.IncNonce() @@ -88,7 +88,7 @@ func RunChainReaderEvmTests[T TestingT[T]](t T, it *EVMChainReaderInterfaceTeste } func triggerFourTopics[T TestingT[T]](t T, it *EVMChainReaderInterfaceTester[T], i1, i2, i3 int32) { - tx, err := it.evmTest.ChainReaderTesterTransactor.TriggerWithFourTopics(it.GetAuthWithGasSet(t), i1, i2, i3) + tx, err := it.contractTesters[it.address].ChainReaderTesterTransactor.TriggerWithFourTopics(it.GetAuthWithGasSet(t), i1, i2, i3) require.NoError(t, err) require.NoError(t, err) it.Helper.Commit() diff --git a/core/services/relay/evm/mocks/codec.go b/core/services/relay/evm/mocks/codec.go new file mode 100644 index 00000000000..588ee65fa99 --- /dev/null +++ b/core/services/relay/evm/mocks/codec.go @@ -0,0 +1,132 @@ +// Code generated by mockery v2.43.2. DO NOT EDIT. + +package mocks + +import ( + context "context" + + mock "github.com/stretchr/testify/mock" +) + +// Codec is an autogenerated mock type for the Codec type +type Codec struct { + mock.Mock +} + +// Decode provides a mock function with given fields: ctx, raw, into, itemType +func (_m *Codec) Decode(ctx context.Context, raw []byte, into interface{}, itemType string) error { + ret := _m.Called(ctx, raw, into, itemType) + + if len(ret) == 0 { + panic("no return value specified for Decode") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, []byte, interface{}, string) error); ok { + r0 = rf(ctx, raw, into, itemType) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Encode provides a mock function with given fields: ctx, item, itemType +func (_m *Codec) Encode(ctx context.Context, item interface{}, itemType string) ([]byte, error) { + ret := _m.Called(ctx, item, itemType) + + if len(ret) == 0 { + panic("no return value specified for Encode") + } + + var r0 []byte + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, interface{}, string) ([]byte, error)); ok { + return rf(ctx, item, itemType) + } + if rf, ok := ret.Get(0).(func(context.Context, interface{}, string) []byte); ok { + r0 = rf(ctx, item, itemType) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]byte) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, interface{}, string) error); ok { + r1 = rf(ctx, item, itemType) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetMaxDecodingSize provides a mock function with given fields: ctx, n, itemType +func (_m *Codec) GetMaxDecodingSize(ctx context.Context, n int, itemType string) (int, error) { + ret := _m.Called(ctx, n, itemType) + + if len(ret) == 0 { + panic("no return value specified for GetMaxDecodingSize") + } + + var r0 int + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, int, string) (int, error)); ok { + return rf(ctx, n, itemType) + } + if rf, ok := ret.Get(0).(func(context.Context, int, string) int); ok { + r0 = rf(ctx, n, itemType) + } else { + r0 = ret.Get(0).(int) + } + + if rf, ok := ret.Get(1).(func(context.Context, int, string) error); ok { + r1 = rf(ctx, n, itemType) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetMaxEncodingSize provides a mock function with given fields: ctx, n, itemType +func (_m *Codec) GetMaxEncodingSize(ctx context.Context, n int, itemType string) (int, error) { + ret := _m.Called(ctx, n, itemType) + + if len(ret) == 0 { + panic("no return value specified for GetMaxEncodingSize") + } + + var r0 int + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, int, string) (int, error)); ok { + return rf(ctx, n, itemType) + } + if rf, ok := ret.Get(0).(func(context.Context, int, string) int); ok { + r0 = rf(ctx, n, itemType) + } else { + r0 = ret.Get(0).(int) + } + + if rf, ok := ret.Get(1).(func(context.Context, int, string) error); ok { + r1 = rf(ctx, n, itemType) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// NewCodec creates a new instance of Codec. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewCodec(t interface { + mock.TestingT + Cleanup(func()) +}) *Codec { + mock := &Codec{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/core/services/relay/evm/rpclibmocks/batch_caller.go b/core/services/relay/evm/rpclibmocks/batch_caller.go new file mode 100644 index 00000000000..7fcfbcac6bd --- /dev/null +++ b/core/services/relay/evm/rpclibmocks/batch_caller.go @@ -0,0 +1,59 @@ +// Code generated by mockery v2.43.2. DO NOT EDIT. + +package rpclibmocks + +import ( + context "context" + + evm "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm" + mock "github.com/stretchr/testify/mock" +) + +// BatchCaller is an autogenerated mock type for the BatchCaller type +type BatchCaller struct { + mock.Mock +} + +// BatchCall provides a mock function with given fields: ctx, blockNumber, batchRequests +func (_m *BatchCaller) BatchCall(ctx context.Context, blockNumber uint64, batchRequests evm.BatchCall) (evm.BatchResult, error) { + ret := _m.Called(ctx, blockNumber, batchRequests) + + if len(ret) == 0 { + panic("no return value specified for BatchCall") + } + + var r0 evm.BatchResult + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, uint64, evm.BatchCall) (evm.BatchResult, error)); ok { + return rf(ctx, blockNumber, batchRequests) + } + if rf, ok := ret.Get(0).(func(context.Context, uint64, evm.BatchCall) evm.BatchResult); ok { + r0 = rf(ctx, blockNumber, batchRequests) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(evm.BatchResult) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, uint64, evm.BatchCall) error); ok { + r1 = rf(ctx, blockNumber, batchRequests) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// NewBatchCaller creates a new instance of BatchCaller. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewBatchCaller(t interface { + mock.TestingT + Cleanup(func()) +}) *BatchCaller { + mock := &BatchCaller{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/go.mod b/go.mod index bbcb840763e..1bbb064a2c2 100644 --- a/go.mod +++ b/go.mod @@ -72,11 +72,11 @@ require ( github.com/shopspring/decimal v1.3.1 github.com/smartcontractkit/chain-selectors v1.0.10 github.com/smartcontractkit/chainlink-automation v1.0.4 - github.com/smartcontractkit/chainlink-common v0.1.7-0.20240710165532-ade916a95858 + github.com/smartcontractkit/chainlink-common v0.1.7-0.20240712101200-5b11e6cc6e86 github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240710121324-3ed288aa9b45 github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240702145022-37a2c3a742d1 github.com/smartcontractkit/chainlink-feeds v0.0.0-20240710170203-5b41615da827 - github.com/smartcontractkit/chainlink-solana v1.0.3-0.20240710170818-eccca28888e5 + github.com/smartcontractkit/chainlink-solana v1.0.3-0.20240712132946-267a37c5ac6e github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20240709043547-03612098f799 github.com/smartcontractkit/libocr v0.0.0-20240702141926-063ceef8c42e github.com/smartcontractkit/tdh2/go/ocr2/decryptionplugin v0.0.0-20230906073235-9e478e5e19f1 diff --git a/go.sum b/go.sum index f16fa8adb88..81ac500868f 100644 --- a/go.sum +++ b/go.sum @@ -1131,16 +1131,16 @@ github.com/smartcontractkit/chain-selectors v1.0.10 h1:t9kJeE6B6G+hKD0GYR4kGJSCq github.com/smartcontractkit/chain-selectors v1.0.10/go.mod h1:d4Hi+E1zqjy9HqMkjBE5q1vcG9VGgxf5VxiRHfzi2kE= github.com/smartcontractkit/chainlink-automation v1.0.4 h1:iyW181JjKHLNMnDleI8umfIfVVlwC7+n5izbLSFgjw8= github.com/smartcontractkit/chainlink-automation v1.0.4/go.mod h1:u4NbPZKJ5XiayfKHD/v3z3iflQWqvtdhj13jVZXj/cM= -github.com/smartcontractkit/chainlink-common v0.1.7-0.20240710165532-ade916a95858 h1:nwAe0iA4JN7/oEFz/N2lkTpNh6rxlzbK7g8Els/dDew= -github.com/smartcontractkit/chainlink-common v0.1.7-0.20240710165532-ade916a95858/go.mod h1:fh9eBbrReCmv31bfz52ENCAMa7nTKQbdhb2B3+S2VGo= +github.com/smartcontractkit/chainlink-common v0.1.7-0.20240712101200-5b11e6cc6e86 h1:TYALsn6Jue7xCIcXMel+Ow0SuudVfOUAz6iups946Yw= +github.com/smartcontractkit/chainlink-common v0.1.7-0.20240712101200-5b11e6cc6e86/go.mod h1:fh9eBbrReCmv31bfz52ENCAMa7nTKQbdhb2B3+S2VGo= github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240710121324-3ed288aa9b45 h1:NBQLtqk8zsyY4qTJs+NElI3aDFTcAo83JHvqD04EvB0= github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240710121324-3ed288aa9b45/go.mod h1:LV0h7QBQUpoC2UUi6TcUvcIFm1xjP/DtEcqV8+qeLUs= github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240702145022-37a2c3a742d1 h1:dsTmitRaVizHxoYFoGz4+y/zVa8XnvKUiTaZdx+6t9M= github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240702145022-37a2c3a742d1/go.mod h1:6DgCnHMGdBaIh0bLs1dK0MtdeMZfeNhc/nvBUN6KIUg= github.com/smartcontractkit/chainlink-feeds v0.0.0-20240710170203-5b41615da827 h1:BCHu4pNP6arrcHLEWx61XjLaonOd2coQNyL0NTUcaMc= github.com/smartcontractkit/chainlink-feeds v0.0.0-20240710170203-5b41615da827/go.mod h1:OPX+wC2TWQsyLNpR7daMt2vMpmsNcoBxbZyGTHr6tiA= -github.com/smartcontractkit/chainlink-solana v1.0.3-0.20240710170818-eccca28888e5 h1:gktRCdvNp0tczyqb79JaQOloa/elDS6t33qjAS9SrEU= -github.com/smartcontractkit/chainlink-solana v1.0.3-0.20240710170818-eccca28888e5/go.mod h1:aJUY4hdo1g942mhlPX9Z4FWe5ldEyWvsWSNf7frh7yU= +github.com/smartcontractkit/chainlink-solana v1.0.3-0.20240712132946-267a37c5ac6e h1:PzwzlHNv1YbJ6ZIdl/pIFRoOuOS4V4WLvjZvFUnZFL4= +github.com/smartcontractkit/chainlink-solana v1.0.3-0.20240712132946-267a37c5ac6e/go.mod h1:hsFhop+SlQHKD+DEFjZrMJmbauT1A/wvtZIeeo4PxFU= github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20240709043547-03612098f799 h1:HyLTySm7BR+oNfZqDTkVJ25wnmcTtxBBD31UkFL+kEM= github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20240709043547-03612098f799/go.mod h1:UVFRacRkP7O7TQAzFmR52v5mUlxf+G1ovMlCQAB/cHU= github.com/smartcontractkit/go-plugin v0.0.0-20240208201424-b3b91517de16 h1:TFe+FvzxClblt6qRfqEhUfa4kFQx5UobuoFGO2W4mMo= diff --git a/integration-tests/go.mod b/integration-tests/go.mod index 4b82d7c8d7f..351eb996463 100644 --- a/integration-tests/go.mod +++ b/integration-tests/go.mod @@ -28,7 +28,7 @@ require ( github.com/shopspring/decimal v1.3.1 github.com/slack-go/slack v0.12.2 github.com/smartcontractkit/chainlink-automation v1.0.4 - github.com/smartcontractkit/chainlink-common v0.1.7-0.20240710165532-ade916a95858 + github.com/smartcontractkit/chainlink-common v0.1.7-0.20240712101200-5b11e6cc6e86 github.com/smartcontractkit/chainlink-testing-framework v1.32.2 github.com/smartcontractkit/chainlink-testing-framework/grafana v0.0.0-20240405215812-5a72bc9af239 github.com/smartcontractkit/chainlink/v2 v2.0.0-00010101000000-000000000000 @@ -379,7 +379,7 @@ require ( github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240710121324-3ed288aa9b45 // indirect github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240702145022-37a2c3a742d1 // indirect github.com/smartcontractkit/chainlink-feeds v0.0.0-20240710170203-5b41615da827 // indirect - github.com/smartcontractkit/chainlink-solana v1.0.3-0.20240710170818-eccca28888e5 // indirect + github.com/smartcontractkit/chainlink-solana v1.0.3-0.20240712132946-267a37c5ac6e // indirect github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20240709043547-03612098f799 // indirect github.com/smartcontractkit/tdh2/go/ocr2/decryptionplugin v0.0.0-20230906073235-9e478e5e19f1 // indirect github.com/smartcontractkit/tdh2/go/tdh2 v0.0.0-20230906073235-9e478e5e19f1 // indirect diff --git a/integration-tests/go.sum b/integration-tests/go.sum index 846881aa727..d7d44138631 100644 --- a/integration-tests/go.sum +++ b/integration-tests/go.sum @@ -1514,16 +1514,16 @@ github.com/smartcontractkit/chain-selectors v1.0.10 h1:t9kJeE6B6G+hKD0GYR4kGJSCq github.com/smartcontractkit/chain-selectors v1.0.10/go.mod h1:d4Hi+E1zqjy9HqMkjBE5q1vcG9VGgxf5VxiRHfzi2kE= github.com/smartcontractkit/chainlink-automation v1.0.4 h1:iyW181JjKHLNMnDleI8umfIfVVlwC7+n5izbLSFgjw8= github.com/smartcontractkit/chainlink-automation v1.0.4/go.mod h1:u4NbPZKJ5XiayfKHD/v3z3iflQWqvtdhj13jVZXj/cM= -github.com/smartcontractkit/chainlink-common v0.1.7-0.20240710165532-ade916a95858 h1:nwAe0iA4JN7/oEFz/N2lkTpNh6rxlzbK7g8Els/dDew= -github.com/smartcontractkit/chainlink-common v0.1.7-0.20240710165532-ade916a95858/go.mod h1:fh9eBbrReCmv31bfz52ENCAMa7nTKQbdhb2B3+S2VGo= +github.com/smartcontractkit/chainlink-common v0.1.7-0.20240712101200-5b11e6cc6e86 h1:TYALsn6Jue7xCIcXMel+Ow0SuudVfOUAz6iups946Yw= +github.com/smartcontractkit/chainlink-common v0.1.7-0.20240712101200-5b11e6cc6e86/go.mod h1:fh9eBbrReCmv31bfz52ENCAMa7nTKQbdhb2B3+S2VGo= github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240710121324-3ed288aa9b45 h1:NBQLtqk8zsyY4qTJs+NElI3aDFTcAo83JHvqD04EvB0= github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240710121324-3ed288aa9b45/go.mod h1:LV0h7QBQUpoC2UUi6TcUvcIFm1xjP/DtEcqV8+qeLUs= github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240702145022-37a2c3a742d1 h1:dsTmitRaVizHxoYFoGz4+y/zVa8XnvKUiTaZdx+6t9M= github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240702145022-37a2c3a742d1/go.mod h1:6DgCnHMGdBaIh0bLs1dK0MtdeMZfeNhc/nvBUN6KIUg= github.com/smartcontractkit/chainlink-feeds v0.0.0-20240710170203-5b41615da827 h1:BCHu4pNP6arrcHLEWx61XjLaonOd2coQNyL0NTUcaMc= github.com/smartcontractkit/chainlink-feeds v0.0.0-20240710170203-5b41615da827/go.mod h1:OPX+wC2TWQsyLNpR7daMt2vMpmsNcoBxbZyGTHr6tiA= -github.com/smartcontractkit/chainlink-solana v1.0.3-0.20240710170818-eccca28888e5 h1:gktRCdvNp0tczyqb79JaQOloa/elDS6t33qjAS9SrEU= -github.com/smartcontractkit/chainlink-solana v1.0.3-0.20240710170818-eccca28888e5/go.mod h1:aJUY4hdo1g942mhlPX9Z4FWe5ldEyWvsWSNf7frh7yU= +github.com/smartcontractkit/chainlink-solana v1.0.3-0.20240712132946-267a37c5ac6e h1:PzwzlHNv1YbJ6ZIdl/pIFRoOuOS4V4WLvjZvFUnZFL4= +github.com/smartcontractkit/chainlink-solana v1.0.3-0.20240712132946-267a37c5ac6e/go.mod h1:hsFhop+SlQHKD+DEFjZrMJmbauT1A/wvtZIeeo4PxFU= github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20240709043547-03612098f799 h1:HyLTySm7BR+oNfZqDTkVJ25wnmcTtxBBD31UkFL+kEM= github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20240709043547-03612098f799/go.mod h1:UVFRacRkP7O7TQAzFmR52v5mUlxf+G1ovMlCQAB/cHU= github.com/smartcontractkit/chainlink-testing-framework v1.32.2 h1:j5OQ9Xt2aqAGJfpEkh1pxvOiZMVbxypjFEBZkdIEfZg= diff --git a/integration-tests/load/go.mod b/integration-tests/load/go.mod index 648de75324c..749fee0d2cc 100644 --- a/integration-tests/load/go.mod +++ b/integration-tests/load/go.mod @@ -16,7 +16,7 @@ require ( github.com/rs/zerolog v1.31.0 github.com/slack-go/slack v0.12.2 github.com/smartcontractkit/chainlink-automation v1.0.4 - github.com/smartcontractkit/chainlink-common v0.1.7-0.20240710165532-ade916a95858 + github.com/smartcontractkit/chainlink-common v0.1.7-0.20240712101200-5b11e6cc6e86 github.com/smartcontractkit/chainlink-testing-framework v1.32.2 github.com/smartcontractkit/chainlink/integration-tests v0.0.0-20240214231432-4ad5eb95178c github.com/smartcontractkit/chainlink/v2 v2.9.0-beta0.0.20240216210048-da02459ddad8 @@ -368,7 +368,7 @@ require ( github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240710121324-3ed288aa9b45 // indirect github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240702145022-37a2c3a742d1 // indirect github.com/smartcontractkit/chainlink-feeds v0.0.0-20240710170203-5b41615da827 // indirect - github.com/smartcontractkit/chainlink-solana v1.0.3-0.20240710170818-eccca28888e5 // indirect + github.com/smartcontractkit/chainlink-solana v1.0.3-0.20240712132946-267a37c5ac6e // indirect github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20240709043547-03612098f799 // indirect github.com/smartcontractkit/chainlink-testing-framework/grafana v0.0.0-20240405215812-5a72bc9af239 // indirect github.com/smartcontractkit/tdh2/go/ocr2/decryptionplugin v0.0.0-20230906073235-9e478e5e19f1 // indirect diff --git a/integration-tests/load/go.sum b/integration-tests/load/go.sum index 6eac8d3bf0b..9fffc245963 100644 --- a/integration-tests/load/go.sum +++ b/integration-tests/load/go.sum @@ -1504,16 +1504,16 @@ github.com/smartcontractkit/chain-selectors v1.0.10 h1:t9kJeE6B6G+hKD0GYR4kGJSCq github.com/smartcontractkit/chain-selectors v1.0.10/go.mod h1:d4Hi+E1zqjy9HqMkjBE5q1vcG9VGgxf5VxiRHfzi2kE= github.com/smartcontractkit/chainlink-automation v1.0.4 h1:iyW181JjKHLNMnDleI8umfIfVVlwC7+n5izbLSFgjw8= github.com/smartcontractkit/chainlink-automation v1.0.4/go.mod h1:u4NbPZKJ5XiayfKHD/v3z3iflQWqvtdhj13jVZXj/cM= -github.com/smartcontractkit/chainlink-common v0.1.7-0.20240710165532-ade916a95858 h1:nwAe0iA4JN7/oEFz/N2lkTpNh6rxlzbK7g8Els/dDew= -github.com/smartcontractkit/chainlink-common v0.1.7-0.20240710165532-ade916a95858/go.mod h1:fh9eBbrReCmv31bfz52ENCAMa7nTKQbdhb2B3+S2VGo= +github.com/smartcontractkit/chainlink-common v0.1.7-0.20240712101200-5b11e6cc6e86 h1:TYALsn6Jue7xCIcXMel+Ow0SuudVfOUAz6iups946Yw= +github.com/smartcontractkit/chainlink-common v0.1.7-0.20240712101200-5b11e6cc6e86/go.mod h1:fh9eBbrReCmv31bfz52ENCAMa7nTKQbdhb2B3+S2VGo= github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240710121324-3ed288aa9b45 h1:NBQLtqk8zsyY4qTJs+NElI3aDFTcAo83JHvqD04EvB0= github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240710121324-3ed288aa9b45/go.mod h1:LV0h7QBQUpoC2UUi6TcUvcIFm1xjP/DtEcqV8+qeLUs= github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240702145022-37a2c3a742d1 h1:dsTmitRaVizHxoYFoGz4+y/zVa8XnvKUiTaZdx+6t9M= github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240702145022-37a2c3a742d1/go.mod h1:6DgCnHMGdBaIh0bLs1dK0MtdeMZfeNhc/nvBUN6KIUg= github.com/smartcontractkit/chainlink-feeds v0.0.0-20240710170203-5b41615da827 h1:BCHu4pNP6arrcHLEWx61XjLaonOd2coQNyL0NTUcaMc= github.com/smartcontractkit/chainlink-feeds v0.0.0-20240710170203-5b41615da827/go.mod h1:OPX+wC2TWQsyLNpR7daMt2vMpmsNcoBxbZyGTHr6tiA= -github.com/smartcontractkit/chainlink-solana v1.0.3-0.20240710170818-eccca28888e5 h1:gktRCdvNp0tczyqb79JaQOloa/elDS6t33qjAS9SrEU= -github.com/smartcontractkit/chainlink-solana v1.0.3-0.20240710170818-eccca28888e5/go.mod h1:aJUY4hdo1g942mhlPX9Z4FWe5ldEyWvsWSNf7frh7yU= +github.com/smartcontractkit/chainlink-solana v1.0.3-0.20240712132946-267a37c5ac6e h1:PzwzlHNv1YbJ6ZIdl/pIFRoOuOS4V4WLvjZvFUnZFL4= +github.com/smartcontractkit/chainlink-solana v1.0.3-0.20240712132946-267a37c5ac6e/go.mod h1:hsFhop+SlQHKD+DEFjZrMJmbauT1A/wvtZIeeo4PxFU= github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20240709043547-03612098f799 h1:HyLTySm7BR+oNfZqDTkVJ25wnmcTtxBBD31UkFL+kEM= github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20240709043547-03612098f799/go.mod h1:UVFRacRkP7O7TQAzFmR52v5mUlxf+G1ovMlCQAB/cHU= github.com/smartcontractkit/chainlink-testing-framework v1.32.2 h1:j5OQ9Xt2aqAGJfpEkh1pxvOiZMVbxypjFEBZkdIEfZg=