diff --git a/.changeset/short-candles-love.md b/.changeset/short-candles-love.md new file mode 100644 index 00000000000..5923f9796c5 --- /dev/null +++ b/.changeset/short-candles-love.md @@ -0,0 +1,5 @@ +--- +"chainlink": patch +--- + +#internal `ContractReader` interface update to accept `BoundContract` for all methods diff --git a/.mockery.yaml b/.mockery.yaml index 8d9e6de4ee8..161348efbf0 100644 --- a/.mockery.yaml +++ b/.mockery.yaml @@ -326,6 +326,13 @@ packages: interfaces: ExternalInitiatorManager: HTTPClient: + github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/read: + config: + dir: "{{ .InterfaceDir }}/mocks" + interfaces: + Registrar: + Reader: + BatchCaller: github.com/smartcontractkit/chainlink/v2/core/sessions: interfaces: BasicAdminUsersORM: @@ -341,7 +348,7 @@ packages: Codec: config: dir: core/services/relay/evm/mocks - ChainReader: + ContractReader: ChainWriter: github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/evm_2_evm_onramp: config: @@ -581,4 +588,4 @@ packages: mockname: "Mock{{ .InterfaceName }}" github.com/smartcontractkit/chainlink/v2/core/services/registrysyncer: interfaces: - ORM: \ No newline at end of file + ORM: diff --git a/core/capabilities/ccip/ccip_integration_tests/ccipreader/ccipreader_test.go b/core/capabilities/ccip/ccip_integration_tests/ccipreader/ccipreader_test.go index 8f4cd023ce5..788b93e8361 100644 --- a/core/capabilities/ccip/ccip_integration_tests/ccipreader/ccipreader_test.go +++ b/core/capabilities/ccip/ccip_integration_tests/ccipreader/ccipreader_test.go @@ -488,7 +488,7 @@ func testSetup(ctx context.Context, t *testing.T, readerChain, destChain cciptyp contractReaders := map[cciptypes.ChainSelector]contractreader.Extended{readerChain: extendedCr} contractWriters := make(map[cciptypes.ChainSelector]types.ChainWriter) - reader := ccipreaderpkg.NewCCIPReaderWithExtendedContractReaders(lggr, contractReaders, contractWriters, destChain) + reader := ccipreaderpkg.NewCCIPReaderWithExtendedContractReaders(lggr, contractReaders, contractWriters, destChain, nil) t.Cleanup(func() { require.NoError(t, cr.Close()) diff --git a/core/capabilities/ccip/delegate.go b/core/capabilities/ccip/delegate.go index 7623a18af92..84f3e7c1b76 100644 --- a/core/capabilities/ccip/delegate.go +++ b/core/capabilities/ccip/delegate.go @@ -303,12 +303,12 @@ func bindReader(ctx context.Context, capabilityLabelledName, capabilityVersion string, ) (boundReader types.ContractReader, ccipConfigBinding types.BoundContract, err error) { - err = reader.Bind(ctx, []types.BoundContract{ - { - Address: capRegAddress, - Name: consts.ContractNameCapabilitiesRegistry, - }, - }) + boundContract := types.BoundContract{ + Address: capRegAddress, + Name: consts.ContractNameCapabilitiesRegistry, + } + + err = reader.Bind(ctx, []types.BoundContract{boundContract}) if err != nil { return nil, types.BoundContract{}, fmt.Errorf("failed to bind home chain contract reader: %w", err) } @@ -319,9 +319,15 @@ func bindReader(ctx context.Context, } var ccipCapabilityInfo kcr.CapabilitiesRegistryCapabilityInfo - err = reader.GetLatestValue(ctx, consts.ContractNameCapabilitiesRegistry, consts.MethodNameGetCapability, primitives.Unconfirmed, map[string]any{ - "hashedId": hid, - }, &ccipCapabilityInfo) + err = reader.GetLatestValue( + ctx, + boundContract.ReadIdentifier(consts.MethodNameGetCapability), + primitives.Unconfirmed, + map[string]any{ + "hashedId": hid, + }, + &ccipCapabilityInfo, + ) if err != nil { return nil, types.BoundContract{}, fmt.Errorf("failed to get CCIP capability info from chain reader: %w", err) } diff --git a/core/capabilities/targets/mocks/chain_reader.go b/core/capabilities/targets/mocks/chain_reader.go deleted file mode 100644 index 9c1c85ff60c..00000000000 --- a/core/capabilities/targets/mocks/chain_reader.go +++ /dev/null @@ -1,487 +0,0 @@ -// Code generated by mockery v2.43.2. DO NOT EDIT. - -package mocks - -import ( - context "context" - - query "github.com/smartcontractkit/chainlink-common/pkg/types/query" - primitives "github.com/smartcontractkit/chainlink-common/pkg/types/query/primitives" - mock "github.com/stretchr/testify/mock" - - types "github.com/smartcontractkit/chainlink-common/pkg/types" -) - -// ChainReader is an autogenerated mock type for the ChainReader type -type ChainReader struct { - mock.Mock -} - -type ChainReader_Expecter struct { - mock *mock.Mock -} - -func (_m *ChainReader) EXPECT() *ChainReader_Expecter { - return &ChainReader_Expecter{mock: &_m.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 -} - -// ChainReader_BatchGetLatestValues_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'BatchGetLatestValues' -type ChainReader_BatchGetLatestValues_Call struct { - *mock.Call -} - -// BatchGetLatestValues is a helper method to define mock.On call -// - ctx context.Context -// - request types.BatchGetLatestValuesRequest -func (_e *ChainReader_Expecter) BatchGetLatestValues(ctx interface{}, request interface{}) *ChainReader_BatchGetLatestValues_Call { - return &ChainReader_BatchGetLatestValues_Call{Call: _e.mock.On("BatchGetLatestValues", ctx, request)} -} - -func (_c *ChainReader_BatchGetLatestValues_Call) Run(run func(ctx context.Context, request types.BatchGetLatestValuesRequest)) *ChainReader_BatchGetLatestValues_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(types.BatchGetLatestValuesRequest)) - }) - return _c -} - -func (_c *ChainReader_BatchGetLatestValues_Call) Return(_a0 types.BatchGetLatestValuesResult, _a1 error) *ChainReader_BatchGetLatestValues_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *ChainReader_BatchGetLatestValues_Call) RunAndReturn(run func(context.Context, types.BatchGetLatestValuesRequest) (types.BatchGetLatestValuesResult, error)) *ChainReader_BatchGetLatestValues_Call { - _c.Call.Return(run) - return _c -} - -// 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) - - if len(ret) == 0 { - panic("no return value specified for Bind") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, []types.BoundContract) error); ok { - r0 = rf(ctx, bindings) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// ChainReader_Bind_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Bind' -type ChainReader_Bind_Call struct { - *mock.Call -} - -// Bind is a helper method to define mock.On call -// - ctx context.Context -// - bindings []types.BoundContract -func (_e *ChainReader_Expecter) Bind(ctx interface{}, bindings interface{}) *ChainReader_Bind_Call { - return &ChainReader_Bind_Call{Call: _e.mock.On("Bind", ctx, bindings)} -} - -func (_c *ChainReader_Bind_Call) Run(run func(ctx context.Context, bindings []types.BoundContract)) *ChainReader_Bind_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].([]types.BoundContract)) - }) - return _c -} - -func (_c *ChainReader_Bind_Call) Return(_a0 error) *ChainReader_Bind_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *ChainReader_Bind_Call) RunAndReturn(run func(context.Context, []types.BoundContract) error) *ChainReader_Bind_Call { - _c.Call.Return(run) - return _c -} - -// Close provides a mock function with given fields: -func (_m *ChainReader) Close() error { - ret := _m.Called() - - if len(ret) == 0 { - panic("no return value specified for Close") - } - - var r0 error - if rf, ok := ret.Get(0).(func() error); ok { - r0 = rf() - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// ChainReader_Close_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Close' -type ChainReader_Close_Call struct { - *mock.Call -} - -// Close is a helper method to define mock.On call -func (_e *ChainReader_Expecter) Close() *ChainReader_Close_Call { - return &ChainReader_Close_Call{Call: _e.mock.On("Close")} -} - -func (_c *ChainReader_Close_Call) Run(run func()) *ChainReader_Close_Call { - _c.Call.Run(func(args mock.Arguments) { - run() - }) - return _c -} - -func (_c *ChainReader_Close_Call) Return(_a0 error) *ChainReader_Close_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *ChainReader_Close_Call) RunAndReturn(run func() error) *ChainReader_Close_Call { - _c.Call.Return(run) - return _c -} - -// GetLatestValue provides a mock function with given fields: ctx, contractName, method, confidenceLevel, params, returnVal -func (_m *ChainReader) GetLatestValue(ctx context.Context, contractName string, method string, confidenceLevel primitives.ConfidenceLevel, params interface{}, returnVal interface{}) error { - ret := _m.Called(ctx, contractName, method, confidenceLevel, params, returnVal) - - if len(ret) == 0 { - panic("no return value specified for GetLatestValue") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string, string, primitives.ConfidenceLevel, interface{}, interface{}) error); ok { - r0 = rf(ctx, contractName, method, confidenceLevel, params, returnVal) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// ChainReader_GetLatestValue_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetLatestValue' -type ChainReader_GetLatestValue_Call struct { - *mock.Call -} - -// GetLatestValue is a helper method to define mock.On call -// - ctx context.Context -// - contractName string -// - method string -// - confidenceLevel primitives.ConfidenceLevel -// - params interface{} -// - returnVal interface{} -func (_e *ChainReader_Expecter) GetLatestValue(ctx interface{}, contractName interface{}, method interface{}, confidenceLevel interface{}, params interface{}, returnVal interface{}) *ChainReader_GetLatestValue_Call { - return &ChainReader_GetLatestValue_Call{Call: _e.mock.On("GetLatestValue", ctx, contractName, method, confidenceLevel, params, returnVal)} -} - -func (_c *ChainReader_GetLatestValue_Call) Run(run func(ctx context.Context, contractName string, method string, confidenceLevel primitives.ConfidenceLevel, params interface{}, returnVal interface{})) *ChainReader_GetLatestValue_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(string), args[2].(string), args[3].(primitives.ConfidenceLevel), args[4].(interface{}), args[5].(interface{})) - }) - return _c -} - -func (_c *ChainReader_GetLatestValue_Call) Return(_a0 error) *ChainReader_GetLatestValue_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *ChainReader_GetLatestValue_Call) RunAndReturn(run func(context.Context, string, string, primitives.ConfidenceLevel, interface{}, interface{}) error) *ChainReader_GetLatestValue_Call { - _c.Call.Return(run) - return _c -} - -// HealthReport provides a mock function with given fields: -func (_m *ChainReader) HealthReport() map[string]error { - ret := _m.Called() - - if len(ret) == 0 { - panic("no return value specified for HealthReport") - } - - var r0 map[string]error - if rf, ok := ret.Get(0).(func() map[string]error); ok { - r0 = rf() - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(map[string]error) - } - } - - return r0 -} - -// ChainReader_HealthReport_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'HealthReport' -type ChainReader_HealthReport_Call struct { - *mock.Call -} - -// HealthReport is a helper method to define mock.On call -func (_e *ChainReader_Expecter) HealthReport() *ChainReader_HealthReport_Call { - return &ChainReader_HealthReport_Call{Call: _e.mock.On("HealthReport")} -} - -func (_c *ChainReader_HealthReport_Call) Run(run func()) *ChainReader_HealthReport_Call { - _c.Call.Run(func(args mock.Arguments) { - run() - }) - return _c -} - -func (_c *ChainReader_HealthReport_Call) Return(_a0 map[string]error) *ChainReader_HealthReport_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *ChainReader_HealthReport_Call) RunAndReturn(run func() map[string]error) *ChainReader_HealthReport_Call { - _c.Call.Return(run) - return _c -} - -// Name provides a mock function with given fields: -func (_m *ChainReader) Name() string { - ret := _m.Called() - - if len(ret) == 0 { - panic("no return value specified for Name") - } - - var r0 string - if rf, ok := ret.Get(0).(func() string); ok { - r0 = rf() - } else { - r0 = ret.Get(0).(string) - } - - return r0 -} - -// ChainReader_Name_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Name' -type ChainReader_Name_Call struct { - *mock.Call -} - -// Name is a helper method to define mock.On call -func (_e *ChainReader_Expecter) Name() *ChainReader_Name_Call { - return &ChainReader_Name_Call{Call: _e.mock.On("Name")} -} - -func (_c *ChainReader_Name_Call) Run(run func()) *ChainReader_Name_Call { - _c.Call.Run(func(args mock.Arguments) { - run() - }) - return _c -} - -func (_c *ChainReader_Name_Call) Return(_a0 string) *ChainReader_Name_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *ChainReader_Name_Call) RunAndReturn(run func() string) *ChainReader_Name_Call { - _c.Call.Return(run) - return _c -} - -// QueryKey provides a mock function with given fields: ctx, contractName, filter, limitAndSort, sequenceDataType -func (_m *ChainReader) QueryKey(ctx context.Context, contractName string, filter query.KeyFilter, limitAndSort query.LimitAndSort, sequenceDataType interface{}) ([]types.Sequence, error) { - ret := _m.Called(ctx, contractName, filter, limitAndSort, sequenceDataType) - - if len(ret) == 0 { - panic("no return value specified for QueryKey") - } - - var r0 []types.Sequence - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, string, query.KeyFilter, query.LimitAndSort, interface{}) ([]types.Sequence, error)); ok { - return rf(ctx, contractName, filter, limitAndSort, sequenceDataType) - } - if rf, ok := ret.Get(0).(func(context.Context, string, query.KeyFilter, query.LimitAndSort, interface{}) []types.Sequence); ok { - r0 = rf(ctx, contractName, filter, limitAndSort, sequenceDataType) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]types.Sequence) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, string, query.KeyFilter, query.LimitAndSort, interface{}) error); ok { - r1 = rf(ctx, contractName, filter, limitAndSort, sequenceDataType) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// ChainReader_QueryKey_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'QueryKey' -type ChainReader_QueryKey_Call struct { - *mock.Call -} - -// QueryKey is a helper method to define mock.On call -// - ctx context.Context -// - contractName string -// - filter query.KeyFilter -// - limitAndSort query.LimitAndSort -// - sequenceDataType interface{} -func (_e *ChainReader_Expecter) QueryKey(ctx interface{}, contractName interface{}, filter interface{}, limitAndSort interface{}, sequenceDataType interface{}) *ChainReader_QueryKey_Call { - return &ChainReader_QueryKey_Call{Call: _e.mock.On("QueryKey", ctx, contractName, filter, limitAndSort, sequenceDataType)} -} - -func (_c *ChainReader_QueryKey_Call) Run(run func(ctx context.Context, contractName string, filter query.KeyFilter, limitAndSort query.LimitAndSort, sequenceDataType interface{})) *ChainReader_QueryKey_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(string), args[2].(query.KeyFilter), args[3].(query.LimitAndSort), args[4].(interface{})) - }) - return _c -} - -func (_c *ChainReader_QueryKey_Call) Return(_a0 []types.Sequence, _a1 error) *ChainReader_QueryKey_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *ChainReader_QueryKey_Call) RunAndReturn(run func(context.Context, string, query.KeyFilter, query.LimitAndSort, interface{}) ([]types.Sequence, error)) *ChainReader_QueryKey_Call { - _c.Call.Return(run) - return _c -} - -// Ready provides a mock function with given fields: -func (_m *ChainReader) Ready() error { - ret := _m.Called() - - if len(ret) == 0 { - panic("no return value specified for Ready") - } - - var r0 error - if rf, ok := ret.Get(0).(func() error); ok { - r0 = rf() - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// ChainReader_Ready_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Ready' -type ChainReader_Ready_Call struct { - *mock.Call -} - -// Ready is a helper method to define mock.On call -func (_e *ChainReader_Expecter) Ready() *ChainReader_Ready_Call { - return &ChainReader_Ready_Call{Call: _e.mock.On("Ready")} -} - -func (_c *ChainReader_Ready_Call) Run(run func()) *ChainReader_Ready_Call { - _c.Call.Run(func(args mock.Arguments) { - run() - }) - return _c -} - -func (_c *ChainReader_Ready_Call) Return(_a0 error) *ChainReader_Ready_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *ChainReader_Ready_Call) RunAndReturn(run func() error) *ChainReader_Ready_Call { - _c.Call.Return(run) - return _c -} - -// Start provides a mock function with given fields: _a0 -func (_m *ChainReader) Start(_a0 context.Context) error { - ret := _m.Called(_a0) - - if len(ret) == 0 { - panic("no return value specified for Start") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context) error); ok { - r0 = rf(_a0) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// ChainReader_Start_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Start' -type ChainReader_Start_Call struct { - *mock.Call -} - -// Start is a helper method to define mock.On call -// - _a0 context.Context -func (_e *ChainReader_Expecter) Start(_a0 interface{}) *ChainReader_Start_Call { - return &ChainReader_Start_Call{Call: _e.mock.On("Start", _a0)} -} - -func (_c *ChainReader_Start_Call) Run(run func(_a0 context.Context)) *ChainReader_Start_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context)) - }) - return _c -} - -func (_c *ChainReader_Start_Call) Return(_a0 error) *ChainReader_Start_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *ChainReader_Start_Call) RunAndReturn(run func(context.Context) error) *ChainReader_Start_Call { - _c.Call.Return(run) - return _c -} - -// NewChainReader creates a new instance of ChainReader. 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 NewChainReader(t interface { - mock.TestingT - Cleanup(func()) -}) *ChainReader { - mock := &ChainReader{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/core/capabilities/targets/mocks/contract_reader.go b/core/capabilities/targets/mocks/contract_reader.go new file mode 100644 index 00000000000..dd92195ec2b --- /dev/null +++ b/core/capabilities/targets/mocks/contract_reader.go @@ -0,0 +1,533 @@ +// Code generated by mockery v2.43.2. DO NOT EDIT. + +package mocks + +import ( + context "context" + + query "github.com/smartcontractkit/chainlink-common/pkg/types/query" + primitives "github.com/smartcontractkit/chainlink-common/pkg/types/query/primitives" + mock "github.com/stretchr/testify/mock" + + types "github.com/smartcontractkit/chainlink-common/pkg/types" +) + +// ContractReader is an autogenerated mock type for the ContractReader type +type ContractReader struct { + mock.Mock +} + +type ContractReader_Expecter struct { + mock *mock.Mock +} + +func (_m *ContractReader) EXPECT() *ContractReader_Expecter { + return &ContractReader_Expecter{mock: &_m.Mock} +} + +// BatchGetLatestValues provides a mock function with given fields: ctx, request +func (_m *ContractReader) 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 +} + +// ContractReader_BatchGetLatestValues_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'BatchGetLatestValues' +type ContractReader_BatchGetLatestValues_Call struct { + *mock.Call +} + +// BatchGetLatestValues is a helper method to define mock.On call +// - ctx context.Context +// - request types.BatchGetLatestValuesRequest +func (_e *ContractReader_Expecter) BatchGetLatestValues(ctx interface{}, request interface{}) *ContractReader_BatchGetLatestValues_Call { + return &ContractReader_BatchGetLatestValues_Call{Call: _e.mock.On("BatchGetLatestValues", ctx, request)} +} + +func (_c *ContractReader_BatchGetLatestValues_Call) Run(run func(ctx context.Context, request types.BatchGetLatestValuesRequest)) *ContractReader_BatchGetLatestValues_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(types.BatchGetLatestValuesRequest)) + }) + return _c +} + +func (_c *ContractReader_BatchGetLatestValues_Call) Return(_a0 types.BatchGetLatestValuesResult, _a1 error) *ContractReader_BatchGetLatestValues_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *ContractReader_BatchGetLatestValues_Call) RunAndReturn(run func(context.Context, types.BatchGetLatestValuesRequest) (types.BatchGetLatestValuesResult, error)) *ContractReader_BatchGetLatestValues_Call { + _c.Call.Return(run) + return _c +} + +// Bind provides a mock function with given fields: ctx, bindings +func (_m *ContractReader) Bind(ctx context.Context, bindings []types.BoundContract) error { + ret := _m.Called(ctx, bindings) + + if len(ret) == 0 { + panic("no return value specified for Bind") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, []types.BoundContract) error); ok { + r0 = rf(ctx, bindings) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// ContractReader_Bind_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Bind' +type ContractReader_Bind_Call struct { + *mock.Call +} + +// Bind is a helper method to define mock.On call +// - ctx context.Context +// - bindings []types.BoundContract +func (_e *ContractReader_Expecter) Bind(ctx interface{}, bindings interface{}) *ContractReader_Bind_Call { + return &ContractReader_Bind_Call{Call: _e.mock.On("Bind", ctx, bindings)} +} + +func (_c *ContractReader_Bind_Call) Run(run func(ctx context.Context, bindings []types.BoundContract)) *ContractReader_Bind_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].([]types.BoundContract)) + }) + return _c +} + +func (_c *ContractReader_Bind_Call) Return(_a0 error) *ContractReader_Bind_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *ContractReader_Bind_Call) RunAndReturn(run func(context.Context, []types.BoundContract) error) *ContractReader_Bind_Call { + _c.Call.Return(run) + return _c +} + +// Close provides a mock function with given fields: +func (_m *ContractReader) Close() error { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for Close") + } + + var r0 error + if rf, ok := ret.Get(0).(func() error); ok { + r0 = rf() + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// ContractReader_Close_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Close' +type ContractReader_Close_Call struct { + *mock.Call +} + +// Close is a helper method to define mock.On call +func (_e *ContractReader_Expecter) Close() *ContractReader_Close_Call { + return &ContractReader_Close_Call{Call: _e.mock.On("Close")} +} + +func (_c *ContractReader_Close_Call) Run(run func()) *ContractReader_Close_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *ContractReader_Close_Call) Return(_a0 error) *ContractReader_Close_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *ContractReader_Close_Call) RunAndReturn(run func() error) *ContractReader_Close_Call { + _c.Call.Return(run) + return _c +} + +// GetLatestValue provides a mock function with given fields: ctx, readIdentifier, confidenceLevel, params, returnVal +func (_m *ContractReader) GetLatestValue(ctx context.Context, readIdentifier string, confidenceLevel primitives.ConfidenceLevel, params interface{}, returnVal interface{}) error { + ret := _m.Called(ctx, readIdentifier, confidenceLevel, params, returnVal) + + if len(ret) == 0 { + panic("no return value specified for GetLatestValue") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, primitives.ConfidenceLevel, interface{}, interface{}) error); ok { + r0 = rf(ctx, readIdentifier, confidenceLevel, params, returnVal) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// ContractReader_GetLatestValue_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetLatestValue' +type ContractReader_GetLatestValue_Call struct { + *mock.Call +} + +// GetLatestValue is a helper method to define mock.On call +// - ctx context.Context +// - readIdentifier string +// - confidenceLevel primitives.ConfidenceLevel +// - params interface{} +// - returnVal interface{} +func (_e *ContractReader_Expecter) GetLatestValue(ctx interface{}, readIdentifier interface{}, confidenceLevel interface{}, params interface{}, returnVal interface{}) *ContractReader_GetLatestValue_Call { + return &ContractReader_GetLatestValue_Call{Call: _e.mock.On("GetLatestValue", ctx, readIdentifier, confidenceLevel, params, returnVal)} +} + +func (_c *ContractReader_GetLatestValue_Call) Run(run func(ctx context.Context, readIdentifier string, confidenceLevel primitives.ConfidenceLevel, params interface{}, returnVal interface{})) *ContractReader_GetLatestValue_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string), args[2].(primitives.ConfidenceLevel), args[3].(interface{}), args[4].(interface{})) + }) + return _c +} + +func (_c *ContractReader_GetLatestValue_Call) Return(_a0 error) *ContractReader_GetLatestValue_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *ContractReader_GetLatestValue_Call) RunAndReturn(run func(context.Context, string, primitives.ConfidenceLevel, interface{}, interface{}) error) *ContractReader_GetLatestValue_Call { + _c.Call.Return(run) + return _c +} + +// HealthReport provides a mock function with given fields: +func (_m *ContractReader) HealthReport() map[string]error { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for HealthReport") + } + + var r0 map[string]error + if rf, ok := ret.Get(0).(func() map[string]error); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(map[string]error) + } + } + + return r0 +} + +// ContractReader_HealthReport_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'HealthReport' +type ContractReader_HealthReport_Call struct { + *mock.Call +} + +// HealthReport is a helper method to define mock.On call +func (_e *ContractReader_Expecter) HealthReport() *ContractReader_HealthReport_Call { + return &ContractReader_HealthReport_Call{Call: _e.mock.On("HealthReport")} +} + +func (_c *ContractReader_HealthReport_Call) Run(run func()) *ContractReader_HealthReport_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *ContractReader_HealthReport_Call) Return(_a0 map[string]error) *ContractReader_HealthReport_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *ContractReader_HealthReport_Call) RunAndReturn(run func() map[string]error) *ContractReader_HealthReport_Call { + _c.Call.Return(run) + return _c +} + +// Name provides a mock function with given fields: +func (_m *ContractReader) Name() string { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for Name") + } + + var r0 string + if rf, ok := ret.Get(0).(func() string); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(string) + } + + return r0 +} + +// ContractReader_Name_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Name' +type ContractReader_Name_Call struct { + *mock.Call +} + +// Name is a helper method to define mock.On call +func (_e *ContractReader_Expecter) Name() *ContractReader_Name_Call { + return &ContractReader_Name_Call{Call: _e.mock.On("Name")} +} + +func (_c *ContractReader_Name_Call) Run(run func()) *ContractReader_Name_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *ContractReader_Name_Call) Return(_a0 string) *ContractReader_Name_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *ContractReader_Name_Call) RunAndReturn(run func() string) *ContractReader_Name_Call { + _c.Call.Return(run) + return _c +} + +// QueryKey provides a mock function with given fields: ctx, contract, filter, limitAndSort, sequenceDataType +func (_m *ContractReader) QueryKey(ctx context.Context, contract types.BoundContract, filter query.KeyFilter, limitAndSort query.LimitAndSort, sequenceDataType interface{}) ([]types.Sequence, error) { + ret := _m.Called(ctx, contract, filter, limitAndSort, sequenceDataType) + + if len(ret) == 0 { + panic("no return value specified for QueryKey") + } + + var r0 []types.Sequence + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, types.BoundContract, query.KeyFilter, query.LimitAndSort, interface{}) ([]types.Sequence, error)); ok { + return rf(ctx, contract, filter, limitAndSort, sequenceDataType) + } + if rf, ok := ret.Get(0).(func(context.Context, types.BoundContract, query.KeyFilter, query.LimitAndSort, interface{}) []types.Sequence); ok { + r0 = rf(ctx, contract, filter, limitAndSort, sequenceDataType) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]types.Sequence) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, types.BoundContract, query.KeyFilter, query.LimitAndSort, interface{}) error); ok { + r1 = rf(ctx, contract, filter, limitAndSort, sequenceDataType) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ContractReader_QueryKey_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'QueryKey' +type ContractReader_QueryKey_Call struct { + *mock.Call +} + +// QueryKey is a helper method to define mock.On call +// - ctx context.Context +// - contract types.BoundContract +// - filter query.KeyFilter +// - limitAndSort query.LimitAndSort +// - sequenceDataType interface{} +func (_e *ContractReader_Expecter) QueryKey(ctx interface{}, contract interface{}, filter interface{}, limitAndSort interface{}, sequenceDataType interface{}) *ContractReader_QueryKey_Call { + return &ContractReader_QueryKey_Call{Call: _e.mock.On("QueryKey", ctx, contract, filter, limitAndSort, sequenceDataType)} +} + +func (_c *ContractReader_QueryKey_Call) Run(run func(ctx context.Context, contract types.BoundContract, filter query.KeyFilter, limitAndSort query.LimitAndSort, sequenceDataType interface{})) *ContractReader_QueryKey_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(types.BoundContract), args[2].(query.KeyFilter), args[3].(query.LimitAndSort), args[4].(interface{})) + }) + return _c +} + +func (_c *ContractReader_QueryKey_Call) Return(_a0 []types.Sequence, _a1 error) *ContractReader_QueryKey_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *ContractReader_QueryKey_Call) RunAndReturn(run func(context.Context, types.BoundContract, query.KeyFilter, query.LimitAndSort, interface{}) ([]types.Sequence, error)) *ContractReader_QueryKey_Call { + _c.Call.Return(run) + return _c +} + +// Ready provides a mock function with given fields: +func (_m *ContractReader) Ready() error { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for Ready") + } + + var r0 error + if rf, ok := ret.Get(0).(func() error); ok { + r0 = rf() + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// ContractReader_Ready_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Ready' +type ContractReader_Ready_Call struct { + *mock.Call +} + +// Ready is a helper method to define mock.On call +func (_e *ContractReader_Expecter) Ready() *ContractReader_Ready_Call { + return &ContractReader_Ready_Call{Call: _e.mock.On("Ready")} +} + +func (_c *ContractReader_Ready_Call) Run(run func()) *ContractReader_Ready_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *ContractReader_Ready_Call) Return(_a0 error) *ContractReader_Ready_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *ContractReader_Ready_Call) RunAndReturn(run func() error) *ContractReader_Ready_Call { + _c.Call.Return(run) + return _c +} + +// Start provides a mock function with given fields: _a0 +func (_m *ContractReader) Start(_a0 context.Context) error { + ret := _m.Called(_a0) + + if len(ret) == 0 { + panic("no return value specified for Start") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context) error); ok { + r0 = rf(_a0) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// ContractReader_Start_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Start' +type ContractReader_Start_Call struct { + *mock.Call +} + +// Start is a helper method to define mock.On call +// - _a0 context.Context +func (_e *ContractReader_Expecter) Start(_a0 interface{}) *ContractReader_Start_Call { + return &ContractReader_Start_Call{Call: _e.mock.On("Start", _a0)} +} + +func (_c *ContractReader_Start_Call) Run(run func(_a0 context.Context)) *ContractReader_Start_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *ContractReader_Start_Call) Return(_a0 error) *ContractReader_Start_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *ContractReader_Start_Call) RunAndReturn(run func(context.Context) error) *ContractReader_Start_Call { + _c.Call.Return(run) + return _c +} + +// Unbind provides a mock function with given fields: ctx, bindings +func (_m *ContractReader) Unbind(ctx context.Context, bindings []types.BoundContract) error { + ret := _m.Called(ctx, bindings) + + if len(ret) == 0 { + panic("no return value specified for Unbind") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, []types.BoundContract) error); ok { + r0 = rf(ctx, bindings) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// ContractReader_Unbind_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Unbind' +type ContractReader_Unbind_Call struct { + *mock.Call +} + +// Unbind is a helper method to define mock.On call +// - ctx context.Context +// - bindings []types.BoundContract +func (_e *ContractReader_Expecter) Unbind(ctx interface{}, bindings interface{}) *ContractReader_Unbind_Call { + return &ContractReader_Unbind_Call{Call: _e.mock.On("Unbind", ctx, bindings)} +} + +func (_c *ContractReader_Unbind_Call) Run(run func(ctx context.Context, bindings []types.BoundContract)) *ContractReader_Unbind_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].([]types.BoundContract)) + }) + return _c +} + +func (_c *ContractReader_Unbind_Call) Return(_a0 error) *ContractReader_Unbind_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *ContractReader_Unbind_Call) RunAndReturn(run func(context.Context, []types.BoundContract) error) *ContractReader_Unbind_Call { + _c.Call.Return(run) + return _c +} + +// NewContractReader creates a new instance of ContractReader. 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 NewContractReader(t interface { + mock.TestingT + Cleanup(func()) +}) *ContractReader { + mock := &ContractReader{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/core/capabilities/targets/write_target.go b/core/capabilities/targets/write_target.go index a3ebe77c978..83c39e34578 100644 --- a/core/capabilities/targets/write_target.go +++ b/core/capabilities/targets/write_target.go @@ -25,6 +25,7 @@ var ( type WriteTarget struct { cr commontypes.ContractReader cw commontypes.ChainWriter + binding commontypes.BoundContract forwarderAddress string // The minimum amount of gas that the receiver contract must get to process the forwarder report receiverGasMinimum uint64 @@ -59,6 +60,10 @@ func NewWriteTarget(lggr logger.Logger, id string, cr commontypes.ContractReader return &WriteTarget{ cr, cw, + commontypes.BoundContract{ + Address: forwarderAddress, + Name: "forwarder", + }, forwarderAddress, txGasLimit - FORWARDER_CONTRACT_LOGIC_GAS_COST, info, @@ -176,10 +181,7 @@ func (cap *WriteTarget) Execute(ctx context.Context, rawRequest capabilities.Cap // cannot be run during initialization. if !cap.bound { cap.lggr.Debugw("Binding to forwarder address") - err := cap.cr.Bind(ctx, []commontypes.BoundContract{{ - Address: cap.forwarderAddress, - Name: "forwarder", - }}) + err := cap.cr.Bind(ctx, []commontypes.BoundContract{cap.binding}) if err != nil { return capabilities.CapabilityResponse{}, err } @@ -209,7 +211,7 @@ func (cap *WriteTarget) Execute(ctx context.Context, rawRequest capabilities.Cap ReportId: request.Inputs.SignedReport.ID, } var transmissionInfo TransmissionInfo - if err = cap.cr.GetLatestValue(ctx, "forwarder", "getTransmissionInfo", primitives.Unconfirmed, queryInputs, &transmissionInfo); err != nil { + if err = cap.cr.GetLatestValue(ctx, cap.binding.ReadIdentifier("getTransmissionInfo"), primitives.Unconfirmed, queryInputs, &transmissionInfo); err != nil { return capabilities.CapabilityResponse{}, fmt.Errorf("failed to getTransmissionInfo latest value: %w", err) } diff --git a/core/capabilities/targets/write_target_test.go b/core/capabilities/targets/write_target_test.go index aa0717aa0ed..cdef0cc83b5 100644 --- a/core/capabilities/targets/write_target_test.go +++ b/core/capabilities/targets/write_target_test.go @@ -14,7 +14,9 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/capabilities" "github.com/smartcontractkit/chainlink-common/pkg/types" + "github.com/smartcontractkit/chainlink-common/pkg/types/query/primitives" "github.com/smartcontractkit/chainlink-common/pkg/values" + "github.com/smartcontractkit/chainlink/v2/core/capabilities/targets" "github.com/smartcontractkit/chainlink/v2/core/capabilities/targets/mocks" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" @@ -26,7 +28,7 @@ func TestWriteTarget(t *testing.T) { ctx := context.Background() cw := mocks.NewChainWriter(t) - cr := mocks.NewChainReader(t) + cr := mocks.NewContractReader(t) forwarderA := testutils.NewAddress() forwarderAddr := forwarderA.Hex() @@ -69,15 +71,15 @@ func TestWriteTarget(t *testing.T) { WorkflowExecutionID: hex.EncodeToString(reportMetadata.WorkflowExecutionID[:]), } - cr.On("Bind", mock.Anything, []types.BoundContract{ - { - Address: forwarderAddr, - Name: "forwarder", - }, - }).Return(nil) + binding := types.BoundContract{ + Address: forwarderAddr, + Name: "forwarder", + } + + cr.On("Bind", mock.Anything, []types.BoundContract{binding}).Return(nil) - cr.On("GetLatestValue", mock.Anything, "forwarder", "getTransmissionInfo", mock.Anything, mock.Anything, mock.Anything).Return(nil).Run(func(args mock.Arguments) { - transmissionInfo := args.Get(5).(*targets.TransmissionInfo) + cr.EXPECT().GetLatestValue(mock.Anything, binding.ReadIdentifier("getTransmissionInfo"), mock.Anything, mock.Anything, mock.Anything).Return(nil).Run(func(_ context.Context, _ string, _ primitives.ConfidenceLevel, _, retVal any) { + transmissionInfo := retVal.(*targets.TransmissionInfo) *transmissionInfo = targets.TransmissionInfo{ GasLimit: big.NewInt(0), InvalidReceiver: false, @@ -170,7 +172,7 @@ func TestWriteTarget(t *testing.T) { Config: config, Inputs: validInputs, } - cr.On("GetLatestValue", mock.Anything, "forwarder", "getTransmissionInfo", mock.Anything, mock.Anything, mock.Anything).Return(errors.New("reader error")) + cr.EXPECT().GetLatestValue(mock.Anything, binding.ReadIdentifier("getTransmissionInfo"), mock.Anything, mock.Anything, mock.Anything).Return(errors.New("reader error")) _, err = writeTarget.Execute(ctx, req) require.Error(t, err) diff --git a/core/scripts/go.mod b/core/scripts/go.mod index af64d0a0b39..1a19bf69273 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.4.0 github.com/smartcontractkit/chainlink-automation v1.0.4 - github.com/smartcontractkit/chainlink-common v0.2.2-0.20240906132254-14a5c7af361f + github.com/smartcontractkit/chainlink-common v0.2.2-0.20240909141252-663388d38293 github.com/smartcontractkit/chainlink/v2 v2.0.0-00010101000000-000000000000 github.com/smartcontractkit/libocr v0.0.0-20240717100443-f6226e09bee7 github.com/spf13/cobra v1.8.0 @@ -270,10 +270,10 @@ require ( github.com/shirou/gopsutil v3.21.11+incompatible // indirect github.com/shirou/gopsutil/v3 v3.24.3 // indirect github.com/smartcontractkit/chain-selectors v1.0.21 // indirect - github.com/smartcontractkit/chainlink-ccip v0.0.0-20240905153234-86019f205c9b // indirect + github.com/smartcontractkit/chainlink-ccip v0.0.0-20240909152240-a6969c1002e6 // indirect github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240710121324-3ed288aa9b45 // indirect github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240904093355-e40169857652 // indirect - github.com/smartcontractkit/chainlink-feeds v0.0.0-20240710170203-5b41615da827 // indirect + github.com/smartcontractkit/chainlink-feeds v0.0.0-20240909142234-299749c4c0b5 // indirect github.com/smartcontractkit/chainlink-solana v1.1.1-0.20240904154226-abc1ed5c962a // indirect github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20240709043547-03612098f799 // indirect github.com/smartcontractkit/grpc-proxy v0.0.0-20240830132753-a7e17fec5ab7 // indirect diff --git a/core/scripts/go.sum b/core/scripts/go.sum index a536f2bdd96..5644fafbb2f 100644 --- a/core/scripts/go.sum +++ b/core/scripts/go.sum @@ -1188,16 +1188,16 @@ github.com/smartcontractkit/chain-selectors v1.0.21 h1:KCR9SA7PhOexaBzFieHoLv1Wo github.com/smartcontractkit/chain-selectors v1.0.21/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-ccip v0.0.0-20240905153234-86019f205c9b h1:/PQDTP/ETmEXCv3qokVs5JqMcHDFP8TWdkcQAzs/nQg= -github.com/smartcontractkit/chainlink-ccip v0.0.0-20240905153234-86019f205c9b/go.mod h1:Z9lQ5t20kRk28pzRLnqAJZUVOw8E6/siA3P3MLyKqoM= -github.com/smartcontractkit/chainlink-common v0.2.2-0.20240906132254-14a5c7af361f h1:a6zEDLYgTQ4G+9PgLX8G2bF40BdsOY5//5i+whYwLlQ= -github.com/smartcontractkit/chainlink-common v0.2.2-0.20240906132254-14a5c7af361f/go.mod h1:D/qaCoq0SxXzg5NRN5FtBRv98VBf+D2NOC++RbvvuOc= +github.com/smartcontractkit/chainlink-ccip v0.0.0-20240909152240-a6969c1002e6 h1:3VbeaqoBblboQ3ytpM7UZzL7MXoHctaaXGGkJ8XkDhY= +github.com/smartcontractkit/chainlink-ccip v0.0.0-20240909152240-a6969c1002e6/go.mod h1:v8hmGodMN1s1TQnvZepZ3Pbo+PyzWVdXag7JzJnvrkI= +github.com/smartcontractkit/chainlink-common v0.2.2-0.20240909141252-663388d38293 h1:Q19HQPzISHKEIP0rNFGEiIFvMEeAgb1YRXEFnyBMnNM= +github.com/smartcontractkit/chainlink-common v0.2.2-0.20240909141252-663388d38293/go.mod h1:D/qaCoq0SxXzg5NRN5FtBRv98VBf+D2NOC++RbvvuOc= 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-20240904093355-e40169857652 h1:0aZ3HiEz2bMM5ywHAyKlFMN95qTzpNDn7uvnHLrFX6s= github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240904093355-e40169857652/go.mod h1:PwPcmQNAzVmU8r8JWKrDRgvXesDwxnqbMD6DvYt/Z7M= -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-feeds v0.0.0-20240909142234-299749c4c0b5 h1:88ZNrxZd0Uxn9934G/3L5AABTX13iorQJdUXcSUewwg= +github.com/smartcontractkit/chainlink-feeds v0.0.0-20240909142234-299749c4c0b5/go.mod h1:/QPAcfj5RQ4pNmDBLJ/Or7EjMAvFU3Xb1pM9gl0jfao= github.com/smartcontractkit/chainlink-solana v1.1.1-0.20240904154226-abc1ed5c962a h1:0zbG9reudNAJC/Rwe3tYX7kshVtOEXXAhk0uLg/62Y4= github.com/smartcontractkit/chainlink-solana v1.1.1-0.20240904154226-abc1ed5c962a/go.mod h1:4jS7gAEHONm1dE7SkiUV16FXzuCMycG8X/9WXwNAHQI= github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20240709043547-03612098f799 h1:HyLTySm7BR+oNfZqDTkVJ25wnmcTtxBBD31UkFL+kEM= diff --git a/core/services/gateway/handlers/functions/allowlist/allowlist_test.go b/core/services/gateway/handlers/functions/allowlist/allowlist_test.go index 500985acc31..b8735bbf8af 100644 --- a/core/services/gateway/handlers/functions/allowlist/allowlist_test.go +++ b/core/services/gateway/handlers/functions/allowlist/allowlist_test.go @@ -21,7 +21,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/gateway/handlers/functions/allowlist" amocks "github.com/smartcontractkit/chainlink/v2/core/services/gateway/handlers/functions/allowlist/mocks" - "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm" + "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/codec" "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/types" ) @@ -376,7 +376,7 @@ func encodeTypeAndVersionResponse(typeAndVersion string) ([]byte, error) { codecConfig := types.CodecConfig{Configs: map[string]types.ChainCodecConfig{ codecName: {TypeABI: evmEncoderConfig}, }} - encoder, err := evm.NewCodec(codecConfig) + encoder, err := codec.NewCodec(codecConfig) if err != nil { return nil, err } diff --git a/core/services/ocr2/plugins/median/services.go b/core/services/ocr2/plugins/median/services.go index 897531b82fb..866044fcc92 100644 --- a/core/services/ocr2/plugins/median/services.go +++ b/core/services/ocr2/plugins/median/services.go @@ -167,11 +167,11 @@ func NewMedianServices(ctx context.Context, abort() return } - median := loop.NewMedianService(lggr, telem, cmdFn, medianProvider, dataSource, juelsPerFeeCoinSource, gasPriceSubunitsDataSource, errorLog) + median := loop.NewMedianService(lggr, telem, cmdFn, medianProvider, spec.ContractID, dataSource, juelsPerFeeCoinSource, gasPriceSubunitsDataSource, errorLog) argsNoPlugin.ReportingPluginFactory = median srvs = append(srvs, median) } else { - argsNoPlugin.ReportingPluginFactory, err = median.NewPlugin(lggr).NewMedianFactory(ctx, medianProvider, dataSource, juelsPerFeeCoinSource, gasPriceSubunitsDataSource, errorLog) + argsNoPlugin.ReportingPluginFactory, err = median.NewPlugin(lggr).NewMedianFactory(ctx, medianProvider, spec.ContractID, dataSource, juelsPerFeeCoinSource, gasPriceSubunitsDataSource, errorLog) if err != nil { err = fmt.Errorf("failed to create median factory: %w", err) abort() diff --git a/core/services/registrysyncer/syncer.go b/core/services/registrysyncer/syncer.go index ab50c448b5c..1ef78b6edcc 100644 --- a/core/services/registrysyncer/syncer.go +++ b/core/services/registrysyncer/syncer.go @@ -44,13 +44,13 @@ type RegistrySyncer interface { type registrySyncer struct { services.StateMachine - stopCh services.StopChan - launchers []Launcher - reader types.ContractReader - initReader func(ctx context.Context, lggr logger.Logger, relayer ContractReaderFactory, registryAddress string) (types.ContractReader, error) - relayer ContractReaderFactory - registryAddress string - getPeerID func() (p2ptypes.PeerID, error) + stopCh services.StopChan + launchers []Launcher + reader types.ContractReader + initReader func(ctx context.Context, lggr logger.Logger, relayer ContractReaderFactory, capabilitiesContract types.BoundContract) (types.ContractReader, error) + relayer ContractReaderFactory + capabilitiesContract types.BoundContract + getPeerID func() (p2ptypes.PeerID, error) orm ORM @@ -76,21 +76,24 @@ func New( orm ORM, ) (RegistrySyncer, error) { return ®istrySyncer{ - stopCh: make(services.StopChan), - updateChan: make(chan *LocalRegistry), - lggr: lggr.Named("RegistrySyncer"), - relayer: relayer, - registryAddress: registryAddress, - initReader: newReader, - orm: orm, - getPeerID: getPeerID, + stopCh: make(services.StopChan), + updateChan: make(chan *LocalRegistry), + lggr: lggr.Named("RegistrySyncer"), + relayer: relayer, + capabilitiesContract: types.BoundContract{ + Address: registryAddress, + Name: "CapabilitiesRegistry", + }, + initReader: newReader, + orm: orm, + getPeerID: getPeerID, }, nil } // NOTE: this can't be called while initializing the syncer and needs to be called in the sync loop. // This is because Bind() makes an onchain call to verify that the contract address exists, and if // called during initialization, this results in a "no live nodes" error. -func newReader(ctx context.Context, lggr logger.Logger, relayer ContractReaderFactory, remoteRegistryAddress string) (types.ContractReader, error) { +func newReader(ctx context.Context, lggr logger.Logger, relayer ContractReaderFactory, capabilitiesContract types.BoundContract) (types.ContractReader, error) { contractReaderConfig := evmrelaytypes.ChainReaderConfig{ Contracts: map[string]evmrelaytypes.ChainContractReader{ "CapabilitiesRegistry": { @@ -120,12 +123,7 @@ func newReader(ctx context.Context, lggr logger.Logger, relayer ContractReaderFa return nil, err } - err = cr.Bind(ctx, []types.BoundContract{ - { - Address: remoteRegistryAddress, - Name: "CapabilitiesRegistry", - }, - }) + err = cr.Bind(ctx, []types.BoundContract{capabilitiesContract}) return cr, err } @@ -197,8 +195,9 @@ func (s *registrySyncer) updateStateLoop() { } func (s *registrySyncer) localRegistry(ctx context.Context) (*LocalRegistry, error) { - var caps []kcr.CapabilitiesRegistryCapabilityInfo - err := s.reader.GetLatestValue(ctx, "CapabilitiesRegistry", "getCapabilities", primitives.Unconfirmed, nil, &caps) + caps := []kcr.CapabilitiesRegistryCapabilityInfo{} + + err := s.reader.GetLatestValue(ctx, s.capabilitiesContract.ReadIdentifier("getCapabilities"), primitives.Unconfirmed, nil, &caps) if err != nil { return nil, err } @@ -215,8 +214,9 @@ func (s *registrySyncer) localRegistry(ctx context.Context) (*LocalRegistry, err hashedIDsToCapabilityIDs[c.HashedId] = cid } - var dons []kcr.CapabilitiesRegistryDONInfo - err = s.reader.GetLatestValue(ctx, "CapabilitiesRegistry", "getDONs", primitives.Unconfirmed, nil, &dons) + dons := []kcr.CapabilitiesRegistryDONInfo{} + + err = s.reader.GetLatestValue(ctx, s.capabilitiesContract.ReadIdentifier("getDONs"), primitives.Unconfirmed, nil, &dons) if err != nil { return nil, err } @@ -241,8 +241,9 @@ func (s *registrySyncer) localRegistry(ctx context.Context) (*LocalRegistry, err } } - var nodes []kcr.CapabilitiesRegistryNodeInfo - err = s.reader.GetLatestValue(ctx, "CapabilitiesRegistry", "getNodes", primitives.Unconfirmed, nil, &nodes) + nodes := []kcr.CapabilitiesRegistryNodeInfo{} + + err = s.reader.GetLatestValue(ctx, s.capabilitiesContract.ReadIdentifier("getNodes"), primitives.Unconfirmed, nil, &nodes) if err != nil { return nil, err } @@ -271,7 +272,7 @@ func (s *registrySyncer) Sync(ctx context.Context, isInitialSync bool) error { } if s.reader == nil { - reader, err := s.initReader(ctx, s.lggr, s.relayer, s.registryAddress) + reader, err := s.initReader(ctx, s.lggr, s.relayer, s.capabilitiesContract) if err != nil { return err } diff --git a/core/services/relay/evm/binding.go b/core/services/relay/evm/binding.go deleted file mode 100644 index 412e33c6094..00000000000 --- a/core/services/relay/evm/binding.go +++ /dev/null @@ -1,18 +0,0 @@ -package evm - -import ( - "context" - - commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" - "github.com/smartcontractkit/chainlink-common/pkg/types/query" - "github.com/smartcontractkit/chainlink-common/pkg/types/query/primitives" -) - -type readBinding interface { - Bind(ctx context.Context, binding commontypes.BoundContract) error - SetCodec(codec commontypes.RemoteCodec) - Register(ctx context.Context) error - Unregister(ctx context.Context) error - GetLatestValue(ctx context.Context, confidenceLevel primitives.ConfidenceLevel, params, returnVal any) error - QueryKey(ctx context.Context, filter query.KeyFilter, limitAndSort query.LimitAndSort, sequenceDataType any) ([]commontypes.Sequence, error) -} diff --git a/core/services/relay/evm/bindings.go b/core/services/relay/evm/bindings.go deleted file mode 100644 index 4d4760db52c..00000000000 --- a/core/services/relay/evm/bindings.go +++ /dev/null @@ -1,120 +0,0 @@ -package evm - -import ( - "context" - "fmt" - - commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" -) - -// bindings manage all contract bindings, key is contract name. - -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.contractBindings[contractName] - if !cbExists { - return nil, fmt.Errorf("%w: no contract named %s", commontypes.ErrInvalidType, contractName) - } - - rb, rbExists := cb.readBindings[readName] - if !rbExists { - return nil, fmt.Errorf("%w: no readName named %s in contract %s", commontypes.ErrInvalidType, readName, contractName) - } - return rb, nil -} - -// 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.contractBindings[contractName] - if !cbExists { - cb = &contractBinding{ - name: contractName, - readBindings: make(map[string]readBinding), - } - b.contractBindings[contractName] = cb - } - cb.readBindings[readName] = rb -} - -// Bind binds contract addresses to contract bindings and read bindings. -// 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.contractBindings[bc.Name] - if !cbExists { - return fmt.Errorf("%w: no contract named %s", commontypes.ErrInvalidConfig, bc.Name) - } - - if err := cb.Bind(ctx, lp, bc); err != nil { - return err - } - - for _, rb := range cb.readBindings { - if err := rb.Bind(ctx, bc); err != nil { - return err - } - } - } - 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.contractBindings { - if err := fn(ctx, cb); err != nil { - return err - } - } - return nil -} diff --git a/core/services/relay/evm/cap_encoder.go b/core/services/relay/evm/cap_encoder.go index 790114e4c03..2a6f288a5de 100644 --- a/core/services/relay/evm/cap_encoder.go +++ b/core/services/relay/evm/cap_encoder.go @@ -10,7 +10,9 @@ import ( consensustypes "github.com/smartcontractkit/chainlink-common/pkg/capabilities/consensus/ocr3/types" commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" "github.com/smartcontractkit/chainlink-common/pkg/values" + abiutil "github.com/smartcontractkit/chainlink/v2/core/chains/evm/abi" + "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/codec" "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/types" ) @@ -47,7 +49,7 @@ func NewEVMEncoder(config *values.Map) (consensustypes.Encoder, error) { codecConfig := types.CodecConfig{Configs: map[string]types.ChainCodecConfig{ encoderName: {TypeABI: string(jsonSelector)}, }} - c, err := NewCodec(codecConfig) + c, err := codec.NewCodec(codecConfig) if err != nil { return nil, err } diff --git a/core/services/relay/evm/chain_reader.go b/core/services/relay/evm/chain_reader.go index 0f1f6e72dd7..c9cc18e34a7 100644 --- a/core/services/relay/evm/chain_reader.go +++ b/core/services/relay/evm/chain_reader.go @@ -7,12 +7,12 @@ import ( "reflect" "slices" "strings" - "sync" "time" "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" - "github.com/smartcontractkit/chainlink-common/pkg/codec" + commoncodec "github.com/smartcontractkit/chainlink-common/pkg/codec" "github.com/smartcontractkit/chainlink-common/pkg/logger" commonservices "github.com/smartcontractkit/chainlink-common/pkg/services" commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" @@ -23,6 +23,8 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" "github.com/smartcontractkit/chainlink/v2/core/services" + "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/codec" + "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/read" "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/types" ) @@ -32,13 +34,13 @@ type ChainReaderService interface { } type chainReader struct { - lggr logger.Logger - ht logpoller.HeadTracker - lp logpoller.LogPoller - client evmclient.Client - parsed *ParsedTypes - bindings - codec commontypes.RemoteCodec + lggr logger.Logger + ht logpoller.HeadTracker + lp logpoller.LogPoller + client evmclient.Client + parsed *codec.ParsedTypes + bindings *read.BindingsRegistry + codec commontypes.RemoteCodec commonservices.StateMachine } @@ -53,8 +55,8 @@ func NewChainReaderService(ctx context.Context, lggr logger.Logger, lp logpoller 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{}}, + bindings: read.NewBindingsRegistry(), + parsed: &codec.ParsedTypes{EncoderDefs: map[string]types.CodecEntry{}, DecoderDefs: map[string]types.CodecEntry{}}, } var err error @@ -66,21 +68,16 @@ func NewChainReaderService(ctx context.Context, lggr logger.Logger, lp logpoller return nil, err } - cr.bindings.BatchCaller = NewDynamicLimitedBatchCaller( + cr.bindings.SetBatchCaller(read.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) - } - return nil - }) + read.DefaultRpcBatchSizeLimit, + read.DefaultRpcBatchBackOffMultiplier, + read.DefaultMaxParallelRpcCalls, + )) + + cr.bindings.SetCodecAll(cr.codec) return cr, err } @@ -129,10 +126,13 @@ func (cr *chainReader) init(chainContractReaders map[string]types.ChainContractR } } - if cr.bindings.contractBindings[contractName] == nil { + if !cr.bindings.HasContractBinding(contractName) { return fmt.Errorf("%w: no read bindings added for contract: %s", commontypes.ErrInvalidConfig, contractName) } - cr.bindings.contractBindings[contractName].pollingFilter = chainContractReader.PollingFilter.ToLPFilter(eventSigsForContractFilter) + + if err := cr.bindings.SetFilter(contractName, chainContractReader.PollingFilter.ToLPFilter(eventSigsForContractFilter)); err != nil { + return err + } } return nil } @@ -142,14 +142,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.bindings.ForEach(ctx, func(c context.Context, cb *contractBinding) error { - for _, rb := range cb.readBindings { - if err := rb.Register(ctx); err != nil { - return err - } - } - return cb.Register(ctx, cr.lp) - }) + return cr.bindings.RegisterAll(ctx, cr.lp) }) } @@ -158,14 +151,8 @@ func (cr *chainReader) Close() error { return cr.StopOnce("ChainReader", func() error { ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() - 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 - } - } - return cb.Unregister(ctx, cr.lp) - }) + + return cr.bindings.UnregisterAll(ctx, cr.lp) }) } @@ -175,13 +162,13 @@ func (cr *chainReader) HealthReport() map[string]error { return map[string]error{cr.Name(): nil} } -func (cr *chainReader) GetLatestValue(ctx context.Context, contractName, method string, confidenceLevel primitives.ConfidenceLevel, params, returnVal any) error { - b, err := cr.bindings.GetReadBinding(contractName, method) +func (cr *chainReader) GetLatestValue(ctx context.Context, readName string, confidenceLevel primitives.ConfidenceLevel, params any, returnVal any) error { + binding, address, err := cr.bindings.GetReader(readName) if err != nil { return err } - return b.GetLatestValue(ctx, confidenceLevel, params, returnVal) + return binding.GetLatestValue(ctx, common.HexToAddress(address), confidenceLevel, params, returnVal) } func (cr *chainReader) BatchGetLatestValues(ctx context.Context, request commontypes.BatchGetLatestValuesRequest) (commontypes.BatchGetLatestValuesResult, error) { @@ -192,17 +179,27 @@ func (cr *chainReader) Bind(ctx context.Context, bindings []commontypes.BoundCon 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.bindings.GetReadBinding(contractName, filter.Key) +func (cr *chainReader) Unbind(ctx context.Context, bindings []commontypes.BoundContract) error { + return cr.bindings.Unbind(ctx, cr.lp, bindings) +} + +func (cr *chainReader) QueryKey( + ctx context.Context, + contract commontypes.BoundContract, + filter query.KeyFilter, + limitAndSort query.LimitAndSort, + sequenceDataType any, +) ([]commontypes.Sequence, error) { + binding, address, err := cr.bindings.GetReader(contract.ReadIdentifier(filter.Key)) if err != nil { return nil, err } - return b.QueryKey(ctx, filter, limitAndSort, sequenceDataType) + return binding.QueryKey(ctx, common.HexToAddress(address), filter, limitAndSort, sequenceDataType) } -func (cr *chainReader) CreateContractType(contractName, itemType string, forEncoding bool) (any, error) { - return cr.codec.CreateType(WrapItemType(contractName, itemType, forEncoding), forEncoding) +func (cr *chainReader) CreateContractType(readIdentifier string, forEncoding bool) (any, error) { + return cr.codec.CreateType(cr.bindings.ReadTypeIdentifier(readIdentifier, forEncoding), forEncoding) } func WrapItemType(contractName, itemType string, isParams bool) string { @@ -227,14 +224,7 @@ func (cr *chainReader) addMethod( return err } - cr.bindings.AddReadBinding(contractName, methodName, &methodBinding{ - lggr: cr.lggr, - contractName: contractName, - method: methodName, - ht: cr.ht, - client: cr.client, - confirmationsMapping: confirmations, - }) + cr.bindings.AddReader(contractName, methodName, read.NewMethodBinding(contractName, methodName, cr.client, cr.ht, confirmations, cr.lggr)) if err := cr.addEncoderDef(contractName, methodName, method.Inputs, method.ID, chainReaderDefinition.InputModifications); err != nil { return err @@ -278,65 +268,49 @@ func (cr *chainReader) addEvent(contractName, eventName string, a abi.ABI, chain return err } - eb := &eventBinding{ - contractName: contractName, - eventName: eventName, - lp: cr.lp, - hash: event.ID, - inputInfo: inputInfo, - inputModifier: inputModifier, - codecTopicInfo: codecTopicInfo, - topics: make(map[string]topicDetail), - eventDataWords: make(map[string]uint8), - confirmationsMapping: confirmations, - } - + eb := read.NewEventBinding(contractName, eventName, cr.lp, event.ID, inputInfo, inputModifier, codecTopicInfo, confirmations) if eventDefinitions := chainReaderDefinition.EventDefinitions; eventDefinitions != nil { if eventDefinitions.PollingFilter != nil { - eb.filterRegisterer = &filterRegisterer{ - pollingFilter: eventDefinitions.PollingFilter.ToLPFilter(evmtypes.HashArray{a.Events[event.Name].ID}), - filterLock: sync.Mutex{}, - } + eb.SetFilter(eventDefinitions.PollingFilter.ToLPFilter(evmtypes.HashArray{a.Events[event.Name].ID})) } if eventDefinitions.GenericDataWordNames != nil { - eb.eventDataWords = eventDefinitions.GenericDataWordNames + eb.SetDataWords(eventDefinitions.GenericDataWordNames) } cr.addQueryingReadBindings(contractName, eventDefinitions.GenericTopicNames, event.Inputs, eb) } - cr.bindings.AddReadBinding(contractName, eventName, eb) + cr.bindings.AddReader(contractName, eventName, eb) return cr.addDecoderDef(contractName, eventName, event.Inputs, chainReaderDefinition.OutputModifications) } // addQueryingReadBindings reuses the eventBinding and maps it to topic and dataWord keys used for QueryKey. -func (cr *chainReader) addQueryingReadBindings(contractName string, genericTopicNames map[string]string, eventInputs abi.Arguments, eb *eventBinding) { +func (cr *chainReader) addQueryingReadBindings(contractName string, genericTopicNames map[string]string, eventInputs abi.Arguments, eb *read.EventBinding) { // add topic readBindings for QueryKey for topicIndex, topic := range eventInputs { genericTopicName, ok := genericTopicNames[topic.Name] if ok { - eb.topics[genericTopicName] = topicDetail{ - Argument: topic, - Index: uint64(topicIndex), - } + eb.WithTopic(genericTopicName, topic, uint64(topicIndex)) } - cr.bindings.AddReadBinding(contractName, genericTopicName, eb) + + cr.bindings.AddReader(contractName, genericTopicName, eb) } // add data word readBindings for QueryKey - for genericDataWordName := range eb.eventDataWords { - cr.bindings.AddReadBinding(contractName, genericDataWordName, eb) + for genericDataWordName := range eb.GetDataWords() { + cr.bindings.AddReader(contractName, genericDataWordName, eb) } } // getEventInput returns codec entry for expected incoming event params and the modifier to be applied to the params. func (cr *chainReader) getEventInput(def types.ChainReaderDefinition, contractName, eventName string) ( - types.CodecEntry, codec.Modifier, error) { + types.CodecEntry, commoncodec.Modifier, error) { inputInfo := cr.parsed.EncoderDefs[WrapItemType(contractName, eventName, true)] + // TODO can this be simplified? Isn't this same as inputInfo.Modifier()? BCI-3909 - inMod, err := def.InputModifications.ToModifier(DecoderHooks...) + inMod, err := def.InputModifications.ToModifier(codec.DecoderHooks...) if err != nil { return nil, nil, err } @@ -349,18 +323,9 @@ func (cr *chainReader) getEventInput(def types.ChainReaderDefinition, contractNa return inputInfo, inMod, nil } -func verifyEventIndexedInputsUsed(eventName string, inputFields []string, indexArgNames map[string]bool) error { - for _, value := range inputFields { - if !indexArgNames[abi.ToCamelCase(value)] { - return fmt.Errorf("%w: %s is not an indexed argument of event %s", commontypes.ErrInvalidConfig, value, eventName) - } - } - return nil -} - -func (cr *chainReader) addEncoderDef(contractName, itemType string, args abi.Arguments, prefix []byte, inputModifications codec.ModifiersConfig) error { +func (cr *chainReader) addEncoderDef(contractName, itemType string, args abi.Arguments, prefix []byte, inputModifications commoncodec.ModifiersConfig) error { // ABI.Pack prepends the method.ID to the encodings, we'll need the encoder to do the same. - inputMod, err := inputModifications.ToModifier(DecoderHooks...) + inputMod, err := inputModifications.ToModifier(codec.DecoderHooks...) if err != nil { return err } @@ -374,16 +339,25 @@ func (cr *chainReader) addEncoderDef(contractName, itemType string, args abi.Arg return nil } -func (cr *chainReader) addDecoderDef(contractName, itemType string, outputs abi.Arguments, outputModifications codec.ModifiersConfig) error { - mod, err := outputModifications.ToModifier(DecoderHooks...) +func (cr *chainReader) addDecoderDef(contractName, itemType string, outputs abi.Arguments, outputModifications commoncodec.ModifiersConfig) error { + mod, err := outputModifications.ToModifier(codec.DecoderHooks...) if err != nil { return err } output := types.NewCodecEntry(outputs, nil, mod) - cr.parsed.DecoderDefs[WrapItemType(contractName, itemType, false)] = output + cr.parsed.DecoderDefs[read.WrapItemType(contractName, itemType, false)] = output return output.Init() } +func verifyEventIndexedInputsUsed(eventName string, inputFields []string, indexArgNames map[string]bool) error { + for _, value := range inputFields { + if !indexArgNames[abi.ToCamelCase(value)] { + return fmt.Errorf("%w: %s is not an indexed argument of event %s", commontypes.ErrInvalidConfig, value, eventName) + } + } + return nil +} + // setupEventInput returns abi args where indexed flag is set to false because we expect caller to filter with params that aren't hashed. // codecEntry has expected onchain types set, for e.g. indexed topics of type string or uint8[32] array are expected as common.Hash onchain. func setupEventInput(event abi.Event, inputFields []string) ([]abi.Argument, types.CodecEntry, map[string]bool) { @@ -441,12 +415,3 @@ func ConfirmationsFromConfig(values map[string]int) (map[primitives.ConfidenceLe return mappings, nil } - -// confidenceToConfirmations matches predefined chain agnostic confidence levels to predefined EVM finality. -func confidenceToConfirmations(confirmationsMapping map[primitives.ConfidenceLevel]evmtypes.Confirmations, confidenceLevel primitives.ConfidenceLevel) (evmtypes.Confirmations, error) { - confirmations, exists := confirmationsMapping[confidenceLevel] - if !exists { - return 0, fmt.Errorf("missing mapping for confidence level: %s", confidenceLevel) - } - return confirmations, nil -} diff --git a/core/services/relay/evm/chain_reader_historical_client_wrapper_test.go b/core/services/relay/evm/chain_reader_historical_client_wrapper_test.go index a3ea97650ec..6779b5d0209 100644 --- a/core/services/relay/evm/chain_reader_historical_client_wrapper_test.go +++ b/core/services/relay/evm/chain_reader_historical_client_wrapper_test.go @@ -17,6 +17,8 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" + "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/codec" + "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/read" "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/types" ) @@ -30,7 +32,7 @@ type ClientWithContractHistory struct { func (cwh *ClientWithContractHistory) Init(_ context.Context, config types.ChainReaderConfig) error { cwh.valsWithCall = make(map[int64]valWithCall) - parsedTypes := ParsedTypes{ + parsedTypes := codec.ParsedTypes{ EncoderDefs: make(map[string]types.CodecEntry), DecoderDefs: make(map[string]types.CodecEntry), } @@ -47,12 +49,12 @@ func (cwh *ClientWithContractHistory) Init(_ context.Context, config types.Chain continue } - inputMod, err := readDef.InputModifications.ToModifier(DecoderHooks...) + inputMod, err := readDef.InputModifications.ToModifier(codec.DecoderHooks...) if err != nil { return err } - outputMod, err := readDef.OutputModifications.ToModifier(DecoderHooks...) + outputMod, err := readDef.OutputModifications.ToModifier(codec.DecoderHooks...) if err != nil { return err } @@ -67,16 +69,18 @@ func (cwh *ClientWithContractHistory) Init(_ context.Context, config types.Chain return err } - parsedTypes.EncoderDefs[WrapItemType(contractName, genericName, true)] = input - parsedTypes.DecoderDefs[WrapItemType(contractName, genericName, false)] = output + parsedTypes.EncoderDefs[read.WrapItemType(contractName, genericName, true)] = input + parsedTypes.DecoderDefs[read.WrapItemType(contractName, genericName, false)] = output } } - codec, err := parsedTypes.ToCodec() + parsedCodec, err := parsedTypes.ToCodec() if err != nil { return err } - cwh.codec = codec + + cwh.codec = parsedCodec + return nil } @@ -122,7 +126,7 @@ func (cwh *ClientWithContractHistory) CallContract(ctx context.Context, msg ethe } // encode the expected call to compare with the actual call - dataToCmp, err := cwh.codec.Encode(ctx, valAndCall.Params, WrapItemType(valAndCall.ContractName, valAndCall.ReadName, true)) + dataToCmp, err := cwh.codec.Encode(ctx, valAndCall.Params, read.WrapItemType(valAndCall.ContractName, valAndCall.ReadName, true)) if err != nil { return nil, err } diff --git a/core/services/relay/evm/chain_writer.go b/core/services/relay/evm/chain_writer.go index 1e07003b881..fddd865dbbe 100644 --- a/core/services/relay/evm/chain_writer.go +++ b/core/services/relay/evm/chain_writer.go @@ -20,6 +20,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas" evmtxmgr "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr" "github.com/smartcontractkit/chainlink/v2/core/services" + "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/codec" "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/types" ) @@ -45,7 +46,7 @@ func NewChainWriterService(logger logger.Logger, client evmclient.Client, txm ev sendStrategy: txmgr.NewSendEveryStrategy(), contracts: config.Contracts, - parsedContracts: &ParsedTypes{EncoderDefs: map[string]types.CodecEntry{}, DecoderDefs: map[string]types.CodecEntry{}}, + parsedContracts: &codec.ParsedTypes{EncoderDefs: map[string]types.CodecEntry{}, DecoderDefs: map[string]types.CodecEntry{}}, } if config.SendStrategy != nil { @@ -75,7 +76,7 @@ type chainWriter struct { sendStrategy txmgrtypes.TxStrategy contracts map[string]*types.ContractConfig - parsedContracts *ParsedTypes + parsedContracts *codec.ParsedTypes encoder commontypes.Encoder } @@ -161,7 +162,7 @@ func (w *chainWriter) parseContracts() error { } // ABI.Pack prepends the method.ID to the encodings, we'll need the encoder to do the same. - inputMod, err := methodConfig.InputModifications.ToModifier(DecoderHooks...) + inputMod, err := methodConfig.InputModifications.ToModifier(codec.DecoderHooks...) if err != nil { return fmt.Errorf("%w: failed to create input mods", err) } diff --git a/core/services/relay/evm/codec.go b/core/services/relay/evm/codec/codec.go similarity index 92% rename from core/services/relay/evm/codec.go rename to core/services/relay/evm/codec/codec.go index 45810bb1ab4..5cd606755ca 100644 --- a/core/services/relay/evm/codec.go +++ b/core/services/relay/evm/codec/codec.go @@ -1,4 +1,4 @@ -package evm +package codec import ( "encoding/json" @@ -11,8 +11,7 @@ import ( "github.com/ethereum/go-ethereum/common/hexutil" "github.com/go-viper/mapstructure/v2" - "github.com/smartcontractkit/chainlink-common/pkg/codec" - + commoncodec "github.com/smartcontractkit/chainlink-common/pkg/codec" commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/types" @@ -33,10 +32,10 @@ import ( // it was a *big.Int var DecoderHooks = []mapstructure.DecodeHookFunc{ decodeAccountAndAllowArraySliceHook, - codec.BigIntHook, - codec.SliceToArrayVerifySizeHook, + commoncodec.BigIntHook, + commoncodec.SliceToArrayVerifySizeHook, sizeVerifyBigIntHook, - codec.NumberHook, + commoncodec.NumberHook, } // NewCodec creates a new [commontypes.RemoteCodec] for EVM. @@ -103,7 +102,7 @@ func sizeVerifyBigIntHook(from, to reflect.Type, data any) (any, error) { if from.Implements(types.SizedBigIntType()) && !to.Implements(types.SizedBigIntType()) && !reflect.PointerTo(to).Implements(types.SizedBigIntType()) { - return codec.BigIntHook(from, bigIntType, reflect.ValueOf(data).Convert(bigIntType).Interface()) + return commoncodec.BigIntHook(from, bigIntType, reflect.ValueOf(data).Convert(bigIntType).Interface()) } if !to.Implements(types.SizedBigIntType()) { @@ -111,7 +110,7 @@ func sizeVerifyBigIntHook(from, to reflect.Type, data any) (any, error) { } var err error - data, err = codec.BigIntHook(from, bigIntType, data) + data, err = commoncodec.BigIntHook(from, bigIntType, data) if err != nil { return nil, err } diff --git a/core/services/relay/evm/codec_fuzz_test.go b/core/services/relay/evm/codec/codec_fuzz_test.go similarity index 92% rename from core/services/relay/evm/codec_fuzz_test.go rename to core/services/relay/evm/codec/codec_fuzz_test.go index 5870e9d77ad..1b65a1bc29d 100644 --- a/core/services/relay/evm/codec_fuzz_test.go +++ b/core/services/relay/evm/codec/codec_fuzz_test.go @@ -1,4 +1,4 @@ -package evm_test +package codec_test import ( "testing" diff --git a/core/services/relay/evm/codec_test.go b/core/services/relay/evm/codec/codec_test.go similarity index 96% rename from core/services/relay/evm/codec_test.go rename to core/services/relay/evm/codec/codec_test.go index af3170abf0e..d43f1180108 100644 --- a/core/services/relay/evm/codec_test.go +++ b/core/services/relay/evm/codec/codec_test.go @@ -1,4 +1,4 @@ -package evm_test +package codec_test import ( "encoding/json" @@ -12,7 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/smartcontractkit/chainlink-common/pkg/codec" + commoncodec "github.com/smartcontractkit/chainlink-common/pkg/codec" "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/evmtesting" looptestutils "github.com/smartcontractkit/chainlink-common/pkg/loop/testutils" //nolint common practice to import test mods with . @@ -21,7 +21,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/chain_reader_tester" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm" + "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/codec" "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/types" ) @@ -45,7 +45,7 @@ func TestCodec(t *testing.T) { codecConfig := types.CodecConfig{Configs: map[string]types.ChainCodecConfig{ codecName: {TypeABI: evmEncoderConfig}, }} - c, err := evm.NewCodec(codecConfig) + c, err := codec.NewCodec(codecConfig) require.NoError(t, err) result, err := c.Encode(testutils.Context(t), encode, codecName) @@ -91,7 +91,7 @@ func TestCodec_SimpleEncode(t *testing.T) { codecConfig := types.CodecConfig{Configs: map[string]types.ChainCodecConfig{ codecName: {TypeABI: evmEncoderConfig}, }} - c, err := evm.NewCodec(codecConfig) + c, err := codec.NewCodec(codecConfig) require.NoError(t, err) result, err := c.Encode(testutils.Context(t), input, codecName) @@ -120,7 +120,7 @@ func TestCodec_EncodeTuple(t *testing.T) { codecConfig := types.CodecConfig{Configs: map[string]types.ChainCodecConfig{ codecName: {TypeABI: evmEncoderConfig}, }} - c, err := evm.NewCodec(codecConfig) + c, err := codec.NewCodec(codecConfig) require.NoError(t, err) result, err := c.Encode(testutils.Context(t), input, codecName) @@ -152,7 +152,7 @@ func TestCodec_EncodeTupleWithLists(t *testing.T) { codecConfig := types.CodecConfig{Configs: map[string]types.ChainCodecConfig{ codecName: {TypeABI: evmEncoderConfig}, }} - c, err := evm.NewCodec(codecConfig) + c, err := codec.NewCodec(codecConfig) require.NoError(t, err) result, err := c.Encode(testutils.Context(t), input, codecName) @@ -204,13 +204,13 @@ func (it *codecInterfaceTester) GetCodec(t *testing.T) commontypes.Codec { entry.TypeABI = string(defBytes) if k != sizeItemType && k != NilType { - entry.ModifierConfigs = codec.ModifiersConfig{ - &codec.RenameModifierConfig{Fields: map[string]string{"NestedStruct.Inner.IntVal": "I"}}, + entry.ModifierConfigs = commoncodec.ModifiersConfig{ + &commoncodec.RenameModifierConfig{Fields: map[string]string{"NestedStruct.Inner.IntVal": "I"}}, } } if k == TestItemWithConfigExtra { - hardCode := &codec.HardCodeModifierConfig{ + hardCode := &commoncodec.HardCodeModifierConfig{ OnChainValues: map[string]any{ "BigField": testStruct.BigField.String(), "Account": hexutil.Encode(testStruct.Account), @@ -222,7 +222,7 @@ func (it *codecInterfaceTester) GetCodec(t *testing.T) commontypes.Codec { codecConfig.Configs[k] = entry } - c, err := evm.NewCodec(codecConfig) + c, err := codec.NewCodec(codecConfig) require.NoError(t, err) return c } diff --git a/core/services/relay/evm/decoder.go b/core/services/relay/evm/codec/decoder.go similarity index 94% rename from core/services/relay/evm/decoder.go rename to core/services/relay/evm/codec/decoder.go index e86ade3ac72..70c0f1d668b 100644 --- a/core/services/relay/evm/decoder.go +++ b/core/services/relay/evm/codec/decoder.go @@ -1,4 +1,4 @@ -package evm +package codec import ( "context" @@ -45,7 +45,7 @@ func (m *decoder) Decode(_ context.Context, raw []byte, into any, itemType strin iInto.Set(reflect.MakeSlice(iInto.Type(), length, length)) return setElements(length, rDecode, iInto) default: - return mapstructureDecode(decode, into) + return MapstructureDecode(decode, into) } } @@ -83,7 +83,7 @@ func extractDecoding(info types.CodecEntry, raw []byte) (any, error) { func setElements(length int, rDecode reflect.Value, iInto reflect.Value) error { for i := 0; i < length; i++ { - if err := mapstructureDecode(rDecode.Index(i).Interface(), iInto.Index(i).Addr().Interface()); err != nil { + if err := MapstructureDecode(rDecode.Index(i).Interface(), iInto.Index(i).Addr().Interface()); err != nil { return err } } @@ -91,7 +91,7 @@ func setElements(length int, rDecode reflect.Value, iInto reflect.Value) error { return nil } -func mapstructureDecode(src, dest any) error { +func MapstructureDecode(src, dest any) error { mDecoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ DecodeHook: mapstructure.ComposeDecodeHookFunc(DecoderHooks...), Result: dest, diff --git a/core/services/relay/evm/encoder.go b/core/services/relay/evm/codec/encoder.go similarity index 90% rename from core/services/relay/evm/encoder.go rename to core/services/relay/evm/codec/encoder.go index ae60e4ab35a..b7facde328a 100644 --- a/core/services/relay/evm/encoder.go +++ b/core/services/relay/evm/codec/encoder.go @@ -1,4 +1,4 @@ -package evm +package codec import ( "context" @@ -52,13 +52,13 @@ func encode(item reflect.Value, info types.CodecEntry) ([]byte, error) { } switch item.Kind() { case reflect.Array, reflect.Slice: - native, err := representArray(item, info) + native, err := RepresentArray(item, info) if err != nil { return nil, err } return pack(info, native) case reflect.Struct, reflect.Map: - values, err := unrollItem(item, info) + values, err := UnrollItem(item, info) if err != nil { return nil, err } @@ -68,7 +68,7 @@ func encode(item reflect.Value, info types.CodecEntry) ([]byte, error) { } } -func representArray(item reflect.Value, info types.CodecEntry) (any, error) { +func RepresentArray(item reflect.Value, info types.CodecEntry) (any, error) { length := item.Len() checkedType := info.CheckedType() checked := reflect.New(checkedType) @@ -87,7 +87,7 @@ func representArray(item reflect.Value, info types.CodecEntry) (any, error) { checkedElm := checkedType.Elem() for i := 0; i < length; i++ { tmp := reflect.New(checkedElm) - if err := mapstructureDecode(item.Index(i).Interface(), tmp.Interface()); err != nil { + if err := MapstructureDecode(item.Index(i).Interface(), tmp.Interface()); err != nil { return nil, err } iChecked.Index(i).Set(tmp.Elem()) @@ -100,7 +100,7 @@ func representArray(item reflect.Value, info types.CodecEntry) (any, error) { return native.Elem().Interface(), nil } -func unrollItem(item reflect.Value, info types.CodecEntry) ([]any, error) { +func UnrollItem(item reflect.Value, info types.CodecEntry) ([]any, error) { checkedType := info.CheckedType() if item.CanAddr() { item = item.Addr() @@ -114,7 +114,7 @@ func unrollItem(item reflect.Value, info types.CodecEntry) ([]any, error) { } else if !info.IsNativePointer(item.Type()) { var err error checked := reflect.New(checkedType) - if err = mapstructureDecode(item.Interface(), checked.Interface()); err != nil { + if err = MapstructureDecode(item.Interface(), checked.Interface()); err != nil { return nil, err } if item, err = info.ToNative(checked); err != nil { diff --git a/core/services/relay/evm/parsed_types.go b/core/services/relay/evm/codec/parsed_types.go similarity index 80% rename from core/services/relay/evm/parsed_types.go rename to core/services/relay/evm/codec/parsed_types.go index 902c182e1d8..31c95caf4dd 100644 --- a/core/services/relay/evm/parsed_types.go +++ b/core/services/relay/evm/codec/parsed_types.go @@ -1,10 +1,10 @@ -package evm +package codec import ( "fmt" "reflect" - "github.com/smartcontractkit/chainlink-common/pkg/codec" + commoncodec "github.com/smartcontractkit/chainlink-common/pkg/codec" commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/types" @@ -16,7 +16,7 @@ type ParsedTypes struct { } func (parsed *ParsedTypes) ToCodec() (commontypes.RemoteCodec, error) { - modByTypeName := map[string]codec.Modifier{} + modByTypeName := map[string]commoncodec.Modifier{} if err := AddEntries(parsed.EncoderDefs, modByTypeName); err != nil { return nil, err } @@ -24,7 +24,7 @@ func (parsed *ParsedTypes) ToCodec() (commontypes.RemoteCodec, error) { return nil, err } - mod, err := codec.NewByItemTypeModifier(modByTypeName) + mod, err := commoncodec.NewByItemTypeModifier(modByTypeName) if err != nil { return nil, err } @@ -33,12 +33,12 @@ func (parsed *ParsedTypes) ToCodec() (commontypes.RemoteCodec, error) { decoder: &decoder{Definitions: parsed.DecoderDefs}, ParsedTypes: parsed, } - return codec.NewModifierCodec(underlying, mod, DecoderHooks...) + return commoncodec.NewModifierCodec(underlying, mod, DecoderHooks...) } // AddEntries extracts the mods from codecEntry and adds them to modByTypeName use with codec.NewByItemTypeModifier // Since each input/output can have its own modifications, we need to keep track of them by type name -func AddEntries(defs map[string]types.CodecEntry, modByTypeName map[string]codec.Modifier) error { +func AddEntries(defs map[string]types.CodecEntry, modByTypeName map[string]commoncodec.Modifier) error { for k, def := range defs { modByTypeName[k] = def.Modifier() _, err := def.Modifier().RetypeToOffChain(reflect.PointerTo(def.CheckedType()), k) diff --git a/core/services/relay/evm/contract_binding.go b/core/services/relay/evm/contract_binding.go deleted file mode 100644 index da2d7ed9bd1..00000000000 --- a/core/services/relay/evm/contract_binding.go +++ /dev/null @@ -1,100 +0,0 @@ -package evm - -import ( - "context" - "fmt" - "sync" - - "github.com/ethereum/go-ethereum/common" - "github.com/google/uuid" - - commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" - evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" -) - -type filterRegisterer struct { - pollingFilter logpoller.Filter - filterLock sync.Mutex - // registerCalled is used to determine if Register was called during Chain Reader service Start. - // This is done to avoid calling Register while the service is not running because log poller is most likely also not running. - registerCalled bool -} - -// contractBinding stores read bindings and manages the common contract event filter. -type contractBinding struct { - name string - // filterRegisterer is used to manage polling filter registration for the common contract filter. - // The common contract filter should be used by events that share filtering args. - filterRegisterer - // key is read name method, event or event keys used for queryKey. - readBindings map[string]readBinding - // bound determines if address is set to the contract binding. - bound bool - bindLock sync.Mutex -} - -// Bind binds contract addresses to contract binding and registers the common contract polling filter. -func (cb *contractBinding) Bind(ctx context.Context, lp logpoller.LogPoller, boundContract commontypes.BoundContract) error { - // it's enough to just lock bound here since Register/Unregister are only called from here and from Start/Close - // even if they somehow happen at the same time it will be fine because of filter lock and hasFilter check - cb.bindLock.Lock() - defer cb.bindLock.Unlock() - - if cb.bound { - // we are changing contract address reference, so we need to unregister old filter it exists - if err := cb.Unregister(ctx, lp); err != nil { - return err - } - } - - cb.pollingFilter.Addresses = evmtypes.AddressArray{common.HexToAddress(boundContract.Address)} - cb.pollingFilter.Name = logpoller.FilterName(boundContract.Name+"."+uuid.NewString(), boundContract.Address) - cb.bound = true - - if cb.registerCalled { - return cb.Register(ctx, lp) - } - - return nil -} - -// Register registers the common contract filter. -func (cb *contractBinding) Register(ctx context.Context, lp logpoller.LogPoller) error { - cb.filterLock.Lock() - defer cb.filterLock.Unlock() - - cb.registerCalled = true - // can't be true before filters params are set so there is no race with a bad filter outcome - if !cb.bound { - return nil - } - - if len(cb.pollingFilter.EventSigs) > 0 && !lp.HasFilter(cb.pollingFilter.Name) { - if err := lp.RegisterFilter(ctx, cb.pollingFilter); err != nil { - return fmt.Errorf("%w: %w", commontypes.ErrInternal, err) - } - } - - return nil -} - -// Unregister unregisters the common contract filter. -func (cb *contractBinding) Unregister(ctx context.Context, lp logpoller.LogPoller) error { - cb.filterLock.Lock() - defer cb.filterLock.Unlock() - - if !cb.bound { - return nil - } - - if !lp.HasFilter(cb.pollingFilter.Name) { - return nil - } - - if err := lp.UnregisterFilter(ctx, cb.pollingFilter.Name); err != nil { - return fmt.Errorf("%w: %w", commontypes.ErrInternal, err) - } - - return nil -} diff --git a/core/services/relay/evm/event_binding.go b/core/services/relay/evm/event_binding.go deleted file mode 100644 index ae67fae9fcd..00000000000 --- a/core/services/relay/evm/event_binding.go +++ /dev/null @@ -1,476 +0,0 @@ -package evm - -import ( - "context" - "fmt" - "reflect" - "strings" - "sync" - - "github.com/ethereum/go-ethereum/accounts/abi" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" - "github.com/google/uuid" - - "github.com/smartcontractkit/chainlink-common/pkg/codec" - commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" - "github.com/smartcontractkit/chainlink-common/pkg/types/query" - "github.com/smartcontractkit/chainlink-common/pkg/types/query/primitives" - - "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" - evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" - "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/types" -) - -type eventBinding struct { - address common.Address - contractName string - eventName string - lp logpoller.LogPoller - // filterRegisterer in eventBinding is to be used as an override for lp filter defined in the contract binding. - // If filterRegisterer is nil, this event should be registered with the lp filter defined in the contract binding. - *filterRegisterer - hash common.Hash - codec commontypes.RemoteCodec - // bound determines if address is set to the contract binding. - bound bool - bindLock sync.Mutex - inputInfo types.CodecEntry - inputModifier codec.Modifier - codecTopicInfo types.CodecEntry - // topics maps a generic topic name (key) to topic data - topics map[string]topicDetail - // eventDataWords maps a generic name to a word index - // key is a predefined generic name for evm log event data word - // for e.g. first evm data word(32bytes) of USDC log event is value so the key can be called value - eventDataWords map[string]uint8 - confirmationsMapping map[primitives.ConfidenceLevel]evmtypes.Confirmations -} - -type topicDetail struct { - abi.Argument - Index uint64 -} - -var _ readBinding = &eventBinding{} - -func (e *eventBinding) SetCodec(codec commontypes.RemoteCodec) { - e.codec = codec -} - -func (e *eventBinding) Bind(ctx context.Context, binding commontypes.BoundContract) error { - // it's enough to just lock bound here since Register/Unregister are only called from here and from Start/Close - // even if they somehow happen at the same time it will be fine because of filter lock and hasFilter check - e.bindLock.Lock() - defer e.bindLock.Unlock() - - if e.bound { - // we are changing contract address reference, so we need to unregister old filter it exists - if err := e.Unregister(ctx); err != nil { - return err - } - } - - e.address = common.HexToAddress(binding.Address) - - // filterRegisterer isn't required here because the event can also be polled for by the contractBinding common filter. - if e.filterRegisterer != nil { - id := fmt.Sprintf("%s.%s.%s", e.contractName, e.eventName, uuid.NewString()) - e.pollingFilter.Name = logpoller.FilterName(id, e.address) - e.pollingFilter.Addresses = evmtypes.AddressArray{e.address} - e.bound = true - if e.registerCalled { - return e.Register(ctx) - } - } - e.bound = true - return nil -} - -func (e *eventBinding) Register(ctx context.Context) error { - if e.filterRegisterer == nil { - return nil - } - - e.filterLock.Lock() - defer e.filterLock.Unlock() - - e.registerCalled = true - // can't be true before filters params are set so there is no race with a bad filter outcome - if !e.bound { - return nil - } - - if e.lp.HasFilter(e.pollingFilter.Name) { - return nil - } - - if err := e.lp.RegisterFilter(ctx, e.pollingFilter); err != nil { - return fmt.Errorf("%w: %w", commontypes.ErrInternal, err) - } - - return nil -} - -func (e *eventBinding) Unregister(ctx context.Context) error { - if e.filterRegisterer == nil { - return nil - } - - e.filterLock.Lock() - defer e.filterLock.Unlock() - - if !e.bound { - return nil - } - - if !e.lp.HasFilter(e.pollingFilter.Name) { - return nil - } - - if err := e.lp.UnregisterFilter(ctx, e.pollingFilter.Name); err != nil { - return fmt.Errorf("%w: %w", commontypes.ErrInternal, err) - } - - return nil -} - -func (e *eventBinding) GetLatestValue(ctx context.Context, confidenceLevel primitives.ConfidenceLevel, params, into any) error { - if err := e.validateBound(); err != nil { - return err - } - - confirmations, err := confidenceToConfirmations(e.confirmationsMapping, confidenceLevel) - if err != nil { - return err - } - - if len(e.inputInfo.Args()) == 0 { - return e.getLatestValueWithoutFilters(ctx, confirmations, into) - } - - return e.getLatestValueWithFilters(ctx, confirmations, params, into) -} - -func (e *eventBinding) QueryKey(ctx context.Context, filter query.KeyFilter, limitAndSort query.LimitAndSort, sequenceDataType any) ([]commontypes.Sequence, error) { - if err := e.validateBound(); err != nil { - return nil, err - } - - remapped, err := e.remap(filter) - if err != nil { - return nil, err - } - - // filter should always use the address and event sig - defaultExpressions := []query.Expression{ - logpoller.NewAddressFilter(e.address), - logpoller.NewEventSigFilter(e.hash), - } - remapped.Expressions = append(defaultExpressions, remapped.Expressions...) - - logs, err := e.lp.FilteredLogs(ctx, remapped.Expressions, limitAndSort, e.contractName+"-"+e.address.String()+"-"+e.eventName) - if err != nil { - return nil, err - } - - // no need to return an error. an empty list is fine - if len(logs) == 0 { - return []commontypes.Sequence{}, nil - } - - return e.decodeLogsIntoSequences(ctx, logs, sequenceDataType) -} - -func (e *eventBinding) validateBound() error { - if !e.bound { - return fmt.Errorf( - "%w: event %s that belongs to contract: %s, not bound", - commontypes.ErrInvalidType, - e.eventName, - e.contractName, - ) - } - return nil -} - -func (e *eventBinding) getLatestValueWithoutFilters(ctx context.Context, confs evmtypes.Confirmations, into any) error { - log, err := e.lp.LatestLogByEventSigWithConfs(ctx, e.hash, e.address, confs) - if err = wrapInternalErr(err); err != nil { - return err - } - - return e.decodeLog(ctx, log, into) -} - -func (e *eventBinding) getLatestValueWithFilters( - ctx context.Context, confs evmtypes.Confirmations, params, into any) error { - offChain, err := e.convertToOffChainType(params) - if err != nil { - return err - } - - // convert caller chain agnostic params types to types representing onchain abi types, for e.g. bytes32. - checkedParams, err := e.inputModifier.TransformToOnChain(offChain, "" /* unused */) - if err != nil { - return err - } - - // convert onchain params to native types similarly to generated abi wrappers, for e.g. fixed bytes32 abi type to [32]uint8. - nativeParams, err := e.inputInfo.ToNative(reflect.ValueOf(checkedParams)) - if err != nil { - return err - } - - filtersAndIndices, err := e.encodeParams(nativeParams) - if err != nil { - return err - } - - // Create limiter and filter for the query. - limiter := query.NewLimitAndSort(query.CountLimit(1), query.NewSortBySequence(query.Desc)) - filter, err := logpoller.Where( - logpoller.NewAddressFilter(e.address), - logpoller.NewEventSigFilter(e.hash), - logpoller.NewConfirmationsFilter(confs), - createTopicFilters(filtersAndIndices), - ) - if err != nil { - return wrapInternalErr(err) - } - - // Gets the latest log that matches the filter and limiter. - logs, err := e.lp.FilteredLogs(ctx, filter, limiter, e.contractName+"-"+e.address.String()+"-"+e.eventName) - if err != nil { - return wrapInternalErr(err) - } - - if len(logs) == 0 { - return fmt.Errorf("%w: no events found", commontypes.ErrNotFound) - } - - return e.decodeLog(ctx, &logs[0], into) -} - -func createTopicFilters(filtersAndIndices []common.Hash) query.Expression { - var expressions []query.Expression - for topicID, fai := range filtersAndIndices { - // first topic index is 1-based, so we add 1. - expressions = append(expressions, logpoller.NewEventByTopicFilter( - uint64(topicID+1), []primitives.ValueComparator{{Value: fai.Hex(), Operator: primitives.Eq}}, - )) - } - return query.And(expressions...) -} - -// convertToOffChainType creates a struct based on contract abi with applied codec modifiers. -// Created type shouldn't have hashed types for indexed topics since incoming params wouldn't be hashed. -func (e *eventBinding) convertToOffChainType(params any) (any, error) { - offChain, err := e.codec.CreateType(WrapItemType(e.contractName, e.eventName, true), true) - if err != nil { - return nil, err - } - - if err = mapstructureDecode(params, offChain); err != nil { - return nil, err - } - - return offChain, nil -} - -// encodeParams accepts nativeParams and encodes them to match onchain topics. -func (e *eventBinding) encodeParams(nativeParams reflect.Value) ([]common.Hash, error) { - for nativeParams.Kind() == reflect.Pointer { - nativeParams = reflect.Indirect(nativeParams) - } - - var params []any - switch nativeParams.Kind() { - case reflect.Array, reflect.Slice: - native, err := representArray(nativeParams, e.inputInfo) - if err != nil { - return nil, err - } - params = []any{native} - case reflect.Struct, reflect.Map: - var err error - if params, err = unrollItem(nativeParams, e.inputInfo); err != nil { - return nil, err - } - default: - return nil, fmt.Errorf("%w: cannot encode kind %v", commontypes.ErrInvalidType, nativeParams.Kind()) - } - - // abi params allow you to Pack a pointers, but makeTopics doesn't work with pointers. - if err := e.derefTopics(params); err != nil { - return nil, err - } - - return e.makeTopics(params) -} - -func (e *eventBinding) derefTopics(topics []any) error { - for i, topic := range topics { - rTopic := reflect.ValueOf(topic) - if rTopic.Kind() == reflect.Pointer { - if rTopic.IsNil() { - return fmt.Errorf( - "%w: input topic %s cannot be nil", commontypes.ErrInvalidType, e.inputInfo.Args()[i].Name) - } - topics[i] = rTopic.Elem().Interface() - } - } - return nil -} - -// makeTopics encodes and hashes params filtering values to match onchain indexed topics. -func (e *eventBinding) makeTopics(params []any) ([]common.Hash, error) { - // make topic value for non-fixed bytes array manually because geth MakeTopics doesn't support it - for i, topic := range params { - if abiArg := e.inputInfo.Args()[i]; abiArg.Type.T == abi.ArrayTy && (abiArg.Type.Elem != nil && abiArg.Type.Elem.T == abi.UintTy) { - packed, err := abi.Arguments{abiArg}.Pack(topic) - if err != nil { - return nil, err - } - params[i] = crypto.Keccak256Hash(packed) - } - } - - hashes, err := abi.MakeTopics(params) - if err != nil { - return nil, wrapInternalErr(err) - } - - if len(hashes) != 1 { - return nil, fmt.Errorf("%w: expected 1 filter set, got %d", commontypes.ErrInternal, len(hashes)) - } - - return hashes[0], nil -} - -func (e *eventBinding) decodeLog(ctx context.Context, log *logpoller.Log, into any) error { - // decode non indexed topics and apply output modifiers - if err := e.codec.Decode(ctx, log.Data, into, WrapItemType(e.contractName, e.eventName, false)); err != nil { - return err - } - - // decode indexed topics which is rarely useful since most indexed topic types get Keccak256 hashed and should be just used for log filtering. - topics := make([]common.Hash, len(e.codecTopicInfo.Args())) - if len(log.Topics) < len(topics)+1 { - return fmt.Errorf("%w: not enough topics to decode", commontypes.ErrInvalidType) - } - - for i := 0; i < len(topics); i++ { - topics[i] = common.Hash(log.Topics[i+1]) - } - - topicsInto := map[string]any{} - if err := abi.ParseTopicsIntoMap(topicsInto, e.codecTopicInfo.Args(), topics); err != nil { - return fmt.Errorf("%w: %w", commontypes.ErrInvalidType, err) - } - - return mapstructureDecode(topicsInto, into) -} - -func (e *eventBinding) decodeLogsIntoSequences(ctx context.Context, logs []logpoller.Log, into any) ([]commontypes.Sequence, error) { - sequences := make([]commontypes.Sequence, len(logs)) - - for idx := range logs { - sequences[idx] = commontypes.Sequence{ - Cursor: fmt.Sprintf("%s-%s-%d", logs[idx].BlockHash, logs[idx].TxHash, logs[idx].LogIndex), - Head: commontypes.Head{ - Identifier: fmt.Sprint(logs[idx].BlockNumber), - Hash: logs[idx].BlockHash.Bytes(), - Timestamp: uint64(logs[idx].BlockTimestamp.Unix()), - }, - } - - var typeVal reflect.Value - - typeInto := reflect.TypeOf(into) - if typeInto.Kind() == reflect.Pointer { - typeVal = reflect.New(typeInto.Elem()) - } else { - typeVal = reflect.Indirect(reflect.New(typeInto)) - } - - // create a new value of the same type as 'into' for the data to be extracted to - sequences[idx].Data = typeVal.Interface() - - if err := e.decodeLog(ctx, &logs[idx], sequences[idx].Data); err != nil { - return nil, err - } - } - - return sequences, nil -} - -func (e *eventBinding) remap(filter query.KeyFilter) (query.KeyFilter, error) { - remapped := query.KeyFilter{} - - for _, expression := range filter.Expressions { - remappedExpression, err := e.remapExpression(filter.Key, expression) - if err != nil { - return query.KeyFilter{}, err - } - - remapped.Expressions = append(remapped.Expressions, remappedExpression) - } - - return remapped, nil -} - -func (e *eventBinding) remapExpression(key string, expression query.Expression) (query.Expression, error) { - if !expression.IsPrimitive() { - remappedBoolExpressions := make([]query.Expression, len(expression.BoolExpression.Expressions)) - - for i := range expression.BoolExpression.Expressions { - remapped, err := e.remapExpression(key, expression.BoolExpression.Expressions[i]) - if err != nil { - return query.Expression{}, err - } - - remappedBoolExpressions[i] = remapped - } - - if expression.BoolExpression.BoolOperator == query.AND { - return query.And(remappedBoolExpressions...), nil - } - - return query.Or(remappedBoolExpressions...), nil - } - - return e.remapPrimitive(key, expression) -} - -// remap chain agnostic primitives to chain specific -func (e *eventBinding) remapPrimitive(key string, expression query.Expression) (query.Expression, error) { - switch primitive := expression.Primitive.(type) { - // TODO comparator primitive should undergo codec transformations and do hashed types handling similarly to how GetLatestValue handles it BCI-3910 - case *primitives.Comparator: - if val, ok := e.eventDataWords[primitive.Name]; ok { - return logpoller.NewEventByWordFilter(e.hash, val, primitive.ValueComparators), nil - } - return logpoller.NewEventByTopicFilter(e.topics[key].Index, primitive.ValueComparators), nil - case *primitives.Confidence: - confirmations, err := confidenceToConfirmations(e.confirmationsMapping, primitive.ConfidenceLevel) - if err != nil { - return query.Expression{}, err - } - return logpoller.NewConfirmationsFilter(confirmations), nil - default: - return expression, nil - } -} - -func wrapInternalErr(err error) error { - if err == nil { - return nil - } - - errStr := err.Error() - if strings.Contains(errStr, "not found") || strings.Contains(errStr, "no rows") { - return fmt.Errorf("%w: %w", commontypes.ErrNotFound, err) - } - return fmt.Errorf("%w: %w", commontypes.ErrInternal, err) -} diff --git a/core/services/relay/evm/evm.go b/core/services/relay/evm/evm.go index f2b4cbc7a9b..e708233fdc4 100644 --- a/core/services/relay/evm/evm.go +++ b/core/services/relay/evm/evm.go @@ -49,6 +49,7 @@ import ( 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/mercury" mercuryutils "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/utils" @@ -798,7 +799,7 @@ func (r *Relayer) NewMedianProvider(rargs commontypes.RelayArgs, pargs commontyp medianProvider.chainReader = chainReaderService if relayConfig.Codec != nil { - medianProvider.codec, err = NewCodec(*relayConfig.Codec) + medianProvider.codec, err = codec.NewCodec(*relayConfig.Codec) if err != nil { return nil, err } diff --git a/core/services/relay/evm/evmtesting/run_tests.go b/core/services/relay/evm/evmtesting/run_tests.go index 8aba7c1b05d..85ed9f86758 100644 --- a/core/services/relay/evm/evmtesting/run_tests.go +++ b/core/services/relay/evm/evmtesting/run_tests.go @@ -13,7 +13,7 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/types" clcommontypes "github.com/smartcontractkit/chainlink-common/pkg/types" "github.com/smartcontractkit/chainlink-common/pkg/types/query/primitives" - "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm" + "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/read" . "github.com/smartcontractkit/chainlink-common/pkg/types/interfacetests" //nolint common practice to import test mods with . ) @@ -33,21 +33,28 @@ func RunContractReaderEvmTests[T TestingT[T]](t T, it *EVMChainComponentsInterfa ctx := it.Helper.Context(t) cr := it.GetChainReader(t) - require.NoError(t, cr.Bind(ctx, it.GetBindings(t))) - contracts := it.GetBindings(t) + bindings := it.GetBindings(t) + require.NoError(t, cr.Bind(ctx, bindings)) + type DynamicEvent struct { Field string } - SubmitTransactionToCW(t, it, "triggerEventWithDynamicTopic", DynamicEvent{Field: anyString}, contracts[0], types.Unconfirmed) + SubmitTransactionToCW(t, it, "triggerEventWithDynamicTopic", DynamicEvent{Field: anyString}, bindings[0], types.Unconfirmed) input := struct{ Field string }{Field: anyString} tp := cr.(clcommontypes.ContractTypeProvider) - output, err := tp.CreateContractType(AnyContractName, triggerWithDynamicTopic, false) + + readName := types.BoundContract{ + Address: bindings[0].Address, + Name: AnyContractName, + }.ReadIdentifier(triggerWithDynamicTopic) + + output, err := tp.CreateContractType(readName, false) require.NoError(t, err) rOutput := reflect.Indirect(reflect.ValueOf(output)) require.Eventually(t, func() bool { - return cr.GetLatestValue(ctx, AnyContractName, triggerWithDynamicTopic, primitives.Unconfirmed, input, output) == nil + return cr.GetLatestValue(ctx, readName, primitives.Unconfirmed, input, output) == nil }, it.MaxWaitTimeForEvents(), 100*time.Millisecond) assert.Equal(t, &anyString, rOutput.FieldByName("Field").Interface()) @@ -60,19 +67,28 @@ func RunContractReaderEvmTests[T TestingT[T]](t T, it *EVMChainComponentsInterfa it.Setup(t) ctx := it.Helper.Context(t) cr := it.GetChainReader(t) - require.NoError(t, cr.Bind(ctx, it.GetBindings(t))) + bindings := it.GetBindings(t) + + require.NoError(t, cr.Bind(ctx, bindings)) triggerFourTopics(t, it, int32(1), int32(2), int32(3)) triggerFourTopics(t, it, int32(2), int32(2), int32(3)) triggerFourTopics(t, it, int32(1), int32(3), int32(3)) triggerFourTopics(t, it, int32(1), int32(2), int32(4)) + var bound types.BoundContract + for idx := range bindings { + if bindings[idx].Name == AnyContractName { + bound = bindings[idx] + } + } + var latest struct{ Field1, Field2, Field3 int32 } params := struct{ Field1, Field2, Field3 int32 }{Field1: 1, Field2: 2, Field3: 3} time.Sleep(it.MaxWaitTimeForEvents()) - require.NoError(t, cr.GetLatestValue(ctx, AnyContractName, triggerWithAllTopics, primitives.Unconfirmed, params, &latest)) + require.NoError(t, cr.GetLatestValue(ctx, bound.ReadIdentifier(triggerWithAllTopics), primitives.Unconfirmed, params, &latest)) assert.Equal(t, int32(1), latest.Field1) assert.Equal(t, int32(2), latest.Field2) assert.Equal(t, int32(3), latest.Field3) @@ -83,12 +99,21 @@ func RunContractReaderEvmTests[T TestingT[T]](t T, it *EVMChainComponentsInterfa cr := it.GetChainReader(t) ctx := it.Helper.Context(t) - require.NoError(t, cr.Bind(ctx, it.GetBindings(t))) + bindings := it.GetBindings(t) + + require.NoError(t, cr.Bind(ctx, bindings)) triggerFourTopicsWithHashed(t, it, "1", [32]uint8{2}, [32]byte{5}) triggerFourTopicsWithHashed(t, it, "2", [32]uint8{2}, [32]byte{3}) triggerFourTopicsWithHashed(t, it, "1", [32]uint8{3}, [32]byte{3}) + var bound types.BoundContract + for idx := range bindings { + if bindings[idx].Name == AnyContractName { + bound = bindings[idx] + } + } + var latest struct { Field3 [32]byte } @@ -99,7 +124,7 @@ func RunContractReaderEvmTests[T TestingT[T]](t T, it *EVMChainComponentsInterfa }{Field1: "1", Field2: [32]uint8{2}, Field3: [32]byte{5}} time.Sleep(it.MaxWaitTimeForEvents()) - require.NoError(t, cr.GetLatestValue(ctx, AnyContractName, triggerWithAllTopicsWithHashed, primitives.Unconfirmed, params, &latest)) + require.NoError(t, cr.GetLatestValue(ctx, bound.ReadIdentifier(triggerWithAllTopicsWithHashed), primitives.Unconfirmed, params, &latest)) // only checking Field3 topic makes sense since it isn't hashed, to check other fields we'd have to replicate solidity encoding and hashing assert.Equal(t, [32]uint8{5}, latest.Field3) }) @@ -113,7 +138,7 @@ func RunContractReaderEvmTests[T TestingT[T]](t T, it *EVMChainComponentsInterfa ctx := it.Helper.Context(t) err := reader.Bind(ctx, []clcommontypes.BoundContract{{Name: AnyContractName, Address: addr.Hex()}}) - require.ErrorIs(t, err, evm.NoContractExistsError{Address: addr}) + require.ErrorIs(t, err, read.NoContractExistsError{Address: addr}) }) } diff --git a/core/services/relay/evm/method_binding.go b/core/services/relay/evm/method_binding.go deleted file mode 100644 index 9ff57fd9342..00000000000 --- a/core/services/relay/evm/method_binding.go +++ /dev/null @@ -1,130 +0,0 @@ -package evm - -import ( - "context" - "fmt" - "math/big" - - "github.com/ethereum/go-ethereum" - "github.com/ethereum/go-ethereum/common" - - "github.com/smartcontractkit/chainlink-common/pkg/logger" - commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" - "github.com/smartcontractkit/chainlink-common/pkg/types/query" - "github.com/smartcontractkit/chainlink-common/pkg/types/query/primitives" - - evmclient "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" - evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" -) - -type NoContractExistsError struct { - Address common.Address -} - -func (e NoContractExistsError) Error() string { - return fmt.Sprintf("contract does not exist at address: %s", e.Address) -} - -type methodBinding struct { - lggr logger.Logger - ht logpoller.HeadTracker - address common.Address - contractName string - method string - client evmclient.Client - codec commontypes.Codec - bound bool - confirmationsMapping map[primitives.ConfidenceLevel]evmtypes.Confirmations -} - -var _ readBinding = &methodBinding{} - -func (m *methodBinding) SetCodec(codec commontypes.RemoteCodec) { - m.codec = codec -} - -func (m *methodBinding) Bind(ctx context.Context, binding commontypes.BoundContract) error { - addr := common.HexToAddress(binding.Address) - - // check for contract byte code at the latest block and provided address - byteCode, err := m.client.CodeAt(ctx, addr, nil) - if err != nil { - return err - } - - if len(byteCode) == 0 { - return NoContractExistsError{Address: addr} - } - - m.address = addr - m.bound = true - - return nil -} - -func (m *methodBinding) Register(_ context.Context) error { - return nil -} - -func (m *methodBinding) Unregister(_ context.Context) error { - return nil -} - -func (m *methodBinding) GetLatestValue(ctx context.Context, confidenceLevel primitives.ConfidenceLevel, params, returnVal any) error { - if !m.bound { - return fmt.Errorf("%w: method not bound", commontypes.ErrInvalidType) - } - - data, err := m.codec.Encode(ctx, params, WrapItemType(m.contractName, m.method, true)) - if err != nil { - return err - } - - callMsg := ethereum.CallMsg{ - To: &m.address, - From: m.address, - Data: data, - } - - block, err := m.blockNumberFromConfidence(ctx, confidenceLevel) - if err != nil { - return err - } - - bytes, err := m.client.CallContract(ctx, callMsg, block) - if err != nil { - return fmt.Errorf("%w: %w", commontypes.ErrInternal, err) - } - - return m.codec.Decode(ctx, bytes, returnVal, WrapItemType(m.contractName, m.method, false)) -} - -func (m *methodBinding) QueryKey(_ context.Context, _ query.KeyFilter, _ query.LimitAndSort, _ any) ([]commontypes.Sequence, error) { - return nil, nil -} - -func (m *methodBinding) blockNumberFromConfidence(ctx context.Context, confidenceLevel primitives.ConfidenceLevel) (*big.Int, error) { - confirmations, err := confidenceToConfirmations(m.confirmationsMapping, confidenceLevel) - if err != nil { - err = fmt.Errorf("%w for contract: %s, method: %s", err, m.contractName, m.method) - if confidenceLevel == primitives.Unconfirmed { - m.lggr.Errorf("%v, now falling back to default contract call behaviour that calls latest state", err) - return nil, nil - } - return nil, err - } - - _, finalized, err := m.ht.LatestAndFinalizedBlock(ctx) - if err != nil { - return nil, err - } - - if confirmations == evmtypes.Finalized { - return big.NewInt(finalized.Number), nil - } else if confirmations == evmtypes.Unconfirmed { - return nil, nil - } - - return nil, fmt.Errorf("unknown evm confirmations: %v for contract: %s, method: %s", confirmations, m.contractName, m.method) -} diff --git a/core/services/relay/evm/mocks/loop_relay_adapter.go b/core/services/relay/evm/mocks/loop_relay_adapter.go index 50b1dd5f397..c770b52c888 100644 --- a/core/services/relay/evm/mocks/loop_relay_adapter.go +++ b/core/services/relay/evm/mocks/loop_relay_adapter.go @@ -459,23 +459,23 @@ func (_c *LoopRelayAdapter_NewConfigProvider_Call) RunAndReturn(run func(context } // NewContractReader provides a mock function with given fields: ctx, contractReaderConfig -func (_m *LoopRelayAdapter) NewContractReader(ctx context.Context, contractReaderConfig []byte) (types.ChainReader, error) { +func (_m *LoopRelayAdapter) NewContractReader(ctx context.Context, contractReaderConfig []byte) (types.ContractReader, error) { ret := _m.Called(ctx, contractReaderConfig) if len(ret) == 0 { panic("no return value specified for NewContractReader") } - var r0 types.ChainReader + var r0 types.ContractReader var r1 error - if rf, ok := ret.Get(0).(func(context.Context, []byte) (types.ChainReader, error)); ok { + if rf, ok := ret.Get(0).(func(context.Context, []byte) (types.ContractReader, error)); ok { return rf(ctx, contractReaderConfig) } - if rf, ok := ret.Get(0).(func(context.Context, []byte) types.ChainReader); ok { + if rf, ok := ret.Get(0).(func(context.Context, []byte) types.ContractReader); ok { r0 = rf(ctx, contractReaderConfig) } else { if ret.Get(0) != nil { - r0 = ret.Get(0).(types.ChainReader) + r0 = ret.Get(0).(types.ContractReader) } } @@ -507,12 +507,12 @@ func (_c *LoopRelayAdapter_NewContractReader_Call) Run(run func(ctx context.Cont return _c } -func (_c *LoopRelayAdapter_NewContractReader_Call) Return(_a0 types.ChainReader, _a1 error) *LoopRelayAdapter_NewContractReader_Call { +func (_c *LoopRelayAdapter_NewContractReader_Call) Return(_a0 types.ContractReader, _a1 error) *LoopRelayAdapter_NewContractReader_Call { _c.Call.Return(_a0, _a1) return _c } -func (_c *LoopRelayAdapter_NewContractReader_Call) RunAndReturn(run func(context.Context, []byte) (types.ChainReader, error)) *LoopRelayAdapter_NewContractReader_Call { +func (_c *LoopRelayAdapter_NewContractReader_Call) RunAndReturn(run func(context.Context, []byte) (types.ContractReader, error)) *LoopRelayAdapter_NewContractReader_Call { _c.Call.Return(run) return _c } diff --git a/core/services/relay/evm/batch_caller.go b/core/services/relay/evm/read/batch.go similarity index 96% rename from core/services/relay/evm/batch_caller.go rename to core/services/relay/evm/read/batch.go index ce5a2bd722a..412d9a11383 100644 --- a/core/services/relay/evm/batch_caller.go +++ b/core/services/relay/evm/read/batch.go @@ -1,4 +1,4 @@ -package evm +package read import ( "context" @@ -38,6 +38,7 @@ const ( type BatchResult map[string]ContractResults type ContractResults []MethodCallResult type MethodCallResult struct { + Address string MethodName string ReturnValue any Err error @@ -164,6 +165,7 @@ func (c *defaultEvmBatchCaller) batchCall(ctx context.Context, blockNumber uint6 results := make([]dataAndErr, len(batchCall)) for i, call := range batchCall { results[i] = dataAndErr{ + address: call.ContractAddress.Hex(), contractName: call.ContractName, methodName: call.MethodName, returnVal: call.ReturnVal, @@ -226,6 +228,7 @@ func (c *defaultEvmBatchCaller) batchCallDynamicLimitRetries(ctx context.Context } type dataAndErr struct { + address string contractName, methodName string returnVal any err error @@ -296,6 +299,7 @@ func convertToBatchResult(data []dataAndErr) BatchResult { batchResult := make(BatchResult) for _, d := range data { methodCall := MethodCallResult{ + Address: d.address, MethodName: d.methodName, ReturnValue: d.returnVal, Err: d.err, @@ -310,3 +314,11 @@ func convertToBatchResult(data []dataAndErr) BatchResult { return batchResult } + +func WrapItemType(contractName, itemType string, isParams bool) string { + if isParams { + return fmt.Sprintf("params.%s.%s", contractName, itemType) + } + + return fmt.Sprintf("return.%s.%s", contractName, itemType) +} diff --git a/core/services/relay/evm/batch_caller_test.go b/core/services/relay/evm/read/batch_caller_test.go similarity index 89% rename from core/services/relay/evm/batch_caller_test.go rename to core/services/relay/evm/read/batch_caller_test.go index 048df90daba..4f50bdc6911 100644 --- a/core/services/relay/evm/batch_caller_test.go +++ b/core/services/relay/evm/read/batch_caller_test.go @@ -1,4 +1,4 @@ -package evm_test +package read_test import ( "fmt" @@ -16,8 +16,9 @@ import ( 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/codec" "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mocks" + "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/read" evmtypes "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/types" ) @@ -31,8 +32,8 @@ func TestDefaultEvmBatchCaller_BatchCallDynamicLimit(t *testing.T) { }{ { name: "defaults", - maxBatchSize: evm.DefaultRpcBatchSizeLimit, - backOffMultiplier: evm.DefaultRpcBatchBackOffMultiplier, + maxBatchSize: read.DefaultRpcBatchSizeLimit, + backOffMultiplier: read.DefaultRpcBatchBackOffMultiplier, numCalls: 200, expectedBatchSizesOnEachRetry: []int{100, 20, 4, 1}, }, @@ -92,12 +93,12 @@ func TestDefaultEvmBatchCaller_BatchCallDynamicLimit(t *testing.T) { batchSizes = append(batchSizes, len(evmCalls)) }).Return(errors.New("some error")) - calls := make(evm.BatchCall, tc.numCalls) + calls := make(read.BatchCall, tc.numCalls) for i := range calls { - calls[i] = evm.Call{} + calls[i] = read.Call{} } - bc := evm.NewDynamicLimitedBatchCaller(logger.TestLogger(t), mockCodec, ec, tc.maxBatchSize, tc.backOffMultiplier, 1) + bc := read.NewDynamicLimitedBatchCaller(logger.TestLogger(t), mockCodec, ec, tc.maxBatchSize, tc.backOffMultiplier, 1) _, _ = bc.BatchCall(testutils.Context(t), 123, calls) assert.Equal(t, tc.expectedBatchSizesOnEachRetry, batchSizes) }) @@ -131,7 +132,7 @@ func TestDefaultEvmBatchCaller_batchCallLimit(t *testing.T) { 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) + calls := make(read.BatchCall, tc.numCalls) for j := range calls { contractName := fmt.Sprintf("testCase_%d", i) methodName := fmt.Sprintf("method_%d", j) @@ -140,7 +141,7 @@ func TestDefaultEvmBatchCaller_batchCallLimit(t *testing.T) { params := MethodParam{A: uint64(j)} var returnVal MethodReturn - calls[j] = evm.Call{ + calls[j] = read.Call{ ContractName: contractName, MethodName: methodName, Params: ¶ms, @@ -159,9 +160,9 @@ func TestDefaultEvmBatchCaller_batchCallLimit(t *testing.T) { } }).Return(nil) - testCodec, err := evm.NewCodec(codecConfig) + testCodec, err := codec.NewCodec(codecConfig) require.NoError(t, err) - bc := evm.NewDynamicLimitedBatchCaller(logger.TestLogger(t), testCodec, ec, tc.batchSize, 99999, tc.parallelRpcCallsLimit) + bc := read.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) diff --git a/core/services/relay/evm/read/bindings.go b/core/services/relay/evm/read/bindings.go new file mode 100644 index 00000000000..0b6ea311109 --- /dev/null +++ b/core/services/relay/evm/read/bindings.go @@ -0,0 +1,279 @@ +package read + +import ( + "context" + "errors" + "fmt" + "sync" + + "github.com/ethereum/go-ethereum/common" + + commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" + "github.com/smartcontractkit/chainlink-common/pkg/types/query" + "github.com/smartcontractkit/chainlink-common/pkg/types/query/primitives" + + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" + evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" +) + +type Reader interface { + BatchCall(address common.Address, params, retVal any) (Call, error) + GetLatestValue(ctx context.Context, addr common.Address, confidence primitives.ConfidenceLevel, params, returnVal any) error + QueryKey(context.Context, common.Address, query.KeyFilter, query.LimitAndSort, any) ([]commontypes.Sequence, error) + + Bind(context.Context, ...common.Address) error + Unbind(context.Context, ...common.Address) error + SetCodec(commontypes.RemoteCodec) + + Register(context.Context) error + Unregister(context.Context) error +} + +type BindingsRegistry struct { + // dependencies + batch BatchCaller + + // private state + mu sync.RWMutex + contractBindings map[string]*contractBinding + contractLookup *lookup +} + +func NewBindingsRegistry() *BindingsRegistry { + return &BindingsRegistry{ + contractBindings: make(map[string]*contractBinding), + contractLookup: newLookup(), + } +} + +func (b *BindingsRegistry) SetBatchCaller(caller BatchCaller) { + b.mu.Lock() + defer b.mu.Unlock() + + b.batch = caller +} + +func (b *BindingsRegistry) HasContractBinding(contractName string) bool { + b.mu.RLock() + defer b.mu.RUnlock() + + _, ok := b.contractBindings[contractName] + + return ok +} + +// GetReader should only be called after Chain Reader is started. +func (b *BindingsRegistry) GetReader(readName string) (Reader, string, error) { + b.mu.RLock() + defer b.mu.RUnlock() + + values, ok := b.contractLookup.getContractForReadName(readName) + if !ok { + return nil, "", fmt.Errorf("%w: no reader for read name %s", commontypes.ErrInvalidType, readName) + } + + cb, cbExists := b.contractBindings[values.contract] + if !cbExists { + return nil, "", fmt.Errorf("%w: no contract named %s", commontypes.ErrInvalidType, values.contract) + } + + binding, rbExists := cb.GetReaderNamed(values.readName) + if !rbExists { + return nil, "", fmt.Errorf("%w: no reader named %s in contract %s", commontypes.ErrInvalidType, values.readName, values.contract) + } + + return binding, values.address, nil +} + +func (b *BindingsRegistry) AddReader(contractName, readName string, rdr Reader) { + b.mu.Lock() + defer b.mu.Unlock() + + b.contractLookup.addReadNameForContract(contractName, readName) + + cb, cbExists := b.contractBindings[contractName] + if !cbExists { + cb = newContractBinding(contractName) + b.contractBindings[contractName] = cb + } + + cb.AddReaderNamed(readName, rdr) +} + +// Bind binds contract addresses to contract bindings and read bindings. +// Bind also registers the common contract polling filter and eventBindings polling filters. +func (b *BindingsRegistry) Bind(ctx context.Context, reg Registrar, bindings []commontypes.BoundContract) error { + b.mu.RLock() + defer b.mu.RUnlock() + + for _, binding := range bindings { + contract, exists := b.contractBindings[binding.Name] + if !exists { + return fmt.Errorf("%w: no contract named %s", commontypes.ErrInvalidConfig, binding.Name) + } + + b.contractLookup.bindAddressForContract(binding.Name, binding.Address) + + if err := errors.Join( + contract.Bind(ctx, reg, common.HexToAddress(binding.Address)), + contract.BindReaders(ctx, common.HexToAddress(binding.Address)), + ); err != nil { + return err + } + } + + return nil +} + +func (b *BindingsRegistry) BatchGetLatestValues(ctx context.Context, request commontypes.BatchGetLatestValuesRequest) (commontypes.BatchGetLatestValuesResult, error) { + b.mu.RLock() + defer b.mu.RUnlock() + + var batchCall BatchCall + + for binding, contractBatch := range request { + cb := b.contractBindings[binding.Name] + + for i := range contractBatch { + req := contractBatch[i] + + values, ok := b.contractLookup.getContractForReadName(binding.ReadIdentifier(req.ReadName)) + if !ok { + return nil, fmt.Errorf("%w: no method for read name %s", commontypes.ErrInvalidType, binding.ReadIdentifier(req.ReadName)) + } + + rdr, exists := cb.GetReaderNamed(values.readName) + if !exists { + return nil, fmt.Errorf("%w: no contract read binding for %s", commontypes.ErrInvalidType, values.readName) + } + + call, err := rdr.BatchCall(common.HexToAddress(values.address), req.Params, req.ReturnVal) + if err != nil { + return nil, err + } + + batchCall = append(batchCall, call) + } + } + + results, err := b.batch.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 { + for _, methodResult := range contractResult { + binding := commontypes.BoundContract{ + Name: contractName, + Address: methodResult.Address, + } + + brr := commontypes.BatchReadResult{ + ReadName: methodResult.MethodName, + } + + brr.SetResult(methodResult.ReturnValue, methodResult.Err) + + if _, ok := batchGetLatestValuesResults[binding]; !ok { + batchGetLatestValuesResults[binding] = make(commontypes.ContractBatchResults, 0) + } + + batchGetLatestValuesResults[binding] = append(batchGetLatestValuesResults[binding], brr) + } + } + + return batchGetLatestValuesResults, err +} + +// Unbind unbinds contract addresses to contract bindings and read bindings. +func (b *BindingsRegistry) Unbind(ctx context.Context, reg Registrar, bindings []commontypes.BoundContract) error { + b.mu.RLock() + defer b.mu.RUnlock() + + for _, binding := range bindings { + contract, exists := b.contractBindings[binding.Name] + if !exists { + return fmt.Errorf("%w: no contract named %s", commontypes.ErrInvalidConfig, binding.Name) + } + + b.contractLookup.unbindAddressForContract(binding.Name, binding.Address) + + if err := errors.Join( + contract.Unbind(ctx, reg, common.HexToAddress(binding.Address)), + contract.UnbindReaders(ctx, common.HexToAddress(binding.Address)), + ); err != nil { + return err + } + } + + return nil +} + +func (b *BindingsRegistry) RegisterAll(ctx context.Context, reg Registrar) error { + b.mu.RLock() + defer b.mu.RUnlock() + + for _, cb := range b.contractBindings { + if err := errors.Join(cb.RegisterReaders(ctx), cb.Register(ctx, reg)); err != nil { + return err + } + } + + return nil +} + +func (b *BindingsRegistry) UnregisterAll(ctx context.Context, reg Registrar) error { + b.mu.RLock() + defer b.mu.RUnlock() + + for _, cb := range b.contractBindings { + if err := errors.Join(cb.UnregisterReaders(ctx), cb.Unregister(ctx, reg)); err != nil { + return err + } + } + + return nil +} + +func (b *BindingsRegistry) SetCodecAll(codec commontypes.RemoteCodec) { + b.mu.RLock() + defer b.mu.RUnlock() + + for _, cb := range b.contractBindings { + cb.SetCodecAll(codec) + } +} + +func (b *BindingsRegistry) SetFilter(name string, filter logpoller.Filter) error { + b.mu.RLock() + defer b.mu.RUnlock() + + contract, ok := b.contractBindings[name] + if !ok { + return fmt.Errorf("%w: no contract binding for %s", commontypes.ErrInvalidConfig, name) + } + + contract.SetFilter(filter) + + return nil +} + +func (b *BindingsRegistry) ReadTypeIdentifier(readName string, forEncoding bool) string { + values, ok := b.contractLookup.getContractForReadName(readName) + if !ok { + return "" + } + + return WrapItemType(values.contract, values.readName, forEncoding) +} + +// confidenceToConfirmations matches predefined chain agnostic confidence levels to predefined EVM finality. +func confidenceToConfirmations(confirmationsMapping map[primitives.ConfidenceLevel]evmtypes.Confirmations, confidenceLevel primitives.ConfidenceLevel) (evmtypes.Confirmations, error) { + confirmations, exists := confirmationsMapping[confidenceLevel] + if !exists { + return 0, fmt.Errorf("missing mapping for confidence level: %s", confidenceLevel) + } + return confirmations, nil +} diff --git a/core/services/relay/evm/read/bindings_test.go b/core/services/relay/evm/read/bindings_test.go new file mode 100644 index 00000000000..a60cc2933e3 --- /dev/null +++ b/core/services/relay/evm/read/bindings_test.go @@ -0,0 +1,112 @@ +package read_test + +import ( + "context" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + + commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" + "github.com/smartcontractkit/chainlink-common/pkg/types/query/primitives" + + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" + "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/read" + "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/read/mocks" +) + +var ( + contractName1 = "Contract1" + methodName1 = "Method1" + methodName2 = "Method2" + eventName0 = "EventName0" + filterWithSigs = logpoller.Filter{ + Name: eventName0, + EventSigs: []common.Hash{common.HexToHash("0x25"), common.HexToHash("0x29")}, + } +) + +func TestBindingsRegistry(t *testing.T) { + t.Parallel() + + t.Run("readers are addable, bindable, and retrievable", func(t *testing.T) { + t.Parallel() + + mBatch := new(mocks.BatchCaller) + mRdr := new(mocks.Reader) + mReg := new(mocks.Registrar) + + mRdr.EXPECT().Bind(mock.Anything, mock.Anything).Return(nil) + + named := read.NewBindingsRegistry() + named.SetBatchCaller(mBatch) + + named.AddReader(contractName1, methodName1, mRdr) + + bindings := []commontypes.BoundContract{{Address: "0x24", Name: contractName1}} + _ = named.Bind(context.Background(), mReg, bindings) + + rdr, _, err := named.GetReader(bindings[0].ReadIdentifier(methodName1)) + + require.NoError(t, err) + require.NotNil(t, rdr) + }) + + t.Run("register all before bind", func(t *testing.T) { + t.Parallel() + + mBatch := new(mocks.BatchCaller) + mRdr0 := new(mocks.Reader) + mRdr1 := new(mocks.Reader) + mReg := new(mocks.Registrar) + + named := read.NewBindingsRegistry() + named.SetBatchCaller(mBatch) + + // register is called once through RegisterAll and again in Bind + mRdr0.EXPECT().Register(mock.Anything).Return(nil) + mRdr1.EXPECT().Register(mock.Anything).Return(nil) + + mRdr0.EXPECT().Bind(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil) + mRdr1.EXPECT().Bind(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil) + + mReg.EXPECT().HasFilter(mock.Anything).Return(false) + mReg.EXPECT().RegisterFilter(mock.Anything, mock.Anything).Return(nil) + mRdr0.EXPECT().GetLatestValue(mock.Anything, common.HexToAddress("0x25"), mock.Anything, mock.Anything, mock.Anything).Return(nil) + mRdr0.EXPECT().GetLatestValue(mock.Anything, common.HexToAddress("0x24"), mock.Anything, mock.Anything, mock.Anything).Return(nil) + mRdr1.EXPECT().GetLatestValue(mock.Anything, common.HexToAddress("0x26"), mock.Anything, mock.Anything, mock.Anything).Return(nil) + + // part of the init phase of chain reader + named.AddReader(contractName1, methodName1, mRdr0) + named.AddReader(contractName1, methodName2, mRdr1) + _ = named.SetFilter(contractName1, filterWithSigs) + + // run within the start phase of chain reader + require.NoError(t, named.RegisterAll(context.Background(), mReg)) + + bindings := []commontypes.BoundContract{ + {Address: "0x24", Name: contractName1}, + {Address: "0x25", Name: contractName1}, + {Address: "0x26", Name: contractName1}, + } + + // calling bind will also call register for each reader + _ = named.Bind(context.Background(), mReg, bindings) + + rdr1, _, err := named.GetReader(bindings[0].ReadIdentifier(methodName1)) + require.NoError(t, err) + + rdr2, _, err := named.GetReader(bindings[0].ReadIdentifier(methodName2)) + require.NoError(t, err) + + require.NoError(t, rdr1.GetLatestValue(context.Background(), common.HexToAddress("0x25"), primitives.Finalized, nil, nil)) + require.NoError(t, rdr1.GetLatestValue(context.Background(), common.HexToAddress("0x24"), primitives.Finalized, nil, nil)) + require.NoError(t, rdr2.GetLatestValue(context.Background(), common.HexToAddress("0x26"), primitives.Finalized, nil, nil)) + + mBatch.AssertExpectations(t) + mRdr0.AssertExpectations(t) + mRdr1.AssertExpectations(t) + mReg.AssertExpectations(t) + }) +} diff --git a/core/services/relay/evm/read/contract.go b/core/services/relay/evm/read/contract.go new file mode 100644 index 00000000000..1b47c9ee870 --- /dev/null +++ b/core/services/relay/evm/read/contract.go @@ -0,0 +1,243 @@ +package read + +import ( + "context" + "errors" + "sync" + + "github.com/ethereum/go-ethereum/common" + "github.com/google/uuid" + + commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" + + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" +) + +// contractBinding stores read bindings and manages the common contract event filter. +type contractBinding struct { + // filterRegistrar is used to manage polling filter registration for the common contract filter. + // The common contract filter should be used by events that share filtering args. + registrar *syncedFilter + + // registered is used to determine if Register was called during Chain Reader service Start. + // This is done to avoid calling Register while the service is not running because log poller is most likely also not running. + registerCalled bool + + // internal properties + name string + readers map[string]Reader // key is read name method, event or event keys used for queryKey. + bound map[common.Address]bool // bound determines if address is set to the contract binding. + mu sync.RWMutex +} + +func newContractBinding(name string) *contractBinding { + return &contractBinding{ + name: name, + readers: make(map[string]Reader), + bound: make(map[common.Address]bool), + registrar: newSyncedFilter(), + } +} + +// GetReaderNamed returns a reader for the provided contract name. This method is thread safe. +func (cb *contractBinding) GetReaderNamed(name string) (Reader, bool) { + cb.mu.RLock() + defer cb.mu.RUnlock() + + binding, exists := cb.readers[name] + + return binding, exists +} + +// AddReaderNamed adds a new reader to the contract bindings for the provided contract name. This +// method is thread safe. +func (cb *contractBinding) AddReaderNamed(name string, rdr Reader) { + cb.mu.Lock() + defer cb.mu.Unlock() + + cb.readers[name] = rdr +} + +// Bind binds contract addresses to contract binding and registers the common contract polling filter. +func (cb *contractBinding) Bind(ctx context.Context, registrar Registrar, bindings ...common.Address) error { + if cb.isBound() { + if err := cb.Unregister(ctx, registrar); err != nil { + return err + } + } + + for _, binding := range bindings { + if cb.bindingExists(binding) { + continue + } + + cb.registrar.SetName(logpoller.FilterName(cb.name + "." + uuid.NewString())) + cb.registrar.AddAddress(binding) + cb.addBinding(binding) + } + + // registerCalled during ChainReader start + if cb.registered() { + return cb.Register(ctx, registrar) + } + + return nil +} + +func (cb *contractBinding) BindReaders(ctx context.Context, addresses ...common.Address) error { + cb.mu.RLock() + defer cb.mu.RUnlock() + + var err error + + for _, reader := range cb.readers { + err = errors.Join(err, reader.Bind(ctx, addresses...)) + } + + return err +} + +// Unbind unbinds contract addresses from contract binding and unregisters the common contract polling filter. +func (cb *contractBinding) Unbind(ctx context.Context, registrar Registrar, bindings ...common.Address) error { + for _, binding := range bindings { + if !cb.bindingExists(binding) { + continue + } + + cb.registrar.RemoveAddress(binding) + cb.removeBinding(binding) + } + + // we are changing contract address reference, so we need to unregister old filter or re-register existing filter + if cb.registrar.Count() == 0 { + cb.registrar.SetName("") + + return cb.Unregister(ctx, registrar) + } else if cb.registered() { + return cb.Register(ctx, registrar) + } + + return nil +} + +func (cb *contractBinding) UnbindReaders(ctx context.Context, addresses ...common.Address) error { + cb.mu.RLock() + defer cb.mu.RUnlock() + + var err error + + for _, reader := range cb.readers { + err = errors.Join(reader.Unbind(ctx, addresses...)) + } + + return err +} + +func (cb *contractBinding) SetCodecAll(codec commontypes.RemoteCodec) { + cb.mu.RLock() + defer cb.mu.RUnlock() + + for _, binding := range cb.readers { + binding.SetCodec(codec) + } +} + +// Register registers the common contract filter. +func (cb *contractBinding) Register(ctx context.Context, registrar Registrar) error { + cb.setRegistered() + + if !cb.isBound() { + return nil + } + + if cb.registrar.HasEventSigs() { + if err := cb.registrar.Register(ctx, registrar); err != nil { + return err + } + } + + return nil +} + +func (cb *contractBinding) RegisterReaders(ctx context.Context) error { + cb.mu.RLock() + defer cb.mu.RUnlock() + + for _, binding := range cb.readers { + if err := binding.Register(ctx); err != nil { + return err + } + } + + return nil +} + +// Unregister unregisters the common contract filter. +func (cb *contractBinding) Unregister(ctx context.Context, registrar Registrar) error { + if !cb.isBound() { + return nil + } + + return cb.registrar.Unregister(ctx, registrar) +} + +func (cb *contractBinding) UnregisterReaders(ctx context.Context) error { + cb.mu.RLock() + defer cb.mu.RUnlock() + + for _, binding := range cb.readers { + if err := binding.Unregister(ctx); err != nil { + return err + } + } + + return nil +} + +func (cb *contractBinding) SetFilter(filter logpoller.Filter) { + cb.registrar.SetFilter(filter) +} + +func (cb *contractBinding) isBound() bool { + cb.mu.RLock() + defer cb.mu.RUnlock() + + return len(cb.bound) > 0 +} + +func (cb *contractBinding) bindingExists(binding common.Address) bool { + cb.mu.RLock() + defer cb.mu.RUnlock() + + bound, exists := cb.bound[binding] + + return exists && bound +} + +func (cb *contractBinding) addBinding(binding common.Address) { + cb.mu.Lock() + defer cb.mu.Unlock() + + cb.bound[binding] = true +} + +func (cb *contractBinding) removeBinding(binding common.Address) { + cb.mu.Lock() + defer cb.mu.Unlock() + + delete(cb.bound, binding) +} + +func (cb *contractBinding) registered() bool { + cb.mu.RLock() + defer cb.mu.RUnlock() + + return cb.registerCalled +} + +func (cb *contractBinding) setRegistered() { + cb.mu.Lock() + defer cb.mu.Unlock() + + cb.registerCalled = true +} diff --git a/core/services/relay/evm/read/event.go b/core/services/relay/evm/read/event.go new file mode 100644 index 00000000000..693a8d5bd70 --- /dev/null +++ b/core/services/relay/evm/read/event.go @@ -0,0 +1,654 @@ +package read + +import ( + "context" + "fmt" + "reflect" + "strings" + "sync" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/google/uuid" + + commoncodec "github.com/smartcontractkit/chainlink-common/pkg/codec" + commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" + "github.com/smartcontractkit/chainlink-common/pkg/types/query" + "github.com/smartcontractkit/chainlink-common/pkg/types/query/primitives" + + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" + evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" + "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/codec" + "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/types" +) + +type EventBinding struct { + // read-only properties + contractName string + eventName string + hash common.Hash + inputInfo types.CodecEntry + inputModifier commoncodec.Modifier + codecTopicInfo types.CodecEntry + + // dependencies + // filterRegistrar in EventBinding is to be used as an override for lp filter defined in the contract binding. + // If filterRegisterer is nil, this event should be registered with the lp filter defined in the contract binding. + registrar *syncedFilter + registerCalled bool + lp logpoller.LogPoller + + // internal properties / state + codec commontypes.RemoteCodec + bound map[common.Address]bool // bound determines if address is set to the contract binding. + mu sync.RWMutex + topics map[string]topicDetail // topics maps a generic topic name (key) to topic data + // eventDataWords maps a generic name to a word index + // key is a predefined generic name for evm log event data word + // for e.g. first evm data word(32bytes) of USDC log event is value so the key can be called value + eventDataWords map[string]uint8 + confirmationsMapping map[primitives.ConfidenceLevel]evmtypes.Confirmations +} + +func NewEventBinding( + contract, event string, + poller logpoller.LogPoller, + hash common.Hash, + inputInfo types.CodecEntry, + inputModifier commoncodec.Modifier, + codecTopicInfo types.CodecEntry, + confirmations map[primitives.ConfidenceLevel]evmtypes.Confirmations, +) *EventBinding { + return &EventBinding{ + contractName: contract, + eventName: event, + lp: poller, + hash: hash, + inputInfo: inputInfo, + inputModifier: inputModifier, + codecTopicInfo: codecTopicInfo, + confirmationsMapping: confirmations, + topics: make(map[string]topicDetail), + eventDataWords: make(map[string]uint8), + bound: make(map[common.Address]bool), + } +} + +type topicDetail struct { + abi.Argument + Index uint64 +} + +var _ Reader = &EventBinding{} + +func (b *EventBinding) SetCodec(codec commontypes.RemoteCodec) { + b.mu.Lock() + defer b.mu.Unlock() + + b.codec = codec +} + +func (b *EventBinding) Bind(ctx context.Context, bindings ...common.Address) error { + if b.hasBindings() { + // we are changing contract address reference, so we need to unregister old filter if it exists + if err := b.Unregister(ctx); err != nil { + return err + } + } + + // filterRegisterer isn't required here because the event can also be polled for by the contractBinding common filter. + if b.registrar != nil { + b.registrar.SetName(logpoller.FilterName(fmt.Sprintf("%s.%s.%s", b.contractName, b.eventName, uuid.NewString()))) + } + + for _, binding := range bindings { + if b.isBound(binding) { + continue + } + + if b.registrar != nil { + b.registrar.AddAddress(binding) + } + + b.addBinding(binding) + } + + if b.registered() { + return b.Register(ctx) + } + + return nil +} + +func (b *EventBinding) Unbind(ctx context.Context, bindings ...common.Address) error { + for _, binding := range bindings { + if !b.isBound(binding) { + continue + } + + if b.registrar != nil { + b.registrar.RemoveAddress(binding) + } + + b.removeBinding(binding) + } + + if err := b.Unregister(ctx); err != nil { + return err + } + + // we are changing contract address reference, so we need to unregister old filter or re-register existing filter + if b.registrar != nil { + if b.registrar.Count() == 0 { + b.registrar.SetName("") + + return b.Unregister(ctx) + } else if b.registered() { + return b.Register(ctx) + } + } + + return nil +} + +func (b *EventBinding) Register(ctx context.Context) error { + b.mu.Lock() + defer b.mu.Unlock() + + if b.registrar == nil { + return nil + } + + b.registerCalled = true + + // can't be true before filters params are set so there is no race with a bad filter outcome + if len(b.bound) == 0 { + return nil + } + + return b.registrar.Register(ctx, b.lp) +} + +func (b *EventBinding) Unregister(ctx context.Context) error { + b.mu.Lock() + defer b.mu.Unlock() + + if b.registrar == nil { + return nil + } + + if len(b.bound) == 0 { + return nil + } + + return b.registrar.Unregister(ctx, b.lp) +} + +func (b *EventBinding) BatchCall(_ common.Address, _, _ any) (Call, error) { + return Call{}, fmt.Errorf("%w: events are not yet supported in batch get latest values", commontypes.ErrInvalidType) +} + +func (b *EventBinding) GetLatestValue(ctx context.Context, address common.Address, confidenceLevel primitives.ConfidenceLevel, params, into any) error { + if err := b.validateBound(address); err != nil { + return err + } + + confirmations, err := confidenceToConfirmations(b.confirmationsMapping, confidenceLevel) + if err != nil { + return err + } + + if len(b.inputInfo.Args()) == 0 { + return b.getLatestValueWithoutFilters(ctx, address, confirmations, into) + } + + return b.getLatestValueWithFilters(ctx, address, confirmations, params, into) +} + +func (b *EventBinding) QueryKey( + ctx context.Context, + address common.Address, + filter query.KeyFilter, + limitAndSort query.LimitAndSort, + sequenceDataType any, +) ([]commontypes.Sequence, error) { + if err := b.validateBound(address); err != nil { + return nil, err + } + + remapped, err := b.remap(filter) + if err != nil { + return nil, err + } + + // filter should always use the address and event sig + defaultExpressions := []query.Expression{ + logpoller.NewAddressFilter(address), + logpoller.NewEventSigFilter(b.hash), + } + + remapped.Expressions = append(defaultExpressions, remapped.Expressions...) + + logs, err := b.lp.FilteredLogs(ctx, remapped.Expressions, limitAndSort, b.contractName+"-"+address.String()+""+b.eventName) + if err != nil { + return nil, err + } + + // no need to return an error. an empty list is fine + if len(logs) == 0 { + return []commontypes.Sequence{}, nil + } + + return b.decodeLogsIntoSequences(ctx, logs, sequenceDataType) +} + +func (b *EventBinding) SetFilter(filter logpoller.Filter) { + b.mu.Lock() + defer b.mu.Unlock() + + b.registrar = newSyncedFilter() + b.registrar.SetFilter(filter) +} + +func (b *EventBinding) WithTopic(name string, topic abi.Argument, index uint64) { + b.mu.Lock() + defer b.mu.Unlock() + + b.topics[name] = topicDetail{ + Argument: topic, + Index: index, + } +} + +func (b *EventBinding) SetDataWords(eventDataWords map[string]uint8) { + b.mu.Lock() + defer b.mu.Unlock() + + b.eventDataWords = eventDataWords +} + +func (b *EventBinding) GetDataWords() map[string]uint8 { + b.mu.RLock() + defer b.mu.RUnlock() + + return b.eventDataWords +} + +func (b *EventBinding) validateBound(address common.Address) error { + b.mu.Lock() + defer b.mu.Unlock() + + bound, exists := b.bound[address] + if !exists || !bound { + return fmt.Errorf( + "%w: event %s that belongs to contract: %s, not bound", + commontypes.ErrInvalidType, + b.eventName, + b.contractName, + ) + } + + return nil +} + +func (b *EventBinding) getLatestValueWithoutFilters( + ctx context.Context, + address common.Address, + confs evmtypes.Confirmations, + into any, +) error { + log, err := b.lp.LatestLogByEventSigWithConfs(ctx, b.hash, address, confs) + if err = wrapInternalErr(err); err != nil { + return err + } + + return b.decodeLog(ctx, log, into) +} + +func (b *EventBinding) getLatestValueWithFilters( + ctx context.Context, + address common.Address, + confs evmtypes.Confirmations, + params, into any, +) error { + offChain, err := b.convertToOffChainType(params) + if err != nil { + return err + } + + checkedParams, err := b.inputModifier.TransformToOnChain(offChain, "" /* unused */) + if err != nil { + return err + } + + nativeParams, err := b.inputInfo.ToNative(reflect.ValueOf(checkedParams)) + if err != nil { + return err + } + + filtersAndIndices, err := b.encodeParams(nativeParams) + if err != nil { + return err + } + + remainingFilters := filtersAndIndices[1:] + + // Create limiter and filter for the query. + limiter := query.NewLimitAndSort(query.CountLimit(1), query.NewSortBySequence(query.Desc)) + filter, err := query.Where( + "", + logpoller.NewAddressFilter(address), + logpoller.NewEventSigFilter(b.hash), + logpoller.NewConfirmationsFilter(confs), + createTopicFilters(filtersAndIndices), + ) + if err != nil { + return wrapInternalErr(err) + } + + // Gets the latest log that matches the filter and limiter. + logs, err := b.lp.FilteredLogs(ctx, filter.Expressions, limiter, b.contractName+"-"+address.String()+"-"+b.eventName) + if err != nil { + return wrapInternalErr(err) + } + + // TODO: there should be a better way to ask log poller to filter these + // First, you should be able to ask for as many topics to match + // Second, you should be able to get the latest only + var logToUse *logpoller.Log + for _, log := range logs { + tmp := log + if compareLogs(&tmp, logToUse) > 0 && matchesRemainingFilters(&tmp, remainingFilters) { + // copy so that it's not pointing to the changing variable + logToUse = &tmp + } + } + + if logToUse == nil { + return fmt.Errorf("%w: no events found", commontypes.ErrNotFound) + } + + return b.decodeLog(ctx, logToUse, into) +} + +func (b *EventBinding) convertToOffChainType(params any) (any, error) { + offChain, err := b.codec.CreateType(WrapItemType(b.contractName, b.eventName, true), true) + if err != nil { + return nil, err + } + + if err = codec.MapstructureDecode(params, offChain); err != nil { + return nil, err + } + + return offChain, nil +} + +func (b *EventBinding) encodeParams(item reflect.Value) ([]common.Hash, error) { + for item.Kind() == reflect.Pointer { + item = reflect.Indirect(item) + } + + var params []any + switch item.Kind() { + case reflect.Array, reflect.Slice: + native, err := codec.RepresentArray(item, b.inputInfo) + if err != nil { + return nil, err + } + params = []any{native} + case reflect.Struct, reflect.Map: + var err error + if params, err = codec.UnrollItem(item, b.inputInfo); err != nil { + return nil, err + } + default: + return nil, fmt.Errorf("%w: cannot encode kind %v", commontypes.ErrInvalidType, item.Kind()) + } + + // abi params allow you to Pack a pointers, but MakeTopics doesn't work with pointers. + if err := b.derefTopics(params); err != nil { + return nil, err + } + + return b.makeTopics(params) +} + +func (b *EventBinding) derefTopics(topics []any) error { + for i, topic := range topics { + rTopic := reflect.ValueOf(topic) + if rTopic.Kind() == reflect.Pointer { + if rTopic.IsNil() { + return fmt.Errorf( + "%w: input topic %s cannot be nil", commontypes.ErrInvalidType, b.inputInfo.Args()[i].Name) + } + + topics[i] = rTopic.Elem().Interface() + } + } + + return nil +} + +// makeTopics encodes and hashes params filtering values to match onchain indexed topics. +func (b *EventBinding) makeTopics(params []any) ([]common.Hash, error) { + // make topic value for non-fixed bytes array manually because geth MakeTopics doesn't support it + for i, topic := range params { + if abiArg := b.inputInfo.Args()[i]; abiArg.Type.T == abi.ArrayTy && (abiArg.Type.Elem != nil && abiArg.Type.Elem.T == abi.UintTy) { + packed, err := abi.Arguments{abiArg}.Pack(topic) + if err != nil { + return nil, err + } + params[i] = crypto.Keccak256Hash(packed) + } + } + + hashes, err := abi.MakeTopics(params) + if err != nil { + return nil, wrapInternalErr(err) + } + + if len(hashes) != 1 { + return nil, fmt.Errorf("%w: expected 1 filter set, got %d", commontypes.ErrInternal, len(hashes)) + } + + return hashes[0], nil +} + +func (b *EventBinding) decodeLog(ctx context.Context, log *logpoller.Log, into any) error { + if err := b.codec.Decode(ctx, log.Data, into, WrapItemType(b.contractName, b.eventName, false)); err != nil { + return err + } + + topics := make([]common.Hash, len(b.codecTopicInfo.Args())) + if len(log.Topics) < len(topics)+1 { + return fmt.Errorf("%w: not enough topics to decode", commontypes.ErrInvalidType) + } + + for i := 0; i < len(topics); i++ { + topics[i] = common.Hash(log.Topics[i+1]) + } + + topicsInto := map[string]any{} + if err := abi.ParseTopicsIntoMap(topicsInto, b.codecTopicInfo.Args(), topics); err != nil { + return fmt.Errorf("%w: %w", commontypes.ErrInvalidType, err) + } + + return codec.MapstructureDecode(topicsInto, into) +} + +func (b *EventBinding) decodeLogsIntoSequences(ctx context.Context, logs []logpoller.Log, into any) ([]commontypes.Sequence, error) { + sequences := make([]commontypes.Sequence, len(logs)) + + for idx := range logs { + sequences[idx] = commontypes.Sequence{ + Cursor: fmt.Sprintf("%s-%s-%d", logs[idx].BlockHash, logs[idx].TxHash, logs[idx].LogIndex), + Head: commontypes.Head{ + Identifier: fmt.Sprint(logs[idx].BlockNumber), + Hash: logs[idx].BlockHash.Bytes(), + Timestamp: uint64(logs[idx].BlockTimestamp.Unix()), + }, + } + + var typeVal reflect.Value + + typeInto := reflect.TypeOf(into) + if typeInto.Kind() == reflect.Pointer { + typeVal = reflect.New(typeInto.Elem()) + } else { + typeVal = reflect.Indirect(reflect.New(typeInto)) + } + + // create a new value of the same type as 'into' for the data to be extracted to + sequences[idx].Data = typeVal.Interface() + + if err := b.decodeLog(ctx, &logs[idx], sequences[idx].Data); err != nil { + return nil, err + } + } + + return sequences, nil +} + +func (b *EventBinding) remap(filter query.KeyFilter) (query.KeyFilter, error) { + remapped := query.KeyFilter{} + + for _, expression := range filter.Expressions { + remappedExpression, err := b.remapExpression(filter.Key, expression) + if err != nil { + return query.KeyFilter{}, err + } + + remapped.Expressions = append(remapped.Expressions, remappedExpression) + } + + return remapped, nil +} + +func (b *EventBinding) remapExpression(key string, expression query.Expression) (query.Expression, error) { + if !expression.IsPrimitive() { + remappedBoolExpressions := make([]query.Expression, len(expression.BoolExpression.Expressions)) + + for i := range expression.BoolExpression.Expressions { + remapped, err := b.remapExpression(key, expression.BoolExpression.Expressions[i]) + if err != nil { + return query.Expression{}, err + } + + remappedBoolExpressions[i] = remapped + } + + if expression.BoolExpression.BoolOperator == query.AND { + return query.And(remappedBoolExpressions...), nil + } + + return query.Or(remappedBoolExpressions...), nil + } + + return b.remapPrimitive(key, expression) +} + +// remap chain agnostic primitives to chain specific +func (b *EventBinding) remapPrimitive(key string, expression query.Expression) (query.Expression, error) { + switch primitive := expression.Primitive.(type) { + case *primitives.Comparator: + if val, ok := b.eventDataWords[primitive.Name]; ok { + return logpoller.NewEventByWordFilter(b.hash, val, primitive.ValueComparators), nil + } + + return logpoller.NewEventByTopicFilter(b.topics[key].Index, primitive.ValueComparators), nil + case *primitives.Confidence: + confirmations, err := confidenceToConfirmations(b.confirmationsMapping, primitive.ConfidenceLevel) + if err != nil { + return query.Expression{}, err + } + + return logpoller.NewConfirmationsFilter(confirmations), nil + default: + return expression, nil + } +} + +func (b *EventBinding) hasBindings() bool { + b.mu.RLock() + defer b.mu.RUnlock() + + return len(b.bound) > 0 +} + +func (b *EventBinding) isBound(binding common.Address) bool { + b.mu.RLock() + defer b.mu.RUnlock() + + _, exists := b.bound[binding] + + return exists +} + +func (b *EventBinding) addBinding(binding common.Address) { + b.mu.Lock() + defer b.mu.Unlock() + + b.bound[binding] = true +} + +func (b *EventBinding) removeBinding(binding common.Address) { + b.mu.Lock() + defer b.mu.Unlock() + + delete(b.bound, binding) +} + +func (b *EventBinding) registered() bool { + b.mu.RLock() + defer b.mu.RUnlock() + + return b.registerCalled +} + +func compareLogs(log, use *logpoller.Log) int64 { + if use == nil { + return 1 + } + + if log.BlockNumber != use.BlockNumber { + return log.BlockNumber - use.BlockNumber + } + + return log.LogIndex - use.LogIndex +} + +func matchesRemainingFilters(log *logpoller.Log, filters []common.Hash) bool { + for i, rfai := range filters { + if !reflect.DeepEqual(rfai[:], log.Topics[i+2]) { + return false + } + } + + return true +} + +func wrapInternalErr(err error) error { + if err == nil { + return nil + } + + errStr := err.Error() + if strings.Contains(errStr, "not found") || strings.Contains(errStr, "no rows") { + return fmt.Errorf("%w: %w", commontypes.ErrNotFound, err) + } + + return fmt.Errorf("%w: %w", commontypes.ErrInternal, err) +} + +func createTopicFilters(filtersAndIndices []common.Hash) query.Expression { + var expressions []query.Expression + for topicID, fai := range filtersAndIndices { + // first topic index is 1-based, so we add 1. + expressions = append(expressions, logpoller.NewEventByTopicFilter( + uint64(topicID+1), []primitives.ValueComparator{{Value: fai.Hex(), Operator: primitives.Eq}}, + )) + } + return query.And(expressions...) +} diff --git a/core/services/relay/evm/read/filter.go b/core/services/relay/evm/read/filter.go new file mode 100644 index 00000000000..08f45729ece --- /dev/null +++ b/core/services/relay/evm/read/filter.go @@ -0,0 +1,107 @@ +package read + +import ( + "context" + "fmt" + "sync" + + "github.com/ethereum/go-ethereum/common" + + commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" + + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" +) + +type Registrar interface { + HasFilter(string) bool + RegisterFilter(context.Context, logpoller.Filter) error + UnregisterFilter(context.Context, string) error +} + +type syncedFilter struct { + // internal state properties + mu sync.RWMutex + filter logpoller.Filter +} + +func newSyncedFilter() *syncedFilter { + return &syncedFilter{} +} + +func (r *syncedFilter) Register(ctx context.Context, registrar Registrar) error { + r.mu.RLock() + defer r.mu.RUnlock() + + if !registrar.HasFilter(r.filter.Name) { + if err := registrar.RegisterFilter(ctx, r.filter); err != nil { + return fmt.Errorf("%w: %w", commontypes.ErrInternal, err) + } + } + + return nil +} + +func (r *syncedFilter) Unregister(ctx context.Context, registrar Registrar) error { + r.mu.RLock() + defer r.mu.RUnlock() + + if !registrar.HasFilter(r.filter.Name) { + return nil + } + + if err := registrar.UnregisterFilter(ctx, r.filter.Name); err != nil { + return fmt.Errorf("%w: %w", commontypes.ErrInternal, err) + } + + return nil +} + +func (r *syncedFilter) SetFilter(filter logpoller.Filter) { + r.mu.Lock() + defer r.mu.Unlock() + + r.filter = filter +} + +func (r *syncedFilter) SetName(name string) { + r.mu.Lock() + defer r.mu.Unlock() + + r.filter.Name = name +} + +func (r *syncedFilter) AddAddress(address common.Address) { + r.mu.Lock() + defer r.mu.Unlock() + + r.filter.Addresses = append(r.filter.Addresses, address) +} + +func (r *syncedFilter) RemoveAddress(address common.Address) { + r.mu.Lock() + defer r.mu.Unlock() + + var addrIdx int + for idx, addr := range r.filter.Addresses { + if addr.Hex() == address.Hex() { + addrIdx = idx + } + } + + r.filter.Addresses[addrIdx] = r.filter.Addresses[len(r.filter.Addresses)-1] + r.filter.Addresses = r.filter.Addresses[:len(r.filter.Addresses)-1] +} + +func (r *syncedFilter) Count() int { + r.mu.RLock() + defer r.mu.RUnlock() + + return len(r.filter.Addresses) +} + +func (r *syncedFilter) HasEventSigs() bool { + r.mu.RLock() + defer r.mu.RUnlock() + + return len(r.filter.EventSigs) > 0 && len(r.filter.Addresses) > 0 +} diff --git a/core/services/relay/evm/read/lookup.go b/core/services/relay/evm/read/lookup.go new file mode 100644 index 00000000000..5679d8cee06 --- /dev/null +++ b/core/services/relay/evm/read/lookup.go @@ -0,0 +1,84 @@ +package read + +import ( + "sync" + + "github.com/smartcontractkit/chainlink-common/pkg/types" +) + +type readValues struct { + address string + contract string + readName string +} + +// lookup provides basic utilities for mapping a complete readIdentifier to +// finite contract read information +type lookup struct { + mu sync.RWMutex + // contractReadNames maps a contract name to all available readNames (method, log, event, etc.) + contractReadNames map[string][]string + // readIdentifiers maps from a complete readIdentifier string to finite read data + // a readIdentifier is a combination of address, contract, and readName as a concatenated string + readIdentifiers map[string]readValues +} + +func newLookup() *lookup { + return &lookup{ + contractReadNames: make(map[string][]string), + readIdentifiers: make(map[string]readValues), + } +} + +func (l *lookup) addReadNameForContract(contract, readName string) { + l.mu.Lock() + defer l.mu.Unlock() + + readNames, exists := l.contractReadNames[contract] + if !exists { + readNames = []string{} + } + + l.contractReadNames[contract] = append(readNames, readName) +} + +func (l *lookup) bindAddressForContract(contract, address string) { + l.mu.Lock() + defer l.mu.Unlock() + + for _, readName := range l.contractReadNames[contract] { + readIdentifier := types.BoundContract{ + Address: address, + Name: contract, + }.ReadIdentifier(readName) + + l.readIdentifiers[readIdentifier] = readValues{ + address: address, + contract: contract, + readName: readName, + } + } +} + +func (l *lookup) unbindAddressForContract(contract, address string) { + l.mu.Lock() + defer l.mu.Unlock() + + for _, readName := range l.contractReadNames[contract] { + readIdentifier := types.BoundContract{ + Address: address, + Name: contract, + }.ReadIdentifier(readName) + + delete(l.readIdentifiers, readIdentifier) + } +} + +func (l *lookup) getContractForReadName(readName string) (readValues, bool) { + l.mu.RLock() + defer l.mu.RUnlock() + + contract, ok := l.readIdentifiers[readName] + + return contract, ok +} diff --git a/core/services/relay/evm/read/method.go b/core/services/relay/evm/read/method.go new file mode 100644 index 00000000000..3f29b00ce95 --- /dev/null +++ b/core/services/relay/evm/read/method.go @@ -0,0 +1,203 @@ +package read + +import ( + "context" + "fmt" + "math/big" + "sync" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/common" + + "github.com/smartcontractkit/chainlink-common/pkg/logger" + commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" + "github.com/smartcontractkit/chainlink-common/pkg/types/query" + "github.com/smartcontractkit/chainlink-common/pkg/types/query/primitives" + + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" + evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" + + evmclient "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" +) + +type NoContractExistsError struct { + Address common.Address +} + +func (e NoContractExistsError) Error() string { + return fmt.Sprintf("contract does not exist at address: %s", e.Address) +} + +type MethodBinding struct { + // read-only properties + contractName string + method string + + // dependencies + client evmclient.Client + ht logpoller.HeadTracker + lggr logger.Logger + confirmationsMapping map[primitives.ConfidenceLevel]evmtypes.Confirmations + + // internal state properties + codec commontypes.Codec + bindings map[common.Address]struct{} + mu sync.RWMutex +} + +func NewMethodBinding( + name, method string, + client evmclient.Client, + heads logpoller.HeadTracker, + confs map[primitives.ConfidenceLevel]evmtypes.Confirmations, + lggr logger.Logger, +) *MethodBinding { + return &MethodBinding{ + contractName: name, + method: method, + client: client, + ht: heads, + lggr: lggr, + confirmationsMapping: confs, + bindings: make(map[common.Address]struct{}), + } +} + +var _ Reader = &MethodBinding{} + +func (b *MethodBinding) Bind(ctx context.Context, bindings ...common.Address) error { + for _, binding := range bindings { + if b.isBound(binding) { + continue + } + + // check for contract byte code at the latest block and provided address + byteCode, err := b.client.CodeAt(ctx, binding, nil) + if err != nil { + return err + } + + if len(byteCode) == 0 { + return NoContractExistsError{Address: binding} + } + + b.setBinding(binding) + } + + return nil +} + +func (b *MethodBinding) Unbind(ctx context.Context, bindings ...common.Address) error { + b.mu.Lock() + defer b.mu.Unlock() + + for _, binding := range bindings { + delete(b.bindings, binding) + } + + return nil +} + +func (b *MethodBinding) SetCodec(codec commontypes.RemoteCodec) { + b.mu.Lock() + defer b.mu.Unlock() + + b.codec = codec +} + +func (b *MethodBinding) BatchCall(address common.Address, params, retVal any) (Call, error) { + if !b.isBound(address) { + return Call{}, fmt.Errorf("%w: address (%s) not bound to method (%s) for contract (%s)", commontypes.ErrInvalidConfig, address.Hex(), b.method, b.contractName) + } + + return Call{ + ContractAddress: address, + ContractName: b.contractName, + MethodName: b.method, + Params: params, + ReturnVal: retVal, + }, nil +} + +func (b *MethodBinding) GetLatestValue(ctx context.Context, addr common.Address, confidenceLevel primitives.ConfidenceLevel, params, returnVal any) error { + if !b.isBound(addr) { + return fmt.Errorf("%w: method not bound", commontypes.ErrInvalidType) + } + + data, err := b.codec.Encode(ctx, params, WrapItemType(b.contractName, b.method, true)) + if err != nil { + return err + } + + callMsg := ethereum.CallMsg{ + To: &addr, + From: addr, + Data: data, + } + + block, err := b.blockNumberFromConfidence(ctx, confidenceLevel) + if err != nil { + return err + } + + bytes, err := b.client.CallContract(ctx, callMsg, block) + if err != nil { + return fmt.Errorf("%w: %w", commontypes.ErrInternal, err) + } + + return b.codec.Decode(ctx, bytes, returnVal, WrapItemType(b.contractName, b.method, false)) +} + +func (b *MethodBinding) QueryKey( + _ context.Context, + _ common.Address, + _ query.KeyFilter, + _ query.LimitAndSort, + _ any, +) ([]commontypes.Sequence, error) { + return nil, nil +} + +func (b *MethodBinding) Register(_ context.Context) error { return nil } +func (b *MethodBinding) Unregister(_ context.Context) error { return nil } + +func (b *MethodBinding) blockNumberFromConfidence(ctx context.Context, confidenceLevel primitives.ConfidenceLevel) (*big.Int, error) { + confirmations, err := confidenceToConfirmations(b.confirmationsMapping, confidenceLevel) + if err != nil { + err = fmt.Errorf("%w for contract: %s, method: %s", err, b.contractName, b.method) + if confidenceLevel == primitives.Unconfirmed { + b.lggr.Errorf("%v, now falling back to default contract call behaviour that calls latest state", err) + return nil, nil + } + return nil, err + } + + _, finalized, err := b.ht.LatestAndFinalizedBlock(ctx) + if err != nil { + return nil, err + } + + if confirmations == evmtypes.Finalized { + return big.NewInt(finalized.Number), nil + } else if confirmations == evmtypes.Unconfirmed { + return nil, nil + } + + return nil, fmt.Errorf("unknown evm confirmations: %v for contract: %s, method: %s", confirmations, b.contractName, b.method) +} + +func (b *MethodBinding) isBound(binding common.Address) bool { + b.mu.RLock() + defer b.mu.RUnlock() + + _, exists := b.bindings[binding] + + return exists +} + +func (b *MethodBinding) setBinding(binding common.Address) { + b.mu.Lock() + defer b.mu.Unlock() + + b.bindings[binding] = struct{}{} +} diff --git a/core/services/relay/evm/rpclibmocks/batch_caller.go b/core/services/relay/evm/read/mocks/batch_caller.go similarity index 69% rename from core/services/relay/evm/rpclibmocks/batch_caller.go rename to core/services/relay/evm/read/mocks/batch_caller.go index 0bb2c7f4fa7..5f1144b5d81 100644 --- a/core/services/relay/evm/rpclibmocks/batch_caller.go +++ b/core/services/relay/evm/read/mocks/batch_caller.go @@ -1,11 +1,11 @@ // Code generated by mockery v2.43.2. DO NOT EDIT. -package rpclibmocks +package mocks import ( context "context" - evm "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm" + read "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/read" mock "github.com/stretchr/testify/mock" ) @@ -23,27 +23,27 @@ func (_m *BatchCaller) EXPECT() *BatchCaller_Expecter { } // 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) { +func (_m *BatchCaller) BatchCall(ctx context.Context, blockNumber uint64, batchRequests read.BatchCall) (read.BatchResult, error) { ret := _m.Called(ctx, blockNumber, batchRequests) if len(ret) == 0 { panic("no return value specified for BatchCall") } - var r0 evm.BatchResult + var r0 read.BatchResult var r1 error - if rf, ok := ret.Get(0).(func(context.Context, uint64, evm.BatchCall) (evm.BatchResult, error)); ok { + if rf, ok := ret.Get(0).(func(context.Context, uint64, read.BatchCall) (read.BatchResult, error)); ok { return rf(ctx, blockNumber, batchRequests) } - if rf, ok := ret.Get(0).(func(context.Context, uint64, evm.BatchCall) evm.BatchResult); ok { + if rf, ok := ret.Get(0).(func(context.Context, uint64, read.BatchCall) read.BatchResult); ok { r0 = rf(ctx, blockNumber, batchRequests) } else { if ret.Get(0) != nil { - r0 = ret.Get(0).(evm.BatchResult) + r0 = ret.Get(0).(read.BatchResult) } } - if rf, ok := ret.Get(1).(func(context.Context, uint64, evm.BatchCall) error); ok { + if rf, ok := ret.Get(1).(func(context.Context, uint64, read.BatchCall) error); ok { r1 = rf(ctx, blockNumber, batchRequests) } else { r1 = ret.Error(1) @@ -60,24 +60,24 @@ type BatchCaller_BatchCall_Call struct { // BatchCall is a helper method to define mock.On call // - ctx context.Context // - blockNumber uint64 -// - batchRequests evm.BatchCall +// - batchRequests read.BatchCall func (_e *BatchCaller_Expecter) BatchCall(ctx interface{}, blockNumber interface{}, batchRequests interface{}) *BatchCaller_BatchCall_Call { return &BatchCaller_BatchCall_Call{Call: _e.mock.On("BatchCall", ctx, blockNumber, batchRequests)} } -func (_c *BatchCaller_BatchCall_Call) Run(run func(ctx context.Context, blockNumber uint64, batchRequests evm.BatchCall)) *BatchCaller_BatchCall_Call { +func (_c *BatchCaller_BatchCall_Call) Run(run func(ctx context.Context, blockNumber uint64, batchRequests read.BatchCall)) *BatchCaller_BatchCall_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(uint64), args[2].(evm.BatchCall)) + run(args[0].(context.Context), args[1].(uint64), args[2].(read.BatchCall)) }) return _c } -func (_c *BatchCaller_BatchCall_Call) Return(_a0 evm.BatchResult, _a1 error) *BatchCaller_BatchCall_Call { +func (_c *BatchCaller_BatchCall_Call) Return(_a0 read.BatchResult, _a1 error) *BatchCaller_BatchCall_Call { _c.Call.Return(_a0, _a1) return _c } -func (_c *BatchCaller_BatchCall_Call) RunAndReturn(run func(context.Context, uint64, evm.BatchCall) (evm.BatchResult, error)) *BatchCaller_BatchCall_Call { +func (_c *BatchCaller_BatchCall_Call) RunAndReturn(run func(context.Context, uint64, read.BatchCall) (read.BatchResult, error)) *BatchCaller_BatchCall_Call { _c.Call.Return(run) return _c } diff --git a/core/services/relay/evm/read/mocks/reader.go b/core/services/relay/evm/read/mocks/reader.go new file mode 100644 index 00000000000..6bb9dd70cc1 --- /dev/null +++ b/core/services/relay/evm/read/mocks/reader.go @@ -0,0 +1,463 @@ +// Code generated by mockery v2.43.2. DO NOT EDIT. + +package mocks + +import ( + context "context" + + common "github.com/ethereum/go-ethereum/common" + + mock "github.com/stretchr/testify/mock" + + primitives "github.com/smartcontractkit/chainlink-common/pkg/types/query/primitives" + + query "github.com/smartcontractkit/chainlink-common/pkg/types/query" + + read "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/read" + + types "github.com/smartcontractkit/chainlink-common/pkg/types" +) + +// Reader is an autogenerated mock type for the Reader type +type Reader struct { + mock.Mock +} + +type Reader_Expecter struct { + mock *mock.Mock +} + +func (_m *Reader) EXPECT() *Reader_Expecter { + return &Reader_Expecter{mock: &_m.Mock} +} + +// BatchCall provides a mock function with given fields: address, params, retVal +func (_m *Reader) BatchCall(address common.Address, params interface{}, retVal interface{}) (read.Call, error) { + ret := _m.Called(address, params, retVal) + + if len(ret) == 0 { + panic("no return value specified for BatchCall") + } + + var r0 read.Call + var r1 error + if rf, ok := ret.Get(0).(func(common.Address, interface{}, interface{}) (read.Call, error)); ok { + return rf(address, params, retVal) + } + if rf, ok := ret.Get(0).(func(common.Address, interface{}, interface{}) read.Call); ok { + r0 = rf(address, params, retVal) + } else { + r0 = ret.Get(0).(read.Call) + } + + if rf, ok := ret.Get(1).(func(common.Address, interface{}, interface{}) error); ok { + r1 = rf(address, params, retVal) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Reader_BatchCall_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'BatchCall' +type Reader_BatchCall_Call struct { + *mock.Call +} + +// BatchCall is a helper method to define mock.On call +// - address common.Address +// - params interface{} +// - retVal interface{} +func (_e *Reader_Expecter) BatchCall(address interface{}, params interface{}, retVal interface{}) *Reader_BatchCall_Call { + return &Reader_BatchCall_Call{Call: _e.mock.On("BatchCall", address, params, retVal)} +} + +func (_c *Reader_BatchCall_Call) Run(run func(address common.Address, params interface{}, retVal interface{})) *Reader_BatchCall_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(common.Address), args[1].(interface{}), args[2].(interface{})) + }) + return _c +} + +func (_c *Reader_BatchCall_Call) Return(_a0 read.Call, _a1 error) *Reader_BatchCall_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *Reader_BatchCall_Call) RunAndReturn(run func(common.Address, interface{}, interface{}) (read.Call, error)) *Reader_BatchCall_Call { + _c.Call.Return(run) + return _c +} + +// Bind provides a mock function with given fields: _a0, _a1 +func (_m *Reader) Bind(_a0 context.Context, _a1 ...common.Address) error { + _va := make([]interface{}, len(_a1)) + for _i := range _a1 { + _va[_i] = _a1[_i] + } + var _ca []interface{} + _ca = append(_ca, _a0) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + if len(ret) == 0 { + panic("no return value specified for Bind") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, ...common.Address) error); ok { + r0 = rf(_a0, _a1...) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Reader_Bind_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Bind' +type Reader_Bind_Call struct { + *mock.Call +} + +// Bind is a helper method to define mock.On call +// - _a0 context.Context +// - _a1 ...common.Address +func (_e *Reader_Expecter) Bind(_a0 interface{}, _a1 ...interface{}) *Reader_Bind_Call { + return &Reader_Bind_Call{Call: _e.mock.On("Bind", + append([]interface{}{_a0}, _a1...)...)} +} + +func (_c *Reader_Bind_Call) Run(run func(_a0 context.Context, _a1 ...common.Address)) *Reader_Bind_Call { + _c.Call.Run(func(args mock.Arguments) { + variadicArgs := make([]common.Address, len(args)-1) + for i, a := range args[1:] { + if a != nil { + variadicArgs[i] = a.(common.Address) + } + } + run(args[0].(context.Context), variadicArgs...) + }) + return _c +} + +func (_c *Reader_Bind_Call) Return(_a0 error) *Reader_Bind_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Reader_Bind_Call) RunAndReturn(run func(context.Context, ...common.Address) error) *Reader_Bind_Call { + _c.Call.Return(run) + return _c +} + +// GetLatestValue provides a mock function with given fields: ctx, addr, confidence, params, returnVal +func (_m *Reader) GetLatestValue(ctx context.Context, addr common.Address, confidence primitives.ConfidenceLevel, params interface{}, returnVal interface{}) error { + ret := _m.Called(ctx, addr, confidence, params, returnVal) + + if len(ret) == 0 { + panic("no return value specified for GetLatestValue") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, common.Address, primitives.ConfidenceLevel, interface{}, interface{}) error); ok { + r0 = rf(ctx, addr, confidence, params, returnVal) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Reader_GetLatestValue_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetLatestValue' +type Reader_GetLatestValue_Call struct { + *mock.Call +} + +// GetLatestValue is a helper method to define mock.On call +// - ctx context.Context +// - addr common.Address +// - confidence primitives.ConfidenceLevel +// - params interface{} +// - returnVal interface{} +func (_e *Reader_Expecter) GetLatestValue(ctx interface{}, addr interface{}, confidence interface{}, params interface{}, returnVal interface{}) *Reader_GetLatestValue_Call { + return &Reader_GetLatestValue_Call{Call: _e.mock.On("GetLatestValue", ctx, addr, confidence, params, returnVal)} +} + +func (_c *Reader_GetLatestValue_Call) Run(run func(ctx context.Context, addr common.Address, confidence primitives.ConfidenceLevel, params interface{}, returnVal interface{})) *Reader_GetLatestValue_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(common.Address), args[2].(primitives.ConfidenceLevel), args[3].(interface{}), args[4].(interface{})) + }) + return _c +} + +func (_c *Reader_GetLatestValue_Call) Return(_a0 error) *Reader_GetLatestValue_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Reader_GetLatestValue_Call) RunAndReturn(run func(context.Context, common.Address, primitives.ConfidenceLevel, interface{}, interface{}) error) *Reader_GetLatestValue_Call { + _c.Call.Return(run) + return _c +} + +// QueryKey provides a mock function with given fields: _a0, _a1, _a2, _a3, _a4 +func (_m *Reader) QueryKey(_a0 context.Context, _a1 common.Address, _a2 query.KeyFilter, _a3 query.LimitAndSort, _a4 interface{}) ([]types.Sequence, error) { + ret := _m.Called(_a0, _a1, _a2, _a3, _a4) + + if len(ret) == 0 { + panic("no return value specified for QueryKey") + } + + var r0 []types.Sequence + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, common.Address, query.KeyFilter, query.LimitAndSort, interface{}) ([]types.Sequence, error)); ok { + return rf(_a0, _a1, _a2, _a3, _a4) + } + if rf, ok := ret.Get(0).(func(context.Context, common.Address, query.KeyFilter, query.LimitAndSort, interface{}) []types.Sequence); ok { + r0 = rf(_a0, _a1, _a2, _a3, _a4) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]types.Sequence) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, common.Address, query.KeyFilter, query.LimitAndSort, interface{}) error); ok { + r1 = rf(_a0, _a1, _a2, _a3, _a4) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Reader_QueryKey_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'QueryKey' +type Reader_QueryKey_Call struct { + *mock.Call +} + +// QueryKey is a helper method to define mock.On call +// - _a0 context.Context +// - _a1 common.Address +// - _a2 query.KeyFilter +// - _a3 query.LimitAndSort +// - _a4 interface{} +func (_e *Reader_Expecter) QueryKey(_a0 interface{}, _a1 interface{}, _a2 interface{}, _a3 interface{}, _a4 interface{}) *Reader_QueryKey_Call { + return &Reader_QueryKey_Call{Call: _e.mock.On("QueryKey", _a0, _a1, _a2, _a3, _a4)} +} + +func (_c *Reader_QueryKey_Call) Run(run func(_a0 context.Context, _a1 common.Address, _a2 query.KeyFilter, _a3 query.LimitAndSort, _a4 interface{})) *Reader_QueryKey_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(common.Address), args[2].(query.KeyFilter), args[3].(query.LimitAndSort), args[4].(interface{})) + }) + return _c +} + +func (_c *Reader_QueryKey_Call) Return(_a0 []types.Sequence, _a1 error) *Reader_QueryKey_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *Reader_QueryKey_Call) RunAndReturn(run func(context.Context, common.Address, query.KeyFilter, query.LimitAndSort, interface{}) ([]types.Sequence, error)) *Reader_QueryKey_Call { + _c.Call.Return(run) + return _c +} + +// Register provides a mock function with given fields: _a0 +func (_m *Reader) Register(_a0 context.Context) error { + ret := _m.Called(_a0) + + if len(ret) == 0 { + panic("no return value specified for Register") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context) error); ok { + r0 = rf(_a0) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Reader_Register_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Register' +type Reader_Register_Call struct { + *mock.Call +} + +// Register is a helper method to define mock.On call +// - _a0 context.Context +func (_e *Reader_Expecter) Register(_a0 interface{}) *Reader_Register_Call { + return &Reader_Register_Call{Call: _e.mock.On("Register", _a0)} +} + +func (_c *Reader_Register_Call) Run(run func(_a0 context.Context)) *Reader_Register_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *Reader_Register_Call) Return(_a0 error) *Reader_Register_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Reader_Register_Call) RunAndReturn(run func(context.Context) error) *Reader_Register_Call { + _c.Call.Return(run) + return _c +} + +// SetCodec provides a mock function with given fields: _a0 +func (_m *Reader) SetCodec(_a0 types.RemoteCodec) { + _m.Called(_a0) +} + +// Reader_SetCodec_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SetCodec' +type Reader_SetCodec_Call struct { + *mock.Call +} + +// SetCodec is a helper method to define mock.On call +// - _a0 types.RemoteCodec +func (_e *Reader_Expecter) SetCodec(_a0 interface{}) *Reader_SetCodec_Call { + return &Reader_SetCodec_Call{Call: _e.mock.On("SetCodec", _a0)} +} + +func (_c *Reader_SetCodec_Call) Run(run func(_a0 types.RemoteCodec)) *Reader_SetCodec_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(types.RemoteCodec)) + }) + return _c +} + +func (_c *Reader_SetCodec_Call) Return() *Reader_SetCodec_Call { + _c.Call.Return() + return _c +} + +func (_c *Reader_SetCodec_Call) RunAndReturn(run func(types.RemoteCodec)) *Reader_SetCodec_Call { + _c.Call.Return(run) + return _c +} + +// Unbind provides a mock function with given fields: _a0, _a1 +func (_m *Reader) Unbind(_a0 context.Context, _a1 ...common.Address) error { + _va := make([]interface{}, len(_a1)) + for _i := range _a1 { + _va[_i] = _a1[_i] + } + var _ca []interface{} + _ca = append(_ca, _a0) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + if len(ret) == 0 { + panic("no return value specified for Unbind") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, ...common.Address) error); ok { + r0 = rf(_a0, _a1...) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Reader_Unbind_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Unbind' +type Reader_Unbind_Call struct { + *mock.Call +} + +// Unbind is a helper method to define mock.On call +// - _a0 context.Context +// - _a1 ...common.Address +func (_e *Reader_Expecter) Unbind(_a0 interface{}, _a1 ...interface{}) *Reader_Unbind_Call { + return &Reader_Unbind_Call{Call: _e.mock.On("Unbind", + append([]interface{}{_a0}, _a1...)...)} +} + +func (_c *Reader_Unbind_Call) Run(run func(_a0 context.Context, _a1 ...common.Address)) *Reader_Unbind_Call { + _c.Call.Run(func(args mock.Arguments) { + variadicArgs := make([]common.Address, len(args)-1) + for i, a := range args[1:] { + if a != nil { + variadicArgs[i] = a.(common.Address) + } + } + run(args[0].(context.Context), variadicArgs...) + }) + return _c +} + +func (_c *Reader_Unbind_Call) Return(_a0 error) *Reader_Unbind_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Reader_Unbind_Call) RunAndReturn(run func(context.Context, ...common.Address) error) *Reader_Unbind_Call { + _c.Call.Return(run) + return _c +} + +// Unregister provides a mock function with given fields: _a0 +func (_m *Reader) Unregister(_a0 context.Context) error { + ret := _m.Called(_a0) + + if len(ret) == 0 { + panic("no return value specified for Unregister") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context) error); ok { + r0 = rf(_a0) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Reader_Unregister_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Unregister' +type Reader_Unregister_Call struct { + *mock.Call +} + +// Unregister is a helper method to define mock.On call +// - _a0 context.Context +func (_e *Reader_Expecter) Unregister(_a0 interface{}) *Reader_Unregister_Call { + return &Reader_Unregister_Call{Call: _e.mock.On("Unregister", _a0)} +} + +func (_c *Reader_Unregister_Call) Run(run func(_a0 context.Context)) *Reader_Unregister_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *Reader_Unregister_Call) Return(_a0 error) *Reader_Unregister_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Reader_Unregister_Call) RunAndReturn(run func(context.Context) error) *Reader_Unregister_Call { + _c.Call.Return(run) + return _c +} + +// NewReader creates a new instance of Reader. 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 NewReader(t interface { + mock.TestingT + Cleanup(func()) +}) *Reader { + mock := &Reader{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/core/services/relay/evm/read/mocks/registrar.go b/core/services/relay/evm/read/mocks/registrar.go new file mode 100644 index 00000000000..670c29317a1 --- /dev/null +++ b/core/services/relay/evm/read/mocks/registrar.go @@ -0,0 +1,177 @@ +// Code generated by mockery v2.43.2. DO NOT EDIT. + +package mocks + +import ( + context "context" + + logpoller "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" + mock "github.com/stretchr/testify/mock" +) + +// Registrar is an autogenerated mock type for the Registrar type +type Registrar struct { + mock.Mock +} + +type Registrar_Expecter struct { + mock *mock.Mock +} + +func (_m *Registrar) EXPECT() *Registrar_Expecter { + return &Registrar_Expecter{mock: &_m.Mock} +} + +// HasFilter provides a mock function with given fields: _a0 +func (_m *Registrar) HasFilter(_a0 string) bool { + ret := _m.Called(_a0) + + if len(ret) == 0 { + panic("no return value specified for HasFilter") + } + + var r0 bool + if rf, ok := ret.Get(0).(func(string) bool); ok { + r0 = rf(_a0) + } else { + r0 = ret.Get(0).(bool) + } + + return r0 +} + +// Registrar_HasFilter_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'HasFilter' +type Registrar_HasFilter_Call struct { + *mock.Call +} + +// HasFilter is a helper method to define mock.On call +// - _a0 string +func (_e *Registrar_Expecter) HasFilter(_a0 interface{}) *Registrar_HasFilter_Call { + return &Registrar_HasFilter_Call{Call: _e.mock.On("HasFilter", _a0)} +} + +func (_c *Registrar_HasFilter_Call) Run(run func(_a0 string)) *Registrar_HasFilter_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(string)) + }) + return _c +} + +func (_c *Registrar_HasFilter_Call) Return(_a0 bool) *Registrar_HasFilter_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Registrar_HasFilter_Call) RunAndReturn(run func(string) bool) *Registrar_HasFilter_Call { + _c.Call.Return(run) + return _c +} + +// RegisterFilter provides a mock function with given fields: _a0, _a1 +func (_m *Registrar) RegisterFilter(_a0 context.Context, _a1 logpoller.Filter) error { + ret := _m.Called(_a0, _a1) + + if len(ret) == 0 { + panic("no return value specified for RegisterFilter") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, logpoller.Filter) error); ok { + r0 = rf(_a0, _a1) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Registrar_RegisterFilter_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RegisterFilter' +type Registrar_RegisterFilter_Call struct { + *mock.Call +} + +// RegisterFilter is a helper method to define mock.On call +// - _a0 context.Context +// - _a1 logpoller.Filter +func (_e *Registrar_Expecter) RegisterFilter(_a0 interface{}, _a1 interface{}) *Registrar_RegisterFilter_Call { + return &Registrar_RegisterFilter_Call{Call: _e.mock.On("RegisterFilter", _a0, _a1)} +} + +func (_c *Registrar_RegisterFilter_Call) Run(run func(_a0 context.Context, _a1 logpoller.Filter)) *Registrar_RegisterFilter_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(logpoller.Filter)) + }) + return _c +} + +func (_c *Registrar_RegisterFilter_Call) Return(_a0 error) *Registrar_RegisterFilter_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Registrar_RegisterFilter_Call) RunAndReturn(run func(context.Context, logpoller.Filter) error) *Registrar_RegisterFilter_Call { + _c.Call.Return(run) + return _c +} + +// UnregisterFilter provides a mock function with given fields: _a0, _a1 +func (_m *Registrar) UnregisterFilter(_a0 context.Context, _a1 string) error { + ret := _m.Called(_a0, _a1) + + if len(ret) == 0 { + panic("no return value specified for UnregisterFilter") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { + r0 = rf(_a0, _a1) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Registrar_UnregisterFilter_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UnregisterFilter' +type Registrar_UnregisterFilter_Call struct { + *mock.Call +} + +// UnregisterFilter is a helper method to define mock.On call +// - _a0 context.Context +// - _a1 string +func (_e *Registrar_Expecter) UnregisterFilter(_a0 interface{}, _a1 interface{}) *Registrar_UnregisterFilter_Call { + return &Registrar_UnregisterFilter_Call{Call: _e.mock.On("UnregisterFilter", _a0, _a1)} +} + +func (_c *Registrar_UnregisterFilter_Call) Run(run func(_a0 context.Context, _a1 string)) *Registrar_UnregisterFilter_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string)) + }) + return _c +} + +func (_c *Registrar_UnregisterFilter_Call) Return(_a0 error) *Registrar_UnregisterFilter_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Registrar_UnregisterFilter_Call) RunAndReturn(run func(context.Context, string) error) *Registrar_UnregisterFilter_Call { + _c.Call.Return(run) + return _c +} + +// NewRegistrar creates a new instance of Registrar. 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 NewRegistrar(t interface { + mock.TestingT + Cleanup(func()) +}) *Registrar { + mock := &Registrar{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/core/services/relay/evm/write_target.go b/core/services/relay/evm/write_target.go index 6d543360579..8c71030f8a7 100644 --- a/core/services/relay/evm/write_target.go +++ b/core/services/relay/evm/write_target.go @@ -6,6 +6,7 @@ import ( "fmt" chainselectors "github.com/smartcontractkit/chain-selectors" + commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" "github.com/smartcontractkit/chainlink-common/pkg/logger" @@ -47,6 +48,16 @@ func NewWriteTarget(ctx context.Context, relayer *Relayer, chain legacyevm.Chain return nil, err } + err = cr.Bind(ctx, []commontypes.BoundContract{ + { + Name: "forwarder", + Address: config.ForwarderAddress().String(), + }, + }) + if err != nil { + return nil, err + } + chainWriterConfig := relayevmtypes.ChainWriterConfig{ Contracts: map[string]*relayevmtypes.ContractConfig{ "forwarder": { diff --git a/core/services/relay/evm/write_target_test.go b/core/services/relay/evm/write_target_test.go index 795b23f80ef..3721eec62af 100644 --- a/core/services/relay/evm/write_target_test.go +++ b/core/services/relay/evm/write_target_test.go @@ -21,6 +21,7 @@ import ( evmcapabilities "github.com/smartcontractkit/chainlink/v2/core/capabilities" evmclimocks "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client/mocks" gasmocks "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas/mocks" + pollermocks "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller/mocks" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr" txmmocks "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr/mocks" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" @@ -101,6 +102,7 @@ func TestEvmWrite(t *testing.T) { chain := evmmocks.NewChain(t) txManager := txmmocks.NewMockEvmTxManager(t) evmClient := evmclimocks.NewClient(t) + poller := pollermocks.NewLogPoller(t) // This is a very error-prone way to mock an on-chain response to a GetLatestValue("getTransmissionInfo") call // It's a bit of a hack, but it's the best way to do it without a lot of refactoring @@ -111,7 +113,7 @@ func TestEvmWrite(t *testing.T) { chain.On("ID").Return(big.NewInt(11155111)) chain.On("TxManager").Return(txManager) - chain.On("LogPoller").Return(nil) + chain.On("LogPoller").Return(poller) ht := mocks.NewHeadTracker[*types.Head, common.Hash](t) ht.On("LatestAndFinalizedBlock", mock.Anything).Return(&types.Head{}, &types.Head{}, nil) @@ -119,6 +121,8 @@ func TestEvmWrite(t *testing.T) { chain.On("Client").Return(evmClient) + poller.EXPECT().HasFilter(mock.Anything).Return(false) + cfg := configtest.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) { a := testutils.NewAddress() addr, err2 := types.NewEIP55Address(a.Hex()) diff --git a/go.md b/go.md index b0128221ef1..21f43e0d269 100644 --- a/go.md +++ b/go.md @@ -66,6 +66,7 @@ flowchart LR chainlink-data-streams --> grpc-proxy chainlink-feeds --> chainlink-common chainlink-feeds --> libocr + chainlink-feeds --> grpc-proxy chainlink-solana --> chainlink-common chainlink-solana --> libocr chainlink-starknet/relayer --> chainlink-common diff --git a/go.mod b/go.mod index 9c22104b602..da25f470f06 100644 --- a/go.mod +++ b/go.mod @@ -74,11 +74,11 @@ require ( github.com/shopspring/decimal v1.4.0 github.com/smartcontractkit/chain-selectors v1.0.21 github.com/smartcontractkit/chainlink-automation v1.0.4 - github.com/smartcontractkit/chainlink-ccip v0.0.0-20240905153234-86019f205c9b - github.com/smartcontractkit/chainlink-common v0.2.2-0.20240906132254-14a5c7af361f + github.com/smartcontractkit/chainlink-ccip v0.0.0-20240909152240-a6969c1002e6 + github.com/smartcontractkit/chainlink-common v0.2.2-0.20240909141252-663388d38293 github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240710121324-3ed288aa9b45 github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240904093355-e40169857652 - github.com/smartcontractkit/chainlink-feeds v0.0.0-20240710170203-5b41615da827 + github.com/smartcontractkit/chainlink-feeds v0.0.0-20240909142234-299749c4c0b5 github.com/smartcontractkit/chainlink-solana v1.1.1-0.20240904154226-abc1ed5c962a github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20240709043547-03612098f799 github.com/smartcontractkit/libocr v0.0.0-20240717100443-f6226e09bee7 diff --git a/go.sum b/go.sum index 41581f6ede7..748b5fb1e94 100644 --- a/go.sum +++ b/go.sum @@ -1145,16 +1145,16 @@ github.com/smartcontractkit/chain-selectors v1.0.21 h1:KCR9SA7PhOexaBzFieHoLv1Wo github.com/smartcontractkit/chain-selectors v1.0.21/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-ccip v0.0.0-20240905153234-86019f205c9b h1:/PQDTP/ETmEXCv3qokVs5JqMcHDFP8TWdkcQAzs/nQg= -github.com/smartcontractkit/chainlink-ccip v0.0.0-20240905153234-86019f205c9b/go.mod h1:Z9lQ5t20kRk28pzRLnqAJZUVOw8E6/siA3P3MLyKqoM= -github.com/smartcontractkit/chainlink-common v0.2.2-0.20240906132254-14a5c7af361f h1:a6zEDLYgTQ4G+9PgLX8G2bF40BdsOY5//5i+whYwLlQ= -github.com/smartcontractkit/chainlink-common v0.2.2-0.20240906132254-14a5c7af361f/go.mod h1:D/qaCoq0SxXzg5NRN5FtBRv98VBf+D2NOC++RbvvuOc= +github.com/smartcontractkit/chainlink-ccip v0.0.0-20240909152240-a6969c1002e6 h1:3VbeaqoBblboQ3ytpM7UZzL7MXoHctaaXGGkJ8XkDhY= +github.com/smartcontractkit/chainlink-ccip v0.0.0-20240909152240-a6969c1002e6/go.mod h1:v8hmGodMN1s1TQnvZepZ3Pbo+PyzWVdXag7JzJnvrkI= +github.com/smartcontractkit/chainlink-common v0.2.2-0.20240909141252-663388d38293 h1:Q19HQPzISHKEIP0rNFGEiIFvMEeAgb1YRXEFnyBMnNM= +github.com/smartcontractkit/chainlink-common v0.2.2-0.20240909141252-663388d38293/go.mod h1:D/qaCoq0SxXzg5NRN5FtBRv98VBf+D2NOC++RbvvuOc= 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-20240904093355-e40169857652 h1:0aZ3HiEz2bMM5ywHAyKlFMN95qTzpNDn7uvnHLrFX6s= github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240904093355-e40169857652/go.mod h1:PwPcmQNAzVmU8r8JWKrDRgvXesDwxnqbMD6DvYt/Z7M= -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-feeds v0.0.0-20240909142234-299749c4c0b5 h1:88ZNrxZd0Uxn9934G/3L5AABTX13iorQJdUXcSUewwg= +github.com/smartcontractkit/chainlink-feeds v0.0.0-20240909142234-299749c4c0b5/go.mod h1:/QPAcfj5RQ4pNmDBLJ/Or7EjMAvFU3Xb1pM9gl0jfao= github.com/smartcontractkit/chainlink-solana v1.1.1-0.20240904154226-abc1ed5c962a h1:0zbG9reudNAJC/Rwe3tYX7kshVtOEXXAhk0uLg/62Y4= github.com/smartcontractkit/chainlink-solana v1.1.1-0.20240904154226-abc1ed5c962a/go.mod h1:4jS7gAEHONm1dE7SkiUV16FXzuCMycG8X/9WXwNAHQI= github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20240709043547-03612098f799 h1:HyLTySm7BR+oNfZqDTkVJ25wnmcTtxBBD31UkFL+kEM= diff --git a/integration-tests/go.mod b/integration-tests/go.mod index 8b06e6aca69..f36a2ccb8fb 100644 --- a/integration-tests/go.mod +++ b/integration-tests/go.mod @@ -36,8 +36,8 @@ require ( github.com/smartcontractkit/ccip-owner-contracts v0.0.0-20240808195812-ae0378684685 github.com/smartcontractkit/chain-selectors v1.0.21 github.com/smartcontractkit/chainlink-automation v1.0.4 - github.com/smartcontractkit/chainlink-ccip v0.0.0-20240905153234-86019f205c9b - github.com/smartcontractkit/chainlink-common v0.2.2-0.20240906132254-14a5c7af361f + github.com/smartcontractkit/chainlink-ccip v0.0.0-20240909152240-a6969c1002e6 + github.com/smartcontractkit/chainlink-common v0.2.2-0.20240909141252-663388d38293 github.com/smartcontractkit/chainlink-testing-framework/havoc v1.50.0 github.com/smartcontractkit/chainlink-testing-framework/lib v1.50.0 github.com/smartcontractkit/chainlink-testing-framework/lib/grafana v1.50.0 @@ -405,7 +405,7 @@ require ( github.com/sirupsen/logrus v1.9.3 // indirect github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240710121324-3ed288aa9b45 // indirect github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240904093355-e40169857652 // indirect - github.com/smartcontractkit/chainlink-feeds v0.0.0-20240710170203-5b41615da827 // indirect + github.com/smartcontractkit/chainlink-feeds v0.0.0-20240909142234-299749c4c0b5 // indirect github.com/smartcontractkit/chainlink-solana v1.1.1-0.20240904154226-abc1ed5c962a // indirect github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20240709043547-03612098f799 // indirect github.com/smartcontractkit/grpc-proxy v0.0.0-20240830132753-a7e17fec5ab7 // indirect diff --git a/integration-tests/go.sum b/integration-tests/go.sum index 095d822fcab..642679410e4 100644 --- a/integration-tests/go.sum +++ b/integration-tests/go.sum @@ -1423,16 +1423,16 @@ github.com/smartcontractkit/chain-selectors v1.0.21 h1:KCR9SA7PhOexaBzFieHoLv1Wo github.com/smartcontractkit/chain-selectors v1.0.21/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-ccip v0.0.0-20240905153234-86019f205c9b h1:/PQDTP/ETmEXCv3qokVs5JqMcHDFP8TWdkcQAzs/nQg= -github.com/smartcontractkit/chainlink-ccip v0.0.0-20240905153234-86019f205c9b/go.mod h1:Z9lQ5t20kRk28pzRLnqAJZUVOw8E6/siA3P3MLyKqoM= -github.com/smartcontractkit/chainlink-common v0.2.2-0.20240906132254-14a5c7af361f h1:a6zEDLYgTQ4G+9PgLX8G2bF40BdsOY5//5i+whYwLlQ= -github.com/smartcontractkit/chainlink-common v0.2.2-0.20240906132254-14a5c7af361f/go.mod h1:D/qaCoq0SxXzg5NRN5FtBRv98VBf+D2NOC++RbvvuOc= +github.com/smartcontractkit/chainlink-ccip v0.0.0-20240909152240-a6969c1002e6 h1:3VbeaqoBblboQ3ytpM7UZzL7MXoHctaaXGGkJ8XkDhY= +github.com/smartcontractkit/chainlink-ccip v0.0.0-20240909152240-a6969c1002e6/go.mod h1:v8hmGodMN1s1TQnvZepZ3Pbo+PyzWVdXag7JzJnvrkI= +github.com/smartcontractkit/chainlink-common v0.2.2-0.20240909141252-663388d38293 h1:Q19HQPzISHKEIP0rNFGEiIFvMEeAgb1YRXEFnyBMnNM= +github.com/smartcontractkit/chainlink-common v0.2.2-0.20240909141252-663388d38293/go.mod h1:D/qaCoq0SxXzg5NRN5FtBRv98VBf+D2NOC++RbvvuOc= 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-20240904093355-e40169857652 h1:0aZ3HiEz2bMM5ywHAyKlFMN95qTzpNDn7uvnHLrFX6s= github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240904093355-e40169857652/go.mod h1:PwPcmQNAzVmU8r8JWKrDRgvXesDwxnqbMD6DvYt/Z7M= -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-feeds v0.0.0-20240909142234-299749c4c0b5 h1:88ZNrxZd0Uxn9934G/3L5AABTX13iorQJdUXcSUewwg= +github.com/smartcontractkit/chainlink-feeds v0.0.0-20240909142234-299749c4c0b5/go.mod h1:/QPAcfj5RQ4pNmDBLJ/Or7EjMAvFU3Xb1pM9gl0jfao= github.com/smartcontractkit/chainlink-solana v1.1.1-0.20240904154226-abc1ed5c962a h1:0zbG9reudNAJC/Rwe3tYX7kshVtOEXXAhk0uLg/62Y4= github.com/smartcontractkit/chainlink-solana v1.1.1-0.20240904154226-abc1ed5c962a/go.mod h1:4jS7gAEHONm1dE7SkiUV16FXzuCMycG8X/9WXwNAHQI= github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20240709043547-03612098f799 h1:HyLTySm7BR+oNfZqDTkVJ25wnmcTtxBBD31UkFL+kEM= diff --git a/integration-tests/load/go.mod b/integration-tests/load/go.mod index cfd0c02b66b..22c44365bcd 100644 --- a/integration-tests/load/go.mod +++ b/integration-tests/load/go.mod @@ -16,7 +16,7 @@ require ( github.com/rs/zerolog v1.33.0 github.com/slack-go/slack v0.12.2 github.com/smartcontractkit/chainlink-automation v1.0.4 - github.com/smartcontractkit/chainlink-common v0.2.2-0.20240906132254-14a5c7af361f + github.com/smartcontractkit/chainlink-common v0.2.2-0.20240909141252-663388d38293 github.com/smartcontractkit/chainlink-testing-framework/lib v1.50.0 github.com/smartcontractkit/chainlink-testing-framework/seth v1.50.1 github.com/smartcontractkit/chainlink-testing-framework/wasp v1.50.0 @@ -60,7 +60,7 @@ require ( github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/sergi/go-diff v1.3.1 // indirect - github.com/smartcontractkit/chainlink-ccip v0.0.0-20240905153234-86019f205c9b // indirect + github.com/smartcontractkit/chainlink-ccip v0.0.0-20240909152240-a6969c1002e6 // indirect github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240710121324-3ed288aa9b45 // indirect github.com/smartcontractkit/chainlink-testing-framework/havoc v1.50.0 // indirect github.com/smartcontractkit/chainlink-testing-framework/lib/grafana v1.50.0 // indirect @@ -397,7 +397,7 @@ require ( github.com/sirupsen/logrus v1.9.3 // indirect github.com/smartcontractkit/chain-selectors v1.0.21 // indirect github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240904093355-e40169857652 // indirect - github.com/smartcontractkit/chainlink-feeds v0.0.0-20240710170203-5b41615da827 // indirect + github.com/smartcontractkit/chainlink-feeds v0.0.0-20240909142234-299749c4c0b5 // indirect github.com/smartcontractkit/chainlink-solana v1.1.1-0.20240904154226-abc1ed5c962a // 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 diff --git a/integration-tests/load/go.sum b/integration-tests/load/go.sum index 507bc79df4c..07b77243402 100644 --- a/integration-tests/load/go.sum +++ b/integration-tests/load/go.sum @@ -1393,16 +1393,16 @@ github.com/smartcontractkit/chain-selectors v1.0.21 h1:KCR9SA7PhOexaBzFieHoLv1Wo github.com/smartcontractkit/chain-selectors v1.0.21/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-ccip v0.0.0-20240905153234-86019f205c9b h1:/PQDTP/ETmEXCv3qokVs5JqMcHDFP8TWdkcQAzs/nQg= -github.com/smartcontractkit/chainlink-ccip v0.0.0-20240905153234-86019f205c9b/go.mod h1:Z9lQ5t20kRk28pzRLnqAJZUVOw8E6/siA3P3MLyKqoM= -github.com/smartcontractkit/chainlink-common v0.2.2-0.20240906132254-14a5c7af361f h1:a6zEDLYgTQ4G+9PgLX8G2bF40BdsOY5//5i+whYwLlQ= -github.com/smartcontractkit/chainlink-common v0.2.2-0.20240906132254-14a5c7af361f/go.mod h1:D/qaCoq0SxXzg5NRN5FtBRv98VBf+D2NOC++RbvvuOc= +github.com/smartcontractkit/chainlink-ccip v0.0.0-20240909152240-a6969c1002e6 h1:3VbeaqoBblboQ3ytpM7UZzL7MXoHctaaXGGkJ8XkDhY= +github.com/smartcontractkit/chainlink-ccip v0.0.0-20240909152240-a6969c1002e6/go.mod h1:v8hmGodMN1s1TQnvZepZ3Pbo+PyzWVdXag7JzJnvrkI= +github.com/smartcontractkit/chainlink-common v0.2.2-0.20240909141252-663388d38293 h1:Q19HQPzISHKEIP0rNFGEiIFvMEeAgb1YRXEFnyBMnNM= +github.com/smartcontractkit/chainlink-common v0.2.2-0.20240909141252-663388d38293/go.mod h1:D/qaCoq0SxXzg5NRN5FtBRv98VBf+D2NOC++RbvvuOc= 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-20240904093355-e40169857652 h1:0aZ3HiEz2bMM5ywHAyKlFMN95qTzpNDn7uvnHLrFX6s= github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240904093355-e40169857652/go.mod h1:PwPcmQNAzVmU8r8JWKrDRgvXesDwxnqbMD6DvYt/Z7M= -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-feeds v0.0.0-20240909142234-299749c4c0b5 h1:88ZNrxZd0Uxn9934G/3L5AABTX13iorQJdUXcSUewwg= +github.com/smartcontractkit/chainlink-feeds v0.0.0-20240909142234-299749c4c0b5/go.mod h1:/QPAcfj5RQ4pNmDBLJ/Or7EjMAvFU3Xb1pM9gl0jfao= github.com/smartcontractkit/chainlink-solana v1.1.1-0.20240904154226-abc1ed5c962a h1:0zbG9reudNAJC/Rwe3tYX7kshVtOEXXAhk0uLg/62Y4= github.com/smartcontractkit/chainlink-solana v1.1.1-0.20240904154226-abc1ed5c962a/go.mod h1:4jS7gAEHONm1dE7SkiUV16FXzuCMycG8X/9WXwNAHQI= github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20240709043547-03612098f799 h1:HyLTySm7BR+oNfZqDTkVJ25wnmcTtxBBD31UkFL+kEM=