From b1640404ab73530946bdead5db7e29cc93ed6d8e Mon Sep 17 00:00:00 2001 From: Bruno Moura Date: Mon, 22 Jan 2024 14:00:30 +0000 Subject: [PATCH 1/7] pipeline/task.bridge: do not cache invalid external adapter response objects. (#11725) * pipeline/task.bridge: check for external adapter status in response object * pipeline/task.bridge: external adapter response object tests * pipeline: move ea specific utilities into an internal package and add unit tests * pipeline/task.bridge: rebase fix for commonconfig * Update core/services/pipeline/internal/eautils/eautils.go Co-authored-by: Jordan Krage * pipeline/task.bridge: ea status error can also be an object --------- Co-authored-by: Jordan Krage --- .../pipeline/internal/eautils/eautils.go | 39 +++++ .../pipeline/internal/eautils/eautils_test.go | 61 ++++++++ core/services/pipeline/task.bridge.go | 9 +- core/services/pipeline/task.bridge_test.go | 139 +++++++++++++++++- core/services/pipeline/task.http_test.go | 7 +- 5 files changed, 242 insertions(+), 13 deletions(-) create mode 100644 core/services/pipeline/internal/eautils/eautils.go create mode 100644 core/services/pipeline/internal/eautils/eautils_test.go diff --git a/core/services/pipeline/internal/eautils/eautils.go b/core/services/pipeline/internal/eautils/eautils.go new file mode 100644 index 00000000000..30faa826b22 --- /dev/null +++ b/core/services/pipeline/internal/eautils/eautils.go @@ -0,0 +1,39 @@ +package eautils + +import ( + "encoding/json" + "net/http" +) + +type AdapterStatus struct { + ErrorMessage *string `json:"errorMessage"` + Error any `json:"error"` + StatusCode *int `json:"statusCode"` + ProviderStatusCode *int `json:"providerStatusCode"` +} + +func BestEffortExtractEAStatus(responseBytes []byte) (code int, ok bool) { + var status AdapterStatus + err := json.Unmarshal(responseBytes, &status) + if err != nil { + return 0, false + } + + if status.StatusCode == nil { + return 0, false + } + + if *status.StatusCode != http.StatusOK { + return *status.StatusCode, true + } + + if status.ProviderStatusCode != nil && *status.ProviderStatusCode != http.StatusOK { + return *status.ProviderStatusCode, true + } + + if status.Error != nil { + return http.StatusInternalServerError, true + } + + return *status.StatusCode, true +} diff --git a/core/services/pipeline/internal/eautils/eautils_test.go b/core/services/pipeline/internal/eautils/eautils_test.go new file mode 100644 index 00000000000..80183b80d2b --- /dev/null +++ b/core/services/pipeline/internal/eautils/eautils_test.go @@ -0,0 +1,61 @@ +package eautils + +import ( + "net/http" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestBestEffortExtractEAStatus(t *testing.T) { + tests := []struct { + name string + arg []byte + expectCode int + expectOk bool + }{ + { + name: "invalid object", + arg: []byte(`{"error": "invalid json object" `), + expectCode: 0, + expectOk: false, + }, + { + name: "no status code in object", + arg: []byte(`{}`), + expectCode: 0, + expectOk: false, + }, + { + name: "invalid status code", + arg: []byte(`{"statusCode":400}`), + expectCode: http.StatusBadRequest, + expectOk: true, + }, + { + name: "invalid provider status code", + arg: []byte(`{"statusCode":200, "providerStatusCode":500}`), + expectCode: http.StatusInternalServerError, + expectOk: true, + }, + { + name: "valid statuses with error message", + arg: []byte(`{"statusCode":200, "providerStatusCode":200, "error": "unexpected error"}`), + expectCode: http.StatusInternalServerError, + expectOk: true, + }, + { + name: "valid status code", + arg: []byte(`{"statusCode":200}`), + expectCode: http.StatusOK, + expectOk: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + code, ok := BestEffortExtractEAStatus(tt.arg) + assert.Equal(t, tt.expectCode, code) + assert.Equal(t, tt.expectOk, ok) + }) + } +} diff --git a/core/services/pipeline/task.bridge.go b/core/services/pipeline/task.bridge.go index f9490ea791d..1da34d19134 100644 --- a/core/services/pipeline/task.bridge.go +++ b/core/services/pipeline/task.bridge.go @@ -16,6 +16,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/bridges" "github.com/smartcontractkit/chainlink/v2/core/logger" + "github.com/smartcontractkit/chainlink/v2/core/services/pipeline/internal/eautils" ) // NOTE: These metrics generate a new label per bridge, this should be safe @@ -167,7 +168,13 @@ func (t *BridgeTask) Run(ctx context.Context, lggr logger.Logger, vars Vars, inp var cachedResponse bool responseBytes, statusCode, headers, elapsed, err := makeHTTPRequest(requestCtx, lggr, "POST", url, reqHeaders, requestData, t.httpClient, t.config.DefaultHTTPLimit()) - if err != nil { + + // check for external adapter response object status + if code, ok := eautils.BestEffortExtractEAStatus(responseBytes); ok { + statusCode = code + } + + if err != nil || statusCode != http.StatusOK { promBridgeErrors.WithLabelValues(t.Name).Inc() if cacheTTL == 0 { return Result{Error: err}, RunInfo{IsRetryable: isRetryableHTTPError(statusCode, err)} diff --git a/core/services/pipeline/task.bridge_test.go b/core/services/pipeline/task.bridge_test.go index bf1e63d6314..7673add1e35 100644 --- a/core/services/pipeline/task.bridge_test.go +++ b/core/services/pipeline/task.bridge_test.go @@ -19,7 +19,6 @@ import ( "github.com/shopspring/decimal" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "gopkg.in/guregu/null.v4" commonconfig "github.com/smartcontractkit/chainlink-common/pkg/config" "github.com/smartcontractkit/chainlink/v2/core/bridges" @@ -32,6 +31,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" "github.com/smartcontractkit/chainlink/v2/core/services/pg" "github.com/smartcontractkit/chainlink/v2/core/services/pipeline" + "github.com/smartcontractkit/chainlink/v2/core/services/pipeline/internal/eautils" "github.com/smartcontractkit/chainlink/v2/core/store/models" "github.com/smartcontractkit/chainlink/v2/core/utils" ) @@ -59,11 +59,43 @@ type adapterResponseData struct { // adapterResponse is the HTTP response as defined by the external adapter: // https://github.com/smartcontractkit/bnc-adapter type adapterResponse struct { - Data adapterResponseData `json:"data"` - ErrorMessage null.String `json:"errorMessage"` + eautils.AdapterStatus + Data adapterResponseData `json:"data"` } -func (pr adapterResponse) Result() *decimal.Decimal { +func (pr *adapterResponse) SetStatusCode(code int) { + pr.StatusCode = &code +} + +func (pr *adapterResponse) UnsetStatusCode() { + pr.StatusCode = nil +} + +func (pr *adapterResponse) SetProviderStatusCode(code int) { + pr.ProviderStatusCode = &code +} + +func (pr *adapterResponse) UnsetProviderStatusCode() { + pr.ProviderStatusCode = nil +} + +func (pr *adapterResponse) SetError(msg string) { + pr.Error = msg +} + +func (pr *adapterResponse) UnsetError() { + pr.Error = nil +} + +func (pr *adapterResponse) SetErrorMessage(msg string) { + pr.ErrorMessage = &msg +} + +func (pr *adapterResponse) UnsetErrorMessage() { + pr.ErrorMessage = nil +} + +func (pr *adapterResponse) Result() *decimal.Decimal { return pr.Data.Result } @@ -295,7 +327,7 @@ func TestBridgeTask_DoesNotReturnStaleResults(t *testing.T) { task.HelperSetDependencies(cfg.JobPipeline(), cfg.WebServer(), orm, specID, uuid.UUID{}, c) // Insert entry 1m in the past, stale value, should not be used in case of EA failure. - err = queryer.ExecQ(`INSERT INTO bridge_last_value(dot_id, spec_id, value, finished_at) + err = queryer.ExecQ(`INSERT INTO bridge_last_value(dot_id, spec_id, value, finished_at) VALUES($1, $2, $3, $4) ON CONFLICT ON CONSTRAINT bridge_last_value_pkey DO UPDATE SET value = $3, finished_at = $4;`, task.DotID(), specID, big.NewInt(9700).Bytes(), time.Now().Add(-1*time.Minute)) require.NoError(t, err) @@ -786,9 +818,10 @@ func TestBridgeTask_ErrorMessage(t *testing.T) { handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusTooManyRequests) - err := json.NewEncoder(w).Encode(adapterResponse{ - ErrorMessage: null.StringFrom("could not hit data fetcher"), - }) + + resp := &adapterResponse{} + resp.SetErrorMessage("could not hit data fetcher") + err := json.NewEncoder(w).Encode(resp) require.NoError(t, err) }) @@ -1016,3 +1049,93 @@ func TestBridgeTask_Headers(t *testing.T) { assert.Equal(t, []string{"Content-Length", "38", "Content-Type", "footype", "User-Agent", "Go-http-client/1.1", "X-Header-1", "foo", "X-Header-2", "bar"}, allHeaders(headers)) }) } + +func TestBridgeTask_AdapterResponseStatusFailure(t *testing.T) { + t.Parallel() + + db := pgtest.NewSqlxDB(t) + cfg := configtest.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) { + c.WebServer.BridgeCacheTTL = commonconfig.MustNewDuration(1 * time.Minute) + }) + + testAdapterResponse := &adapterResponse{ + Data: adapterResponseData{Result: &decimal.Zero}, + } + + queryer := pg.NewQ(db, logger.TestLogger(t), cfg.Database()) + s1 := httptest.NewServer( + http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + err := json.NewEncoder(w).Encode(testAdapterResponse) + require.NoError(t, err) + })) + defer s1.Close() + + feedURL, err := url.ParseRequestURI(s1.URL) + require.NoError(t, err) + + orm := bridges.NewORM(db, logger.TestLogger(t), cfg.Database()) + _, bridge := cltest.MustCreateBridge(t, db, cltest.BridgeOpts{URL: feedURL.String()}, cfg.Database()) + + task := pipeline.BridgeTask{ + BaseTask: pipeline.NewBaseTask(0, "bridge", nil, nil, 0), + Name: bridge.Name.String(), + RequestData: btcUSDPairing, + } + c := clhttptest.NewTestLocalOnlyHTTPClient() + trORM := pipeline.NewORM(db, logger.TestLogger(t), cfg.Database(), cfg.JobPipeline().MaxSuccessfulRuns()) + specID, err := trORM.CreateSpec(pipeline.Pipeline{}, *models.NewInterval(5 * time.Minute), pg.WithParentCtx(testutils.Context(t))) + require.NoError(t, err) + task.HelperSetDependencies(cfg.JobPipeline(), cfg.WebServer(), orm, specID, uuid.UUID{}, c) + + // Insert entry 1m in the past, stale value, should not be used in case of EA failure. + err = queryer.ExecQ(`INSERT INTO bridge_last_value(dot_id, spec_id, value, finished_at) + VALUES($1, $2, $3, $4) ON CONFLICT ON CONSTRAINT bridge_last_value_pkey + DO UPDATE SET value = $3, finished_at = $4;`, task.DotID(), specID, big.NewInt(9700).Bytes(), time.Now()) + require.NoError(t, err) + + vars := pipeline.NewVarsFrom( + map[string]interface{}{ + "jobRun": map[string]interface{}{ + "meta": map[string]interface{}{ + "shouldFail": true, + }, + }, + }, + ) + + // expect all external adapter response status failures to be served from the cache + testAdapterResponse.SetStatusCode(http.StatusBadRequest) + result, runInfo := task.Run(testutils.Context(t), logger.TestLogger(t), vars, nil) + + require.NoError(t, result.Error) + require.NotNil(t, result.Value) + require.False(t, runInfo.IsRetryable) + require.False(t, runInfo.IsPending) + + testAdapterResponse.SetStatusCode(http.StatusOK) + testAdapterResponse.SetProviderStatusCode(http.StatusBadRequest) + result, runInfo = task.Run(testutils.Context(t), logger.TestLogger(t), vars, nil) + + require.NoError(t, result.Error) + require.NotNil(t, result.Value) + require.False(t, runInfo.IsRetryable) + require.False(t, runInfo.IsPending) + + testAdapterResponse.SetStatusCode(http.StatusOK) + testAdapterResponse.SetProviderStatusCode(http.StatusOK) + testAdapterResponse.SetError("some error") + result, runInfo = task.Run(testutils.Context(t), logger.TestLogger(t), vars, nil) + + require.NoError(t, result.Error) + require.NotNil(t, result.Value) + require.False(t, runInfo.IsRetryable) + require.False(t, runInfo.IsPending) + + testAdapterResponse.SetStatusCode(http.StatusInternalServerError) + result, runInfo = task.Run(testutils.Context(t), logger.TestLogger(t), vars, nil) + + require.NoError(t, result.Error) + require.NotNil(t, result.Value) + require.False(t, runInfo.IsRetryable) + require.False(t, runInfo.IsPending) +} diff --git a/core/services/pipeline/task.http_test.go b/core/services/pipeline/task.http_test.go index c0dd93df430..36ccc147a78 100644 --- a/core/services/pipeline/task.http_test.go +++ b/core/services/pipeline/task.http_test.go @@ -14,7 +14,6 @@ import ( "github.com/shopspring/decimal" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "gopkg.in/guregu/null.v4" "github.com/smartcontractkit/chainlink/v2/core/bridges" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" @@ -264,9 +263,9 @@ func TestHTTPTask_ErrorMessage(t *testing.T) { handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusTooManyRequests) - err := json.NewEncoder(w).Encode(adapterResponse{ - ErrorMessage: null.StringFrom("could not hit data fetcher"), - }) + resp := &adapterResponse{} + resp.SetErrorMessage("could not hit data fetcher") + err := json.NewEncoder(w).Encode(resp) require.NoError(t, err) }) From 41f2497830959a79513d3e80bd5195bdef8b950b Mon Sep 17 00:00:00 2001 From: Dmytro Haidashenko <34754799+dhaidashenko@users.noreply.github.com> Date: Mon, 22 Jan 2024 15:21:06 +0100 Subject: [PATCH 2/7] Fix RPC name override (#11813) * Fix rpc name override * use configured name without additional checks * multi-node metrics test * Fix typo --- core/chains/legacyevm/chain.go | 6 +- testdata/scripts/metrics/multi-node.txtar | 78 +++++++++++++++++++++++ 2 files changed, 80 insertions(+), 4 deletions(-) create mode 100644 testdata/scripts/metrics/multi-node.txtar diff --git a/core/chains/legacyevm/chain.go b/core/chains/legacyevm/chain.go index 0e0e1e65aca..92936299cdb 100644 --- a/core/chains/legacyevm/chain.go +++ b/core/chains/legacyevm/chain.go @@ -474,15 +474,13 @@ func newEthClientFromCfg(cfg evmconfig.NodePool, noNewHeadsThreshold time.Durati var sendonlys []commonclient.SendOnlyNode[*big.Int, evmclient.RPCCLient] for i, node := range nodes { if node.SendOnly != nil && *node.SendOnly { - name := fmt.Sprintf("eth-sendonly-rpc-%d", i) - rpc := evmclient.NewRPCClient(lggr, empty, (*url.URL)(node.HTTPURL), name, int32(i), chainID, + rpc := evmclient.NewRPCClient(lggr, empty, (*url.URL)(node.HTTPURL), *node.Name, int32(i), chainID, commonclient.Secondary) sendonly := commonclient.NewSendOnlyNode[*big.Int, evmclient.RPCCLient](lggr, (url.URL)(*node.HTTPURL), *node.Name, chainID, rpc) sendonlys = append(sendonlys, sendonly) } else { - name := fmt.Sprintf("eth-primary-rpc-%d", i) - rpc := evmclient.NewRPCClient(lggr, (url.URL)(*node.WSURL), (*url.URL)(node.HTTPURL), name, int32(i), + rpc := evmclient.NewRPCClient(lggr, (url.URL)(*node.WSURL), (*url.URL)(node.HTTPURL), *node.Name, int32(i), chainID, commonclient.Primary) primaryNode := commonclient.NewNode[*big.Int, *evmtypes.Head, evmclient.RPCCLient](cfg, noNewHeadsThreshold, lggr, (url.URL)(*node.WSURL), (*url.URL)(node.HTTPURL), *node.Name, int32(i), chainID, *node.Order, diff --git a/testdata/scripts/metrics/multi-node.txtar b/testdata/scripts/metrics/multi-node.txtar new file mode 100644 index 00000000000..c3928160443 --- /dev/null +++ b/testdata/scripts/metrics/multi-node.txtar @@ -0,0 +1,78 @@ +# Check that metrics specified in the expected_metrics are present in /metrics response +# start node +exec sh -c 'eval "echo \"$(cat config.toml.tmpl)\" > config.toml"' +exec chainlink node -c config.toml start -p password -a creds & + +# ensure node is up and running +env NODEURL=http://localhost:$PORT +exec curl --retry 10 --retry-max-time 60 --retry-connrefused $NODEURL + + +# Check +chmod 700 ./script.sh +exec sh -c './script.sh' + +-- script.sh -- + +maxRetries=5 +for retriesNum in $(seq 1 $maxRetries); do + passedAllChecks=true + curl $NODEURL/metrics > metrics.txt + while IFS= read -r expectedMetric; do + grep -q $expectedMetric metrics.txt && continue + + if [[ $retriesNum -ge $maxRetries ]]; then + cat metrics.txt + echo "FAIL Expected metric $expectedMetric to be present in GET /metrics response" + exit 1 + fi + + echo "Metric $expectedMetric is not present in GET /metrics response - retrying after 5s" + passedAllChecks=false + sleep 5 + break + done < expected_metrics.txt + + $passedAllChecks && break +done + +-- testdb.txt -- +CL_DATABASE_URL +-- testport.txt -- +PORT + +-- password -- +T.tLHkcmwePT/p,]sYuntjwHKAsrhm#4eRs4LuKHwvHejWYAC2JP4M8HimwgmbaZ +-- creds -- +notreal@fakeemail.ch +fj293fbBnlQ!f9vNs + +-- config.toml.tmpl -- +[Webserver] +HTTPPort = $PORT + +[[EVM]] +ChainID = '68472' + +[[EVM.Nodes]] +Name = 'BlueEVMPrimaryNode' +WSURL = 'wss://primaryfoo.bar/ws' +HTTPURL = 'https://primaryfoo.bar' + +[[EVM.Nodes]] +Name = 'YellowEVMPrimaryNode' +WSURL = 'wss://sendonlyfoo.bar/ws' +HTTPURL = 'https://sendonlyfoo.bar' +SendOnly = true + +-- expected_metrics.txt -- +evm_pool_rpc_node_dials_total{evmChainID="68472",nodeName="BlueEVMPrimaryNode"} +evm_pool_rpc_node_dials_total{evmChainID="68472",nodeName="YellowEVMPrimaryNode"} +multi_node_states{chainId="68472",network="EVM",state="Alive"} +multi_node_states{chainId="68472",network="EVM",state="Closed"} +multi_node_states{chainId="68472",network="EVM",state="Dialed"} +multi_node_states{chainId="68472",network="EVM",state="InvalidChainID"} +multi_node_states{chainId="68472",network="EVM",state="OutOfSync"} +multi_node_states{chainId="68472",network="EVM",state="Undialed"} +multi_node_states{chainId="68472",network="EVM",state="Unreachable"} +multi_node_states{chainId="68472",network="EVM",state="Unusable"} \ No newline at end of file From f0543e6d8ad919e2f97fd074b316de167ecdbb98 Mon Sep 17 00:00:00 2001 From: Sergey Kudasov Date: Mon, 22 Jan 2024 16:16:43 +0100 Subject: [PATCH 3/7] labels for automatic chaos testing (#11830) --- .../templates/chainlink-db-deployment.yaml | 8 ++++++ .../templates/chainlink-node-deployment.yaml | 25 ++++++++++++++----- .../templates/geth-deployment.yaml | 8 ++++++ 3 files changed, 35 insertions(+), 6 deletions(-) diff --git a/charts/chainlink-cluster/templates/chainlink-db-deployment.yaml b/charts/chainlink-cluster/templates/chainlink-db-deployment.yaml index f335130ea9f..91924ba5005 100644 --- a/charts/chainlink-cluster/templates/chainlink-db-deployment.yaml +++ b/charts/chainlink-cluster/templates/chainlink-db-deployment.yaml @@ -24,12 +24,20 @@ spec: selector: matchLabels: app: {{ $.Release.Name }}-db + # Used for testing. + # havoc-component-group and havoc-network-group are used by "havoc" chaos testing tool + havoc-component-group: db + havoc-network-group: db instance: {{ $cfg.name }}-db release: {{ $.Release.Name }} template: metadata: labels: app: {{ $.Release.Name }}-db + # Used for testing. + # havoc-component-group and havoc-network-group are used by "havoc" chaos testing tool + havoc-component-group: db + havoc-network-group: db instance: {{ $cfg.name }}-db release: {{ $.Release.Name }} {{- range $key, $value := $.Values.labels }} diff --git a/charts/chainlink-cluster/templates/chainlink-node-deployment.yaml b/charts/chainlink-cluster/templates/chainlink-node-deployment.yaml index 884cf0e535b..0ce16fd475b 100644 --- a/charts/chainlink-cluster/templates/chainlink-node-deployment.yaml +++ b/charts/chainlink-cluster/templates/chainlink-node-deployment.yaml @@ -2,7 +2,7 @@ apiVersion: apps/v1 kind: Deployment metadata: - name: {{ $.Release.Name }}-{{ $cfg.name }} + name: {{ if eq $index 0 }}{{ $.Release.Name }}-{{ $cfg.name }}-bootstrap{{ else }}{{ $.Release.Name }}-{{ $cfg.name }}{{ end }} spec: strategy: # Need to recreate the pod to deal with lease lock held by old pod. @@ -10,18 +10,31 @@ spec: selector: matchLabels: app: {{ $.Release.Name }} + # Used for testing. + # havoc-component-group and havoc-network-group are used by "havoc" chaos testing tool + {{ if eq $index 0 }}{{ else }} + havoc-component-group: node + {{ end }} + {{ if eq $index 0 }}{{ else }} + havoc-network-group: {{ if gt $index 2 }}"1"{{ else }}"2"{{ end }} + {{ end }} instance: {{ $cfg.name }} release: {{ $.Release.Name }} template: metadata: labels: app: {{ $.Release.Name }} + # Used for testing. + # havoc-component-group and havoc-network-group are used by "havoc" chaos testing tool + {{ if eq $index 0 }}{{ else }} + havoc-component-group: node + {{ end }} + {{ if eq $index 0 }}{{ else }} + havoc-network-group: {{ if gt $index 2 }}"1"{{ else }}"2"{{ end }} + {{ end }} + instance: {{ $cfg.name }} release: {{ $.Release.Name }} - # Used for testing. Role value should either be: bootstrap or node. - # There should only be one "bootstrap" node, the rest should be "node". - # Here we set the first node to be bootstrap, the rest to be node. - role: {{ if eq $index 0 }}bootstrap{{ else }}node{{ end }} {{- range $key, $value := $.Values.labels }} {{ $key }}: {{ $value | quote }} {{- end }} @@ -43,7 +56,7 @@ spec: {{- toYaml $.Values.chainlink.securityContext | nindent 12 }} image: {{ default "public.ecr.aws/chainlink/chainlink" $cfg.image }} imagePullPolicy: Always - command: ["bash", "-c", "while ! pg_isready -U postgres --host {{ $.Release.Name }}-db-{{ $cfg.name }} --port 5432; do echo \"waiting for database to start\"; sleep 1; done && chainlink -c /etc/node-secrets-volume/default.toml -c /etc/node-secrets-volume/overrides.toml -secrets /etc/node-secrets-volume/secrets.toml node start -d -p /etc/node-secrets-volume/node-password -a /etc/node-secrets-volume/apicredentials --vrfpassword=/etc/node-secrets-volume/apicredentials"] + command: [ "bash", "-c", "while ! pg_isready -U postgres --host {{ $.Release.Name }}-db-{{ $cfg.name }} --port 5432; do echo \"waiting for database to start\"; sleep 1; done && chainlink -c /etc/node-secrets-volume/default.toml -c /etc/node-secrets-volume/overrides.toml -secrets /etc/node-secrets-volume/secrets.toml node start -d -p /etc/node-secrets-volume/node-password -a /etc/node-secrets-volume/apicredentials --vrfpassword=/etc/node-secrets-volume/apicredentials" ] ports: - name: access containerPort: {{ $.Values.chainlink.web_port }} diff --git a/charts/chainlink-cluster/templates/geth-deployment.yaml b/charts/chainlink-cluster/templates/geth-deployment.yaml index 8d2d4d3c76c..abc7853d978 100644 --- a/charts/chainlink-cluster/templates/geth-deployment.yaml +++ b/charts/chainlink-cluster/templates/geth-deployment.yaml @@ -7,11 +7,19 @@ spec: selector: matchLabels: app: geth + # Used for testing. + # havoc-component-group and havoc-network-group are used by "havoc" chaos testing tool + havoc-component-group: "blockchain" + havoc-network-group: "blockchain" release: {{ .Release.Name }} template: metadata: labels: app: geth + # Used for testing. + # havoc-component-group and havoc-network-group are used by "havoc" chaos testing tool + havoc-component-group: "blockchain" + havoc-network-group: "blockchain" release: {{ .Release.Name }} annotations: {{- range $key, $value := .Values.podAnnotations }} From dbcba88fcc12d479ddc12f44c0bcaac7caf259ab Mon Sep 17 00:00:00 2001 From: chainchad <96362174+chainchad@users.noreply.github.com> Date: Mon, 22 Jan 2024 12:27:52 -0500 Subject: [PATCH 4/7] Support custom ingress rules on the chart to support external Ingress (#11829) * Support custom ingress rules on the chart to allow external Ingress to reach pods * Simplify values to be passed in with the helm --set flag --- .../templates/networkpolicy-default-deny.yaml | 27 ------------ .../templates/networkpolicy-default.yaml | 41 +++++++++++++++++++ charts/chainlink-cluster/values.yaml | 9 ++++ 3 files changed, 50 insertions(+), 27 deletions(-) delete mode 100644 charts/chainlink-cluster/templates/networkpolicy-default-deny.yaml create mode 100644 charts/chainlink-cluster/templates/networkpolicy-default.yaml diff --git a/charts/chainlink-cluster/templates/networkpolicy-default-deny.yaml b/charts/chainlink-cluster/templates/networkpolicy-default-deny.yaml deleted file mode 100644 index e2a4407be6a..00000000000 --- a/charts/chainlink-cluster/templates/networkpolicy-default-deny.yaml +++ /dev/null @@ -1,27 +0,0 @@ -apiVersion: networking.k8s.io/v1 -kind: NetworkPolicy -metadata: - name: default-deny -spec: - podSelector: - matchLabels: {} - policyTypes: - - Ingress - - Egress - egress: - - to: - - namespaceSelector: - matchLabels: - kubernetes.io/metadata.name: "{{ $.Release.Namespace }}" - - to: - - namespaceSelector: - matchLabels: - kubernetes.io/metadata.name: kube-system - podSelector: - matchLabels: - k8s-app: kube-dns - ports: - - protocol: TCP - port: 53 - - protocol: UDP - port: 53 diff --git a/charts/chainlink-cluster/templates/networkpolicy-default.yaml b/charts/chainlink-cluster/templates/networkpolicy-default.yaml new file mode 100644 index 00000000000..f2d9416cf15 --- /dev/null +++ b/charts/chainlink-cluster/templates/networkpolicy-default.yaml @@ -0,0 +1,41 @@ +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: default +spec: + podSelector: + matchLabels: {} + policyTypes: + - Ingress + - Egress + ingress: + {{- if and .Values.networkPolicyDefault.ingress.allowCustomCidrs (not (empty .Values.networkPolicyDefault.ingress.customCidrs)) }} + # Using a comma separated list to make it easy to pass in with: + # `helm template ... --set networkPolicyDefault.ingress.customCidrs=...` + {{- $cidrs := splitList "," .Values.networkPolicyDefault.ingress.customCidrs }} + - from: + {{- range $cidr := $cidrs }} + - ipBlock: + cidr: {{ $cidr | quote }} + {{- end }} + {{- else }} + # Deny all ingress if no rules are specified. Rules can still be specified in other templates. + - {} + {{- end }} + egress: + - to: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: "{{ $.Release.Namespace }}" + - to: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: kube-system + podSelector: + matchLabels: + k8s-app: kube-dns + ports: + - protocol: TCP + port: 53 + - protocol: UDP + port: 53 diff --git a/charts/chainlink-cluster/values.yaml b/charts/chainlink-cluster/values.yaml index 9394bda1213..24914a40a91 100644 --- a/charts/chainlink-cluster/values.yaml +++ b/charts/chainlink-cluster/values.yaml @@ -283,3 +283,12 @@ podAnnotations: nodeSelector: tolerations: affinity: + +# Configure the default network policy. +networkPolicyDefault: + ingress: + allowCustomCidrs: false + # String of comma separated CIDRs + customCidrs: null + # Example: + # customCidrs: "10.0.0.0/16,192.168.0.1/24" From 091a9a31a29ada9e49125d3c6e143450f885c8a6 Mon Sep 17 00:00:00 2001 From: Ryan Tinianov Date: Mon, 22 Jan 2024 15:46:26 -0500 Subject: [PATCH 5/7] Add chain reader and codec EVM implementation (#11842) * Implement skeleton interfaces, structs, & methods for ChainReader EVM POC - Read ChainReader config in from RelayConfig - Add some initialization and validation relay skeletons - Use medianProviderWrapper instead of passing medianContract separately This avoids us having to modify the signature of NewMedianFactory, which would require further modifications to all non-evm repos and chainlink-relay - Add chain_reader_test.go with some basic relay tests Co-authored-by: Jordan Krage - Add chain reader config validation - Add chain reader config validation tests - Add config for chain reader median contract to cr validation testcases - Add unimplemented Encode(), Decode(), GetMaxEncodingSize(), GetMaxDecodingSize() - Add ChainReader() method to mock provider for plugin test - Rename relaymercury.ChainReader to MercuryChainReader, resolve name collisions - Add tests for errors during ChainReader construction - Propagate InvalidConfig & any other errors back to client We should ignore Unsupported until node ops have been given ample time to migrate to the new job spec (including a section for ChainReader config) so that we can remove the old product-specific MedianContract component from MedianProvider. All other errors we can immediately start passing back to the client, letting the core node decide how to handle them (eg. displaying an "invalid job spec" message to the UI if the RelayConfig was invalid or the ContractID missing) * Fix lint * Fix go imports lint * Fix go.mod require got split into two Co-authored-by: Jordan Krage * Run make gomodtidy * Add tickets to chainreader TODOs * Log when we're falling back to internal MedianContract We had this before, at some point it accidentally got dropped * Pass contractID, relayConfig instead of relayOpts Also: move test for invalid contract ID to evm_test.go, & add mismatched ChainID test while we're here since it's pretty similar * Revert get solana sha workflow to ref develop Co-authored-by: Jordan Krage * Update err handling for when chain reader config is missing * Update solana repo hash * Improve chain reader config validation tests * Run go mod tidy * Update solana repo go mod reference and tidy * fix logs dir for Solana tests * fix solana logs path once more * print absolue log folder location * get abs folder in a correct way * do it some other way * update solana logs dir in github workflow * Add chain Reader enabled log to NewMedianServices * Update chainlink-solana ref and tidy * Add logs and handle disabled chain reader when median loop is enabled * Update go mod solana repo refs * Improve error messages in evm chain reader and add test cases * Update minor comment * Run tidy * Remove unneeded chain reader codec methods * Remove chain reader return values as they are out of scope for this PR * Add types for the evm * Create a codec entry with from args, allowing us to do type checking and to have structs to use with abi.Arguments's Pack function * Craetes a function to get the max size for abi.Argumetns, given any outermost slices have N elements * Add codec and make chain reader use it. Run the interface tests for both chain reader and codec. * Add modifiers to evm chain reader and codec * Add modifiers to evm chain reader and codec * small fixes after merging * Small cleanups and comments * fix after merge * add events * Don't pass ctx around in tests * Support multiple contract names and mapping them to multiple contracts. * Upadte chain reader interface * update go mod * Fix address type and start the chain reader * Update go mod references * add epoch converting * Simplify chain reader's bindings. * Add smoke test and feature test for chain reader. * lint again * Update solana to point to newer other ones in its own integration tests... * linter * . * newer solana again * newer solana again... * Fix bug with unnamed return params and invalid params containing the same name multiple times * Add generated files to sonar exclude * Update sonar again * Methods per sonar suggestions * Reanme function and type per PR feedback * Generate abi, bin & wrapper for chainreader test with gethwrappers * Update generated-wrapper-dependency-versions-do-not-edit.txt Also: remove extra unused files * prettier -w ChainReaderTestContract.sol * Update to use the ErrSliceWrongLen * Move test contract from contracts/tests to contrats/shared/test * Fix SQ coverage exclusions * Use modifier for time * Update go.mod * .github/workflows: dedupe log artifact name with tag_suffix * [TT-792] Add Resources for New OCR2 Tests (#11702) * Add Resources for New OCR2 Tests * More power to the engines * Log everything * Chill on the power now * Fix missing container env * Update common * Fix broken test * Add encoder defs for events * Revert "Add encoder defs for events" This reverts commit dd99acc5afb9a206130a6a150a832e592400bbc3. * revert the change to codec entry for event and simplify how it's done * Add Marshal method for custom codec types. * Updated common, still seeing not found, not sure why... * Update feeds * Update feeds and common to remove accidental debugging log line * Update feeds to remove accidental log line and to add more logging to warning messages * Fixup go mod * Update feeds * Remove json tag that had no business being where it was in codec entry * Fix small lint error, likely detected from better linter in merge from dev * Add fuzz test for codec * Support indexed events * PR feedback, fix typo, clear up comment, revert making a function public * Refactored function for getting event input. * --fix and add comments * use maps instaed fo ToCamelCase to allow consistnacy, also safer if customers have foo and foo_ both as varibles. * Scope native and checked type conversion to a single struct and add tests to verify that NewAt is safe and fuzz tests for sanity around it too * Chain Reader config improvements (#11679) * core/services/job: remove JSONConfig.Bytes hack * Shorten chain reader cfg abi in median test, change readType string * minimize abi; pretty formatting; use text marshaller * integrations-tests/client: use TOML for relayconfig * minimize chain reader median contract abi in features ocr2 test * pretty ABI JSON * add ModifiersConfig wrapper to include type field * fix TestOCR2TaskJobSpec_String --------- Co-authored-by: ilija * Template must in types gen and small json rename for mods in chain reader def fields * Update test contract for lint, fix toml test files after renaming the mod in prior commit * Update common for a rename and fix tests that I broke a couple commits ago * Move test contract * Commit with fixed contract for codec test and add small check to if statement for arg len * small fix to encoder, and fix config in feature test * Remove debug line since smoke test is passing now and I need to be ready to merge * Fix bad merge conflict * small PR fixes * Couple more error fixes * update common * update feeds to point to main * Fix typo * Update Solana repo refs * Pretty chain reader test solitidty file * Update solana * Update solana in integation tests * Needed to update more than just integation tests for solana. * Update Solana ref to fix relayConfig * Update Solana ref again * Update solana and starknet to point to the develop branches. * update relayer for starknet --------- Co-authored-by: Domino Valdano <2644901+reductionista@users.noreply.github.com> Co-authored-by: ilija Co-authored-by: ilija42 <57732589+ilija42@users.noreply.github.com> Co-authored-by: Jordan Krage Co-authored-by: Bartek Tofel Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Adam Hamrick --- .github/workflows/ci-core.yml | 2 +- .github/workflows/integration-tests.yml | 6 +- .gitignore | 3 + common/txmgr/confirmer.go | 3 +- .../scripts/native_solc_compile_all_shared | 1 + .../test/helpers/ChainReaderTestContract.sol | 117 + .../chain_reader_example.go | 830 +++++ ...rapper-dependency-versions-do-not-edit.txt | 1 + core/gethwrappers/go_generate.go | 4 +- .../features/ocr2/features_ocr2_test.go | 502 ++- core/scripts/go.mod | 10 +- core/scripts/go.sum | 24 +- core/services/job/models.go | 3 +- core/services/job/models_test.go | 190 ++ core/services/job/testdata/compact.toml | 34 + core/services/job/testdata/pretty.toml | 149 + core/services/ocr2/plugins/median/plugin.go | 68 - core/services/ocr2/plugins/median/services.go | 84 +- core/services/relay/evm/binding.go | 15 + core/services/relay/evm/bindings.go | 61 + core/services/relay/evm/chain_reader.go | 292 +- core/services/relay/evm/chain_reader_test.go | 601 ++-- core/services/relay/evm/codec.go | 130 + core/services/relay/evm/codec_fuzz_test.go | 12 + core/services/relay/evm/codec_test.go | 210 ++ core/services/relay/evm/decoder.go | 102 + core/services/relay/evm/encoder.go | 145 + core/services/relay/evm/event_binding.go | 272 ++ core/services/relay/evm/evm.go | 35 +- core/services/relay/evm/functions.go | 4 + .../relay/evm/mercury/v1/data_source_test.go | 1 - core/services/relay/evm/mercury_provider.go | 8 + core/services/relay/evm/method_binding.go | 66 + core/services/relay/evm/ocr2keeper.go | 4 + core/services/relay/evm/ocr2vrf.go | 8 + core/services/relay/evm/parsed_types.go | 50 + core/services/relay/evm/types/abi_types.go | 66 + .../relay/evm/types/byte_types_gen.go | 300 ++ core/services/relay/evm/types/codec_entry.go | 251 ++ .../relay/evm/types/codec_entry_test.go | 284 ++ .../relay/evm/types/gen/bytes.go.tmpl | 14 + .../services/relay/evm/types/gen/ints.go.tmpl | 74 + core/services/relay/evm/types/gen/main.go | 73 + .../services/relay/evm/types/int_types_gen.go | 2710 +++++++++++++++++ .../relay/evm/types/int_types_test.go | 54 + core/services/relay/evm/types/size_helper.go | 69 + .../relay/evm/types/size_helper_test.go | 263 ++ core/services/relay/evm/types/types.go | 105 +- go.mod | 14 +- go.sum | 21 +- integration-tests/actions/ocr2_helpers.go | 9 +- .../actions/ocr2_helpers_local.go | 40 + integration-tests/client/chainlink_models.go | 38 +- .../client/chainlink_models_test.go | 135 + integration-tests/docker/test_env/cl_node.go | 1 + integration-tests/go.mod | 10 +- integration-tests/go.sum | 24 +- .../smoke/forwarders_ocr2_test.go | 2 +- integration-tests/smoke/ocr2_test.go | 24 +- plugins/medianpoc/plugin_test.go | 5 + sonar-project.properties | 2 +- tools/bin/go_core_fuzz | 36 + 62 files changed, 7955 insertions(+), 716 deletions(-) create mode 100644 contracts/src/v0.8/shared/test/helpers/ChainReaderTestContract.sol create mode 100644 core/gethwrappers/generated/chain_reader_example/chain_reader_example.go create mode 100644 core/services/job/testdata/compact.toml create mode 100644 core/services/job/testdata/pretty.toml delete mode 100644 core/services/ocr2/plugins/median/plugin.go create mode 100644 core/services/relay/evm/binding.go create mode 100644 core/services/relay/evm/bindings.go create mode 100644 core/services/relay/evm/codec.go create mode 100644 core/services/relay/evm/codec_fuzz_test.go create mode 100644 core/services/relay/evm/codec_test.go create mode 100644 core/services/relay/evm/decoder.go create mode 100644 core/services/relay/evm/encoder.go create mode 100644 core/services/relay/evm/event_binding.go create mode 100644 core/services/relay/evm/method_binding.go create mode 100644 core/services/relay/evm/parsed_types.go create mode 100644 core/services/relay/evm/types/abi_types.go create mode 100644 core/services/relay/evm/types/byte_types_gen.go create mode 100644 core/services/relay/evm/types/codec_entry.go create mode 100644 core/services/relay/evm/types/codec_entry_test.go create mode 100644 core/services/relay/evm/types/gen/bytes.go.tmpl create mode 100644 core/services/relay/evm/types/gen/ints.go.tmpl create mode 100644 core/services/relay/evm/types/gen/main.go create mode 100644 core/services/relay/evm/types/int_types_gen.go create mode 100644 core/services/relay/evm/types/int_types_test.go create mode 100644 core/services/relay/evm/types/size_helper.go create mode 100644 core/services/relay/evm/types/size_helper_test.go create mode 100644 integration-tests/client/chainlink_models_test.go create mode 100755 tools/bin/go_core_fuzz diff --git a/.github/workflows/ci-core.yml b/.github/workflows/ci-core.yml index 0d9d2912718..30534873134 100644 --- a/.github/workflows/ci-core.yml +++ b/.github/workflows/ci-core.yml @@ -41,7 +41,7 @@ jobs: strategy: fail-fast: false matrix: - cmd: ["go_core_tests", "go_core_race_tests"] + cmd: ["go_core_tests", "go_core_race_tests", "go_core_fuzz"] name: Core Tests (${{ matrix.cmd }}) runs-on: ubuntu20.04-64cores-256GB env: diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index 3f6c669d679..8ff6c3d4cd6 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -340,12 +340,12 @@ jobs: file: ocr pyroscope_env: ci-smoke-ocr-evm-simulated - name: ocr2 - nodes: 4 + nodes: 6 os: ubuntu-latest file: ocr2 pyroscope_env: ci-smoke-ocr2-evm-simulated - name: ocr2 - nodes: 4 + nodes: 6 os: ubuntu-latest pyroscope_env: ci-smoke-ocr2-plugins-evm-simulated tag_suffix: "-plugins" @@ -472,7 +472,7 @@ jobs: cl_repo: ${{ env.CHAINLINK_IMAGE }} cl_image_tag: ${{ github.sha }}${{ matrix.product.tag_suffix }} aws_registries: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }} - artifacts_name: ${{ matrix.product.name }}-test-logs + artifacts_name: ${{ matrix.product.name }}${{ matrix.product.tag_suffix }}-test-logs artifacts_location: ./integration-tests/smoke/logs/ publish_check_name: ${{ matrix.product.name }} token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index 2f68ec2c945..45ec5eb2bd4 100644 --- a/.gitignore +++ b/.gitignore @@ -85,3 +85,6 @@ tools/flakeytests/coverage.txt .test_summary/ .run.id + +# Fuzz tests can create these files +**/testdata/fuzz/* diff --git a/common/txmgr/confirmer.go b/common/txmgr/confirmer.go index f10481fef56..d55f982c11f 100644 --- a/common/txmgr/confirmer.go +++ b/common/txmgr/confirmer.go @@ -14,10 +14,11 @@ import ( "github.com/prometheus/client_golang/prometheus/promauto" "go.uber.org/multierr" + commonhex "github.com/smartcontractkit/chainlink-common/pkg/utils/hex" + "github.com/smartcontractkit/chainlink-common/pkg/chains/label" "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink-common/pkg/services" - commonhex "github.com/smartcontractkit/chainlink-common/pkg/utils/hex" "github.com/smartcontractkit/chainlink-common/pkg/utils/mailbox" "github.com/smartcontractkit/chainlink/v2/common/client" diff --git a/contracts/scripts/native_solc_compile_all_shared b/contracts/scripts/native_solc_compile_all_shared index 9178237b8a5..eeaa9902346 100755 --- a/contracts/scripts/native_solc_compile_all_shared +++ b/contracts/scripts/native_solc_compile_all_shared @@ -32,3 +32,4 @@ compileContract shared/token/ERC677/BurnMintERC677.sol compileContract shared/token/ERC677/LinkToken.sol compileContract shared/mocks/WERC20Mock.sol compileContract vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/ERC20.sol +compileContract shared/test/helpers/ChainReaderTestContract.sol diff --git a/contracts/src/v0.8/shared/test/helpers/ChainReaderTestContract.sol b/contracts/src/v0.8/shared/test/helpers/ChainReaderTestContract.sol new file mode 100644 index 00000000000..050f5fb390e --- /dev/null +++ b/contracts/src/v0.8/shared/test/helpers/ChainReaderTestContract.sol @@ -0,0 +1,117 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8; + +struct TestStruct { + int32 Field; + string DifferentField; + uint8 OracleId; + uint8[32] OracleIds; + address Account; + address[] Accounts; + int192 BigField; + MidLevelTestStruct NestedStruct; +} + +struct MidLevelTestStruct { + bytes2 FixedBytes; + InnerTestStruct Inner; +} + +struct InnerTestStruct { + int64 IntVal; + string S; +} + +contract LatestValueHolder { + event Triggered( + int32 indexed field, + string differentField, + uint8 oracleId, + uint8[32] oracleIds, + address Account, + address[] Accounts, + int192 bigField, + MidLevelTestStruct nestedStruct + ); + + event TriggeredEventWithDynamicTopic(string indexed fieldHash, string field); + + // First topic is event hash + event TriggeredWithFourTopics(int32 indexed field1, int32 indexed field2, int32 indexed field3); + + TestStruct[] private s_seen; + uint64[] private s_arr; + + constructor() { + // See chain_reader_interface_tests.go in chainlink-relay + s_arr.push(3); + s_arr.push(4); + } + + function addTestStruct( + int32 field, + string calldata differentField, + uint8 oracleId, + uint8[32] calldata oracleIds, + address account, + address[] calldata accounts, + int192 bigField, + MidLevelTestStruct calldata nestedStruct + ) public { + s_seen.push(TestStruct(field, differentField, oracleId, oracleIds, account, accounts, bigField, nestedStruct)); + } + + function returnSeen( + int32 field, + string calldata differentField, + uint8 oracleId, + uint8[32] calldata oracleIds, + address account, + address[] calldata accounts, + int192 bigField, + MidLevelTestStruct calldata nestedStruct + ) public pure returns (TestStruct memory) { + return TestStruct(field, differentField, oracleId, oracleIds, account, accounts, bigField, nestedStruct); + } + + function getElementAtIndex(uint256 i) public view returns (TestStruct memory) { + // See chain_reader_interface_tests.go in chainlink-relay + return s_seen[i - 1]; + } + + function getPrimitiveValue() public pure returns (uint64) { + // See chain_reader_interface_tests.go in chainlink-relay + return 3; + } + + function getDifferentPrimitiveValue() public pure returns (uint64) { + // See chain_reader_interface_tests.go in chainlink-relay + return 1990; + } + + function getSliceValue() public view returns (uint64[] memory) { + return s_arr; + } + + function triggerEvent( + int32 field, + string calldata differentField, + uint8 oracleId, + uint8[32] calldata oracleIds, + address account, + address[] calldata accounts, + int192 bigField, + MidLevelTestStruct calldata nestedStruct + ) public { + emit Triggered(field, differentField, oracleId, oracleIds, account, accounts, bigField, nestedStruct); + } + + function triggerEventWithDynamicTopic(string calldata field) public { + emit TriggeredEventWithDynamicTopic(field, field); + } + + // first topic is the event signature + function triggerWithFourTopics(int32 field1, int32 field2, int32 field3) public { + emit TriggeredWithFourTopics(field1, field2, field3); + } +} diff --git a/core/gethwrappers/generated/chain_reader_example/chain_reader_example.go b/core/gethwrappers/generated/chain_reader_example/chain_reader_example.go new file mode 100644 index 00000000000..f6dce0bb6c3 --- /dev/null +++ b/core/gethwrappers/generated/chain_reader_example/chain_reader_example.go @@ -0,0 +1,830 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package chain_reader_example + +import ( + "errors" + "fmt" + "math/big" + "strings" + + ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/event" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated" +) + +var ( + _ = errors.New + _ = big.NewInt + _ = strings.NewReader + _ = ethereum.NotFound + _ = bind.Bind + _ = common.Big1 + _ = types.BloomLookup + _ = event.NewSubscription + _ = abi.ConvertType +) + +type InnerTestStruct struct { + IntVal int64 + S string +} + +type MidLevelTestStruct struct { + FixedBytes [2]byte + Inner InnerTestStruct +} + +type TestStruct struct { + Field int32 + DifferentField string + OracleId uint8 + OracleIds [32]uint8 + Account common.Address + Accounts []common.Address + BigField *big.Int + NestedStruct MidLevelTestStruct +} + +var LatestValueHolderMetaData = &bind.MetaData{ + ABI: "[{\"inputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"int32\",\"name\":\"field\",\"type\":\"int32\"},{\"indexed\":false,\"internalType\":\"string\",\"name\":\"differentField\",\"type\":\"string\"},{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"oracleId\",\"type\":\"uint8\"},{\"indexed\":false,\"internalType\":\"uint8[32]\",\"name\":\"oracleIds\",\"type\":\"uint8[32]\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"Account\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address[]\",\"name\":\"Accounts\",\"type\":\"address[]\"},{\"indexed\":false,\"internalType\":\"int192\",\"name\":\"bigField\",\"type\":\"int192\"},{\"components\":[{\"internalType\":\"bytes2\",\"name\":\"FixedBytes\",\"type\":\"bytes2\"},{\"components\":[{\"internalType\":\"int64\",\"name\":\"IntVal\",\"type\":\"int64\"},{\"internalType\":\"string\",\"name\":\"S\",\"type\":\"string\"}],\"internalType\":\"structInnerTestStruct\",\"name\":\"Inner\",\"type\":\"tuple\"}],\"indexed\":false,\"internalType\":\"structMidLevelTestStruct\",\"name\":\"nestedStruct\",\"type\":\"tuple\"}],\"name\":\"Triggered\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"string\",\"name\":\"fieldHash\",\"type\":\"string\"},{\"indexed\":false,\"internalType\":\"string\",\"name\":\"field\",\"type\":\"string\"}],\"name\":\"TriggeredEventWithDynamicTopic\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"int32\",\"name\":\"field1\",\"type\":\"int32\"},{\"indexed\":true,\"internalType\":\"int32\",\"name\":\"field2\",\"type\":\"int32\"},{\"indexed\":true,\"internalType\":\"int32\",\"name\":\"field3\",\"type\":\"int32\"}],\"name\":\"TriggeredWithFourTopics\",\"type\":\"event\"},{\"inputs\":[{\"internalType\":\"int32\",\"name\":\"field\",\"type\":\"int32\"},{\"internalType\":\"string\",\"name\":\"differentField\",\"type\":\"string\"},{\"internalType\":\"uint8\",\"name\":\"oracleId\",\"type\":\"uint8\"},{\"internalType\":\"uint8[32]\",\"name\":\"oracleIds\",\"type\":\"uint8[32]\"},{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"},{\"internalType\":\"address[]\",\"name\":\"accounts\",\"type\":\"address[]\"},{\"internalType\":\"int192\",\"name\":\"bigField\",\"type\":\"int192\"},{\"components\":[{\"internalType\":\"bytes2\",\"name\":\"FixedBytes\",\"type\":\"bytes2\"},{\"components\":[{\"internalType\":\"int64\",\"name\":\"IntVal\",\"type\":\"int64\"},{\"internalType\":\"string\",\"name\":\"S\",\"type\":\"string\"}],\"internalType\":\"structInnerTestStruct\",\"name\":\"Inner\",\"type\":\"tuple\"}],\"internalType\":\"structMidLevelTestStruct\",\"name\":\"nestedStruct\",\"type\":\"tuple\"}],\"name\":\"addTestStruct\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getDifferentPrimitiveValue\",\"outputs\":[{\"internalType\":\"uint64\",\"name\":\"\",\"type\":\"uint64\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"i\",\"type\":\"uint256\"}],\"name\":\"getElementAtIndex\",\"outputs\":[{\"components\":[{\"internalType\":\"int32\",\"name\":\"Field\",\"type\":\"int32\"},{\"internalType\":\"string\",\"name\":\"DifferentField\",\"type\":\"string\"},{\"internalType\":\"uint8\",\"name\":\"OracleId\",\"type\":\"uint8\"},{\"internalType\":\"uint8[32]\",\"name\":\"OracleIds\",\"type\":\"uint8[32]\"},{\"internalType\":\"address\",\"name\":\"Account\",\"type\":\"address\"},{\"internalType\":\"address[]\",\"name\":\"Accounts\",\"type\":\"address[]\"},{\"internalType\":\"int192\",\"name\":\"BigField\",\"type\":\"int192\"},{\"components\":[{\"internalType\":\"bytes2\",\"name\":\"FixedBytes\",\"type\":\"bytes2\"},{\"components\":[{\"internalType\":\"int64\",\"name\":\"IntVal\",\"type\":\"int64\"},{\"internalType\":\"string\",\"name\":\"S\",\"type\":\"string\"}],\"internalType\":\"structInnerTestStruct\",\"name\":\"Inner\",\"type\":\"tuple\"}],\"internalType\":\"structMidLevelTestStruct\",\"name\":\"NestedStruct\",\"type\":\"tuple\"}],\"internalType\":\"structTestStruct\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getPrimitiveValue\",\"outputs\":[{\"internalType\":\"uint64\",\"name\":\"\",\"type\":\"uint64\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getSliceValue\",\"outputs\":[{\"internalType\":\"uint64[]\",\"name\":\"\",\"type\":\"uint64[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"int32\",\"name\":\"field\",\"type\":\"int32\"},{\"internalType\":\"string\",\"name\":\"differentField\",\"type\":\"string\"},{\"internalType\":\"uint8\",\"name\":\"oracleId\",\"type\":\"uint8\"},{\"internalType\":\"uint8[32]\",\"name\":\"oracleIds\",\"type\":\"uint8[32]\"},{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"},{\"internalType\":\"address[]\",\"name\":\"accounts\",\"type\":\"address[]\"},{\"internalType\":\"int192\",\"name\":\"bigField\",\"type\":\"int192\"},{\"components\":[{\"internalType\":\"bytes2\",\"name\":\"FixedBytes\",\"type\":\"bytes2\"},{\"components\":[{\"internalType\":\"int64\",\"name\":\"IntVal\",\"type\":\"int64\"},{\"internalType\":\"string\",\"name\":\"S\",\"type\":\"string\"}],\"internalType\":\"structInnerTestStruct\",\"name\":\"Inner\",\"type\":\"tuple\"}],\"internalType\":\"structMidLevelTestStruct\",\"name\":\"nestedStruct\",\"type\":\"tuple\"}],\"name\":\"returnSeen\",\"outputs\":[{\"components\":[{\"internalType\":\"int32\",\"name\":\"Field\",\"type\":\"int32\"},{\"internalType\":\"string\",\"name\":\"DifferentField\",\"type\":\"string\"},{\"internalType\":\"uint8\",\"name\":\"OracleId\",\"type\":\"uint8\"},{\"internalType\":\"uint8[32]\",\"name\":\"OracleIds\",\"type\":\"uint8[32]\"},{\"internalType\":\"address\",\"name\":\"Account\",\"type\":\"address\"},{\"internalType\":\"address[]\",\"name\":\"Accounts\",\"type\":\"address[]\"},{\"internalType\":\"int192\",\"name\":\"BigField\",\"type\":\"int192\"},{\"components\":[{\"internalType\":\"bytes2\",\"name\":\"FixedBytes\",\"type\":\"bytes2\"},{\"components\":[{\"internalType\":\"int64\",\"name\":\"IntVal\",\"type\":\"int64\"},{\"internalType\":\"string\",\"name\":\"S\",\"type\":\"string\"}],\"internalType\":\"structInnerTestStruct\",\"name\":\"Inner\",\"type\":\"tuple\"}],\"internalType\":\"structMidLevelTestStruct\",\"name\":\"NestedStruct\",\"type\":\"tuple\"}],\"internalType\":\"structTestStruct\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"int32\",\"name\":\"field\",\"type\":\"int32\"},{\"internalType\":\"string\",\"name\":\"differentField\",\"type\":\"string\"},{\"internalType\":\"uint8\",\"name\":\"oracleId\",\"type\":\"uint8\"},{\"internalType\":\"uint8[32]\",\"name\":\"oracleIds\",\"type\":\"uint8[32]\"},{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"},{\"internalType\":\"address[]\",\"name\":\"accounts\",\"type\":\"address[]\"},{\"internalType\":\"int192\",\"name\":\"bigField\",\"type\":\"int192\"},{\"components\":[{\"internalType\":\"bytes2\",\"name\":\"FixedBytes\",\"type\":\"bytes2\"},{\"components\":[{\"internalType\":\"int64\",\"name\":\"IntVal\",\"type\":\"int64\"},{\"internalType\":\"string\",\"name\":\"S\",\"type\":\"string\"}],\"internalType\":\"structInnerTestStruct\",\"name\":\"Inner\",\"type\":\"tuple\"}],\"internalType\":\"structMidLevelTestStruct\",\"name\":\"nestedStruct\",\"type\":\"tuple\"}],\"name\":\"triggerEvent\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"string\",\"name\":\"field\",\"type\":\"string\"}],\"name\":\"triggerEventWithDynamicTopic\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"int32\",\"name\":\"field1\",\"type\":\"int32\"},{\"internalType\":\"int32\",\"name\":\"field2\",\"type\":\"int32\"},{\"internalType\":\"int32\",\"name\":\"field3\",\"type\":\"int32\"}],\"name\":\"triggerWithFourTopics\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]", + Bin: "0x608060405234801561001057600080fd5b50600180548082018255600082905260048082047fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6908101805460086003958616810261010090810a8088026001600160401b0391820219909416939093179093558654808801909755848704909301805496909516909202900a91820291021990921691909117905561176c806100a96000396000f3fe608060405234801561001057600080fd5b50600436106100a35760003560e01c80637f002d6711610076578063dbfd73321161005b578063dbfd73321461013e578063ef4e1ced14610151578063f6f871c81461015857600080fd5b80637f002d671461010e578063ab5e0b381461012157600080fd5b80632c45576f146100a85780633272b66c146100d157806349eac2ac146100e6578063679004a4146100f9575b600080fd5b6100bb6100b6366004610baa565b61016b565b6040516100c89190610d09565b60405180910390f35b6100e46100df366004610e48565b610446565b005b6100e46100f4366004610f5d565b61049b565b61010161079e565b6040516100c8919061104f565b6100e461011c366004610f5d565b61082a565b6107c65b60405167ffffffffffffffff90911681526020016100c8565b6100e461014c36600461109d565b610881565b6003610125565b6100bb610166366004610f5d565b6108be565b6101736109c7565b60006101806001846110e0565b815481106101905761019061111a565b6000918252602091829020604080516101008101909152600a90920201805460030b825260018101805492939192918401916101cb90611149565b80601f01602080910402602001604051908101604052809291908181526020018280546101f790611149565b80156102445780601f1061021957610100808354040283529160200191610244565b820191906000526020600020905b81548152906001019060200180831161022757829003601f168201915b5050509183525050600282015460ff166020808301919091526040805161040081018083529190930192916003850191826000855b825461010083900a900460ff1681526020600192830181810494850194909303909202910180841161027957505050928452505050600482015473ffffffffffffffffffffffffffffffffffffffff16602080830191909152600583018054604080518285028101850182528281529401939283018282801561033257602002820191906000526020600020905b815473ffffffffffffffffffffffffffffffffffffffff168152600190910190602001808311610307575b5050509183525050600682015460170b6020808301919091526040805180820182526007808601805460f01b7fffff0000000000000000000000000000000000000000000000000000000000001683528351808501855260088801805490930b815260098801805495909701969395919486830194919392840191906103b790611149565b80601f01602080910402602001604051908101604052809291908181526020018280546103e390611149565b80156104305780601f1061040557610100808354040283529160200191610430565b820191906000526020600020905b81548152906001019060200180831161041357829003601f168201915b5050509190925250505090525090525092915050565b8181604051610456929190611196565b60405180910390207f3d969732b1bbbb9f1d7eb9f3f14e4cb50a74d950b3ef916a397b85dfbab93c67838360405161048f9291906111ef565b60405180910390a25050565b60006040518061010001604052808c60030b81526020018b8b8080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525050509082525060ff8a166020808301919091526040805161040081810183529190930192918b9183908390808284376000920191909152505050815273ffffffffffffffffffffffffffffffffffffffff8816602080830191909152604080518883028181018401835289825291909301929189918991829190850190849080828437600092019190915250505090825250601785900b602082015260400161058d846112ec565b905281546001808201845560009384526020938490208351600a9093020180547fffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000001663ffffffff9093169290921782559282015191929091908201906105f39082611446565b5060408201516002820180547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660ff90921691909117905560608201516106419060038301906020610a16565b5060808201516004820180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff90921691909117905560a082015180516106a8916005840191602090910190610aa9565b5060c08201516006820180547fffffffffffffffff0000000000000000000000000000000000000000000000001677ffffffffffffffffffffffffffffffffffffffffffffffff90921691909117905560e082015180516007830180547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00001660f09290921c91909117815560208083015180516008860180547fffffffffffffffffffffffffffffffffffffffffffffffff00000000000000001667ffffffffffffffff90921691909117815591810151909190600986019061078b9082611446565b5050505050505050505050505050505050565b6060600180548060200260200160405190810160405280929190818152602001828054801561082057602002820191906000526020600020906000905b82829054906101000a900467ffffffffffffffff1667ffffffffffffffff16815260200190600801906020826007010492830192600103820291508084116107db5790505b5050505050905090565b8960030b7f7188419dcd8b51877b71766f075f3626586c0ff190e7d056aa65ce9acb649a3d8a8a8a8a8a8a8a8a8a60405161086d999897969594939291906116a5565b60405180910390a250505050505050505050565b8060030b8260030b8460030b7f91c80dc390f3d041b3a04b0099b19634499541ea26972250986ee4b24a12fac560405160405180910390a4505050565b6108c66109c7565b6040518061010001604052808c60030b81526020018b8b8080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525050509082525060ff8a166020808301919091526040805161040081810183529190930192918b9183908390808284376000920191909152505050815273ffffffffffffffffffffffffffffffffffffffff8816602080830191909152604080518883028181018401835289825291909301929189918991829190850190849080828437600092019190915250505090825250601785900b60208201526040016109b6846112ec565b90529b9a5050505050505050505050565b60408051610100810182526000808252606060208301819052928201529081016109ef610b23565b8152600060208201819052606060408301819052820152608001610a11610b42565b905290565b600183019183908215610a995791602002820160005b83821115610a6a57835183826101000a81548160ff021916908360ff1602179055509260200192600101602081600001049283019260010302610a2c565b8015610a975782816101000a81549060ff0219169055600101602081600001049283019260010302610a6a565b505b50610aa5929150610b95565b5090565b828054828255906000526020600020908101928215610a99579160200282015b82811115610a9957825182547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff909116178255602090920191600190910190610ac9565b6040518061040001604052806020906020820280368337509192915050565b604051806040016040528060007dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff19168152602001610a116040518060400160405280600060070b8152602001606081525090565b5b80821115610aa55760008155600101610b96565b600060208284031215610bbc57600080fd5b5035919050565b6000815180845260005b81811015610be957602081850181015186830182015201610bcd565b5060006020828601015260207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f83011685010191505092915050565b8060005b6020808210610c3a5750610c51565b825160ff1685529384019390910190600101610c2b565b50505050565b600081518084526020808501945080840160005b83811015610c9d57815173ffffffffffffffffffffffffffffffffffffffff1687529582019590820190600101610c6b565b509495945050505050565b7fffff00000000000000000000000000000000000000000000000000000000000081511682526000602082015160406020850152805160070b60408501526020810151905060406060850152610d016080850182610bc3565b949350505050565b60208152610d1d60208201835160030b9052565b600060208301516104e0806040850152610d3b610500850183610bc3565b91506040850151610d51606086018260ff169052565b506060850151610d646080860182610c27565b50608085015173ffffffffffffffffffffffffffffffffffffffff1661048085015260a08501517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe085840381016104a0870152610dc18483610c57565b935060c08701519150610dda6104c087018360170b9052565b60e0870151915080868503018387015250610df58382610ca8565b9695505050505050565b60008083601f840112610e1157600080fd5b50813567ffffffffffffffff811115610e2957600080fd5b602083019150836020828501011115610e4157600080fd5b9250929050565b60008060208385031215610e5b57600080fd5b823567ffffffffffffffff811115610e7257600080fd5b610e7e85828601610dff565b90969095509350505050565b8035600381900b8114610e9c57600080fd5b919050565b803560ff81168114610e9c57600080fd5b806104008101831015610ec457600080fd5b92915050565b803573ffffffffffffffffffffffffffffffffffffffff81168114610e9c57600080fd5b60008083601f840112610f0057600080fd5b50813567ffffffffffffffff811115610f1857600080fd5b6020830191508360208260051b8501011115610e4157600080fd5b8035601781900b8114610e9c57600080fd5b600060408284031215610f5757600080fd5b50919050565b6000806000806000806000806000806104e08b8d031215610f7d57600080fd5b610f868b610e8a565b995060208b013567ffffffffffffffff80821115610fa357600080fd5b610faf8e838f01610dff565b909b509950899150610fc360408e01610ea1565b9850610fd28e60608f01610eb2565b9750610fe16104608e01610eca565b96506104808d0135915080821115610ff857600080fd5b6110048e838f01610eee565b90965094508491506110196104a08e01610f33565b93506104c08d013591508082111561103057600080fd5b5061103d8d828e01610f45565b9150509295989b9194979a5092959850565b6020808252825182820181905260009190848201906040850190845b8181101561109157835167ffffffffffffffff168352928401929184019160010161106b565b50909695505050505050565b6000806000606084860312156110b257600080fd5b6110bb84610e8a565b92506110c960208501610e8a565b91506110d760408501610e8a565b90509250925092565b81810381811115610ec4577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b600181811c9082168061115d57607f821691505b602082108103610f57577f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b8183823760009101908152919050565b8183528181602085013750600060208284010152600060207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f840116840101905092915050565b602081526000610d016020830184866111a6565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6040805190810167ffffffffffffffff8111828210171561125557611255611203565b60405290565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff811182821017156112a2576112a2611203565b604052919050565b80357fffff00000000000000000000000000000000000000000000000000000000000081168114610e9c57600080fd5b8035600781900b8114610e9c57600080fd5b6000604082360312156112fe57600080fd5b611306611232565b61130f836112aa565b815260208084013567ffffffffffffffff8082111561132d57600080fd5b81860191506040823603121561134257600080fd5b61134a611232565b611353836112da565b8152838301358281111561136657600080fd5b929092019136601f84011261137a57600080fd5b82358281111561138c5761138c611203565b6113bc857fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8401160161125b565b925080835236858286010111156113d257600080fd5b8085850186850137600090830185015280840191909152918301919091525092915050565b601f82111561144157600081815260208120601f850160051c8101602086101561141e5750805b601f850160051c820191505b8181101561143d5782815560010161142a565b5050505b505050565b815167ffffffffffffffff81111561146057611460611203565b6114748161146e8454611149565b846113f7565b602080601f8311600181146114c757600084156114915750858301515b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600386901b1c1916600185901b17855561143d565b6000858152602081207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08616915b82811015611514578886015182559484019460019091019084016114f5565b508582101561155057878501517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600388901b60f8161c191681555b5050505050600190811b01905550565b8183526000602080850194508260005b85811015610c9d5773ffffffffffffffffffffffffffffffffffffffff61159683610eca565b1687529582019590820190600101611570565b7fffff0000000000000000000000000000000000000000000000000000000000006115d3826112aa565b168252600060208201357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc183360301811261160d57600080fd5b60406020850152820161161f816112da565b60070b604085015260208101357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe182360301811261165c57600080fd5b0160208101903567ffffffffffffffff81111561167857600080fd5b80360382131561168757600080fd5b6040606086015261169c6080860182846111a6565b95945050505050565b60006104c08083526116ba8184018c8e6111a6565b9050602060ff808c1682860152604085018b60005b848110156116f457836116e183610ea1565b16835291840191908401906001016116cf565b505050505073ffffffffffffffffffffffffffffffffffffffff881661044084015282810361046084015261172a818789611560565b905061173c61048084018660170b9052565b8281036104a084015261174f81856115a9565b9c9b50505050505050505050505056fea164736f6c6343000813000a", +} + +var LatestValueHolderABI = LatestValueHolderMetaData.ABI + +var LatestValueHolderBin = LatestValueHolderMetaData.Bin + +func DeployLatestValueHolder(auth *bind.TransactOpts, backend bind.ContractBackend) (common.Address, *types.Transaction, *LatestValueHolder, error) { + parsed, err := LatestValueHolderMetaData.GetAbi() + if err != nil { + return common.Address{}, nil, nil, err + } + if parsed == nil { + return common.Address{}, nil, nil, errors.New("GetABI returned nil") + } + + address, tx, contract, err := bind.DeployContract(auth, *parsed, common.FromHex(LatestValueHolderBin), backend) + if err != nil { + return common.Address{}, nil, nil, err + } + return address, tx, &LatestValueHolder{address: address, abi: *parsed, LatestValueHolderCaller: LatestValueHolderCaller{contract: contract}, LatestValueHolderTransactor: LatestValueHolderTransactor{contract: contract}, LatestValueHolderFilterer: LatestValueHolderFilterer{contract: contract}}, nil +} + +type LatestValueHolder struct { + address common.Address + abi abi.ABI + LatestValueHolderCaller + LatestValueHolderTransactor + LatestValueHolderFilterer +} + +type LatestValueHolderCaller struct { + contract *bind.BoundContract +} + +type LatestValueHolderTransactor struct { + contract *bind.BoundContract +} + +type LatestValueHolderFilterer struct { + contract *bind.BoundContract +} + +type LatestValueHolderSession struct { + Contract *LatestValueHolder + CallOpts bind.CallOpts + TransactOpts bind.TransactOpts +} + +type LatestValueHolderCallerSession struct { + Contract *LatestValueHolderCaller + CallOpts bind.CallOpts +} + +type LatestValueHolderTransactorSession struct { + Contract *LatestValueHolderTransactor + TransactOpts bind.TransactOpts +} + +type LatestValueHolderRaw struct { + Contract *LatestValueHolder +} + +type LatestValueHolderCallerRaw struct { + Contract *LatestValueHolderCaller +} + +type LatestValueHolderTransactorRaw struct { + Contract *LatestValueHolderTransactor +} + +func NewLatestValueHolder(address common.Address, backend bind.ContractBackend) (*LatestValueHolder, error) { + abi, err := abi.JSON(strings.NewReader(LatestValueHolderABI)) + if err != nil { + return nil, err + } + contract, err := bindLatestValueHolder(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &LatestValueHolder{address: address, abi: abi, LatestValueHolderCaller: LatestValueHolderCaller{contract: contract}, LatestValueHolderTransactor: LatestValueHolderTransactor{contract: contract}, LatestValueHolderFilterer: LatestValueHolderFilterer{contract: contract}}, nil +} + +func NewLatestValueHolderCaller(address common.Address, caller bind.ContractCaller) (*LatestValueHolderCaller, error) { + contract, err := bindLatestValueHolder(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &LatestValueHolderCaller{contract: contract}, nil +} + +func NewLatestValueHolderTransactor(address common.Address, transactor bind.ContractTransactor) (*LatestValueHolderTransactor, error) { + contract, err := bindLatestValueHolder(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &LatestValueHolderTransactor{contract: contract}, nil +} + +func NewLatestValueHolderFilterer(address common.Address, filterer bind.ContractFilterer) (*LatestValueHolderFilterer, error) { + contract, err := bindLatestValueHolder(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &LatestValueHolderFilterer{contract: contract}, nil +} + +func bindLatestValueHolder(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := LatestValueHolderMetaData.GetAbi() + if err != nil { + return nil, err + } + return bind.NewBoundContract(address, *parsed, caller, transactor, filterer), nil +} + +func (_LatestValueHolder *LatestValueHolderRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _LatestValueHolder.Contract.LatestValueHolderCaller.contract.Call(opts, result, method, params...) +} + +func (_LatestValueHolder *LatestValueHolderRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _LatestValueHolder.Contract.LatestValueHolderTransactor.contract.Transfer(opts) +} + +func (_LatestValueHolder *LatestValueHolderRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _LatestValueHolder.Contract.LatestValueHolderTransactor.contract.Transact(opts, method, params...) +} + +func (_LatestValueHolder *LatestValueHolderCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _LatestValueHolder.Contract.contract.Call(opts, result, method, params...) +} + +func (_LatestValueHolder *LatestValueHolderTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _LatestValueHolder.Contract.contract.Transfer(opts) +} + +func (_LatestValueHolder *LatestValueHolderTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _LatestValueHolder.Contract.contract.Transact(opts, method, params...) +} + +func (_LatestValueHolder *LatestValueHolderCaller) GetDifferentPrimitiveValue(opts *bind.CallOpts) (uint64, error) { + var out []interface{} + err := _LatestValueHolder.contract.Call(opts, &out, "getDifferentPrimitiveValue") + + if err != nil { + return *new(uint64), err + } + + out0 := *abi.ConvertType(out[0], new(uint64)).(*uint64) + + return out0, err + +} + +func (_LatestValueHolder *LatestValueHolderSession) GetDifferentPrimitiveValue() (uint64, error) { + return _LatestValueHolder.Contract.GetDifferentPrimitiveValue(&_LatestValueHolder.CallOpts) +} + +func (_LatestValueHolder *LatestValueHolderCallerSession) GetDifferentPrimitiveValue() (uint64, error) { + return _LatestValueHolder.Contract.GetDifferentPrimitiveValue(&_LatestValueHolder.CallOpts) +} + +func (_LatestValueHolder *LatestValueHolderCaller) GetElementAtIndex(opts *bind.CallOpts, i *big.Int) (TestStruct, error) { + var out []interface{} + err := _LatestValueHolder.contract.Call(opts, &out, "getElementAtIndex", i) + + if err != nil { + return *new(TestStruct), err + } + + out0 := *abi.ConvertType(out[0], new(TestStruct)).(*TestStruct) + + return out0, err + +} + +func (_LatestValueHolder *LatestValueHolderSession) GetElementAtIndex(i *big.Int) (TestStruct, error) { + return _LatestValueHolder.Contract.GetElementAtIndex(&_LatestValueHolder.CallOpts, i) +} + +func (_LatestValueHolder *LatestValueHolderCallerSession) GetElementAtIndex(i *big.Int) (TestStruct, error) { + return _LatestValueHolder.Contract.GetElementAtIndex(&_LatestValueHolder.CallOpts, i) +} + +func (_LatestValueHolder *LatestValueHolderCaller) GetPrimitiveValue(opts *bind.CallOpts) (uint64, error) { + var out []interface{} + err := _LatestValueHolder.contract.Call(opts, &out, "getPrimitiveValue") + + if err != nil { + return *new(uint64), err + } + + out0 := *abi.ConvertType(out[0], new(uint64)).(*uint64) + + return out0, err + +} + +func (_LatestValueHolder *LatestValueHolderSession) GetPrimitiveValue() (uint64, error) { + return _LatestValueHolder.Contract.GetPrimitiveValue(&_LatestValueHolder.CallOpts) +} + +func (_LatestValueHolder *LatestValueHolderCallerSession) GetPrimitiveValue() (uint64, error) { + return _LatestValueHolder.Contract.GetPrimitiveValue(&_LatestValueHolder.CallOpts) +} + +func (_LatestValueHolder *LatestValueHolderCaller) GetSliceValue(opts *bind.CallOpts) ([]uint64, error) { + var out []interface{} + err := _LatestValueHolder.contract.Call(opts, &out, "getSliceValue") + + if err != nil { + return *new([]uint64), err + } + + out0 := *abi.ConvertType(out[0], new([]uint64)).(*[]uint64) + + return out0, err + +} + +func (_LatestValueHolder *LatestValueHolderSession) GetSliceValue() ([]uint64, error) { + return _LatestValueHolder.Contract.GetSliceValue(&_LatestValueHolder.CallOpts) +} + +func (_LatestValueHolder *LatestValueHolderCallerSession) GetSliceValue() ([]uint64, error) { + return _LatestValueHolder.Contract.GetSliceValue(&_LatestValueHolder.CallOpts) +} + +func (_LatestValueHolder *LatestValueHolderCaller) ReturnSeen(opts *bind.CallOpts, field int32, differentField string, oracleId uint8, oracleIds [32]uint8, account common.Address, accounts []common.Address, bigField *big.Int, nestedStruct MidLevelTestStruct) (TestStruct, error) { + var out []interface{} + err := _LatestValueHolder.contract.Call(opts, &out, "returnSeen", field, differentField, oracleId, oracleIds, account, accounts, bigField, nestedStruct) + + if err != nil { + return *new(TestStruct), err + } + + out0 := *abi.ConvertType(out[0], new(TestStruct)).(*TestStruct) + + return out0, err + +} + +func (_LatestValueHolder *LatestValueHolderSession) ReturnSeen(field int32, differentField string, oracleId uint8, oracleIds [32]uint8, account common.Address, accounts []common.Address, bigField *big.Int, nestedStruct MidLevelTestStruct) (TestStruct, error) { + return _LatestValueHolder.Contract.ReturnSeen(&_LatestValueHolder.CallOpts, field, differentField, oracleId, oracleIds, account, accounts, bigField, nestedStruct) +} + +func (_LatestValueHolder *LatestValueHolderCallerSession) ReturnSeen(field int32, differentField string, oracleId uint8, oracleIds [32]uint8, account common.Address, accounts []common.Address, bigField *big.Int, nestedStruct MidLevelTestStruct) (TestStruct, error) { + return _LatestValueHolder.Contract.ReturnSeen(&_LatestValueHolder.CallOpts, field, differentField, oracleId, oracleIds, account, accounts, bigField, nestedStruct) +} + +func (_LatestValueHolder *LatestValueHolderTransactor) AddTestStruct(opts *bind.TransactOpts, field int32, differentField string, oracleId uint8, oracleIds [32]uint8, account common.Address, accounts []common.Address, bigField *big.Int, nestedStruct MidLevelTestStruct) (*types.Transaction, error) { + return _LatestValueHolder.contract.Transact(opts, "addTestStruct", field, differentField, oracleId, oracleIds, account, accounts, bigField, nestedStruct) +} + +func (_LatestValueHolder *LatestValueHolderSession) AddTestStruct(field int32, differentField string, oracleId uint8, oracleIds [32]uint8, account common.Address, accounts []common.Address, bigField *big.Int, nestedStruct MidLevelTestStruct) (*types.Transaction, error) { + return _LatestValueHolder.Contract.AddTestStruct(&_LatestValueHolder.TransactOpts, field, differentField, oracleId, oracleIds, account, accounts, bigField, nestedStruct) +} + +func (_LatestValueHolder *LatestValueHolderTransactorSession) AddTestStruct(field int32, differentField string, oracleId uint8, oracleIds [32]uint8, account common.Address, accounts []common.Address, bigField *big.Int, nestedStruct MidLevelTestStruct) (*types.Transaction, error) { + return _LatestValueHolder.Contract.AddTestStruct(&_LatestValueHolder.TransactOpts, field, differentField, oracleId, oracleIds, account, accounts, bigField, nestedStruct) +} + +func (_LatestValueHolder *LatestValueHolderTransactor) TriggerEvent(opts *bind.TransactOpts, field int32, differentField string, oracleId uint8, oracleIds [32]uint8, account common.Address, accounts []common.Address, bigField *big.Int, nestedStruct MidLevelTestStruct) (*types.Transaction, error) { + return _LatestValueHolder.contract.Transact(opts, "triggerEvent", field, differentField, oracleId, oracleIds, account, accounts, bigField, nestedStruct) +} + +func (_LatestValueHolder *LatestValueHolderSession) TriggerEvent(field int32, differentField string, oracleId uint8, oracleIds [32]uint8, account common.Address, accounts []common.Address, bigField *big.Int, nestedStruct MidLevelTestStruct) (*types.Transaction, error) { + return _LatestValueHolder.Contract.TriggerEvent(&_LatestValueHolder.TransactOpts, field, differentField, oracleId, oracleIds, account, accounts, bigField, nestedStruct) +} + +func (_LatestValueHolder *LatestValueHolderTransactorSession) TriggerEvent(field int32, differentField string, oracleId uint8, oracleIds [32]uint8, account common.Address, accounts []common.Address, bigField *big.Int, nestedStruct MidLevelTestStruct) (*types.Transaction, error) { + return _LatestValueHolder.Contract.TriggerEvent(&_LatestValueHolder.TransactOpts, field, differentField, oracleId, oracleIds, account, accounts, bigField, nestedStruct) +} + +func (_LatestValueHolder *LatestValueHolderTransactor) TriggerEventWithDynamicTopic(opts *bind.TransactOpts, field string) (*types.Transaction, error) { + return _LatestValueHolder.contract.Transact(opts, "triggerEventWithDynamicTopic", field) +} + +func (_LatestValueHolder *LatestValueHolderSession) TriggerEventWithDynamicTopic(field string) (*types.Transaction, error) { + return _LatestValueHolder.Contract.TriggerEventWithDynamicTopic(&_LatestValueHolder.TransactOpts, field) +} + +func (_LatestValueHolder *LatestValueHolderTransactorSession) TriggerEventWithDynamicTopic(field string) (*types.Transaction, error) { + return _LatestValueHolder.Contract.TriggerEventWithDynamicTopic(&_LatestValueHolder.TransactOpts, field) +} + +func (_LatestValueHolder *LatestValueHolderTransactor) TriggerWithFourTopics(opts *bind.TransactOpts, field1 int32, field2 int32, field3 int32) (*types.Transaction, error) { + return _LatestValueHolder.contract.Transact(opts, "triggerWithFourTopics", field1, field2, field3) +} + +func (_LatestValueHolder *LatestValueHolderSession) TriggerWithFourTopics(field1 int32, field2 int32, field3 int32) (*types.Transaction, error) { + return _LatestValueHolder.Contract.TriggerWithFourTopics(&_LatestValueHolder.TransactOpts, field1, field2, field3) +} + +func (_LatestValueHolder *LatestValueHolderTransactorSession) TriggerWithFourTopics(field1 int32, field2 int32, field3 int32) (*types.Transaction, error) { + return _LatestValueHolder.Contract.TriggerWithFourTopics(&_LatestValueHolder.TransactOpts, field1, field2, field3) +} + +type LatestValueHolderTriggeredIterator struct { + Event *LatestValueHolderTriggered + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *LatestValueHolderTriggeredIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(LatestValueHolderTriggered) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + + select { + case log := <-it.logs: + it.Event = new(LatestValueHolderTriggered) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +func (it *LatestValueHolderTriggeredIterator) Error() error { + return it.fail +} + +func (it *LatestValueHolderTriggeredIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type LatestValueHolderTriggered struct { + Field int32 + DifferentField string + OracleId uint8 + OracleIds [32]uint8 + Account common.Address + Accounts []common.Address + BigField *big.Int + NestedStruct MidLevelTestStruct + Raw types.Log +} + +func (_LatestValueHolder *LatestValueHolderFilterer) FilterTriggered(opts *bind.FilterOpts, field []int32) (*LatestValueHolderTriggeredIterator, error) { + + var fieldRule []interface{} + for _, fieldItem := range field { + fieldRule = append(fieldRule, fieldItem) + } + + logs, sub, err := _LatestValueHolder.contract.FilterLogs(opts, "Triggered", fieldRule) + if err != nil { + return nil, err + } + return &LatestValueHolderTriggeredIterator{contract: _LatestValueHolder.contract, event: "Triggered", logs: logs, sub: sub}, nil +} + +func (_LatestValueHolder *LatestValueHolderFilterer) WatchTriggered(opts *bind.WatchOpts, sink chan<- *LatestValueHolderTriggered, field []int32) (event.Subscription, error) { + + var fieldRule []interface{} + for _, fieldItem := range field { + fieldRule = append(fieldRule, fieldItem) + } + + logs, sub, err := _LatestValueHolder.contract.WatchLogs(opts, "Triggered", fieldRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + + event := new(LatestValueHolderTriggered) + if err := _LatestValueHolder.contract.UnpackLog(event, "Triggered", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +func (_LatestValueHolder *LatestValueHolderFilterer) ParseTriggered(log types.Log) (*LatestValueHolderTriggered, error) { + event := new(LatestValueHolderTriggered) + if err := _LatestValueHolder.contract.UnpackLog(event, "Triggered", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +type LatestValueHolderTriggeredEventWithDynamicTopicIterator struct { + Event *LatestValueHolderTriggeredEventWithDynamicTopic + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *LatestValueHolderTriggeredEventWithDynamicTopicIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(LatestValueHolderTriggeredEventWithDynamicTopic) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + + select { + case log := <-it.logs: + it.Event = new(LatestValueHolderTriggeredEventWithDynamicTopic) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +func (it *LatestValueHolderTriggeredEventWithDynamicTopicIterator) Error() error { + return it.fail +} + +func (it *LatestValueHolderTriggeredEventWithDynamicTopicIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type LatestValueHolderTriggeredEventWithDynamicTopic struct { + FieldHash common.Hash + Field string + Raw types.Log +} + +func (_LatestValueHolder *LatestValueHolderFilterer) FilterTriggeredEventWithDynamicTopic(opts *bind.FilterOpts, fieldHash []string) (*LatestValueHolderTriggeredEventWithDynamicTopicIterator, error) { + + var fieldHashRule []interface{} + for _, fieldHashItem := range fieldHash { + fieldHashRule = append(fieldHashRule, fieldHashItem) + } + + logs, sub, err := _LatestValueHolder.contract.FilterLogs(opts, "TriggeredEventWithDynamicTopic", fieldHashRule) + if err != nil { + return nil, err + } + return &LatestValueHolderTriggeredEventWithDynamicTopicIterator{contract: _LatestValueHolder.contract, event: "TriggeredEventWithDynamicTopic", logs: logs, sub: sub}, nil +} + +func (_LatestValueHolder *LatestValueHolderFilterer) WatchTriggeredEventWithDynamicTopic(opts *bind.WatchOpts, sink chan<- *LatestValueHolderTriggeredEventWithDynamicTopic, fieldHash []string) (event.Subscription, error) { + + var fieldHashRule []interface{} + for _, fieldHashItem := range fieldHash { + fieldHashRule = append(fieldHashRule, fieldHashItem) + } + + logs, sub, err := _LatestValueHolder.contract.WatchLogs(opts, "TriggeredEventWithDynamicTopic", fieldHashRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + + event := new(LatestValueHolderTriggeredEventWithDynamicTopic) + if err := _LatestValueHolder.contract.UnpackLog(event, "TriggeredEventWithDynamicTopic", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +func (_LatestValueHolder *LatestValueHolderFilterer) ParseTriggeredEventWithDynamicTopic(log types.Log) (*LatestValueHolderTriggeredEventWithDynamicTopic, error) { + event := new(LatestValueHolderTriggeredEventWithDynamicTopic) + if err := _LatestValueHolder.contract.UnpackLog(event, "TriggeredEventWithDynamicTopic", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +type LatestValueHolderTriggeredWithFourTopicsIterator struct { + Event *LatestValueHolderTriggeredWithFourTopics + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *LatestValueHolderTriggeredWithFourTopicsIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(LatestValueHolderTriggeredWithFourTopics) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + + select { + case log := <-it.logs: + it.Event = new(LatestValueHolderTriggeredWithFourTopics) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +func (it *LatestValueHolderTriggeredWithFourTopicsIterator) Error() error { + return it.fail +} + +func (it *LatestValueHolderTriggeredWithFourTopicsIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type LatestValueHolderTriggeredWithFourTopics struct { + Field1 int32 + Field2 int32 + Field3 int32 + Raw types.Log +} + +func (_LatestValueHolder *LatestValueHolderFilterer) FilterTriggeredWithFourTopics(opts *bind.FilterOpts, field1 []int32, field2 []int32, field3 []int32) (*LatestValueHolderTriggeredWithFourTopicsIterator, error) { + + var field1Rule []interface{} + for _, field1Item := range field1 { + field1Rule = append(field1Rule, field1Item) + } + var field2Rule []interface{} + for _, field2Item := range field2 { + field2Rule = append(field2Rule, field2Item) + } + var field3Rule []interface{} + for _, field3Item := range field3 { + field3Rule = append(field3Rule, field3Item) + } + + logs, sub, err := _LatestValueHolder.contract.FilterLogs(opts, "TriggeredWithFourTopics", field1Rule, field2Rule, field3Rule) + if err != nil { + return nil, err + } + return &LatestValueHolderTriggeredWithFourTopicsIterator{contract: _LatestValueHolder.contract, event: "TriggeredWithFourTopics", logs: logs, sub: sub}, nil +} + +func (_LatestValueHolder *LatestValueHolderFilterer) WatchTriggeredWithFourTopics(opts *bind.WatchOpts, sink chan<- *LatestValueHolderTriggeredWithFourTopics, field1 []int32, field2 []int32, field3 []int32) (event.Subscription, error) { + + var field1Rule []interface{} + for _, field1Item := range field1 { + field1Rule = append(field1Rule, field1Item) + } + var field2Rule []interface{} + for _, field2Item := range field2 { + field2Rule = append(field2Rule, field2Item) + } + var field3Rule []interface{} + for _, field3Item := range field3 { + field3Rule = append(field3Rule, field3Item) + } + + logs, sub, err := _LatestValueHolder.contract.WatchLogs(opts, "TriggeredWithFourTopics", field1Rule, field2Rule, field3Rule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + + event := new(LatestValueHolderTriggeredWithFourTopics) + if err := _LatestValueHolder.contract.UnpackLog(event, "TriggeredWithFourTopics", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +func (_LatestValueHolder *LatestValueHolderFilterer) ParseTriggeredWithFourTopics(log types.Log) (*LatestValueHolderTriggeredWithFourTopics, error) { + event := new(LatestValueHolderTriggeredWithFourTopics) + if err := _LatestValueHolder.contract.UnpackLog(event, "TriggeredWithFourTopics", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +func (_LatestValueHolder *LatestValueHolder) ParseLog(log types.Log) (generated.AbigenLog, error) { + switch log.Topics[0] { + case _LatestValueHolder.abi.Events["Triggered"].ID: + return _LatestValueHolder.ParseTriggered(log) + case _LatestValueHolder.abi.Events["TriggeredEventWithDynamicTopic"].ID: + return _LatestValueHolder.ParseTriggeredEventWithDynamicTopic(log) + case _LatestValueHolder.abi.Events["TriggeredWithFourTopics"].ID: + return _LatestValueHolder.ParseTriggeredWithFourTopics(log) + + default: + return nil, fmt.Errorf("abigen wrapper received unknown log topic: %v", log.Topics[0]) + } +} + +func (LatestValueHolderTriggered) Topic() common.Hash { + return common.HexToHash("0x7188419dcd8b51877b71766f075f3626586c0ff190e7d056aa65ce9acb649a3d") +} + +func (LatestValueHolderTriggeredEventWithDynamicTopic) Topic() common.Hash { + return common.HexToHash("0x3d969732b1bbbb9f1d7eb9f3f14e4cb50a74d950b3ef916a397b85dfbab93c67") +} + +func (LatestValueHolderTriggeredWithFourTopics) Topic() common.Hash { + return common.HexToHash("0x91c80dc390f3d041b3a04b0099b19634499541ea26972250986ee4b24a12fac5") +} + +func (_LatestValueHolder *LatestValueHolder) Address() common.Address { + return _LatestValueHolder.address +} + +type LatestValueHolderInterface interface { + GetDifferentPrimitiveValue(opts *bind.CallOpts) (uint64, error) + + GetElementAtIndex(opts *bind.CallOpts, i *big.Int) (TestStruct, error) + + GetPrimitiveValue(opts *bind.CallOpts) (uint64, error) + + GetSliceValue(opts *bind.CallOpts) ([]uint64, error) + + ReturnSeen(opts *bind.CallOpts, field int32, differentField string, oracleId uint8, oracleIds [32]uint8, account common.Address, accounts []common.Address, bigField *big.Int, nestedStruct MidLevelTestStruct) (TestStruct, error) + + AddTestStruct(opts *bind.TransactOpts, field int32, differentField string, oracleId uint8, oracleIds [32]uint8, account common.Address, accounts []common.Address, bigField *big.Int, nestedStruct MidLevelTestStruct) (*types.Transaction, error) + + TriggerEvent(opts *bind.TransactOpts, field int32, differentField string, oracleId uint8, oracleIds [32]uint8, account common.Address, accounts []common.Address, bigField *big.Int, nestedStruct MidLevelTestStruct) (*types.Transaction, error) + + TriggerEventWithDynamicTopic(opts *bind.TransactOpts, field string) (*types.Transaction, error) + + TriggerWithFourTopics(opts *bind.TransactOpts, field1 int32, field2 int32, field3 int32) (*types.Transaction, error) + + FilterTriggered(opts *bind.FilterOpts, field []int32) (*LatestValueHolderTriggeredIterator, error) + + WatchTriggered(opts *bind.WatchOpts, sink chan<- *LatestValueHolderTriggered, field []int32) (event.Subscription, error) + + ParseTriggered(log types.Log) (*LatestValueHolderTriggered, error) + + FilterTriggeredEventWithDynamicTopic(opts *bind.FilterOpts, fieldHash []string) (*LatestValueHolderTriggeredEventWithDynamicTopicIterator, error) + + WatchTriggeredEventWithDynamicTopic(opts *bind.WatchOpts, sink chan<- *LatestValueHolderTriggeredEventWithDynamicTopic, fieldHash []string) (event.Subscription, error) + + ParseTriggeredEventWithDynamicTopic(log types.Log) (*LatestValueHolderTriggeredEventWithDynamicTopic, error) + + FilterTriggeredWithFourTopics(opts *bind.FilterOpts, field1 []int32, field2 []int32, field3 []int32) (*LatestValueHolderTriggeredWithFourTopicsIterator, error) + + WatchTriggeredWithFourTopics(opts *bind.WatchOpts, sink chan<- *LatestValueHolderTriggeredWithFourTopics, field1 []int32, field2 []int32, field3 []int32) (event.Subscription, error) + + ParseTriggeredWithFourTopics(log types.Log) (*LatestValueHolderTriggeredWithFourTopics, error) + + ParseLog(log types.Log) (generated.AbigenLog, error) + + Address() common.Address +} diff --git a/core/gethwrappers/generation/generated-wrapper-dependency-versions-do-not-edit.txt b/core/gethwrappers/generation/generated-wrapper-dependency-versions-do-not-edit.txt index 3e8d6420e57..ea8d1a020e2 100644 --- a/core/gethwrappers/generation/generated-wrapper-dependency-versions-do-not-edit.txt +++ b/core/gethwrappers/generation/generated-wrapper-dependency-versions-do-not-edit.txt @@ -11,6 +11,7 @@ batch_blockhash_store: ../../contracts/solc/v0.8.6/BatchBlockhashStore/BatchBloc batch_vrf_coordinator_v2: ../../contracts/solc/v0.8.6/BatchVRFCoordinatorV2/BatchVRFCoordinatorV2.abi ../../contracts/solc/v0.8.6/BatchVRFCoordinatorV2/BatchVRFCoordinatorV2.bin d0a54963260d8c1f1bbd984b758285e6027cfb5a7e42701bcb562ab123219332 batch_vrf_coordinator_v2plus: ../../contracts/solc/v0.8.6/BatchVRFCoordinatorV2Plus/BatchVRFCoordinatorV2Plus.abi ../../contracts/solc/v0.8.6/BatchVRFCoordinatorV2Plus/BatchVRFCoordinatorV2Plus.bin 7bb76ae241cf1b37b41920830b836cb99f1ad33efd7435ca2398ff6cd2fe5d48 blockhash_store: ../../contracts/solc/v0.8.6/BlockhashStore/BlockhashStore.abi ../../contracts/solc/v0.8.6/BlockhashStore/BlockhashStore.bin 12b0662f1636a341c8863bdec7a20f2ddd97c3a4fd1a7ae353fe316609face4e +chain_reader_example: ../../contracts/solc/v0.8.19/ChainReaderTestContract/LatestValueHolder.abi ../../contracts/solc/v0.8.19/ChainReaderTestContract/LatestValueHolder.bin de88c7e68de36b96aa2bec844bdc96fcd7c9017b38e25062b3b9f9cec42c814f chain_specific_util_helper: ../../contracts/solc/v0.8.6/ChainSpecificUtilHelper/ChainSpecificUtilHelper.abi ../../contracts/solc/v0.8.6/ChainSpecificUtilHelper/ChainSpecificUtilHelper.bin 5f10664e31abc768f4a37901cae7a3bef90146180f97303e5a1bde5a08d84595 consumer_wrapper: ../../contracts/solc/v0.7/Consumer/Consumer.abi ../../contracts/solc/v0.7/Consumer/Consumer.bin 894d1cbd920dccbd36d92918c1037c6ded34f66f417ccb18ec3f33c64ef83ec5 cron_upkeep_factory_wrapper: ../../contracts/solc/v0.8.6/CronUpkeepFactory/CronUpkeepFactory.abi - dacb0f8cdf54ae9d2781c5e720fc314b32ed5e58eddccff512c75d6067292cd7 diff --git a/core/gethwrappers/go_generate.go b/core/gethwrappers/go_generate.go index f5aa4451bb4..9bd3f2fd41a 100644 --- a/core/gethwrappers/go_generate.go +++ b/core/gethwrappers/go_generate.go @@ -139,7 +139,9 @@ package gethwrappers //go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/MockAggregatorProxy/MockAggregatorProxy.abi ../../contracts/solc/v0.8.6/MockAggregatorProxy/MockAggregatorProxy.bin MockAggregatorProxy mock_aggregator_proxy // Log tester -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.19/LogEmitter/LogEmitter.abi ../../contracts/solc/v0.8.19/LogEmitter/LogEmitter.bin LogEmitter log_emitter + +// ChainReader test contract +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.19/ChainReaderTestContract/LatestValueHolder.abi ../../contracts/solc/v0.8.19/ChainReaderTestContract/LatestValueHolder.bin LatestValueHolder chain_reader_example // Chainlink Functions //go:generate go generate ./functions diff --git a/core/internal/features/ocr2/features_ocr2_test.go b/core/internal/features/ocr2/features_ocr2_test.go index 863451de733..002f1d192ae 100644 --- a/core/internal/features/ocr2/features_ocr2_test.go +++ b/core/internal/features/ocr2/features_ocr2_test.go @@ -35,6 +35,7 @@ import ( ocrtypes2 "github.com/smartcontractkit/libocr/offchainreporting2plus/types" commonconfig "github.com/smartcontractkit/chainlink-common/pkg/config" + "github.com/smartcontractkit/chainlink/v2/core/bridges" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/forwarders" @@ -188,50 +189,62 @@ func setupNodeOCR2( func TestIntegration_OCR2(t *testing.T) { t.Parallel() - owner, b, ocrContractAddress, ocrContract := setupOCR2Contracts(t) - - lggr := logger.TestLogger(t) - bootstrapNodePort := freeport.GetOne(t) - bootstrapNode := setupNodeOCR2(t, owner, bootstrapNodePort, false /* useForwarders */, b, nil) - - var ( - oracles []confighelper2.OracleIdentityExtra - transmitters []common.Address - kbs []ocr2key.KeyBundle - apps []*cltest.TestApplication - ) - ports := freeport.GetN(t, 4) - for i := 0; i < 4; i++ { - node := setupNodeOCR2(t, owner, ports[i], false /* useForwarders */, b, []commontypes.BootstrapperLocator{ - // Supply the bootstrap IP and port as a V2 peer address - {PeerID: bootstrapNode.peerID, Addrs: []string{fmt.Sprintf("127.0.0.1:%d", bootstrapNodePort)}}, - }) - - kbs = append(kbs, node.keybundle) - apps = append(apps, node.app) - transmitters = append(transmitters, node.transmitter) - oracles = append(oracles, confighelper2.OracleIdentityExtra{ - OracleIdentity: confighelper2.OracleIdentity{ - OnchainPublicKey: node.keybundle.PublicKey(), - TransmitAccount: ocrtypes2.Account(node.transmitter.String()), - OffchainPublicKey: node.keybundle.OffchainPublicKey(), - PeerID: node.peerID, - }, - ConfigEncryptionPublicKey: node.keybundle.ConfigEncryptionPublicKey(), - }) - } + for _, test := range []struct { + name string + chainReaderAndCodec bool + }{ + {"legacy", false}, + {"chain-reader", true}, + } { + test := test + t.Run(test.name, func(t *testing.T) { + t.Parallel() + + owner, b, ocrContractAddress, ocrContract := setupOCR2Contracts(t) + + lggr := logger.TestLogger(t) + bootstrapNodePort := freeport.GetOne(t) + bootstrapNode := setupNodeOCR2(t, owner, bootstrapNodePort, false /* useForwarders */, b, nil) + + var ( + oracles []confighelper2.OracleIdentityExtra + transmitters []common.Address + kbs []ocr2key.KeyBundle + apps []*cltest.TestApplication + ) + ports := freeport.GetN(t, 4) + for i := 0; i < 4; i++ { + node := setupNodeOCR2(t, owner, ports[i], false /* useForwarders */, b, []commontypes.BootstrapperLocator{ + // Supply the bootstrap IP and port as a V2 peer address + {PeerID: bootstrapNode.peerID, Addrs: []string{fmt.Sprintf("127.0.0.1:%d", bootstrapNodePort)}}, + }) + + kbs = append(kbs, node.keybundle) + apps = append(apps, node.app) + transmitters = append(transmitters, node.transmitter) + + oracles = append(oracles, confighelper2.OracleIdentityExtra{ + OracleIdentity: confighelper2.OracleIdentity{ + OnchainPublicKey: node.keybundle.PublicKey(), + TransmitAccount: ocrtypes2.Account(node.transmitter.String()), + OffchainPublicKey: node.keybundle.OffchainPublicKey(), + PeerID: node.peerID, + }, + ConfigEncryptionPublicKey: node.keybundle.ConfigEncryptionPublicKey(), + }) + } - tick := time.NewTicker(1 * time.Second) - defer tick.Stop() - go func() { - for range tick.C { - b.Commit() - } - }() + tick := time.NewTicker(1 * time.Second) + defer tick.Stop() + go func() { + for range tick.C { + b.Commit() + } + }() - blockBeforeConfig := initOCR2(t, lggr, b, ocrContract, owner, bootstrapNode, oracles, transmitters, transmitters, func(blockNum int64) string { - return fmt.Sprintf(` + blockBeforeConfig := initOCR2(t, lggr, b, ocrContract, owner, bootstrapNode, oracles, transmitters, transmitters, func(blockNum int64) string { + return fmt.Sprintf(` type = "bootstrap" name = "bootstrap" relay = "evm" @@ -241,54 +254,189 @@ contractID = "%s" chainID = 1337 fromBlock = %d `, ocrContractAddress, blockNum) - }) - - var jids []int32 - var servers, slowServers = make([]*httptest.Server, 4), make([]*httptest.Server, 4) - // We expect metadata of: - // latestAnswer:nil // First call - // latestAnswer:0 - // latestAnswer:10 - // latestAnswer:20 - // latestAnswer:30 - var metaLock sync.Mutex - expectedMeta := map[string]struct{}{ - "0": {}, "10": {}, "20": {}, "30": {}, - } - for i := 0; i < 4; i++ { - s := i - require.NoError(t, apps[i].Start(testutils.Context(t))) - - // API speed is > observation timeout set in ContractSetConfigArgsForIntegrationTest - slowServers[i] = httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { - time.Sleep(5 * time.Second) - res.WriteHeader(http.StatusOK) - _, err := res.Write([]byte(`{"data":10}`)) - require.NoError(t, err) - })) - t.Cleanup(slowServers[s].Close) - servers[i] = httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { - b, err := io.ReadAll(req.Body) - require.NoError(t, err) - var m bridges.BridgeMetaDataJSON - require.NoError(t, json.Unmarshal(b, &m)) - if m.Meta.LatestAnswer != nil && m.Meta.UpdatedAt != nil { - metaLock.Lock() - delete(expectedMeta, m.Meta.LatestAnswer.String()) - metaLock.Unlock() + }) + + var jids []int32 + var servers, slowServers = make([]*httptest.Server, 4), make([]*httptest.Server, 4) + // We expect metadata of: + // latestAnswer:nil // First call + // latestAnswer:0 + // latestAnswer:10 + // latestAnswer:20 + // latestAnswer:30 + var metaLock sync.Mutex + expectedMeta := map[string]struct{}{ + "0": {}, "10": {}, "20": {}, "30": {}, } - res.WriteHeader(http.StatusOK) - _, err = res.Write([]byte(`{"data":10}`)) - require.NoError(t, err) - })) - t.Cleanup(servers[s].Close) - u, _ := url.Parse(servers[i].URL) - require.NoError(t, apps[i].BridgeORM().CreateBridgeType(&bridges.BridgeType{ - Name: bridges.BridgeName(fmt.Sprintf("bridge%d", i)), - URL: models.WebURL(*u), - })) - - ocrJob, err := validate.ValidatedOracleSpecToml(apps[i].Config.OCR2(), apps[i].Config.Insecure(), fmt.Sprintf(` + returnData := int(10) + for i := 0; i < 4; i++ { + s := i + require.NoError(t, apps[i].Start(testutils.Context(t))) + + // API speed is > observation timeout set in ContractSetConfigArgsForIntegrationTest + slowServers[i] = httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { + time.Sleep(5 * time.Second) + var result string + metaLock.Lock() + result = fmt.Sprintf(`{"data":%d}`, returnData) + metaLock.Unlock() + res.WriteHeader(http.StatusOK) + t.Logf("Slow Bridge %d returning data:10", s) + _, err := res.Write([]byte(result)) + require.NoError(t, err) + })) + t.Cleanup(slowServers[s].Close) + servers[i] = httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { + b, err := io.ReadAll(req.Body) + require.NoError(t, err) + var m bridges.BridgeMetaDataJSON + require.NoError(t, json.Unmarshal(b, &m)) + var result string + metaLock.Lock() + result = fmt.Sprintf(`{"data":%d}`, returnData) + metaLock.Unlock() + if m.Meta.LatestAnswer != nil && m.Meta.UpdatedAt != nil { + t.Logf("Bridge %d deleting %s, from request body: %s", s, m.Meta.LatestAnswer, b) + metaLock.Lock() + delete(expectedMeta, m.Meta.LatestAnswer.String()) + metaLock.Unlock() + } + res.WriteHeader(http.StatusOK) + _, err = res.Write([]byte(result)) + require.NoError(t, err) + })) + t.Cleanup(servers[s].Close) + u, _ := url.Parse(servers[i].URL) + require.NoError(t, apps[i].BridgeORM().CreateBridgeType(&bridges.BridgeType{ + Name: bridges.BridgeName(fmt.Sprintf("bridge%d", i)), + URL: models.WebURL(*u), + })) + + var chainReaderSpec string + if test.chainReaderAndCodec { + chainReaderSpec = ` +[relayConfig.chainReader.contracts.median] +contractABI = ''' +[ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "requester", + "type": "address" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "configDigest", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "uint32", + "name": "epoch", + "type": "uint32" + }, + { + "indexed": false, + "internalType": "uint8", + "name": "round", + "type": "uint8" + } + ], + "name": "RoundRequested", + "type": "event" + }, + { + "inputs": [], + "name": "latestTransmissionDetails", + "outputs": [ + { + "internalType": "bytes32", + "name": "configDigest", + "type": "bytes32" + }, + { + "internalType": "uint32", + "name": "epoch", + "type": "uint32" + }, + { + "internalType": "uint8", + "name": "round", + "type": "uint8" + }, + { + "internalType": "int192", + "name": "latestAnswer_", + "type": "int192" + }, + { + "internalType": "uint64", + "name": "latestTimestamp_", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + } +] +''' + +[relayConfig.chainReader.contracts.median.configs] +LatestRoundRequested = ''' +{ + "chainSpecificName": "RoundRequested", + "readType": "event" +} +''' +LatestTransmissionDetails = ''' +{ + "chainSpecificName": "latestTransmissionDetails", + "outputModifications": [ + { + "Fields": [ + "LatestTimestamp_" + ], + "type": "epoch to time" + }, + { + "Fields": { + "LatestAnswer_": "LatestAnswer", + "LatestTimestamp_": "LatestTimestamp" + }, + "type": "rename" + } + ] +} +''' + +[relayConfig.codec.configs.MedianReport] +typeABI = ''' +[ + { + "Name": "Timestamp", + "Type": "uint32" + }, + { + "Name": "Observers", + "Type": "bytes32" + }, + { + "Name": "Observations", + "Type": "int192[]" + }, + { + "Name": "JuelsPerFeeCoin", + "Type": "int192" + } +] +''' +` + } + ocrJob, err := validate.ValidatedOracleSpecToml(apps[i].Config.OCR2(), apps[i].Config.Insecure(), fmt.Sprintf(` type = "offchainreporting2" relay = "evm" schemaVersion = 1 @@ -315,9 +463,12 @@ observationSource = """ answer1 [type=median index=0]; """ + [relayConfig] chainID = 1337 fromBlock = %d +%s + [pluginConfig] juelsPerFeeCoinSource = """ // data source 1 @@ -335,72 +486,131 @@ juelsPerFeeCoinSource = """ answer1 [type=median index=0]; """ -`, ocrContractAddress, kbs[i].ID(), transmitters[i], fmt.Sprintf("bridge%d", i), i, slowServers[i].URL, i, blockBeforeConfig.Number().Int64(), fmt.Sprintf("bridge%d", i), i, slowServers[i].URL, i)) - require.NoError(t, err) - err = apps[i].AddJobV2(testutils.Context(t), &ocrJob) - require.NoError(t, err) - jids = append(jids, ocrJob.ID) - } +`, ocrContractAddress, kbs[i].ID(), transmitters[i], fmt.Sprintf("bridge%d", i), i, slowServers[i].URL, i, blockBeforeConfig.Number().Int64(), chainReaderSpec, fmt.Sprintf("bridge%d", i), i, slowServers[i].URL, i)) + require.NoError(t, err) + err = apps[i].AddJobV2(testutils.Context(t), &ocrJob) + require.NoError(t, err) + jids = append(jids, ocrJob.ID) + } - // Assert that all the OCR jobs get a run with valid values eventually. - var wg sync.WaitGroup - for i := 0; i < 4; i++ { - ic := i - wg.Add(1) - go func() { - defer wg.Done() - // Want at least 2 runs so we see all the metadata. - pr := cltest.WaitForPipelineComplete(t, ic, jids[ic], 2, 7, apps[ic].JobORM(), 2*time.Minute, 5*time.Second) - jb, err := pr[0].Outputs.MarshalJSON() + // Watch for OCR2AggregatorTransmitted events + start := uint64(0) + txEvents := make(chan *ocr2aggregator.OCR2AggregatorTransmitted) + _, err := ocrContract.WatchTransmitted(&bind.WatchOpts{Start: &start, Context: testutils.Context(t)}, txEvents) require.NoError(t, err) - assert.Equal(t, []byte(fmt.Sprintf("[\"%d\"]", 10*ic)), jb, "pr[0] %+v pr[1] %+v", pr[0], pr[1]) + newTxEvents := make(chan *ocr2aggregator.OCR2AggregatorNewTransmission) + _, err = ocrContract.WatchNewTransmission(&bind.WatchOpts{Start: &start, Context: testutils.Context(t)}, newTxEvents, []uint32{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}) require.NoError(t, err) - }() - } - wg.Wait() - // 4 oracles reporting 0, 10, 20, 30. Answer should be 20 (results[4/2]). - gomega.NewGomegaWithT(t).Eventually(func() string { - answer, err := ocrContract.LatestAnswer(nil) - require.NoError(t, err) - return answer.String() - }, 1*time.Minute, 200*time.Millisecond).Should(gomega.Equal("20")) + go func() { + var newTxEvent *ocr2aggregator.OCR2AggregatorNewTransmission + select { + case txEvent := <-txEvents: + t.Logf("txEvent: %v", txEvent) + if newTxEvent != nil { + assert.Equal(t, txEvent.Epoch, uint32(newTxEvent.EpochAndRound.Uint64())) + } + case newTxEvent = <-newTxEvents: + t.Logf("newTxEvent: %v", newTxEvent) + } + }() - for _, app := range apps { - jobs, _, err := app.JobORM().FindJobs(0, 1000) - require.NoError(t, err) - // No spec errors - for _, j := range jobs { - ignore := 0 - for i := range j.JobSpecErrors { - // Non-fatal timing related error, ignore for testing. - if strings.Contains(j.JobSpecErrors[i].Description, "leader's phase conflicts tGrace timeout") { - ignore++ + for trial := 0; trial < 2; trial++ { + var retVal int + + metaLock.Lock() + returnData = 10 * (trial + 1) + retVal = returnData + for i := 0; i < 4; i++ { + expectedMeta[fmt.Sprintf("%d", returnData*i)] = struct{}{} + } + metaLock.Unlock() + + // Assert that all the OCR jobs get a run with valid values eventually. + var wg sync.WaitGroup + for i := 0; i < 4; i++ { + ic := i + wg.Add(1) + go func() { + defer wg.Done() + completedRuns, err2 := apps[ic].JobORM().FindPipelineRunIDsByJobID(jids[ic], 0, 1000) + require.NoError(t, err2) + // Want at least 2 runs so we see all the metadata. + pr := cltest.WaitForPipelineComplete(t, ic, jids[ic], len(completedRuns)+2, 7, apps[ic].JobORM(), 2*time.Minute, 5*time.Second) + jb, err2 := pr[0].Outputs.MarshalJSON() + require.NoError(t, err2) + assert.Equal(t, []byte(fmt.Sprintf("[\"%d\"]", retVal*ic)), jb, "pr[0] %+v pr[1] %+v", pr[0], pr[1]) + require.NoError(t, err2) + }() + } + wg.Wait() + + // Trail #1: 4 oracles reporting 0, 10, 20, 30. Answer should be 20 (results[4/2]). + // Trial #2: 4 oracles reporting 0, 20, 40, 60. Answer should be 40 (results[4/2]). + gomega.NewGomegaWithT(t).Eventually(func() string { + answer, err2 := ocrContract.LatestAnswer(nil) + require.NoError(t, err2) + return answer.String() + }, 1*time.Minute, 200*time.Millisecond).Should(gomega.Equal(fmt.Sprintf("%d", 2*retVal))) + + for _, app := range apps { + jobs, _, err2 := app.JobORM().FindJobs(0, 1000) + require.NoError(t, err2) + // No spec errors + for _, j := range jobs { + ignore := 0 + for i := range j.JobSpecErrors { + // Non-fatal timing related error, ignore for testing. + if strings.Contains(j.JobSpecErrors[i].Description, "leader's phase conflicts tGrace timeout") { + ignore++ + } + } + require.Len(t, j.JobSpecErrors, ignore) + } + } + em := map[string]struct{}{} + metaLock.Lock() + maps.Copy(em, expectedMeta) + metaLock.Unlock() + assert.Len(t, em, 0, "expected metadata %v", em) + + t.Logf("======= Summary =======") + roundId, err2 := ocrContract.LatestRound(nil) + require.NoError(t, err2) + for i := 0; i <= int(roundId.Int64()); i++ { + roundData, err3 := ocrContract.GetRoundData(nil, big.NewInt(int64(i))) + require.NoError(t, err3) + t.Logf("RoundId: %d, AnsweredInRound: %d, Answer: %d, StartedAt: %v, UpdatedAt: %v", roundData.RoundId, roundData.AnsweredInRound, roundData.Answer, roundData.StartedAt, roundData.UpdatedAt) + } + + expectedAnswer := big.NewInt(2 * int64(retVal)) + + // Assert we can read the latest config digest and epoch after a report has been submitted. + contractABI, err2 := abi.JSON(strings.NewReader(ocr2aggregator.OCR2AggregatorABI)) + require.NoError(t, err2) + apps[0].GetRelayers().LegacyEVMChains().Slice() + ct, err2 := evm.NewOCRContractTransmitter(ocrContractAddress, apps[0].GetRelayers().LegacyEVMChains().Slice()[0].Client(), contractABI, nil, apps[0].GetRelayers().LegacyEVMChains().Slice()[0].LogPoller(), lggr, nil) + require.NoError(t, err2) + configDigest, epoch, err2 := ct.LatestConfigDigestAndEpoch(testutils.Context(t)) + require.NoError(t, err2) + details, err2 := ocrContract.LatestConfigDetails(nil) + require.NoError(t, err2) + assert.True(t, bytes.Equal(configDigest[:], details.ConfigDigest[:])) + digestAndEpoch, err2 := ocrContract.LatestConfigDigestAndEpoch(nil) + require.NoError(t, err2) + assert.Equal(t, digestAndEpoch.Epoch, epoch) + latestTransmissionDetails, err2 := ocrContract.LatestTransmissionDetails(nil) + require.NoError(t, err2) + assert.Equal(t, expectedAnswer, latestTransmissionDetails.LatestAnswer) + require.NoError(t, err2) + newTransmissionEvents, err2 := ocrContract.FilterTransmitted(&bind.FilterOpts{Start: 0, End: nil}) + require.NoError(t, err2) + for newTransmissionEvents.Next() { + assert.Equal(t, 3, newTransmissionEvents.Event.Epoch) } } - require.Len(t, j.JobSpecErrors, ignore) - } + }) } - em := map[string]struct{}{} - metaLock.Lock() - maps.Copy(em, expectedMeta) - metaLock.Unlock() - assert.Len(t, em, 0, "expected metadata %v", em) - - // Assert we can read the latest config digest and epoch after a report has been submitted. - contractABI, err := abi.JSON(strings.NewReader(ocr2aggregator.OCR2AggregatorABI)) - require.NoError(t, err) - apps[0].GetRelayers().LegacyEVMChains().Slice() - ct, err := evm.NewOCRContractTransmitter(ocrContractAddress, apps[0].GetRelayers().LegacyEVMChains().Slice()[0].Client(), contractABI, nil, apps[0].GetRelayers().LegacyEVMChains().Slice()[0].LogPoller(), lggr, nil) - require.NoError(t, err) - configDigest, epoch, err := ct.LatestConfigDigestAndEpoch(testutils.Context(t)) - require.NoError(t, err) - details, err := ocrContract.LatestConfigDetails(nil) - require.NoError(t, err) - assert.True(t, bytes.Equal(configDigest[:], details.ConfigDigest[:])) - digestAndEpoch, err := ocrContract.LatestConfigDigestAndEpoch(nil) - require.NoError(t, err) - assert.Equal(t, digestAndEpoch.Epoch, epoch) } func initOCR2(t *testing.T, lggr logger.Logger, b *backends.SimulatedBackend, diff --git a/core/scripts/go.mod b/core/scripts/go.mod index 50c7ea8bb00..7d82e2b4d75 100644 --- a/core/scripts/go.mod +++ b/core/scripts/go.mod @@ -19,7 +19,7 @@ require ( github.com/pelletier/go-toml/v2 v2.1.1 github.com/shopspring/decimal v1.3.1 github.com/smartcontractkit/chainlink-automation v1.0.2-0.20240118014648-1ab6a88c9429 - github.com/smartcontractkit/chainlink-common v0.1.7-0.20240118012339-4864e2306bb1 + github.com/smartcontractkit/chainlink-common v0.1.7-0.20240119143538-04c7f63ad53a github.com/smartcontractkit/chainlink-vrf v0.0.0-20231120191722-fef03814f868 github.com/smartcontractkit/chainlink/v2 v2.0.0-00010101000000-000000000000 github.com/smartcontractkit/libocr v0.0.0-20240112202000-6359502d2ff1 @@ -244,11 +244,11 @@ require ( github.com/shirou/gopsutil/v3 v3.23.11 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/smartcontractkit/caigo v0.0.0-20230621050857-b29a4ca8c704 // indirect - github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20231218175426-6e0427c661e5 // indirect + github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240120192246-4bb04c997ca0 // indirect github.com/smartcontractkit/chainlink-data-streams v0.0.0-20231204152908-a6e3fe8ff2a1 // indirect - github.com/smartcontractkit/chainlink-feeds v0.0.0-20231127231053-2232d3a6766d // indirect - github.com/smartcontractkit/chainlink-solana v1.0.3-0.20231219140448-151a4725f312 // indirect - github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20231219014050-0c4a7831293a // indirect + github.com/smartcontractkit/chainlink-feeds v0.0.0-20240119021347-3c541a78cdb8 // indirect + github.com/smartcontractkit/chainlink-solana v1.0.3-0.20240122152632-38444d2ad8ba // indirect + github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20240119162652-3a7274645007 // indirect github.com/smartcontractkit/tdh2/go/ocr2/decryptionplugin v0.0.0-20230906073235-9e478e5e19f1 // indirect github.com/smartcontractkit/tdh2/go/tdh2 v0.0.0-20230906073235-9e478e5e19f1 // indirect github.com/smartcontractkit/wsrpc v0.7.2 // indirect diff --git a/core/scripts/go.sum b/core/scripts/go.sum index e4e7406126c..2142547c1ea 100644 --- a/core/scripts/go.sum +++ b/core/scripts/go.sum @@ -1167,18 +1167,18 @@ github.com/smartcontractkit/caigo v0.0.0-20230621050857-b29a4ca8c704 h1:T3lFWumv github.com/smartcontractkit/caigo v0.0.0-20230621050857-b29a4ca8c704/go.mod h1:2QuJdEouTWjh5BDy5o/vgGXQtR4Gz8yH1IYB5eT7u4M= github.com/smartcontractkit/chainlink-automation v1.0.2-0.20240118014648-1ab6a88c9429 h1:xkejUBZhcBpBrTSfxc91Iwzadrb6SXw8ks69bHIQ9Ww= github.com/smartcontractkit/chainlink-automation v1.0.2-0.20240118014648-1ab6a88c9429/go.mod h1:wJmVvDf4XSjsahWtfUq3wvIAYEAuhr7oxmxYnEL/LGQ= -github.com/smartcontractkit/chainlink-common v0.1.7-0.20240118012339-4864e2306bb1 h1:3cWO2/lFVDul5SVTgl4/RX/GXcT8Zq5NGMPeNEz09tY= -github.com/smartcontractkit/chainlink-common v0.1.7-0.20240118012339-4864e2306bb1/go.mod h1:f+0ei9N4PlTJHu7pbGzEjTnBUr45syPdGFu5+31lS5Q= -github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20231218175426-6e0427c661e5 h1:kBnmjv3fxU7krVIqZFvo1m4F6qBc4vPURQFX/mcChhI= -github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20231218175426-6e0427c661e5/go.mod h1:EoM7wQ81mov7wsUzG4zEnnr0EH0POEo/I0hRDg433TU= +github.com/smartcontractkit/chainlink-common v0.1.7-0.20240119143538-04c7f63ad53a h1:lgM0yPo0KqSntLY4Y42RAH3avdv+Kyne8n+VM7cwlxo= +github.com/smartcontractkit/chainlink-common v0.1.7-0.20240119143538-04c7f63ad53a/go.mod h1:05rRF84QKlIOF5LfTBPkHdw4UpBI2G3zxRcuZ65bPjk= +github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240120192246-4bb04c997ca0 h1:NALwENz6vQ972DuD9AZjqRjyNSxH9ptNapizQGLI+2s= +github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240120192246-4bb04c997ca0/go.mod h1:NcVAT/GETDBvIoAej5K6OYqAtDOkF6vO5pYw/hLuYVU= github.com/smartcontractkit/chainlink-data-streams v0.0.0-20231204152908-a6e3fe8ff2a1 h1:xYqRgZO0nMSO8CBCMR0r3WA+LZ4kNL8a6bnbyk/oBtQ= github.com/smartcontractkit/chainlink-data-streams v0.0.0-20231204152908-a6e3fe8ff2a1/go.mod h1:GuPvyXryvbiUZIHmPeLBz4L+yJKeyGUjrDfd1KNne+o= -github.com/smartcontractkit/chainlink-feeds v0.0.0-20231127231053-2232d3a6766d h1:w4MsbOtNk6nD/mcXLstHWk9hB6g7QLtcAfhPjhwvOaQ= -github.com/smartcontractkit/chainlink-feeds v0.0.0-20231127231053-2232d3a6766d/go.mod h1:YPAfLNowdBwiKiYOwgwtbJHi8AJWbcxkbOY0ItAvkfc= -github.com/smartcontractkit/chainlink-solana v1.0.3-0.20231219140448-151a4725f312 h1:ziqC+WW/2/UI6w3DShy7HGzJMWWLIYHT5ev2Qaa3h6I= -github.com/smartcontractkit/chainlink-solana v1.0.3-0.20231219140448-151a4725f312/go.mod h1:vqnojBNdzHNI6asWezJlottUiVEXudMEGf2Mz5R+xps= -github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20231219014050-0c4a7831293a h1:atCXqF8e5U2zfEaA87cKJs+K1MAbOVh3V05gEd60fR0= -github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20231219014050-0c4a7831293a/go.mod h1:YWKpf+hO9XMlzIWQT8yGoky3aeFLzMUVsjbs80LD77M= +github.com/smartcontractkit/chainlink-feeds v0.0.0-20240119021347-3c541a78cdb8 h1:1BcjXuviSAKttOX7BZoVHRZZGfxqoA2+AL8tykmkdoc= +github.com/smartcontractkit/chainlink-feeds v0.0.0-20240119021347-3c541a78cdb8/go.mod h1:vy1L7NybTy2F/Yv7BOh+oZBa1MACD6gzd1+DkcSkfp8= +github.com/smartcontractkit/chainlink-solana v1.0.3-0.20240122152632-38444d2ad8ba h1:6rnQrD8NaLfLOPHszW1hbpviqpU8011gzdZk6wKP1xY= +github.com/smartcontractkit/chainlink-solana v1.0.3-0.20240122152632-38444d2ad8ba/go.mod h1:OZfzyayUdwsVBqxvbEMqwUntQT8HbFbgyqoudvwfVN0= +github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20240119162652-3a7274645007 h1:KwB0H2P/gxJgt823Ku1fTcFLDKMj6zsP3wbQGlBOm4U= +github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20240119162652-3a7274645007/go.mod h1:EbZAlb/2K6mKr26u3+3cLBe/caJaqCHw786On94C43g= github.com/smartcontractkit/chainlink-vrf v0.0.0-20231120191722-fef03814f868 h1:FFdvEzlYwcuVHkdZ8YnZR/XomeMGbz5E2F2HZI3I3w8= github.com/smartcontractkit/chainlink-vrf v0.0.0-20231120191722-fef03814f868/go.mod h1:Kn1Hape05UzFZ7bOUnm3GVsHzP0TNrVmpfXYNHdqGGs= github.com/smartcontractkit/go-plugin v0.0.0-20231003134350-e49dad63b306 h1:ko88+ZznniNJZbZPWAvHQU8SwKAdHngdDZ+pvVgB5ss= @@ -1789,8 +1789,8 @@ google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz513 google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= -google.golang.org/api v0.147.0 h1:Can3FaQo9LlVqxJCodNmeZW/ib3/qKAY3rFeXiHo5gc= -google.golang.org/api v0.147.0/go.mod h1:pQ/9j83DcmPd/5C9e2nFOdjjNkDZ1G+zkbK2uvdkJMs= +google.golang.org/api v0.149.0 h1:b2CqT6kG+zqJIVKRQ3ELJVLN1PwHZ6DJ3dW8yl82rgY= +google.golang.org/api v0.149.0/go.mod h1:Mwn1B7JTXrzXtnvmzQE2BD6bYZQ8DShKZDZbeN9I7qI= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= diff --git a/core/services/job/models.go b/core/services/job/models.go index 31032d58e0e..17bac545be1 100644 --- a/core/services/job/models.go +++ b/core/services/job/models.go @@ -277,7 +277,8 @@ func (s *OCROracleSpec) SetID(value string) error { return nil } -// JSONConfig is a Go mapping for JSON based database properties. +// JSONConfig is a map for config properties which are encoded as JSON in the database by implementing +// sql.Scanner and driver.Valuer. type JSONConfig map[string]interface{} // Bytes returns the raw bytes diff --git a/core/services/job/models_test.go b/core/services/job/models_test.go index ddbb815e730..fa15e3b1b22 100644 --- a/core/services/job/models_test.go +++ b/core/services/job/models_test.go @@ -1,10 +1,20 @@ package job import ( + _ "embed" "reflect" "testing" + "time" + "github.com/pelletier/go-toml/v2" + "github.com/stretchr/testify/require" + "gopkg.in/guregu/null.v4" + + "github.com/smartcontractkit/chainlink-common/pkg/codec" + "github.com/smartcontractkit/chainlink-common/pkg/types" "github.com/smartcontractkit/chainlink/v2/core/services/relay" + evmtypes "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/types" + "github.com/smartcontractkit/chainlink/v2/core/store/models" ) func TestOCR2OracleSpec_RelayIdentifier(t *testing.T) { @@ -71,3 +81,183 @@ func TestOCR2OracleSpec_RelayIdentifier(t *testing.T) { }) } } + +var ( + //go:embed testdata/compact.toml + compact string + //go:embed testdata/pretty.toml + pretty string +) + +func TestOCR2OracleSpec(t *testing.T) { + val := OCR2OracleSpec{ + Relay: relay.EVM, + PluginType: types.Median, + ContractID: "foo", + OCRKeyBundleID: null.StringFrom("bar"), + TransmitterID: null.StringFrom("baz"), + ContractConfigConfirmations: 1, + ContractConfigTrackerPollInterval: *models.NewInterval(time.Second), + RelayConfig: map[string]interface{}{ + "chainID": 1337, + "fromBlock": 42, + "chainReader": evmtypes.ChainReaderConfig{ + Contracts: map[string]evmtypes.ChainContractReader{ + "median": { + ContractABI: `[ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "requester", + "type": "address" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "configDigest", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "uint32", + "name": "epoch", + "type": "uint32" + }, + { + "indexed": false, + "internalType": "uint8", + "name": "round", + "type": "uint8" + } + ], + "name": "RoundRequested", + "type": "event" + }, + { + "inputs": [], + "name": "latestTransmissionDetails", + "outputs": [ + { + "internalType": "bytes32", + "name": "configDigest", + "type": "bytes32" + }, + { + "internalType": "uint32", + "name": "epoch", + "type": "uint32" + }, + { + "internalType": "uint8", + "name": "round", + "type": "uint8" + }, + { + "internalType": "int192", + "name": "latestAnswer_", + "type": "int192" + }, + { + "internalType": "uint64", + "name": "latestTimestamp_", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + } +] +`, + Configs: map[string]*evmtypes.ChainReaderDefinition{ + "LatestTransmissionDetails": { + ChainSpecificName: "latestTransmissionDetails", + OutputModifications: codec.ModifiersConfig{ + &codec.EpochToTimeModifierConfig{ + Fields: []string{"LatestTimestamp_"}, + }, + &codec.RenameModifierConfig{ + Fields: map[string]string{ + "LatestAnswer_": "LatestAnswer", + "LatestTimestamp_": "LatestTimestamp", + }, + }, + }, + }, + "LatestRoundRequested": { + ChainSpecificName: "RoundRequested", + ReadType: evmtypes.Event, + }, + }, + }, + }, + }, + "codec": evmtypes.CodecConfig{ + Configs: map[string]evmtypes.ChainCodecConfig{ + "MedianReport": { + TypeABI: `[ + { + "Name": "Timestamp", + "Type": "uint32" + }, + { + "Name": "Observers", + "Type": "bytes32" + }, + { + "Name": "Observations", + "Type": "int192[]" + }, + { + "Name": "JuelsPerFeeCoin", + "Type": "int192" + } +] +`, + }, + }, + }, + }, + PluginConfig: map[string]interface{}{"juelsPerFeeCoinSource": ` // data source 1 + ds1 [type=bridge name="%s"]; + ds1_parse [type=jsonparse path="data"]; + ds1_multiply [type=multiply times=2]; + + // data source 2 + ds2 [type=http method=GET url="%s"]; + ds2_parse [type=jsonparse path="data"]; + ds2_multiply [type=multiply times=2]; + + ds1 -> ds1_parse -> ds1_multiply -> answer1; + ds2 -> ds2_parse -> ds2_multiply -> answer1; + + answer1 [type=median index=0]; +`, + }, + } + + t.Run("marshal", func(t *testing.T) { + gotB, err := toml.Marshal(val) + require.NoError(t, err) + t.Log("marshaled:", string(gotB)) + require.Equal(t, compact, string(gotB)) + }) + + t.Run("round-trip", func(t *testing.T) { + var gotVal OCR2OracleSpec + require.NoError(t, toml.Unmarshal([]byte(compact), &gotVal)) + gotB, err := toml.Marshal(gotVal) + require.NoError(t, err) + require.Equal(t, compact, string(gotB)) + t.Run("pretty", func(t *testing.T) { + var gotVal OCR2OracleSpec + require.NoError(t, toml.Unmarshal([]byte(pretty), &gotVal)) + gotB, err := toml.Marshal(gotVal) + require.NoError(t, err) + t.Log("marshaled compact:", string(gotB)) + require.Equal(t, compact, string(gotB)) + }) + }) +} diff --git a/core/services/job/testdata/compact.toml b/core/services/job/testdata/compact.toml new file mode 100644 index 00000000000..9f0f54027d2 --- /dev/null +++ b/core/services/job/testdata/compact.toml @@ -0,0 +1,34 @@ +contractID = 'foo' +relay = 'evm' +chainID = '' +p2pv2Bootstrappers = [] +ocrKeyBundleID = 'bar' +monitoringEndpoint = '' +transmitterID = 'baz' +blockchainTimeout = '0s' +contractConfigTrackerPollInterval = '1s' +contractConfigConfirmations = 1 +pluginType = 'median' +captureEATelemetry = false +captureAutomationCustomTelemetry = false + +[relayConfig] +chainID = 1337 +fromBlock = 42 + +[relayConfig.chainReader] +[relayConfig.chainReader.contracts] +[relayConfig.chainReader.contracts.median] +contractABI = "[\n {\n \"anonymous\": false,\n \"inputs\": [\n {\n \"indexed\": true,\n \"internalType\": \"address\",\n \"name\": \"requester\",\n \"type\": \"address\"\n },\n {\n \"indexed\": false,\n \"internalType\": \"bytes32\",\n \"name\": \"configDigest\",\n \"type\": \"bytes32\"\n },\n {\n \"indexed\": false,\n \"internalType\": \"uint32\",\n \"name\": \"epoch\",\n \"type\": \"uint32\"\n },\n {\n \"indexed\": false,\n \"internalType\": \"uint8\",\n \"name\": \"round\",\n \"type\": \"uint8\"\n }\n ],\n \"name\": \"RoundRequested\",\n \"type\": \"event\"\n },\n {\n \"inputs\": [],\n \"name\": \"latestTransmissionDetails\",\n \"outputs\": [\n {\n \"internalType\": \"bytes32\",\n \"name\": \"configDigest\",\n \"type\": \"bytes32\"\n },\n {\n \"internalType\": \"uint32\",\n \"name\": \"epoch\",\n \"type\": \"uint32\"\n },\n {\n \"internalType\": \"uint8\",\n \"name\": \"round\",\n \"type\": \"uint8\"\n },\n {\n \"internalType\": \"int192\",\n \"name\": \"latestAnswer_\",\n \"type\": \"int192\"\n },\n {\n \"internalType\": \"uint64\",\n \"name\": \"latestTimestamp_\",\n \"type\": \"uint64\"\n }\n ],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n }\n]\n" + +[relayConfig.chainReader.contracts.median.configs] +LatestRoundRequested = "{\n \"chainSpecificName\": \"RoundRequested\",\n \"readType\": \"event\"\n}\n" +LatestTransmissionDetails = "{\n \"chainSpecificName\": \"latestTransmissionDetails\",\n \"outputModifications\": [\n {\n \"Fields\": [\n \"LatestTimestamp_\"\n ],\n \"Type\": \"epoch to time\"\n },\n {\n \"Fields\": {\n \"LatestAnswer_\": \"LatestAnswer\",\n \"LatestTimestamp_\": \"LatestTimestamp\"\n },\n \"Type\": \"rename\"\n }\n ]\n}\n" + +[relayConfig.codec] +[relayConfig.codec.configs] +[relayConfig.codec.configs.MedianReport] +typeABI = "[\n {\n \"Name\": \"Timestamp\",\n \"Type\": \"uint32\"\n },\n {\n \"Name\": \"Observers\",\n \"Type\": \"bytes32\"\n },\n {\n \"Name\": \"Observations\",\n \"Type\": \"int192[]\"\n },\n {\n \"Name\": \"JuelsPerFeeCoin\",\n \"Type\": \"int192\"\n }\n]\n" + +[pluginConfig] +juelsPerFeeCoinSource = " // data source 1\n ds1 [type=bridge name=\"%s\"];\n ds1_parse [type=jsonparse path=\"data\"];\n ds1_multiply [type=multiply times=2];\n\n // data source 2\n ds2 [type=http method=GET url=\"%s\"];\n ds2_parse [type=jsonparse path=\"data\"];\n ds2_multiply [type=multiply times=2];\n\n ds1 -> ds1_parse -> ds1_multiply -> answer1;\n ds2 -> ds2_parse -> ds2_multiply -> answer1;\n\n answer1 [type=median index=0];\n" diff --git a/core/services/job/testdata/pretty.toml b/core/services/job/testdata/pretty.toml new file mode 100644 index 00000000000..88bacff7db2 --- /dev/null +++ b/core/services/job/testdata/pretty.toml @@ -0,0 +1,149 @@ +relay = "evm" +pluginType = "median" +contractID = "foo" +ocrKeyBundleID = "bar" +transmitterID = "baz" +contractConfigConfirmations = 1 +contractConfigTrackerPollInterval = "1s" + +[relayConfig] +chainID = 1337 +fromBlock = 42 + +[relayConfig.chainReader.contracts.median] +contractABI = ''' +[ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "requester", + "type": "address" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "configDigest", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "uint32", + "name": "epoch", + "type": "uint32" + }, + { + "indexed": false, + "internalType": "uint8", + "name": "round", + "type": "uint8" + } + ], + "name": "RoundRequested", + "type": "event" + }, + { + "inputs": [], + "name": "latestTransmissionDetails", + "outputs": [ + { + "internalType": "bytes32", + "name": "configDigest", + "type": "bytes32" + }, + { + "internalType": "uint32", + "name": "epoch", + "type": "uint32" + }, + { + "internalType": "uint8", + "name": "round", + "type": "uint8" + }, + { + "internalType": "int192", + "name": "latestAnswer_", + "type": "int192" + }, + { + "internalType": "uint64", + "name": "latestTimestamp_", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + } +] +''' + +[relayConfig.chainReader.contracts.median.configs] +LatestRoundRequested = ''' +{ + "chainSpecificName": "RoundRequested", + "readType": "event" +} +''' +LatestTransmissionDetails = ''' +{ + "chainSpecificName": "latestTransmissionDetails", + "outputModifications": [ + { + "Fields": [ + "LatestTimestamp_" + ], + "Type": "epoch to time" + }, + { + "Fields": { + "LatestAnswer_": "LatestAnswer", + "LatestTimestamp_": "LatestTimestamp" + }, + "Type": "rename" + } + ] +} +''' + +[relayConfig.codec.configs.MedianReport] +typeABI = ''' +[ + { + "Name": "Timestamp", + "Type": "uint32" + }, + { + "Name": "Observers", + "Type": "bytes32" + }, + { + "Name": "Observations", + "Type": "int192[]" + }, + { + "Name": "JuelsPerFeeCoin", + "Type": "int192" + } +] +''' + +[pluginConfig] +juelsPerFeeCoinSource = """ + // data source 1 + ds1 [type=bridge name="%s"]; + ds1_parse [type=jsonparse path="data"]; + ds1_multiply [type=multiply times=2]; + + // data source 2 + ds2 [type=http method=GET url="%s"]; + ds2_parse [type=jsonparse path="data"]; + ds2_multiply [type=multiply times=2]; + + ds1 -> ds1_parse -> ds1_multiply -> answer1; + ds2 -> ds2_parse -> ds2_multiply -> answer1; + + answer1 [type=median index=0]; +""" \ No newline at end of file diff --git a/core/services/ocr2/plugins/median/plugin.go b/core/services/ocr2/plugins/median/plugin.go deleted file mode 100644 index cad2099832d..00000000000 --- a/core/services/ocr2/plugins/median/plugin.go +++ /dev/null @@ -1,68 +0,0 @@ -package median - -import ( - "context" - - "github.com/smartcontractkit/libocr/offchainreporting2/reportingplugin/median" - ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" - - "github.com/smartcontractkit/chainlink-common/pkg/logger" - "github.com/smartcontractkit/chainlink-common/pkg/loop" - "github.com/smartcontractkit/chainlink-common/pkg/services" - "github.com/smartcontractkit/chainlink-common/pkg/types" -) - -type Plugin struct { - loop.Plugin - stop services.StopChan -} - -func NewPlugin(lggr logger.Logger) *Plugin { - return &Plugin{Plugin: loop.Plugin{Logger: lggr}, stop: make(services.StopChan)} -} - -func (p *Plugin) NewMedianFactory(ctx context.Context, provider types.MedianProvider, dataSource, juelsPerFeeCoin median.DataSource, errorLog loop.ErrorLog) (loop.ReportingPluginFactory, error) { - var ctxVals loop.ContextValues - ctxVals.SetValues(ctx) - lggr := logger.With(p.Logger, ctxVals.Args()...) - - factory := median.NumericalMedianFactory{ - ContractTransmitter: provider.MedianContract(), - DataSource: dataSource, - JuelsPerFeeCoinDataSource: juelsPerFeeCoin, - Logger: logger.NewOCRWrapper(lggr, true, func(msg string) { - ctx, cancelFn := p.stop.NewCtx() - defer cancelFn() - if err := errorLog.SaveError(ctx, msg); err != nil { - lggr.Errorw("Unable to save error", "err", msg) - } - }), - OnchainConfigCodec: provider.OnchainConfigCodec(), - ReportCodec: provider.ReportCodec(), - } - s := &reportingPluginFactoryService{lggr: logger.Named(lggr, "ReportingPluginFactory"), ReportingPluginFactory: factory} - - p.SubService(s) - - return s, nil -} - -type reportingPluginFactoryService struct { - services.StateMachine - lggr logger.Logger - ocrtypes.ReportingPluginFactory -} - -func (r *reportingPluginFactoryService) Name() string { return r.lggr.Name() } - -func (r *reportingPluginFactoryService) Start(ctx context.Context) error { - return r.StartOnce("ReportingPluginFactory", func() error { return nil }) -} - -func (r *reportingPluginFactoryService) Close() error { - return r.StopOnce("ReportingPluginFactory", func() error { return nil }) -} - -func (r *reportingPluginFactoryService) HealthReport() map[string]error { - return map[string]error{r.Name(): r.Healthy()} -} diff --git a/core/services/ocr2/plugins/median/services.go b/core/services/ocr2/plugins/median/services.go index 4adfc306d64..bdf6feaed47 100644 --- a/core/services/ocr2/plugins/median/services.go +++ b/core/services/ocr2/plugins/median/services.go @@ -5,14 +5,9 @@ import ( "encoding/json" "errors" "fmt" - "math/big" "time" - "github.com/ethereum/go-ethereum/common" libocr "github.com/smartcontractkit/libocr/offchainreporting2plus" - ocr2types "github.com/smartcontractkit/libocr/offchainreporting2plus/types" - - mediantypes "github.com/smartcontractkit/libocr/offchainreporting2/reportingplugin/median" "github.com/smartcontractkit/chainlink-common/pkg/loop" "github.com/smartcontractkit/chainlink-common/pkg/types" @@ -57,21 +52,6 @@ func (m *medianConfig) JobPipelineResultWriteQueueDepth() uint64 { return m.jobPipelineResultWriteQueueDepth } -// This wrapper avoids the need to modify the signature of NewMedianFactory in all of the non-evm -// relay repos as well as its primary definition in chainlink-common. Once ChainReader is implemented -// and working on all 4 blockchain families, we can remove the original MedianContract() method from -// MedianProvider and pass medianContract as a separate param to NewMedianFactory -type medianProviderWrapper struct { - types.MedianProvider - contract mediantypes.MedianContract -} - -// Override relay's implementation of MedianContract with product plugin's implementation of -// MedianContract, making use of product-agnostic ChainReader to read the contract instead of relay MedianContract -func (m medianProviderWrapper) MedianContract() mediantypes.MedianContract { - return m.contract -} - func NewMedianServices(ctx context.Context, jb job.Job, isNewlyCreatedJob bool, @@ -145,24 +125,10 @@ func NewMedianServices(ctx context.Context, CreatedAt: time.Now(), }, lggr) - medianPluginCmd := env.MedianPluginCmd.Get() - medianLoopEnabled := medianPluginCmd != "" - - // TODO BCF-2821 handle this properly as this blocks Solana chain reader dev - if !medianLoopEnabled && medianProvider.ChainReader() != nil { - lggr.Info("Chain Reader enabled") - medianProvider = medianProviderWrapper{ - medianProvider, // attach newer MedianContract which uses ChainReader - newMedianContract(provider.ChainReader(), common.HexToAddress(spec.ContractID)), - } - } else { - lggr.Info("Chain Reader disabled") - } - - if medianLoopEnabled { + if cmdName := env.MedianPluginCmd.Get(); cmdName != "" { // use unique logger names so we can use it to register a loop medianLggr := lggr.Named("Median").Named(spec.ContractID).Named(spec.GetID()) - cmdFn, telem, err2 := cfg.RegisterLOOP(medianLggr.Name(), medianPluginCmd) + cmdFn, telem, err2 := cfg.RegisterLOOP(medianLggr.Name(), cmdName) if err2 != nil { err = fmt.Errorf("failed to register loop: %w", err2) abort() @@ -192,49 +158,3 @@ func NewMedianServices(ctx context.Context, } return } - -type medianContract struct { - chainReader types.ChainReader - contract types.BoundContract -} - -type latestTransmissionDetailsResponse struct { - configDigest ocr2types.ConfigDigest - epoch uint32 - round uint8 - latestAnswer *big.Int - latestTimestamp time.Time -} - -type latestRoundRequested struct { - configDigest ocr2types.ConfigDigest - epoch uint32 - round uint8 -} - -func (m *medianContract) LatestTransmissionDetails(ctx context.Context) (configDigest ocr2types.ConfigDigest, epoch uint32, round uint8, latestAnswer *big.Int, latestTimestamp time.Time, err error) { - var resp latestTransmissionDetailsResponse - - err = m.chainReader.GetLatestValue(ctx, m.contract, "LatestTransmissionDetails", nil, &resp) - if err != nil { - return - } - - return resp.configDigest, resp.epoch, resp.round, resp.latestAnswer, resp.latestTimestamp, err -} - -func (m *medianContract) LatestRoundRequested(ctx context.Context, lookback time.Duration) (configDigest ocr2types.ConfigDigest, epoch uint32, round uint8, err error) { - var resp latestRoundRequested - - err = m.chainReader.GetLatestValue(ctx, m.contract, "LatestRoundReported", map[string]string{}, &resp) - if err != nil { - return - } - - return resp.configDigest, resp.epoch, resp.round, err -} - -func newMedianContract(chainReader types.ChainReader, address common.Address) *medianContract { - contract := types.BoundContract{Address: address.String(), Name: "median", Pending: true} - return &medianContract{chainReader, contract} -} diff --git a/core/services/relay/evm/binding.go b/core/services/relay/evm/binding.go new file mode 100644 index 00000000000..e78d9f0a770 --- /dev/null +++ b/core/services/relay/evm/binding.go @@ -0,0 +1,15 @@ +package evm + +import ( + "context" + + commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" +) + +type readBinding interface { + GetLatestValue(ctx context.Context, params, returnVal any) error + Bind(binding commontypes.BoundContract) error + SetCodec(codec commontypes.RemoteCodec) + Register() error + Unregister() error +} diff --git a/core/services/relay/evm/bindings.go b/core/services/relay/evm/bindings.go new file mode 100644 index 00000000000..1a23128d19f --- /dev/null +++ b/core/services/relay/evm/bindings.go @@ -0,0 +1,61 @@ +package evm + +import ( + "fmt" + + commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" +) + +// key is contract name +type contractBindings map[string]readBindings + +// key is read name +type readBindings map[string]readBinding + +func (b contractBindings) GetReadBinding(contractName, readName string) (readBinding, error) { + rb, rbExists := b[contractName] + if !rbExists { + return nil, fmt.Errorf("%w: no contract named %s", commontypes.ErrInvalidType, contractName) + } + + reader, readerExists := rb[readName] + if !readerExists { + return nil, fmt.Errorf("%w: no readName named %s in contract %s", commontypes.ErrInvalidType, readName, contractName) + } + return reader, nil +} + +func (b contractBindings) AddReadBinding(contractName, readName string, reader readBinding) { + rbs, rbsExists := b[contractName] + if !rbsExists { + rbs = readBindings{} + b[contractName] = rbs + } + rbs[readName] = reader +} + +func (b contractBindings) Bind(boundContracts []commontypes.BoundContract) error { + for _, bc := range boundContracts { + rbs, rbsExist := b[bc.Name] + if !rbsExist { + return fmt.Errorf("%w: no contract named %s", commontypes.ErrInvalidConfig, bc.Name) + } + for _, r := range rbs { + if err := r.Bind(bc); err != nil { + return err + } + } + } + return nil +} + +func (b contractBindings) ForEach(fn func(readBinding) error) error { + for _, rbs := range b { + for _, rb := range rbs { + if err := fn(rb); err != nil { + return err + } + } + } + return nil +} diff --git a/core/services/relay/evm/chain_reader.go b/core/services/relay/evm/chain_reader.go index e4da4cc1a49..dba05af7e3c 100644 --- a/core/services/relay/evm/chain_reader.go +++ b/core/services/relay/evm/chain_reader.go @@ -3,14 +3,20 @@ package evm import ( "context" "fmt" - "slices" + "reflect" "strings" "github.com/ethereum/go-ethereum/accounts/abi" - "github.com/ethereum/go-ethereum/common" + "github.com/google/uuid" + "github.com/smartcontractkit/chainlink-common/pkg/codec" + + commonservices "github.com/smartcontractkit/chainlink-common/pkg/services" commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" + evmclient "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" + "github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services" @@ -23,132 +29,264 @@ type ChainReaderService interface { } type chainReader struct { - lggr logger.Logger - contractID common.Address - lp logpoller.LogPoller + lggr logger.Logger + lp logpoller.LogPoller + client evmclient.Client + contractBindings contractBindings + parsed *parsedTypes + codec commontypes.RemoteCodec + commonservices.StateMachine } -// NewChainReaderService constructor for ChainReader -func NewChainReaderService(lggr logger.Logger, lp logpoller.LogPoller, contractID common.Address, config types.ChainReaderConfig) (*chainReader, error) { - if err := validateChainReaderConfig(config); err != nil { - return nil, fmt.Errorf("%w: %w", commontypes.ErrInvalidConfig, err) +// NewChainReaderService is a constructor for ChainReader, returns nil if there is any error +func NewChainReaderService(lggr logger.Logger, lp logpoller.LogPoller, chain legacyevm.Chain, config types.ChainReaderConfig) (ChainReaderService, error) { + cr := &chainReader{ + lggr: lggr.Named("ChainReader"), + lp: lp, + client: chain.Client(), + contractBindings: contractBindings{}, + parsed: &parsedTypes{encoderDefs: map[string]types.CodecEntry{}, decoderDefs: map[string]types.CodecEntry{}}, } - // TODO BCF-2814 implement initialisation of chain reading definitions and pass them into chainReader - return &chainReader{lggr.Named("ChainReader"), contractID, lp}, nil -} + var err error + if err = cr.init(config.Contracts); err != nil { + return nil, err + } -func (cr *chainReader) Name() string { return cr.lggr.Name() } + if cr.codec, err = cr.parsed.toCodec(); err != nil { + return nil, err + } -func (cr *chainReader) initialize() error { - // Initialize chain reader, start cache polling loop, etc. - return nil -} + err = cr.contractBindings.ForEach(func(b readBinding) error { + b.SetCodec(cr.codec) + return nil + }) -func (cr *chainReader) Start(ctx context.Context) error { - if err := cr.initialize(); err != nil { - return fmt.Errorf("Failed to initialize ChainReader: %w", err) - } - return nil + return cr, err } -func (cr *chainReader) Close() error { return nil } +func (cr *chainReader) Name() string { return cr.lggr.Name() } -func (cr *chainReader) Ready() error { return nil } +var _ commontypes.ContractTypeProvider = &chainReader{} -func (cr *chainReader) HealthReport() map[string]error { - return map[string]error{cr.Name(): nil} -} +func (cr *chainReader) GetLatestValue(ctx context.Context, contractName, method string, params any, returnVal any) error { + b, err := cr.contractBindings.GetReadBinding(contractName, method) + if err != nil { + return err + } -func (cr *chainReader) GetLatestValue(ctx context.Context, bc commontypes.BoundContract, method string, params any, returnVal any) error { - return commontypes.UnimplementedError("Unimplemented method GetLatestValue called") + return b.GetLatestValue(ctx, params, returnVal) } -func validateChainReaderConfig(cfg types.ChainReaderConfig) error { - if len(cfg.ChainContractReaders) == 0 { - return fmt.Errorf("%w: no contract readers defined", commontypes.ErrInvalidConfig) - } +func (cr *chainReader) Bind(_ context.Context, bindings []commontypes.BoundContract) error { + return cr.contractBindings.Bind(bindings) +} - for contractName, chainContractReader := range cfg.ChainContractReaders { - abi, err := abi.JSON(strings.NewReader(chainContractReader.ContractABI)) +func (cr *chainReader) init(chainContractReaders map[string]types.ChainContractReader) error { + for contractName, chainContractReader := range chainContractReaders { + contractAbi, err := abi.JSON(strings.NewReader(chainContractReader.ContractABI)) if err != nil { - return fmt.Errorf("invalid abi: %w", err) + return err } - for chainReadingDefinitionName, chainReaderDefinition := range chainContractReader.ChainReaderDefinitions { + for typeName, chainReaderDefinition := range chainContractReader.Configs { switch chainReaderDefinition.ReadType { case types.Method: - err = validateMethods(abi, chainReaderDefinition) + err = cr.addMethod(contractName, typeName, contractAbi, *chainReaderDefinition) case types.Event: - err = validateEvents(abi, chainReaderDefinition) + err = cr.addEvent(contractName, typeName, contractAbi, *chainReaderDefinition) default: - return fmt.Errorf("%w: invalid chainreading definition read type: %d for contract: %q", commontypes.ErrInvalidConfig, chainReaderDefinition.ReadType, contractName) + return fmt.Errorf( + "%w: invalid chain reader definition read type: %s", + commontypes.ErrInvalidConfig, + chainReaderDefinition.ReadType) } + if err != nil { - return fmt.Errorf("%w: invalid chainreading definition: %q for contract: %q, err: %w", commontypes.ErrInvalidConfig, chainReadingDefinitionName, contractName, err) + return err } } } - return nil } -func validateEvents(contractABI abi.ABI, chainReaderDefinition types.ChainReaderDefinition) error { - event, methodExists := contractABI.Events[chainReaderDefinition.ChainSpecificName] +func (cr *chainReader) Start(_ context.Context) error { + return cr.StartOnce("ChainReader", func() error { + return cr.contractBindings.ForEach(readBinding.Register) + }) +} + +func (cr *chainReader) Close() error { + return cr.StopOnce("ChainReader", func() error { + return cr.contractBindings.ForEach(readBinding.Unregister) + }) +} + +func (cr *chainReader) Ready() error { return nil } +func (cr *chainReader) HealthReport() map[string]error { + return map[string]error{cr.Name(): nil} +} + +func (cr *chainReader) CreateContractType(contractName, methodName string, forEncoding bool) (any, error) { + return cr.codec.CreateType(wrapItemType(contractName, methodName, forEncoding), forEncoding) +} + +func wrapItemType(contractName, methodName string, isParams bool) string { + if isParams { + return fmt.Sprintf("params.%s.%s", contractName, methodName) + } + return fmt.Sprintf("return.%s.%s", contractName, methodName) +} + +func (cr *chainReader) addMethod( + contractName, + methodName string, + abi abi.ABI, + chainReaderDefinition types.ChainReaderDefinition) error { + method, methodExists := abi.Methods[chainReaderDefinition.ChainSpecificName] if !methodExists { - return fmt.Errorf("event: %s doesn't exist", chainReaderDefinition.ChainSpecificName) + return fmt.Errorf("%w: method %s doesn't exist", commontypes.ErrInvalidConfig, chainReaderDefinition.ChainSpecificName) } - var abiEventIndexedInputs []abi.Argument - for _, eventInput := range event.Inputs { - if eventInput.Indexed { - abiEventIndexedInputs = append(abiEventIndexedInputs, eventInput) - } + if len(chainReaderDefinition.EventInputFields) != 0 { + return fmt.Errorf( + "%w: method %s has event topic fields defined, but is not an event", + commontypes.ErrInvalidConfig, + chainReaderDefinition.ChainSpecificName) } - var chainReaderEventParams []string - for chainReaderEventParam := range chainReaderDefinition.Params { - chainReaderEventParams = append(chainReaderEventParams, chainReaderEventParam) + cr.contractBindings.AddReadBinding(contractName, methodName, &methodBinding{ + contractName: contractName, + method: methodName, + client: cr.client, + }) + + if err := cr.addEncoderDef(contractName, methodName, method.Inputs, method.ID, chainReaderDefinition); err != nil { + return err } - if !areChainReaderArgumentsValid(abiEventIndexedInputs, chainReaderEventParams) { - var abiEventIndexedInputsNames []string - for _, abiEventIndexedInput := range abiEventIndexedInputs { - abiEventIndexedInputsNames = append(abiEventIndexedInputsNames, abiEventIndexedInput.Name) - } - return fmt.Errorf("params: [%s] don't match abi event indexed inputs: [%s]", strings.Join(chainReaderEventParams, ","), strings.Join(abiEventIndexedInputsNames, ",")) + return cr.addDecoderDef(contractName, methodName, method.Outputs, chainReaderDefinition) +} + +func (cr *chainReader) addEvent(contractName, eventName string, a abi.ABI, chainReaderDefinition types.ChainReaderDefinition) error { + event, eventExists := a.Events[chainReaderDefinition.ChainSpecificName] + if !eventExists { + return fmt.Errorf("%w: event %s doesn't exist", commontypes.ErrInvalidConfig, chainReaderDefinition.ChainSpecificName) } - return nil + + filterArgs, topicInfo, indexArgNames := setupEventInput(event, chainReaderDefinition) + if err := verifyEventInputsUsed(chainReaderDefinition, indexArgNames); err != nil { + return err + } + + if err := topicInfo.Init(); err != nil { + return err + } + + // Encoder def's codec won't be used to encode, only for its type as input for GetLatestValue + if err := cr.addEncoderDef(contractName, eventName, filterArgs, nil, chainReaderDefinition); err != nil { + return err + } + + inputInfo, inputModifier, err := cr.getEventInput(chainReaderDefinition, contractName, eventName) + if err != nil { + return err + } + + cr.contractBindings.AddReadBinding(contractName, eventName, &eventBinding{ + contractName: contractName, + eventName: eventName, + lp: cr.lp, + hash: event.ID, + inputInfo: inputInfo, + inputModifier: inputModifier, + topicInfo: topicInfo, + id: wrapItemType(contractName, eventName, false) + uuid.NewString(), + }) + + return cr.addDecoderDef(contractName, eventName, event.Inputs, chainReaderDefinition) } -func validateMethods(abi abi.ABI, chainReaderDefinition types.ChainReaderDefinition) error { - method, methodExists := abi.Methods[chainReaderDefinition.ChainSpecificName] - if !methodExists { - return fmt.Errorf("method: %q doesn't exist", chainReaderDefinition.ChainSpecificName) +func (cr *chainReader) getEventInput(def types.ChainReaderDefinition, contractName, eventName string) ( + types.CodecEntry, codec.Modifier, error) { + inputInfo := cr.parsed.encoderDefs[wrapItemType(contractName, eventName, true)] + inMod, err := def.InputModifications.ToModifier(evmDecoderHooks...) + if err != nil { + return nil, nil, err } - var methodNames []string - for methodName := range chainReaderDefinition.Params { - methodNames = append(methodNames, methodName) + // initialize the modification + if _, err = inMod.RetypeToOffChain(reflect.PointerTo(inputInfo.CheckedType()), ""); err != nil { + return nil, nil, err } - if !areChainReaderArgumentsValid(method.Inputs, methodNames) { - var abiMethodInputs []string - for _, input := range method.Inputs { - abiMethodInputs = append(abiMethodInputs, input.Name) + return inputInfo, inMod, nil +} + +func verifyEventInputsUsed(chainReaderDefinition types.ChainReaderDefinition, indexArgNames map[string]bool) error { + for _, value := range chainReaderDefinition.EventInputFields { + if !indexArgNames[abi.ToCamelCase(value)] { + return fmt.Errorf("%w: %s is not an indexed argument of event %s", commontypes.ErrInvalidConfig, value, chainReaderDefinition.ChainSpecificName) } - return fmt.Errorf("params: [%s] don't match abi method inputs: [%s]", strings.Join(methodNames, ","), strings.Join(abiMethodInputs, ",")) } + return nil +} +func (cr *chainReader) addEncoderDef(contractName, methodName string, args abi.Arguments, prefix []byte, chainReaderDefinition types.ChainReaderDefinition) error { + // ABI.Pack prepends the method.ID to the encodings, we'll need the encoder to do the same. + inputMod, err := chainReaderDefinition.InputModifications.ToModifier(evmDecoderHooks...) + if err != nil { + return err + } + input := types.NewCodecEntry(args, prefix, inputMod) + + if err := input.Init(); err != nil { + return err + } + + cr.parsed.encoderDefs[wrapItemType(contractName, methodName, true)] = input return nil } -func areChainReaderArgumentsValid(contractArgs []abi.Argument, chainReaderArgs []string) bool { - for _, contractArg := range contractArgs { - if !slices.Contains(chainReaderArgs, contractArg.Name) { - return false +func (cr *chainReader) addDecoderDef(contractName, methodName string, outputs abi.Arguments, def types.ChainReaderDefinition) error { + mod, err := def.OutputModifications.ToModifier(evmDecoderHooks...) + if err != nil { + return err + } + output := types.NewCodecEntry(outputs, nil, mod) + cr.parsed.decoderDefs[wrapItemType(contractName, methodName, false)] = output + return output.Init() +} + +func setupEventInput(event abi.Event, def types.ChainReaderDefinition) ([]abi.Argument, types.CodecEntry, map[string]bool) { + topicFieldDefs := map[string]bool{} + for _, value := range def.EventInputFields { + capFirstValue := abi.ToCamelCase(value) + topicFieldDefs[capFirstValue] = true + } + + filterArgs := make([]abi.Argument, 0, types.MaxTopicFields) + inputArgs := make([]abi.Argument, 0, len(event.Inputs)) + indexArgNames := map[string]bool{} + + for _, input := range event.Inputs { + if !input.Indexed { + continue + } + + filterWith := topicFieldDefs[abi.ToCamelCase(input.Name)] + if filterWith { + // When presenting the filter off-chain, + // the user will provide the unhashed version of the input + // The reader will hash topics if needed. + inputUnindexed := input + inputUnindexed.Indexed = false + filterArgs = append(filterArgs, inputUnindexed) } + + inputArgs = append(inputArgs, input) + indexArgNames[abi.ToCamelCase(input.Name)] = true } - return true + return filterArgs, types.NewCodecEntry(inputArgs, nil, nil), indexArgNames } diff --git a/core/services/relay/evm/chain_reader_test.go b/core/services/relay/evm/chain_reader_test.go index ece2234ab55..4981dc3696a 100644 --- a/core/services/relay/evm/chain_reader_test.go +++ b/core/services/relay/evm/chain_reader_test.go @@ -1,272 +1,423 @@ -package evm +package evm_test import ( - "encoding/json" + "crypto/ecdsa" "fmt" - "strings" + "math" + "math/big" + "os" + "reflect" + "strconv" "testing" + "time" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/accounts/abi/bind/backends" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core" + evmtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/smartcontractkit/libocr/commontypes" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" - commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" + "github.com/smartcontractkit/chainlink-common/pkg/codec" - mocklogpoller "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller/mocks" + clcommontypes "github.com/smartcontractkit/chainlink-common/pkg/types" + . "github.com/smartcontractkit/chainlink-common/pkg/types/interfacetests" //nolint common practice to import test mods with . + + commontestutils "github.com/smartcontractkit/chainlink-common/pkg/loop/testutils" + + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" "github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm/mocks" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/chain_reader_example" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" "github.com/smartcontractkit/chainlink/v2/core/logger" - evmtypes "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/types" + "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm" + "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/types" +) + +const ( + commonGasLimitOnEvms = uint64(4712388) + triggerWithDynamicTopic = "TriggeredEventWithDynamicTopic" + triggerWithAllTopics = "TriggeredWithFourTopics" ) -type chainReaderTestHelper struct { +func TestChainReader(t *testing.T) { + t.Parallel() + it := &chainReaderInterfaceTester{} + RunChainReaderInterfaceTests(t, it) + RunChainReaderInterfaceTests(t, commontestutils.WrapChainReaderTesterForLoop(it)) + t.Run("Dynamically typed topics can be used to filter and have type correct in return", func(t *testing.T) { + it.Setup(t) + + anyString := "foo" + tx, err := it.evmTest.LatestValueHolderTransactor.TriggerEventWithDynamicTopic(it.auth, anyString) + require.NoError(t, err) + it.sim.Commit() + it.incNonce() + it.awaitTx(t, tx) + ctx := testutils.Context(t) + + cr := it.GetChainReader(t) + require.NoError(t, cr.Bind(ctx, it.GetBindings(t))) + + input := struct{ Field string }{Field: anyString} + tp := cr.(clcommontypes.ContractTypeProvider) + output, err := tp.CreateContractType(AnyContractName, triggerWithDynamicTopic, false) + require.NoError(t, err) + rOutput := reflect.Indirect(reflect.ValueOf(output)) + + require.Eventually(t, func() bool { + return cr.GetLatestValue(ctx, AnyContractName, triggerWithDynamicTopic, input, output) == nil + }, it.MaxWaitTimeForEvents(), time.Millisecond*10) + + assert.Equal(t, anyString, rOutput.FieldByName("Field").Interface()) + topic, err := abi.MakeTopics([]any{anyString}) + require.NoError(t, err) + assert.Equal(t, topic[0][0], rOutput.FieldByName("FieldHash").Interface()) + }) + + t.Run("Multiple topics can filter together", func(t *testing.T) { + it.Setup(t) + 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)) + + ctx := testutils.Context(t) + cr := it.GetChainReader(t) + require.NoError(t, cr.Bind(ctx, it.GetBindings(t))) + 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, params, &latest)) + assert.Equal(t, int32(1), latest.Field1) + assert.Equal(t, int32(2), latest.Field2) + assert.Equal(t, int32(3), latest.Field3) + }) +} + +func triggerFourTopics(t *testing.T, it *chainReaderInterfaceTester, i1, i2, i3 int32) { + tx, err := it.evmTest.LatestValueHolderTransactor.TriggerWithFourTopics(it.auth, i1, i2, i3) + require.NoError(t, err) + require.NoError(t, err) + it.sim.Commit() + it.incNonce() + it.awaitTx(t, tx) } -func (crTestHelper chainReaderTestHelper) makeChainReaderConfig(abi string, params map[string]any) evmtypes.ChainReaderConfig { - return evmtypes.ChainReaderConfig{ - ChainContractReaders: map[string]evmtypes.ChainContractReader{ - "MyContract": { - ContractABI: abi, - ChainReaderDefinitions: map[string]evmtypes.ChainReaderDefinition{ - "MyGenericMethod": { - ChainSpecificName: "name", - Params: params, - CacheEnabled: false, - ReadType: evmtypes.Method, +type chainReaderInterfaceTester struct { + chain *mocks.Chain + address string + address2 string + chainConfig types.ChainReaderConfig + auth *bind.TransactOpts + sim *backends.SimulatedBackend + pk *ecdsa.PrivateKey + evmTest *chain_reader_example.LatestValueHolder + cr evm.ChainReaderService +} + +func (it *chainReaderInterfaceTester) MaxWaitTimeForEvents() time.Duration { + // From trial and error, when running on CI, sometimes the boxes get slow + maxWaitTime := time.Second * 20 + maxWaitTimeStr, ok := os.LookupEnv("MAX_WAIT_TIME_FOR_EVENTS_S") + if ok { + wiatS, err := strconv.ParseInt(maxWaitTimeStr, 10, 64) + if err != nil { + fmt.Printf("Error parsing MAX_WAIT_TIME_FOR_EVENTS_S: %v, defaulting to %v\n", err, maxWaitTime) + } + maxWaitTime = time.Second * time.Duration(wiatS) + } + + return maxWaitTime +} + +func (it *chainReaderInterfaceTester) Setup(t *testing.T) { + t.Cleanup(func() { + // DB may be closed by the test already, ignore errors + _ = it.cr.Close() + it.cr = nil + it.evmTest = nil + }) + + // can re-use the same chain for tests, just make new contract for each test + if it.chain != nil { + it.deployNewContracts(t) + return + } + + it.chain = &mocks.Chain{} + it.setupChainNoClient(t) + + testStruct := CreateTestStruct(0, it) + + it.chainConfig = types.ChainReaderConfig{ + Contracts: map[string]types.ChainContractReader{ + AnyContractName: { + ContractABI: chain_reader_example.LatestValueHolderMetaData.ABI, + Configs: map[string]*types.ChainReaderDefinition{ + MethodTakingLatestParamsReturningTestStruct: { + ChainSpecificName: "getElementAtIndex", + OutputModifications: codec.ModifiersConfig{ + &codec.RenameModifierConfig{Fields: map[string]string{"NestedStruct.Inner.IntVal": "I"}}, + }, + }, + MethodReturningUint64: { + ChainSpecificName: "getPrimitiveValue", + }, + DifferentMethodReturningUint64: { + ChainSpecificName: "getDifferentPrimitiveValue", + }, + MethodReturningUint64Slice: { + ChainSpecificName: "getSliceValue", + }, + EventName: { + ChainSpecificName: "Triggered", + ReadType: types.Event, + OutputModifications: codec.ModifiersConfig{ + &codec.RenameModifierConfig{Fields: map[string]string{"NestedStruct.Inner.IntVal": "I"}}, + }, + }, + EventWithFilterName: { + ChainSpecificName: "Triggered", + ReadType: types.Event, + EventInputFields: []string{"Field"}, + }, + triggerWithDynamicTopic: { + ChainSpecificName: triggerWithDynamicTopic, + ReadType: types.Event, + EventInputFields: []string{"fieldHash"}, + InputModifications: codec.ModifiersConfig{ + &codec.RenameModifierConfig{Fields: map[string]string{"FieldHash": "Field"}}, + }, + }, + triggerWithAllTopics: { + ChainSpecificName: triggerWithAllTopics, + ReadType: types.Event, + EventInputFields: []string{"Field1", "Field2", "Field3"}, + }, + MethodReturningSeenStruct: { + ChainSpecificName: "returnSeen", + InputModifications: codec.ModifiersConfig{ + &codec.HardCodeModifierConfig{ + OnChainValues: map[string]any{ + "BigField": testStruct.BigField.String(), + "Account": hexutil.Encode(testStruct.Account), + }, + }, + }, + OutputModifications: codec.ModifiersConfig{ + &codec.HardCodeModifierConfig{OffChainValues: map[string]any{"ExtraField": anyExtraValue}}, + &codec.RenameModifierConfig{Fields: map[string]string{"NestedStruct.Inner.IntVal": "I"}}, + }, + }, + }, + }, + AnySecondContractName: { + ContractABI: chain_reader_example.LatestValueHolderMetaData.ABI, + Configs: map[string]*types.ChainReaderDefinition{ + MethodReturningUint64: { + ChainSpecificName: "getDifferentPrimitiveValue", }, }, }, }, } + it.chain.On("Client").Return(client.NewSimulatedBackendClient(t, it.sim, big.NewInt(1337))) + it.deployNewContracts(t) } -func (crTestHelper chainReaderTestHelper) makeChainReaderConfigFromStrings(abi string, chainReadingDefinitions string) (evmtypes.ChainReaderConfig, error) { - chainReaderConfigTemplate := `{ - "chainContractReaders": { - "testContract": { - "contractName": "testContract", - "contractABI": "[%s]", - "chainReaderDefinitions": { - %s - } - } - } - }` - - abi = strings.Replace(abi, `"`, `\"`, -1) - formattedCfgJsonString := fmt.Sprintf(chainReaderConfigTemplate, abi, chainReadingDefinitions) - var chainReaderConfig evmtypes.ChainReaderConfig - err := json.Unmarshal([]byte(formattedCfgJsonString), &chainReaderConfig) - return chainReaderConfig, err +func (it *chainReaderInterfaceTester) Name() string { + return "EVM" } -func TestNewChainReader(t *testing.T) { - lggr := logger.TestLogger(t) - lp := mocklogpoller.NewLogPoller(t) - chain := mocks.NewChain(t) - contractID := testutils.NewAddress() - contractABI := `[{"inputs":[{"internalType":"string","name":"param","type":"string"}],"name":"name","stateMutability":"view","type":"function"}]` - - t.Run("happy path", func(t *testing.T) { - params := make(map[string]any) - params["param"] = "" - chainReaderConfig := chainReaderTestHelper{}.makeChainReaderConfig(contractABI, params) - chain.On("LogPoller").Return(lp) - _, err := NewChainReaderService(lggr, chain.LogPoller(), contractID, chainReaderConfig) - assert.NoError(t, err) - }) +func (it *chainReaderInterfaceTester) GetAccountBytes(i int) []byte { + account := [20]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10} + account[i%20] += byte(i) + account[(i+3)%20] += byte(i + 3) + return account[:] +} - t.Run("invalid config", func(t *testing.T) { - invalidChainReaderConfig := chainReaderTestHelper{}.makeChainReaderConfig(contractABI, map[string]any{}) // missing param - _, err := NewChainReaderService(lggr, chain.LogPoller(), contractID, invalidChainReaderConfig) - assert.ErrorIs(t, err, commontypes.ErrInvalidConfig) - }) +func (it *chainReaderInterfaceTester) GetChainReader(t *testing.T) clcommontypes.ChainReader { + ctx := testutils.Context(t) + if it.cr != nil { + return it.cr + } - t.Run("ChainReader config is empty", func(t *testing.T) { - emptyChainReaderConfig := evmtypes.ChainReaderConfig{} - _, err := NewChainReaderService(lggr, chain.LogPoller(), contractID, emptyChainReaderConfig) - assert.ErrorIs(t, err, commontypes.ErrInvalidConfig) - assert.ErrorContains(t, err, "no contract readers defined") - }) + lggr := logger.NullLogger + db := pgtest.NewSqlxDB(t) + lp := logpoller.NewLogPoller(logpoller.NewORM(testutils.SimulatedChainID, db, lggr, pgtest.NewQConfig(true)), it.chain.Client(), lggr, time.Millisecond, false, 0, 1, 1, 10000) + require.NoError(t, lp.Start(ctx)) + it.chain.On("LogPoller").Return(lp) + cr, err := evm.NewChainReaderService(lggr, lp, it.chain, it.chainConfig) + require.NoError(t, err) + require.NoError(t, cr.Start(ctx)) + it.cr = cr + return cr } -func TestChainReaderStartClose(t *testing.T) { - lggr := logger.TestLogger(t) - lp := mocklogpoller.NewLogPoller(t) - cr := chainReader{ - lggr: lggr, - lp: lp, +func (it *chainReaderInterfaceTester) SetLatestValue(t *testing.T, testStruct *TestStruct) { + it.sendTxWithTestStruct(t, testStruct, (*chain_reader_example.LatestValueHolderTransactor).AddTestStruct) +} + +func (it *chainReaderInterfaceTester) TriggerEvent(t *testing.T, testStruct *TestStruct) { + it.sendTxWithTestStruct(t, testStruct, (*chain_reader_example.LatestValueHolderTransactor).TriggerEvent) +} + +func (it *chainReaderInterfaceTester) GetBindings(t *testing.T) []clcommontypes.BoundContract { + return []clcommontypes.BoundContract{ + {Name: AnyContractName, Address: it.address, Pending: true}, + {Name: AnySecondContractName, Address: it.address2, Pending: true}, } - err := cr.Start(testutils.Context(t)) - assert.NoError(t, err) - err = cr.Close() - assert.NoError(t, err) } -// TODO Chain Reading Definitions return values are WIP, waiting on codec work and BCF-2789 -func TestValidateChainReaderConfig_HappyPath(t *testing.T) { - type testCase struct { - name string - abiInput string - chainReadingDefinitions string +type testStructFn = func(*chain_reader_example.LatestValueHolderTransactor, *bind.TransactOpts, int32, string, uint8, [32]uint8, common.Address, []common.Address, *big.Int, chain_reader_example.MidLevelTestStruct) (*evmtypes.Transaction, error) + +func (it *chainReaderInterfaceTester) sendTxWithTestStruct(t *testing.T, testStruct *TestStruct, fn testStructFn) { + tx, err := fn( + &it.evmTest.LatestValueHolderTransactor, + it.auth, + testStruct.Field, + testStruct.DifferentField, + uint8(testStruct.OracleID), + convertOracleIDs(testStruct.OracleIDs), + common.Address(testStruct.Account), + convertAccounts(testStruct.Accounts), + testStruct.BigField, + midToInternalType(testStruct.NestedStruct), + ) + require.NoError(t, err) + it.sim.Commit() + it.incNonce() + it.awaitTx(t, tx) +} + +func convertOracleIDs(oracleIDs [32]commontypes.OracleID) [32]byte { + convertedIds := [32]byte{} + for i, id := range oracleIDs { + convertedIds[i] = byte(id) } + return convertedIds +} - var testCases []testCase - testCases = append(testCases, - testCase{ - name: "eventWithMultipleIndexedTopics", - abiInput: `{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"}],"name":"Swap","type":"event"}`, - chainReadingDefinitions: `"Swap":{ - "chainSpecificName": "Swap", - "params":{ - "sender": "0x0", - "to": "0x0" - }, - "readType": 1 - }`, - }) - - testCases = append(testCases, - testCase{ - name: "methodWithOneParamAndMultipleResponses", - abiInput: `{"constant":true,"inputs":[{"internalType":"address","name":"_user","type":"address"}],"name":"getUserAccountData","payable":false,"stateMutability":"view","type":"function"}`, - chainReadingDefinitions: `"getUserAccountData":{ - "chainSpecificName": "getUserAccountData", - "params":{ - "_user": "0x0" - }, - "readType": 0 - }`, - }) - - testCases = append(testCases, - testCase{ - name: "methodWithMultipleParamsAndOneResult", - abiInput: `{"inputs":[{"internalType":"address","name":"_input","type":"address"},{"internalType":"address","name":"_output","type":"address"},{"internalType":"uint256","name":"_inputQuantity","type":"uint256"}],"name":"getSwapOutput","stateMutability":"view","type":"function"}`, - chainReadingDefinitions: `"getSwapOutput":{ - "chainSpecificName": "getSwapOutput", - "params":{ - "_input":"0x0", - "_output":"0x0", - "_inputQuantity":"0x0" - }, - "readType": 0 - }`, - }) - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - cfg, err := chainReaderTestHelper{}.makeChainReaderConfigFromStrings(tc.abiInput, tc.chainReadingDefinitions) - assert.NoError(t, err) - assert.NoError(t, validateChainReaderConfig(cfg)) - }) +func convertAccounts(accounts [][]byte) []common.Address { + convertedAccounts := make([]common.Address, len(accounts)) + for i, a := range accounts { + convertedAccounts[i] = common.Address(a) } + return convertedAccounts +} - t.Run("large config with all test cases", func(t *testing.T) { - var largeABI string - var manyChainReadingDefinitions string - for _, tc := range testCases { - largeABI += tc.abiInput + "," - manyChainReadingDefinitions += tc.chainReadingDefinitions + "," - } +func (it *chainReaderInterfaceTester) setupChainNoClient(t require.TestingT) { + privateKey, err := crypto.GenerateKey() + require.NoError(t, err) + it.pk = privateKey - largeABI = largeABI[:len(largeABI)-1] - manyChainReadingDefinitions = manyChainReadingDefinitions[:len(manyChainReadingDefinitions)-1] - cfg, err := chainReaderTestHelper{}.makeChainReaderConfigFromStrings(largeABI, manyChainReadingDefinitions) - assert.NoError(t, err) - assert.NoError(t, validateChainReaderConfig(cfg)) - }) + it.auth, err = bind.NewKeyedTransactorWithChainID(privateKey, big.NewInt(1337)) + require.NoError(t, err) + + it.sim = backends.NewSimulatedBackend(core.GenesisAlloc{it.auth.From: {Balance: big.NewInt(math.MaxInt64)}}, commonGasLimitOnEvms*5000) + it.sim.Commit() } -// TODO Chain Reading Definitions return values are WIP, waiting on codec work and BCF-2789 -func TestValidateChainReaderConfig_BadPath(t *testing.T) { - type testCase struct { - name string - abiInput string - chainReadingDefinitions string - expected error +func (it *chainReaderInterfaceTester) deployNewContracts(t *testing.T) { + it.address = it.deployNewContract(t) + it.address2 = it.deployNewContract(t) +} + +func (it *chainReaderInterfaceTester) deployNewContract(t *testing.T) string { + ctx := testutils.Context(t) + gasPrice, err := it.sim.SuggestGasPrice(ctx) + require.NoError(t, err) + it.auth.GasPrice = gasPrice + + // 105528 was in the error: gas too low: have 0, want 105528 + // Not sure if there's a better way to get it. + it.auth.GasLimit = 10552800 + + address, tx, ts, err := chain_reader_example.DeployLatestValueHolder(it.auth, it.sim) + + require.NoError(t, err) + it.sim.Commit() + if it.evmTest == nil { + it.evmTest = ts } + it.incNonce() + it.awaitTx(t, tx) + return address.String() +} - var testCases []testCase - mismatchedEventArgumentsTestABI := `{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"Swap","type":"event"}` - testCases = append(testCases, - testCase{ - name: "mismatched abi and event chain reading param values", - abiInput: mismatchedEventArgumentsTestABI, - chainReadingDefinitions: `"Swap":{ - "chainSpecificName": "Swap", - "params":{ - "malformedParam": "0x0" - }, - "readType": 1 - }`, - expected: fmt.Errorf("invalid chainreading definition: \"Swap\" for contract: \"testContract\", err: params: [malformedParam] don't match abi event indexed inputs: [sender]"), - }) - - mismatchedFunctionArgumentsTestABI := `{"constant":true,"inputs":[{"internalType":"address","name":"from","type":"address"}],"name":"Swap","payable":false,"stateMutability":"view","type":"function"}` - testCases = append(testCases, - testCase{ - name: "mismatched abi and method chain reading param values", - abiInput: mismatchedFunctionArgumentsTestABI, - chainReadingDefinitions: `"Swap":{ - "chainSpecificName": "Swap", - "params":{ - "malformedParam": "0x0" - }, - "readType": 0 - }`, - expected: fmt.Errorf("invalid chainreading definition: \"Swap\" for contract: \"testContract\", err: params: [malformedParam] don't match abi method inputs: [from]"), - }, - ) +func (it *chainReaderInterfaceTester) awaitTx(t *testing.T, tx *evmtypes.Transaction) { + ctx := testutils.Context(t) + receipt, err := it.sim.TransactionReceipt(ctx, tx.Hash()) + require.NoError(t, err) + require.Equal(t, evmtypes.ReceiptStatusSuccessful, receipt.Status) +} - testCases = append(testCases, - testCase{ - name: "event doesn't exist", - abiInput: `{"constant":true,"inputs":[],"name":"someName","payable":false,"stateMutability":"view","type":"function"}`, - chainReadingDefinitions: `"TestMethod":{ - "chainSpecificName": "Swap", - "readType": 1 - }`, - expected: fmt.Errorf("invalid chainreading definition: \"TestMethod\" for contract: \"testContract\", err: event: Swap doesn't exist"), - }, - ) +func (it *chainReaderInterfaceTester) incNonce() { + if it.auth.Nonce == nil { + it.auth.Nonce = big.NewInt(1) + } else { + it.auth.Nonce = it.auth.Nonce.Add(it.auth.Nonce, big.NewInt(1)) + } +} - testCases = append(testCases, - testCase{ - name: "method doesn't exist", - abiInput: `{"constant":true,"inputs":[],"name":"someName","payable":false,"stateMutability":"view","type":"function"}`, - chainReadingDefinitions: `"TestMethod":{ - "chainSpecificName": "Swap", - "readType": 0 - }`, - expected: fmt.Errorf("invalid chainreading definition: \"TestMethod\" for contract: \"testContract\", err: method: \"Swap\" doesn't exist"), - }, - ) +func getAccounts(first TestStruct) []common.Address { + accountBytes := make([]common.Address, len(first.Accounts)) + for i, account := range first.Accounts { + accountBytes[i] = common.Address(account) + } + return accountBytes +} - testCases = append(testCases, testCase{ - name: "invalid abi", - abiInput: `broken abi`, - chainReadingDefinitions: `"TestMethod":{ - "chainSpecificName": "Swap", - "readType": 0 - }`, - expected: fmt.Errorf("invalid abi"), - }) +func argsFromTestStruct(ts TestStruct) []any { + return []any{ + ts.Field, + ts.DifferentField, + uint8(ts.OracleID), + getOracleIDs(ts), + common.Address(ts.Account), + getAccounts(ts), + ts.BigField, + midToInternalType(ts.NestedStruct), + } +} - testCases = append(testCases, testCase{ - name: "invalid read type", - abiInput: `{"constant":true,"inputs":[],"name":"someName","payable":false,"stateMutability":"view","type":"function"}`, - chainReadingDefinitions: `"TestMethod":{"readType": 59}`, - expected: fmt.Errorf("invalid chainreading definition read type: 59"), - }) +func getOracleIDs(first TestStruct) [32]byte { + oracleIDs := [32]byte{} + for i, oracleID := range first.OracleIDs { + oracleIDs[i] = byte(oracleID) + } + return oracleIDs +} - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - cfg, err := chainReaderTestHelper{}.makeChainReaderConfigFromStrings(tc.abiInput, tc.chainReadingDefinitions) - assert.NoError(t, err) - if tc.expected == nil { - assert.NoError(t, validateChainReaderConfig(cfg)) - } else { - assert.ErrorContains(t, validateChainReaderConfig(cfg), tc.expected.Error()) - } - }) +func toInternalType(testStruct TestStruct) chain_reader_example.TestStruct { + return chain_reader_example.TestStruct{ + Field: testStruct.Field, + DifferentField: testStruct.DifferentField, + OracleId: byte(testStruct.OracleID), + OracleIds: convertOracleIDs(testStruct.OracleIDs), + Account: common.Address(testStruct.Account), + Accounts: convertAccounts(testStruct.Accounts), + BigField: testStruct.BigField, + NestedStruct: midToInternalType(testStruct.NestedStruct), + } +} + +func midToInternalType(m MidLevelTestStruct) chain_reader_example.MidLevelTestStruct { + return chain_reader_example.MidLevelTestStruct{ + FixedBytes: m.FixedBytes, + Inner: chain_reader_example.InnerTestStruct{ + IntVal: int64(m.Inner.I), + S: m.Inner.S, + }, } } diff --git a/core/services/relay/evm/codec.go b/core/services/relay/evm/codec.go new file mode 100644 index 00000000000..090240c666c --- /dev/null +++ b/core/services/relay/evm/codec.go @@ -0,0 +1,130 @@ +package evm + +import ( + "encoding/json" + "fmt" + "math/big" + "reflect" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/mitchellh/mapstructure" + + "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" +) + +// decodeAccountHook allows strings to be converted to [32]byte allowing config to represent them as 0x... +// BigIntHook allows *big.Int to be represented as any integer type or a string and to go back to them. +// Useful for config, or if when a model may use a go type that isn't a *big.Int when Pack expects one. +// Eg: int32 in a go struct from a plugin could require a *big.Int in Pack for int24, if it fits, we shouldn't care. +// SliceToArrayVerifySizeHook verifies that slices have the correct size when converting to an array +// sizeVerifyBigIntHook allows our custom types that verify the number fits in the on-chain type to be converted as-if +// it was a *big.Int +var evmDecoderHooks = []mapstructure.DecodeHookFunc{decodeAccountHook, codec.BigIntHook, codec.SliceToArrayVerifySizeHook, sizeVerifyBigIntHook} + +// NewCodec creates a new [commontypes.RemoteCodec] for EVM. +// Note that names in the ABI are converted to Go names using [abi.ToCamelCase], +// this is per convention in [abi.MakeTopics], [abi.Arguments.Pack] etc. +// This allows names on-chain to be in go convention when generated. +// It means that if you need to use a [codec.Modifier] to reference a field +// you need to use the Go name instead of the name on-chain. +// eg: rename FooBar -> Bar, not foo_bar_ to Bar if the name on-chain is foo_bar_ +func NewCodec(conf types.CodecConfig) (commontypes.RemoteCodec, error) { + parsed := &parsedTypes{ + encoderDefs: map[string]types.CodecEntry{}, + decoderDefs: map[string]types.CodecEntry{}, + } + + for k, v := range conf.Configs { + args := abi.Arguments{} + if err := json.Unmarshal(([]byte)(v.TypeABI), &args); err != nil { + return nil, err + } + + mod, err := v.ModifierConfigs.ToModifier(evmDecoderHooks...) + if err != nil { + return nil, err + } + + item := types.NewCodecEntry(args, nil, mod) + if err = item.Init(); err != nil { + return nil, err + } + + parsed.encoderDefs[k] = item + parsed.decoderDefs[k] = item + } + + return parsed.toCodec() +} + +type evmCodec struct { + *encoder + *decoder + *parsedTypes +} + +func (c *evmCodec) CreateType(itemType string, forEncoding bool) (any, error) { + var itemTypes map[string]types.CodecEntry + if forEncoding { + itemTypes = c.encoderDefs + } else { + itemTypes = c.decoderDefs + } + + def, ok := itemTypes[itemType] + if !ok { + return nil, fmt.Errorf("%w: cannot find type name %s", commontypes.ErrInvalidType, itemType) + } + + return reflect.New(def.CheckedType()).Interface(), nil +} + +var bigIntType = reflect.TypeOf((*big.Int)(nil)) + +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()) + } + + if !to.Implements(types.SizedBigIntType()) { + return data, nil + } + + var err error + data, err = codec.BigIntHook(from, bigIntType, data) + if err != nil { + return nil, err + } + + bi, ok := data.(*big.Int) + if !ok { + return data, nil + } + + converted := reflect.ValueOf(bi).Convert(to).Interface().(types.SizedBigInt) + return converted, converted.Verify() +} + +func decodeAccountHook(from, to reflect.Type, data any) (any, error) { + if from.Kind() == reflect.String && to == reflect.TypeOf(common.Address{}) { + decoded, err := hexutil.Decode(data.(string)) + if err != nil { + return nil, fmt.Errorf("%w: %w", commontypes.ErrInvalidType, err) + } else if len(decoded) != common.AddressLength { + return nil, fmt.Errorf( + "%w: wrong number size for address expected %v got %v", + commontypes.ErrSliceWrongLen, + common.AddressLength, len(decoded)) + } + return common.Address(decoded), nil + } + return data, nil +} diff --git a/core/services/relay/evm/codec_fuzz_test.go b/core/services/relay/evm/codec_fuzz_test.go new file mode 100644 index 00000000000..5870e9d77ad --- /dev/null +++ b/core/services/relay/evm/codec_fuzz_test.go @@ -0,0 +1,12 @@ +package evm_test + +import ( + "testing" + + "github.com/smartcontractkit/chainlink-common/pkg/types/interfacetests" +) + +func FuzzCodec(f *testing.F) { + tester := &codecInterfaceTester{} + interfacetests.RunCodecInterfaceFuzzTests(f, tester) +} diff --git a/core/services/relay/evm/codec_test.go b/core/services/relay/evm/codec_test.go new file mode 100644 index 00000000000..b13051cb010 --- /dev/null +++ b/core/services/relay/evm/codec_test.go @@ -0,0 +1,210 @@ +package evm_test + +import ( + "encoding/json" + "testing" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common/hexutil" + ocr2types "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink-common/pkg/codec" + + looptestutils "github.com/smartcontractkit/chainlink-common/pkg/loop/testutils" //nolint common practice to import test mods with . + commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" + . "github.com/smartcontractkit/chainlink-common/pkg/types/interfacetests" //nolint common practice to import test mods with . + + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/chain_reader_example" + "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/types" +) + +const anyExtraValue = 3 + +func TestCodec(t *testing.T) { + tester := &codecInterfaceTester{} + RunCodecInterfaceTests(t, tester) + RunCodecInterfaceTests(t, looptestutils.WrapCodecTesterForLoop(tester)) + + anyN := 10 + c := tester.GetCodec(t) + t.Run("GetMaxEncodingSize delegates to GetMaxSize", func(t *testing.T) { + actual, err := c.GetMaxEncodingSize(testutils.Context(t), anyN, sizeItemType) + assert.NoError(t, err) + + expected, err := types.GetMaxSize(anyN, parseDefs(t)[sizeItemType]) + require.NoError(t, err) + assert.Equal(t, expected, actual) + }) + + t.Run("GetMaxDecodingSize delegates to GetMaxSize", func(t *testing.T) { + actual, err := c.GetMaxDecodingSize(testutils.Context(t), anyN, sizeItemType) + assert.NoError(t, err) + + expected, err := types.GetMaxSize(anyN, parseDefs(t)[sizeItemType]) + require.NoError(t, err) + assert.Equal(t, expected, actual) + }) +} + +type codecInterfaceTester struct{} + +func (it *codecInterfaceTester) Setup(_ *testing.T) {} + +func (it *codecInterfaceTester) GetAccountBytes(i int) []byte { + account := [20]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10} + // fuzz tests can make -ve numbers + if i < 0 { + i = -i + } + account[i%20] += byte(i) + account[(i+3)%20] += byte(i + 3) + return account[:] +} + +func (it *codecInterfaceTester) EncodeFields(t *testing.T, request *EncodeRequest) []byte { + if request.TestOn == TestItemType { + return encodeFieldsOnItem(t, request) + } + + return encodeFieldsOnSliceOrArray(t, request) +} + +func (it *codecInterfaceTester) GetCodec(t *testing.T) commontypes.Codec { + codecConfig := types.CodecConfig{Configs: map[string]types.ChainCodecConfig{}} + testStruct := CreateTestStruct(0, it) + for k, v := range codecDefs { + defBytes, err := json.Marshal(v) + require.NoError(t, err) + entry := codecConfig.Configs[k] + entry.TypeABI = string(defBytes) + + if k != sizeItemType && k != NilType { + entry.ModifierConfigs = codec.ModifiersConfig{ + &codec.RenameModifierConfig{Fields: map[string]string{"NestedStruct.Inner.IntVal": "I"}}, + } + } + + if k == TestItemWithConfigExtra { + hardCode := &codec.HardCodeModifierConfig{ + OnChainValues: map[string]any{ + "BigField": testStruct.BigField.String(), + "Account": hexutil.Encode(testStruct.Account), + }, + OffChainValues: map[string]any{"ExtraField": anyExtraValue}, + } + entry.ModifierConfigs = append(entry.ModifierConfigs, hardCode) + } + codecConfig.Configs[k] = entry + } + + c, err := evm.NewCodec(codecConfig) + require.NoError(t, err) + return c +} + +func (it *codecInterfaceTester) IncludeArrayEncodingSizeEnforcement() bool { + return true +} +func (it *codecInterfaceTester) Name() string { + return "EVM" +} + +func encodeFieldsOnItem(t *testing.T, request *EncodeRequest) ocr2types.Report { + return packArgs(t, argsFromTestStruct(request.TestStructs[0]), parseDefs(t)[TestItemType], request) +} + +func encodeFieldsOnSliceOrArray(t *testing.T, request *EncodeRequest) []byte { + oargs := parseDefs(t)[request.TestOn] + args := make([]any, 1) + + switch request.TestOn { + case TestItemArray1Type: + args[0] = [1]chain_reader_example.TestStruct{toInternalType(request.TestStructs[0])} + case TestItemArray2Type: + args[0] = [2]chain_reader_example.TestStruct{toInternalType(request.TestStructs[0]), toInternalType(request.TestStructs[1])} + default: + tmp := make([]chain_reader_example.TestStruct, len(request.TestStructs)) + for i, ts := range request.TestStructs { + tmp[i] = toInternalType(ts) + } + args[0] = tmp + } + + return packArgs(t, args, oargs, request) +} + +func packArgs(t *testing.T, allArgs []any, oargs abi.Arguments, request *EncodeRequest) []byte { + // extra capacity in case we add an argument + args := make(abi.Arguments, len(oargs), len(oargs)+1) + copy(args, oargs) + // decoding has extra field to decode + if request.ExtraField { + fakeType, err := abi.NewType("int32", "", []abi.ArgumentMarshaling{}) + require.NoError(t, err) + args = append(args, abi.Argument{Name: "FakeField", Type: fakeType}) + allArgs = append(allArgs, 11) + } + + if request.MissingField { + args = args[1:] //nolint we know it's non-zero len + allArgs = allArgs[1:] //nolint we know it's non-zero len + } + + bytes, err := args.Pack(allArgs...) + require.NoError(t, err) + return bytes +} + +var inner = []abi.ArgumentMarshaling{ + {Name: "IntVal", Type: "int64"}, + {Name: "S", Type: "string"}, +} + +var nested = []abi.ArgumentMarshaling{ + {Name: "FixedBytes", Type: "bytes2"}, + {Name: "Inner", Type: "tuple", Components: inner}, +} + +var ts = []abi.ArgumentMarshaling{ + {Name: "Field", Type: "int32"}, + {Name: "DifferentField", Type: "string"}, + {Name: "OracleId", Type: "uint8"}, + {Name: "OracleIds", Type: "uint8[32]"}, + {Name: "Account", Type: "address"}, + {Name: "Accounts", Type: "address[]"}, + {Name: "BigField", Type: "int192"}, + {Name: "NestedStruct", Type: "tuple", Components: nested}, +} + +const sizeItemType = "item for size" + +var codecDefs = map[string][]abi.ArgumentMarshaling{ + TestItemType: ts, + TestItemSliceType: { + {Name: "", Type: "tuple[]", Components: ts}, + }, + TestItemArray1Type: { + {Name: "", Type: "tuple[1]", Components: ts}, + }, + TestItemArray2Type: { + {Name: "", Type: "tuple[2]", Components: ts}, + }, + sizeItemType: { + {Name: "Stuff", Type: "int256[]"}, + {Name: "OtherStuff", Type: "int256"}, + }, + TestItemWithConfigExtra: ts, + NilType: {}, +} + +func parseDefs(t *testing.T) map[string]abi.Arguments { + bytes, err := json.Marshal(codecDefs) + require.NoError(t, err) + var results map[string]abi.Arguments + require.NoError(t, json.Unmarshal(bytes, &results)) + return results +} diff --git a/core/services/relay/evm/decoder.go b/core/services/relay/evm/decoder.go new file mode 100644 index 00000000000..f16875d80f6 --- /dev/null +++ b/core/services/relay/evm/decoder.go @@ -0,0 +1,102 @@ +package evm + +import ( + "context" + "fmt" + "reflect" + + "github.com/mitchellh/mapstructure" + + commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" + + "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/types" +) + +type decoder struct { + Definitions map[string]types.CodecEntry +} + +var _ commontypes.Decoder = &decoder{} + +func (m *decoder) Decode(_ context.Context, raw []byte, into any, itemType string) error { + info, ok := m.Definitions[itemType] + if !ok { + return fmt.Errorf("%w: cannot find definition for %s", commontypes.ErrInvalidType, itemType) + } + + decode, err := extractDecoding(info, raw) + if err != nil { + return err + } + + rDecode := reflect.ValueOf(decode) + switch rDecode.Kind() { + case reflect.Array: + return m.decodeArray(into, rDecode) + case reflect.Slice: + iInto := reflect.Indirect(reflect.ValueOf(into)) + length := rDecode.Len() + iInto.Set(reflect.MakeSlice(iInto.Type(), length, length)) + return setElements(length, rDecode, iInto) + default: + return mapstructureDecode(decode, into) + } +} + +func (m *decoder) decodeArray(into any, rDecode reflect.Value) error { + iInto := reflect.Indirect(reflect.ValueOf(into)) + length := rDecode.Len() + if length != iInto.Len() { + return commontypes.ErrSliceWrongLen + } + iInto.Set(reflect.New(iInto.Type()).Elem()) + return setElements(length, rDecode, iInto) +} + +func (m *decoder) GetMaxDecodingSize(_ context.Context, n int, itemType string) (int, error) { + entry, ok := m.Definitions[itemType] + if !ok { + return 0, fmt.Errorf("%w: nil entry", commontypes.ErrInvalidType) + } + return entry.GetMaxSize(n) +} + +func extractDecoding(info types.CodecEntry, raw []byte) (any, error) { + unpacked := map[string]any{} + args := info.Args() + if err := args.UnpackIntoMap(unpacked, raw); err != nil { + return nil, fmt.Errorf("%w: %w: for args %#v", commontypes.ErrInvalidEncoding, err, args) + } + var decode any = unpacked + + if noName, ok := unpacked[""]; ok { + decode = noName + } + return decode, nil +} + +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 { + return err + } + } + + return nil +} + +func mapstructureDecode(src, dest any) error { + mDecoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ + DecodeHook: mapstructure.ComposeDecodeHookFunc(evmDecoderHooks...), + Result: dest, + Squash: true, + }) + if err != nil { + return fmt.Errorf("%w: %w", commontypes.ErrInvalidType, err) + } + + if err = mDecoder.Decode(src); err != nil { + return fmt.Errorf("%w: %w", commontypes.ErrInvalidType, err) + } + return nil +} diff --git a/core/services/relay/evm/encoder.go b/core/services/relay/evm/encoder.go new file mode 100644 index 00000000000..ae60e4ab35a --- /dev/null +++ b/core/services/relay/evm/encoder.go @@ -0,0 +1,145 @@ +package evm + +import ( + "context" + "fmt" + "reflect" + + commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" + + "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/types" +) + +type encoder struct { + Definitions map[string]types.CodecEntry +} + +var _ commontypes.Encoder = &encoder{} + +func (e *encoder) Encode(_ context.Context, item any, itemType string) (res []byte, err error) { + // nil values can cause abi.Arguments.Pack to panic. + defer func() { + if r := recover(); r != nil { + res = nil + err = fmt.Errorf("%w: cannot encode type", commontypes.ErrInvalidType) + } + }() + info, ok := e.Definitions[itemType] + if !ok { + return nil, fmt.Errorf("%w: cannot find definition for %s", commontypes.ErrInvalidType, itemType) + } + + if len(info.Args()) == 0 { + return info.EncodingPrefix(), nil + } else if item == nil { + return nil, fmt.Errorf("%w: cannot encode nil value for %s", commontypes.ErrInvalidType, itemType) + } + + return encode(reflect.ValueOf(item), info) +} + +func (e *encoder) GetMaxEncodingSize(_ context.Context, n int, itemType string) (int, error) { + entry, ok := e.Definitions[itemType] + if !ok { + return 0, fmt.Errorf("%w: nil entry", commontypes.ErrInvalidType) + } + return entry.GetMaxSize(n) +} + +func encode(item reflect.Value, info types.CodecEntry) ([]byte, error) { + for item.Kind() == reflect.Pointer { + item = reflect.Indirect(item) + } + switch item.Kind() { + case reflect.Array, reflect.Slice: + 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) + if err != nil { + return nil, err + } + return pack(info, values...) + default: + return nil, fmt.Errorf("%w: cannot encode kind %v", commontypes.ErrInvalidType, item.Kind()) + } +} + +func representArray(item reflect.Value, info types.CodecEntry) (any, error) { + length := item.Len() + checkedType := info.CheckedType() + checked := reflect.New(checkedType) + iChecked := reflect.Indirect(checked) + switch checkedType.Kind() { + case reflect.Array: + if checkedType.Len() != length { + return nil, commontypes.ErrSliceWrongLen + } + case reflect.Slice: + iChecked.Set(reflect.MakeSlice(checkedType, length, length)) + default: + return nil, fmt.Errorf("%w: cannot encode %v as array", commontypes.ErrInvalidType, checkedType.Kind()) + } + + checkedElm := checkedType.Elem() + for i := 0; i < length; i++ { + tmp := reflect.New(checkedElm) + if err := mapstructureDecode(item.Index(i).Interface(), tmp.Interface()); err != nil { + return nil, err + } + iChecked.Index(i).Set(tmp.Elem()) + } + native, err := info.ToNative(checked) + if err != nil { + return nil, err + } + + return native.Elem().Interface(), nil +} + +func unrollItem(item reflect.Value, info types.CodecEntry) ([]any, error) { + checkedType := info.CheckedType() + if item.CanAddr() { + item = item.Addr() + } + + if item.Type() == reflect.PointerTo(checkedType) { + var err error + if item, err = info.ToNative(item); err != nil { + return nil, err + } + } else if !info.IsNativePointer(item.Type()) { + var err error + checked := reflect.New(checkedType) + if err = mapstructureDecode(item.Interface(), checked.Interface()); err != nil { + return nil, err + } + if item, err = info.ToNative(checked); err != nil { + return nil, err + } + } + + item = reflect.Indirect(item) + length := item.NumField() + values := make([]any, length) + iType := item.Type() + for i := 0; i < length; i++ { + if iType.Field(i).IsExported() { + values[i] = item.Field(i).Interface() + } + } + return values, nil +} + +func pack(info types.CodecEntry, values ...any) ([]byte, error) { + bytes, err := info.Args().Pack(values...) + if err != nil { + return nil, fmt.Errorf("%w: %w", commontypes.ErrInvalidType, err) + } + + withPrefix := info.EncodingPrefix() + return append(withPrefix, bytes...), nil +} diff --git a/core/services/relay/evm/event_binding.go b/core/services/relay/evm/event_binding.go new file mode 100644 index 00000000000..85028853c79 --- /dev/null +++ b/core/services/relay/evm/event_binding.go @@ -0,0 +1,272 @@ +package evm + +import ( + "context" + "fmt" + "reflect" + "strings" + "sync" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + + "github.com/smartcontractkit/chainlink-common/pkg/codec" + 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" + "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/types" +) + +type eventBinding struct { + address common.Address + contractName string + eventName string + lp logpoller.LogPoller + hash common.Hash + codec commontypes.RemoteCodec + pending bool + bound bool + registerCalled bool + lock sync.Mutex + inputInfo types.CodecEntry + inputModifier codec.Modifier + topicInfo types.CodecEntry + // used to allow Register and Unregister to be unique in case two bindings have the same event. + // otherwise, if one unregisters, it'll unregister both with the LogPoller. + id string +} + +var _ readBinding = &eventBinding{} + +func (e *eventBinding) SetCodec(codec commontypes.RemoteCodec) { + e.codec = codec +} + +func (e *eventBinding) Register() error { + e.lock.Lock() + defer e.lock.Unlock() + + e.registerCalled = true + if !e.bound || e.lp.HasFilter(e.id) { + return nil + } + + if err := e.lp.RegisterFilter(logpoller.Filter{ + Name: e.id, + EventSigs: evmtypes.HashArray{e.hash}, + Addresses: evmtypes.AddressArray{e.address}, + }); err != nil { + return fmt.Errorf("%w: %w", commontypes.ErrInternal, err) + } + return nil +} + +func (e *eventBinding) Unregister() error { + e.lock.Lock() + defer e.lock.Unlock() + + if !e.lp.HasFilter(e.id) { + return nil + } + + if err := e.lp.UnregisterFilter(e.id); err != nil { + return fmt.Errorf("%w: %w", commontypes.ErrInternal, err) + } + return nil +} + +func (e *eventBinding) GetLatestValue(ctx context.Context, params, into any) error { + if !e.bound { + return fmt.Errorf("%w: event not bound", commontypes.ErrInvalidType) + } + + confs := logpoller.Finalized + if e.pending { + confs = logpoller.Unconfirmed + } + + if len(e.inputInfo.Args()) == 0 { + return e.getLatestValueWithoutFilters(ctx, confs, into) + } + + return e.getLatestValueWithFilters(ctx, confs, params, into) +} + +func (e *eventBinding) Bind(binding commontypes.BoundContract) error { + if err := e.Unregister(); err != nil { + return err + } + + e.address = common.HexToAddress(binding.Address) + e.pending = binding.Pending + e.bound = true + + if e.registerCalled { + return e.Register() + } + return nil +} + +func (e *eventBinding) getLatestValueWithoutFilters(ctx context.Context, confs logpoller.Confirmations, into any) error { + log, err := e.lp.LatestLogByEventSigWithConfs(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 logpoller.Confirmations, params, into any) error { + offChain, err := e.convertToOffChainType(params) + if err != nil { + return err + } + + checkedParams, err := e.inputModifier.TransformToOnChain(offChain, "" /* unused */) + if err != nil { + return err + } + + nativeParams, err := e.inputInfo.ToNative(reflect.ValueOf(checkedParams)) + if err != nil { + return err + } + + filtersAndIndices, err := e.encodeParams(nativeParams) + if err != nil { + return err + } + + fai := filtersAndIndices[0] + remainingFilters := filtersAndIndices[1:] + + logs, err := e.lp.IndexedLogs(e.hash, e.address, 1, []common.Hash{fai}, confs) + 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 e.decodeLog(ctx, logToUse, into) +} + +func (e *eventBinding) convertToOffChainType(params any) (any, error) { + itemType := wrapItemType(e.contractName, e.eventName, true) + offChain, err := e.codec.CreateType(itemType, true) + if err != nil { + return nil, err + } + + if err = mapstructureDecode(params, offChain); err != nil { + return nil, err + } + + return offChain, nil +} + +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 (e *eventBinding) encodeParams(item reflect.Value) ([]common.Hash, error) { + for item.Kind() == reflect.Pointer { + item = reflect.Indirect(item) + } + + var topics []any + switch item.Kind() { + case reflect.Array, reflect.Slice: + native, err := representArray(item, e.inputInfo) + if err != nil { + return nil, err + } + topics = []any{native} + case reflect.Struct, reflect.Map: + var err error + if topics, err = unrollItem(item, e.inputInfo); err != nil { + return nil, err + } + default: + return nil, fmt.Errorf("%w: cannot encode kind %v", commontypes.ErrInvalidType, item.Kind()) + } + + hashes, err := abi.MakeTopics(topics) + 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 { + dataType := wrapItemType(e.contractName, e.eventName, false) + if err := e.codec.Decode(ctx, log.Data, into, dataType); err != nil { + return err + } + + topics := make([]common.Hash, len(e.topicInfo.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.topicInfo.Args(), topics); err != nil { + return fmt.Errorf("%w: %w", commontypes.ErrInvalidType, err) + } + + return mapstructureDecode(topicsInto, into) +} + +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 633ca69ea67..34d353c48d4 100644 --- a/core/services/relay/evm/evm.go +++ b/core/services/relay/evm/evm.go @@ -52,6 +52,7 @@ type Relayer struct { mercuryPool wsrpc.Pool pgCfg pg.QConfig chainReader commontypes.ChainReader + codec commontypes.Codec } type CSAETHKeystore interface { @@ -183,7 +184,7 @@ func (r *Relayer) NewMercuryProvider(rargs commontypes.RelayArgs, pargs commonty } transmitter := mercury.NewTransmitter(lggr, cw.ContractConfigTracker(), client, privKey.PublicKey, rargs.JobID, *relayConfig.FeedID, r.db, r.pgCfg, transmitterCodec) - return NewMercuryProvider(cw, r.chainReader, NewMercuryChainReader(r.chain.HeadTracker()), transmitter, reportCodecV1, reportCodecV2, reportCodecV3, lggr), nil + return NewMercuryProvider(cw, r.chainReader, r.codec, NewMercuryChainReader(r.chain.HeadTracker()), transmitter, reportCodecV1, reportCodecV2, reportCodecV3, lggr), nil } func (r *Relayer) NewFunctionsProvider(rargs commontypes.RelayArgs, pargs commontypes.PluginArgs) (commontypes.FunctionsProvider, error) { @@ -483,9 +484,14 @@ func (r *Relayer) NewMedianProvider(rargs commontypes.RelayArgs, pargs commontyp } // allow fallback until chain reader is default and median contract is removed, but still log just in case - var chainReaderService commontypes.ChainReader + var chainReaderService ChainReaderService if relayConfig.ChainReader != nil { - if chainReaderService, err = NewChainReaderService(lggr, r.chain.LogPoller(), contractID, *relayConfig.ChainReader); err != nil { + if chainReaderService, err = NewChainReaderService(lggr, r.chain.LogPoller(), r.chain, *relayConfig.ChainReader); err != nil { + return nil, err + } + + boundContracts := []commontypes.BoundContract{{Name: "median", Pending: true, Address: contractID.String()}} + if err = chainReaderService.Bind(context.Background(), boundContracts); err != nil { return nil, err } } else { @@ -493,6 +499,15 @@ func (r *Relayer) NewMedianProvider(rargs commontypes.RelayArgs, pargs commontyp } medianProvider.chainReader = chainReaderService + if relayConfig.Codec != nil { + medianProvider.codec, err = NewCodec(*relayConfig.Codec) + if err != nil { + return nil, err + } + } else { + lggr.Info("Codec missing from RelayConfig; falling back to internal MedianContract") + } + return &medianProvider, nil } @@ -511,14 +526,20 @@ type medianProvider struct { contractTransmitter ContractTransmitter reportCodec median.ReportCodec medianContract *medianContract - chainReader commontypes.ChainReader + chainReader ChainReaderService + codec commontypes.Codec ms services.MultiStart } func (p *medianProvider) Name() string { return p.lggr.Name() } func (p *medianProvider) Start(ctx context.Context) error { - return p.ms.Start(ctx, p.configWatcher, p.contractTransmitter, p.medianContract) + srvcs := []services.StartClose{p.configWatcher, p.contractTransmitter, p.medianContract} + if p.chainReader != nil { + srvcs = append(srvcs, p.chainReader) + } + + return p.ms.Start(ctx, srvcs...) } func (p *medianProvider) Close() error { return p.ms.Close() } @@ -560,3 +581,7 @@ func (p *medianProvider) ContractConfigTracker() ocrtypes.ContractConfigTracker func (p *medianProvider) ChainReader() commontypes.ChainReader { return p.chainReader } + +func (p *medianProvider) Codec() commontypes.Codec { + return p.codec +} diff --git a/core/services/relay/evm/functions.go b/core/services/relay/evm/functions.go index f1d652fd6f8..b957ab56f3b 100644 --- a/core/services/relay/evm/functions.go +++ b/core/services/relay/evm/functions.go @@ -90,6 +90,10 @@ func (p *functionsProvider) ChainReader() commontypes.ChainReader { return nil } +func (p *functionsProvider) Codec() commontypes.Codec { + return nil +} + func NewFunctionsProvider(chain legacyevm.Chain, rargs commontypes.RelayArgs, pargs commontypes.PluginArgs, lggr logger.Logger, ethKeystore keystore.Eth, pluginType functionsRelay.FunctionsPluginType) (evmRelayTypes.FunctionsProvider, error) { relayOpts := evmRelayTypes.NewRelayOpts(rargs) relayConfig, err := relayOpts.RelayConfig() diff --git a/core/services/relay/evm/mercury/v1/data_source_test.go b/core/services/relay/evm/mercury/v1/data_source_test.go index e02efd8d9a4..e0769fe5b64 100644 --- a/core/services/relay/evm/mercury/v1/data_source_test.go +++ b/core/services/relay/evm/mercury/v1/data_source_test.go @@ -432,7 +432,6 @@ func TestMercury_SetLatestBlocks(t *testing.T) { headTracker := commonmocks.NewHeadTracker[*evmtypes.Head, common.Hash](t) // This can happen in some cases e.g. RPC node is offline headTracker.On("LatestChain").Return((*evmtypes.Head)(nil)) - ds.mercuryChainReader = evm.NewChainReader(headTracker) obs := v1.Observation{} err := ds.setLatestBlocks(testutils.Context(t), &obs) diff --git a/core/services/relay/evm/mercury_provider.go b/core/services/relay/evm/mercury_provider.go index 2ff882efa6b..d9858ac64c3 100644 --- a/core/services/relay/evm/mercury_provider.go +++ b/core/services/relay/evm/mercury_provider.go @@ -8,6 +8,7 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/services" commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" + mercurytypes "github.com/smartcontractkit/chainlink-common/pkg/types/mercury" v1 "github.com/smartcontractkit/chainlink-common/pkg/types/mercury/v1" v2 "github.com/smartcontractkit/chainlink-common/pkg/types/mercury/v2" @@ -24,6 +25,7 @@ var _ commontypes.MercuryProvider = (*mercuryProvider)(nil) type mercuryProvider struct { configWatcher *configWatcher chainReader commontypes.ChainReader + codec commontypes.Codec transmitter evmmercury.Transmitter reportCodecV1 v1.ReportCodec reportCodecV2 v2.ReportCodec @@ -36,6 +38,7 @@ type mercuryProvider struct { func NewMercuryProvider( configWatcher *configWatcher, chainReader commontypes.ChainReader, + codec commontypes.Codec, mercuryChainReader mercurytypes.ChainReader, transmitter evmmercury.Transmitter, reportCodecV1 v1.ReportCodec, @@ -46,6 +49,7 @@ func NewMercuryProvider( return &mercuryProvider{ configWatcher, chainReader, + codec, transmitter, reportCodecV1, reportCodecV2, @@ -83,6 +87,10 @@ func (p *mercuryProvider) MercuryChainReader() mercurytypes.ChainReader { return p.mercuryChainReader } +func (p *mercuryProvider) Codec() commontypes.Codec { + return p.codec +} + func (p *mercuryProvider) ContractConfigTracker() ocrtypes.ContractConfigTracker { return p.configWatcher.ContractConfigTracker() } diff --git a/core/services/relay/evm/method_binding.go b/core/services/relay/evm/method_binding.go new file mode 100644 index 00000000000..c5e10cce1c1 --- /dev/null +++ b/core/services/relay/evm/method_binding.go @@ -0,0 +1,66 @@ +package evm + +import ( + "context" + "fmt" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/common" + + commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" + + evmclient "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" +) + +type methodBinding struct { + address common.Address + contractName string + method string + client evmclient.Client + codec commontypes.Codec + bound bool +} + +var _ readBinding = &methodBinding{} + +func (m *methodBinding) SetCodec(codec commontypes.RemoteCodec) { + m.codec = codec +} + +func (m *methodBinding) Register() error { + return nil +} + +func (m *methodBinding) Unregister() error { + return nil +} + +func (m *methodBinding) GetLatestValue(ctx context.Context, params, returnValue 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, + } + + bytes, err := m.client.CallContract(ctx, callMsg, nil) + if err != nil { + return fmt.Errorf("%w: %w", commontypes.ErrInternal, err) + } + + return m.codec.Decode(ctx, bytes, returnValue, wrapItemType(m.contractName, m.method, false)) +} + +func (m *methodBinding) Bind(binding commontypes.BoundContract) error { + m.address = common.HexToAddress(binding.Address) + m.bound = true + return nil +} diff --git a/core/services/relay/evm/ocr2keeper.go b/core/services/relay/evm/ocr2keeper.go index 34daf55b835..fd1fdb70480 100644 --- a/core/services/relay/evm/ocr2keeper.go +++ b/core/services/relay/evm/ocr2keeper.go @@ -206,6 +206,10 @@ func (c *ocr2keeperProvider) ChainReader() commontypes.ChainReader { return nil } +func (c *ocr2keeperProvider) Codec() commontypes.Codec { + return nil +} + func newOCR2KeeperConfigProvider(lggr logger.Logger, chain legacyevm.Chain, rargs commontypes.RelayArgs) (*configWatcher, error) { var relayConfig types.RelayConfig err := json.Unmarshal(rargs.RelayConfig, &relayConfig) diff --git a/core/services/relay/evm/ocr2vrf.go b/core/services/relay/evm/ocr2vrf.go index 1e05f89d9de..d300c71fbef 100644 --- a/core/services/relay/evm/ocr2vrf.go +++ b/core/services/relay/evm/ocr2vrf.go @@ -114,6 +114,10 @@ func (c *dkgProvider) ChainReader() commontypes.ChainReader { return nil } +func (c *dkgProvider) Codec() commontypes.Codec { + return nil +} + type ocr2vrfProvider struct { *configWatcher contractTransmitter ContractTransmitter @@ -127,6 +131,10 @@ func (c *ocr2vrfProvider) ChainReader() commontypes.ChainReader { return nil } +func (c *ocr2vrfProvider) Codec() commontypes.Codec { + return nil +} + func newOCR2VRFConfigProvider(lggr logger.Logger, chain legacyevm.Chain, rargs commontypes.RelayArgs) (*configWatcher, error) { var relayConfig types.RelayConfig err := json.Unmarshal(rargs.RelayConfig, &relayConfig) diff --git a/core/services/relay/evm/parsed_types.go b/core/services/relay/evm/parsed_types.go new file mode 100644 index 00000000000..168057e998d --- /dev/null +++ b/core/services/relay/evm/parsed_types.go @@ -0,0 +1,50 @@ +package evm + +import ( + "fmt" + "reflect" + + "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" +) + +type parsedTypes struct { + encoderDefs map[string]types.CodecEntry + decoderDefs map[string]types.CodecEntry +} + +func (parsed *parsedTypes) toCodec() (commontypes.RemoteCodec, error) { + modByTypeName := map[string]codec.Modifier{} + if err := addEntries(parsed.encoderDefs, modByTypeName); err != nil { + return nil, err + } + if err := addEntries(parsed.decoderDefs, modByTypeName); err != nil { + return nil, err + } + + mod, err := codec.NewByItemTypeModifier(modByTypeName) + if err != nil { + return nil, err + } + underlying := &evmCodec{ + encoder: &encoder{Definitions: parsed.encoderDefs}, + decoder: &decoder{Definitions: parsed.decoderDefs}, + parsedTypes: parsed, + } + return codec.NewModifierCodec(underlying, mod, evmDecoderHooks...) +} + +// 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 { + for k, def := range defs { + modByTypeName[k] = def.Modifier() + _, err := def.Modifier().RetypeToOffChain(reflect.PointerTo(def.CheckedType()), k) + if err != nil { + return fmt.Errorf("%w: cannot retype %v: %w", commontypes.ErrInvalidConfig, k, err) + } + } + return nil +} diff --git a/core/services/relay/evm/types/abi_types.go b/core/services/relay/evm/types/abi_types.go new file mode 100644 index 00000000000..34b12d885b4 --- /dev/null +++ b/core/services/relay/evm/types/abi_types.go @@ -0,0 +1,66 @@ +package types + +import ( + "reflect" + + "github.com/ethereum/go-ethereum/common" +) + +//go:generate go run ./gen/main.go + +var typeMap = map[string]*ABIEncodingType{ + "bool": { + native: reflect.TypeOf(true), + checked: reflect.TypeOf(true), + }, + "int8": { + native: reflect.TypeOf(int8(0)), + checked: reflect.TypeOf(int8(0)), + }, + "int16": { + native: reflect.TypeOf(int16(0)), + checked: reflect.TypeOf(int16(0)), + }, + "int32": { + native: reflect.TypeOf(int32(0)), + checked: reflect.TypeOf(int32(0)), + }, + "int64": { + native: reflect.TypeOf(int64(0)), + checked: reflect.TypeOf(int64(0)), + }, + "uint8": { + native: reflect.TypeOf(uint8(0)), + checked: reflect.TypeOf(uint8(0)), + }, + "uint16": { + native: reflect.TypeOf(uint16(0)), + checked: reflect.TypeOf(uint16(0)), + }, + "uint32": { + native: reflect.TypeOf(uint32(0)), + checked: reflect.TypeOf(uint32(0)), + }, + "uint64": { + native: reflect.TypeOf(uint64(0)), + checked: reflect.TypeOf(uint64(0)), + }, + "string": { + native: reflect.TypeOf(""), + checked: reflect.TypeOf(""), + }, + "address": { + native: reflect.TypeOf(common.Address{}), + checked: reflect.TypeOf(common.Address{}), + }, +} + +type ABIEncodingType struct { + native reflect.Type + checked reflect.Type +} + +func GetAbiEncodingType(name string) (*ABIEncodingType, bool) { + abiType, ok := typeMap[name] + return abiType, ok +} diff --git a/core/services/relay/evm/types/byte_types_gen.go b/core/services/relay/evm/types/byte_types_gen.go new file mode 100644 index 00000000000..cf8d15ccabc --- /dev/null +++ b/core/services/relay/evm/types/byte_types_gen.go @@ -0,0 +1,300 @@ +package types + +import "reflect" + +type bytes1 [1]byte + +func init() { + typeMap["bytes1"] = &ABIEncodingType{ + native: reflect.TypeOf([1]byte{}), + checked: reflect.TypeOf(bytes1{}), + } +} + +type bytes2 [2]byte + +func init() { + typeMap["bytes2"] = &ABIEncodingType{ + native: reflect.TypeOf([2]byte{}), + checked: reflect.TypeOf(bytes2{}), + } +} + +type bytes3 [3]byte + +func init() { + typeMap["bytes3"] = &ABIEncodingType{ + native: reflect.TypeOf([3]byte{}), + checked: reflect.TypeOf(bytes3{}), + } +} + +type bytes4 [4]byte + +func init() { + typeMap["bytes4"] = &ABIEncodingType{ + native: reflect.TypeOf([4]byte{}), + checked: reflect.TypeOf(bytes4{}), + } +} + +type bytes5 [5]byte + +func init() { + typeMap["bytes5"] = &ABIEncodingType{ + native: reflect.TypeOf([5]byte{}), + checked: reflect.TypeOf(bytes5{}), + } +} + +type bytes6 [6]byte + +func init() { + typeMap["bytes6"] = &ABIEncodingType{ + native: reflect.TypeOf([6]byte{}), + checked: reflect.TypeOf(bytes6{}), + } +} + +type bytes7 [7]byte + +func init() { + typeMap["bytes7"] = &ABIEncodingType{ + native: reflect.TypeOf([7]byte{}), + checked: reflect.TypeOf(bytes7{}), + } +} + +type bytes8 [8]byte + +func init() { + typeMap["bytes8"] = &ABIEncodingType{ + native: reflect.TypeOf([8]byte{}), + checked: reflect.TypeOf(bytes8{}), + } +} + +type bytes9 [9]byte + +func init() { + typeMap["bytes9"] = &ABIEncodingType{ + native: reflect.TypeOf([9]byte{}), + checked: reflect.TypeOf(bytes9{}), + } +} + +type bytes10 [10]byte + +func init() { + typeMap["bytes10"] = &ABIEncodingType{ + native: reflect.TypeOf([10]byte{}), + checked: reflect.TypeOf(bytes10{}), + } +} + +type bytes11 [11]byte + +func init() { + typeMap["bytes11"] = &ABIEncodingType{ + native: reflect.TypeOf([11]byte{}), + checked: reflect.TypeOf(bytes11{}), + } +} + +type bytes12 [12]byte + +func init() { + typeMap["bytes12"] = &ABIEncodingType{ + native: reflect.TypeOf([12]byte{}), + checked: reflect.TypeOf(bytes12{}), + } +} + +type bytes13 [13]byte + +func init() { + typeMap["bytes13"] = &ABIEncodingType{ + native: reflect.TypeOf([13]byte{}), + checked: reflect.TypeOf(bytes13{}), + } +} + +type bytes14 [14]byte + +func init() { + typeMap["bytes14"] = &ABIEncodingType{ + native: reflect.TypeOf([14]byte{}), + checked: reflect.TypeOf(bytes14{}), + } +} + +type bytes15 [15]byte + +func init() { + typeMap["bytes15"] = &ABIEncodingType{ + native: reflect.TypeOf([15]byte{}), + checked: reflect.TypeOf(bytes15{}), + } +} + +type bytes16 [16]byte + +func init() { + typeMap["bytes16"] = &ABIEncodingType{ + native: reflect.TypeOf([16]byte{}), + checked: reflect.TypeOf(bytes16{}), + } +} + +type bytes17 [17]byte + +func init() { + typeMap["bytes17"] = &ABIEncodingType{ + native: reflect.TypeOf([17]byte{}), + checked: reflect.TypeOf(bytes17{}), + } +} + +type bytes18 [18]byte + +func init() { + typeMap["bytes18"] = &ABIEncodingType{ + native: reflect.TypeOf([18]byte{}), + checked: reflect.TypeOf(bytes18{}), + } +} + +type bytes19 [19]byte + +func init() { + typeMap["bytes19"] = &ABIEncodingType{ + native: reflect.TypeOf([19]byte{}), + checked: reflect.TypeOf(bytes19{}), + } +} + +type bytes20 [20]byte + +func init() { + typeMap["bytes20"] = &ABIEncodingType{ + native: reflect.TypeOf([20]byte{}), + checked: reflect.TypeOf(bytes20{}), + } +} + +type bytes21 [21]byte + +func init() { + typeMap["bytes21"] = &ABIEncodingType{ + native: reflect.TypeOf([21]byte{}), + checked: reflect.TypeOf(bytes21{}), + } +} + +type bytes22 [22]byte + +func init() { + typeMap["bytes22"] = &ABIEncodingType{ + native: reflect.TypeOf([22]byte{}), + checked: reflect.TypeOf(bytes22{}), + } +} + +type bytes23 [23]byte + +func init() { + typeMap["bytes23"] = &ABIEncodingType{ + native: reflect.TypeOf([23]byte{}), + checked: reflect.TypeOf(bytes23{}), + } +} + +type bytes24 [24]byte + +func init() { + typeMap["bytes24"] = &ABIEncodingType{ + native: reflect.TypeOf([24]byte{}), + checked: reflect.TypeOf(bytes24{}), + } +} + +type bytes25 [25]byte + +func init() { + typeMap["bytes25"] = &ABIEncodingType{ + native: reflect.TypeOf([25]byte{}), + checked: reflect.TypeOf(bytes25{}), + } +} + +type bytes26 [26]byte + +func init() { + typeMap["bytes26"] = &ABIEncodingType{ + native: reflect.TypeOf([26]byte{}), + checked: reflect.TypeOf(bytes26{}), + } +} + +type bytes27 [27]byte + +func init() { + typeMap["bytes27"] = &ABIEncodingType{ + native: reflect.TypeOf([27]byte{}), + checked: reflect.TypeOf(bytes27{}), + } +} + +type bytes28 [28]byte + +func init() { + typeMap["bytes28"] = &ABIEncodingType{ + native: reflect.TypeOf([28]byte{}), + checked: reflect.TypeOf(bytes28{}), + } +} + +type bytes29 [29]byte + +func init() { + typeMap["bytes29"] = &ABIEncodingType{ + native: reflect.TypeOf([29]byte{}), + checked: reflect.TypeOf(bytes29{}), + } +} + +type bytes30 [30]byte + +func init() { + typeMap["bytes30"] = &ABIEncodingType{ + native: reflect.TypeOf([30]byte{}), + checked: reflect.TypeOf(bytes30{}), + } +} + +type bytes31 [31]byte + +func init() { + typeMap["bytes31"] = &ABIEncodingType{ + native: reflect.TypeOf([31]byte{}), + checked: reflect.TypeOf(bytes31{}), + } +} + +type bytes32 [32]byte + +func init() { + typeMap["bytes32"] = &ABIEncodingType{ + native: reflect.TypeOf([32]byte{}), + checked: reflect.TypeOf(bytes32{}), + } +} + +type bytes0 [0]byte + +func init() { + typeMap["bytes0"] = &ABIEncodingType{ + native: reflect.TypeOf([0]byte{}), + checked: reflect.TypeOf(bytes0{}), + } +} diff --git a/core/services/relay/evm/types/codec_entry.go b/core/services/relay/evm/types/codec_entry.go new file mode 100644 index 00000000000..70948ecd6b3 --- /dev/null +++ b/core/services/relay/evm/types/codec_entry.go @@ -0,0 +1,251 @@ +package types + +import ( + "fmt" + "reflect" + "strings" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + + "github.com/smartcontractkit/chainlink-common/pkg/codec" + + commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" +) + +// MaxTopicFields is three because the EVM has a max of four topics, but the first topic is always the event signature. +const MaxTopicFields = 3 + +type CodecEntry interface { + Init() error + Args() abi.Arguments + EncodingPrefix() []byte + GetMaxSize(n int) (int, error) + Modifier() codec.Modifier + + // CheckedType provides a type that can be used to decode into with type-safety around sizes of integers etc. + CheckedType() reflect.Type + + // ToNative converts a pointer to checked value into a pointer of a type to use with the go-ethereum ABI encoder + // Note that modification of the returned value will modify the original checked value and vice versa. + ToNative(checked reflect.Value) (reflect.Value, error) + + // IsNativePointer returns if the type is a pointer to the native type + IsNativePointer(item reflect.Type) bool +} + +func NewCodecEntry(args abi.Arguments, encodingPrefix []byte, mod codec.Modifier) CodecEntry { + if mod == nil { + mod = codec.MultiModifier{} + } + return &codecEntry{args: args, encodingPrefix: encodingPrefix, mod: mod} +} + +type codecEntry struct { + args abi.Arguments + encodingPrefix []byte + checkedType reflect.Type + nativeType reflect.Type + mod codec.Modifier +} + +func (entry *codecEntry) CheckedType() reflect.Type { + return entry.checkedType +} + +func (entry *codecEntry) NativeType() reflect.Type { + return entry.nativeType +} + +func (entry *codecEntry) ToNative(checked reflect.Value) (reflect.Value, error) { + if checked.Type() != reflect.PointerTo(entry.checkedType) { + return reflect.Value{}, fmt.Errorf("%w: checked type %v does not match expected type %v", commontypes.ErrInvalidType, reflect.TypeOf(checked), entry.checkedType) + } + + return reflect.NewAt(entry.nativeType, checked.UnsafePointer()), nil +} + +func (entry *codecEntry) IsNativePointer(item reflect.Type) bool { + return item == reflect.PointerTo(entry.nativeType) +} + +func (entry *codecEntry) Modifier() codec.Modifier { + return entry.mod +} + +func (entry *codecEntry) Args() abi.Arguments { + tmp := make(abi.Arguments, len(entry.args)) + copy(tmp, entry.args) + return tmp +} + +func (entry *codecEntry) EncodingPrefix() []byte { + tmp := make([]byte, len(entry.encodingPrefix)) + copy(tmp, entry.encodingPrefix) + return tmp +} + +func (entry *codecEntry) Init() error { + if entry.checkedType != nil { + return nil + } + + args := unwrapArgs(entry.args) + argLen := len(args) + native := make([]reflect.StructField, argLen) + checked := make([]reflect.StructField, argLen) + + // Single returns that aren't named will return that type + // whereas named parameters will return a struct with the fields + // Eg: function foo() returns (int256) ... will return a *big.Int for the native type + // function foo() returns (int256 i) ... will return a struct { I *big.Int } for the native type + // function foo() returns (int256 i1, int256 i2) ... will return a struct { I1 *big.Int, I2 *big.Int } for the native type + if len(args) == 1 && args[0].Name == "" { + nativeArg, checkedArg, err := getNativeAndCheckedTypesForArg(&args[0]) + if err != nil { + return err + } + entry.nativeType = nativeArg + entry.checkedType = checkedArg + return nil + } + + numIndices := 0 + seenNames := map[string]bool{} + for i, arg := range args { + if arg.Indexed { + if numIndices == MaxTopicFields { + return fmt.Errorf("%w: too many indexed arguments", commontypes.ErrInvalidConfig) + } + numIndices++ + } + + tmp := arg + nativeArg, checkedArg, err := getNativeAndCheckedTypesForArg(&tmp) + if err != nil { + return err + } + if len(arg.Name) == 0 { + return fmt.Errorf("%w: empty field names are not supported for multiple returns", commontypes.ErrInvalidType) + } + + name := strings.ToUpper(arg.Name[:1]) + arg.Name[1:] + if seenNames[name] { + return fmt.Errorf("%w: duplicate field name %s, after ToCamelCase", commontypes.ErrInvalidConfig, name) + } + seenNames[name] = true + native[i] = reflect.StructField{Name: name, Type: nativeArg} + checked[i] = reflect.StructField{Name: name, Type: checkedArg} + } + + entry.nativeType = reflect.StructOf(native) + entry.checkedType = reflect.StructOf(checked) + return nil +} + +func (entry *codecEntry) GetMaxSize(n int) (int, error) { + return GetMaxSize(n, entry.args) +} + +func unwrapArgs(args abi.Arguments) abi.Arguments { + // Unwrap an unnamed tuple so that callers don't need to wrap it + // Eg: If you have struct Foo { ... } and return an unnamed Foo, you should be able ot decode to a go Foo{} directly + if len(args) != 1 || args[0].Name != "" { + return args + } + + elms := args[0].Type.TupleElems + if len(elms) != 0 { + names := args[0].Type.TupleRawNames + args = make(abi.Arguments, len(elms)) + for i, elm := range elms { + args[i] = abi.Argument{ + Name: names[i], + Type: *elm, + } + } + } + return args +} + +func getNativeAndCheckedTypesForArg(arg *abi.Argument) (reflect.Type, reflect.Type, error) { + tmp := arg.Type + if arg.Indexed { + switch arg.Type.T { + case abi.StringTy: + return reflect.TypeOf(common.Hash{}), reflect.TypeOf(common.Hash{}), nil + case abi.ArrayTy: + u8, _ := GetAbiEncodingType("uint8") + if arg.Type.Elem.GetType() == u8.native { + return reflect.TypeOf(common.Hash{}), reflect.TypeOf(common.Hash{}), nil + } + fallthrough + case abi.SliceTy, abi.TupleTy, abi.FixedBytesTy, abi.FixedPointTy, abi.FunctionTy: + // https://github.com/ethereum/go-ethereum/blob/release/1.12/accounts/abi/topics.go#L78 + return nil, nil, fmt.Errorf("%w: unsupported indexed type: %v", commontypes.ErrInvalidConfig, arg.Type) + default: + } + } + + return getNativeAndCheckedTypes(&tmp) +} + +func getNativeAndCheckedTypes(curType *abi.Type) (reflect.Type, reflect.Type, error) { + converter := func(t reflect.Type) reflect.Type { return t } + for curType.Elem != nil { + prior := converter + switch curType.GetType().Kind() { + case reflect.Slice: + converter = func(t reflect.Type) reflect.Type { + return prior(reflect.SliceOf(t)) + } + curType = curType.Elem + case reflect.Array: + tmp := curType + converter = func(t reflect.Type) reflect.Type { + return prior(reflect.ArrayOf(tmp.Size, t)) + } + curType = curType.Elem + default: + return nil, nil, fmt.Errorf( + "%w: cannot create type for kind %v", commontypes.ErrInvalidType, curType.GetType().Kind()) + } + } + base, ok := GetAbiEncodingType(curType.String()) + if ok { + return converter(base.native), converter(base.checked), nil + } + + return createTupleType(curType, converter) +} + +func createTupleType(curType *abi.Type, converter func(reflect.Type) reflect.Type) (reflect.Type, reflect.Type, error) { + if len(curType.TupleElems) == 0 { + if curType.TupleType == nil { + return nil, nil, fmt.Errorf("%w: unsupported solitidy type: %v", commontypes.ErrInvalidType, curType.String()) + } + return curType.TupleType, curType.TupleType, nil + } + + // Create native type ourselves to assure that it'll always have the exact memory layout of checked types + // Otherwise, the "unsafe" casting that will be done to convert from checked to native won't be safe. + // At the time of writing, the way the TupleType is built it will be the same, but I don't want to rely on that + // If they ever add private fields for internal tracking + // or anything it would break us if we don't build the native type. + // As an example of how it could possibly change in the future, I've seen struct{} + // added with tags to the top of generated structs to allow metadata exploration. + nativeFields := make([]reflect.StructField, len(curType.TupleElems)) + checkedFields := make([]reflect.StructField, len(curType.TupleElems)) + for i, elm := range curType.TupleElems { + name := curType.TupleRawNames[i] + nativeFields[i].Name = name + checkedFields[i].Name = name + nativeArgType, checkedArgType, err := getNativeAndCheckedTypes(elm) + if err != nil { + return nil, nil, err + } + nativeFields[i].Type = nativeArgType + checkedFields[i].Type = checkedArgType + } + return converter(reflect.StructOf(nativeFields)), converter(reflect.StructOf(checkedFields)), nil +} diff --git a/core/services/relay/evm/types/codec_entry_test.go b/core/services/relay/evm/types/codec_entry_test.go new file mode 100644 index 00000000000..8cd5e661c9f --- /dev/null +++ b/core/services/relay/evm/types/codec_entry_test.go @@ -0,0 +1,284 @@ +package types + +import ( + "errors" + "math/big" + "reflect" + "testing" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink-common/pkg/codec" + + commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" +) + +func TestCodecEntry(t *testing.T) { + t.Run("basic types", func(t *testing.T) { + type1, err := abi.NewType("uint16", "", []abi.ArgumentMarshaling{}) + require.NoError(t, err) + type2, err := abi.NewType("string", "", []abi.ArgumentMarshaling{}) + require.NoError(t, err) + type3, err := abi.NewType("uint24", "", []abi.ArgumentMarshaling{}) + require.NoError(t, err) + type4, err := abi.NewType("int24", "", []abi.ArgumentMarshaling{}) + require.NoError(t, err) + args := abi.Arguments{ + {Name: "Field1", Type: type1}, + {Name: "Field2", Type: type2}, + {Name: "Field3", Type: type3}, + {Name: "Field4", Type: type4}, + } + entry := NewCodecEntry(args, nil, nil) + require.NoError(t, entry.Init()) + checked := reflect.New(entry.CheckedType()) + iChecked := reflect.Indirect(checked) + iChecked.FieldByName("Field1").Set(reflect.ValueOf(uint16(2))) + iChecked.FieldByName("Field2").Set(reflect.ValueOf("any string")) + + f3 := big.NewInt( /*2^24 - 1*/ 16777215) + setAndVerifyLimit(t, (*uint24)(f3), f3, iChecked.FieldByName("Field3")) + + f4 := big.NewInt( /*2^23 - 1*/ 8388607) + setAndVerifyLimit(t, (*int24)(f4), f4, iChecked.FieldByName("Field4")) + + rNative, err := entry.ToNative(checked) + require.NoError(t, err) + iNative := reflect.Indirect(rNative) + assert.Equal(t, iNative.Field(0).Interface(), iChecked.Field(0).Interface()) + assert.Equal(t, iNative.Field(1).Interface(), iChecked.Field(1).Interface()) + assert.Equal(t, iNative.Field(2).Interface(), f3) + assert.Equal(t, iNative.Field(3).Interface(), f4) + assertHaveSameStructureAndNames(t, iNative.Type(), entry.CheckedType()) + }) + + t.Run("tuples", func(t *testing.T) { + type1, err := abi.NewType("uint16", "", []abi.ArgumentMarshaling{}) + require.NoError(t, err) + tupleType, err := abi.NewType("tuple", "", []abi.ArgumentMarshaling{ + {Name: "Field3", Type: "uint24"}, + {Name: "Field4", Type: "int24"}, + }) + require.NoError(t, err) + args := abi.Arguments{ + {Name: "Field1", Type: type1}, + {Name: "Field2", Type: tupleType}, + } + entry := NewCodecEntry(args, nil, nil) + require.NoError(t, entry.Init()) + + checked := reflect.New(entry.CheckedType()) + iChecked := reflect.Indirect(checked) + iChecked.FieldByName("Field1").Set(reflect.ValueOf(uint16(2))) + f2 := iChecked.FieldByName("Field2") + f3 := big.NewInt( /*2^24 - 1*/ 16777215) + setAndVerifyLimit(t, (*uint24)(f3), f3, f2.FieldByName("Field3")) + f4 := big.NewInt( /*2^23 - 1*/ 8388607) + setAndVerifyLimit(t, (*int24)(f4), f4, f2.FieldByName("Field4")) + + native, err := entry.ToNative(checked) + require.NoError(t, err) + iNative := reflect.Indirect(native) + require.Equal(t, iNative.Field(0).Interface(), iChecked.Field(0).Interface()) + nF2 := iNative.Field(1) + assert.Equal(t, nF2.Field(0).Interface(), f3) + assert.Equal(t, nF2.Field(1).Interface(), f4) + assertHaveSameStructureAndNames(t, iNative.Type(), entry.CheckedType()) + }) + + t.Run("unwrapped types", func(t *testing.T) { + // This exists to allow you to decode single returned values without naming the parameter + wrappedTuple, err := abi.NewType("tuple", "", []abi.ArgumentMarshaling{ + {Name: "Field1", Type: "int16"}, + }) + require.NoError(t, err) + entry := NewCodecEntry(abi.Arguments{{Name: "", Type: wrappedTuple}}, nil, nil) + require.NoError(t, entry.Init()) + checked := reflect.New(entry.CheckedType()) + iChecked := reflect.Indirect(checked) + anyValue := int16(2) + iChecked.FieldByName("Field1").Set(reflect.ValueOf(anyValue)) + native, err := entry.ToNative(checked) + require.NoError(t, err) + iNative := reflect.Indirect(native) + assert.Equal(t, anyValue, iNative.FieldByName("Field1").Interface()) + assertHaveSameStructureAndNames(t, iNative.Type(), entry.CheckedType()) + }) + + t.Run("slice types", func(t *testing.T) { + type1, err := abi.NewType("int16[]", "", []abi.ArgumentMarshaling{}) + require.NoError(t, err) + entry := NewCodecEntry(abi.Arguments{{Name: "Field1", Type: type1}}, nil, nil) + + require.NoError(t, entry.Init()) + checked := reflect.New(entry.CheckedType()) + iChecked := reflect.Indirect(checked) + anySliceValue := []int16{2, 3} + iChecked.FieldByName("Field1").Set(reflect.ValueOf(anySliceValue)) + native, err := entry.ToNative(checked) + require.NoError(t, err) + iNative := reflect.Indirect(native) + assert.Equal(t, anySliceValue, iNative.FieldByName("Field1").Interface()) + assertHaveSameStructureAndNames(t, iNative.Type(), entry.CheckedType()) + }) + + t.Run("array types", func(t *testing.T) { + type1, err := abi.NewType("int16[3]", "", []abi.ArgumentMarshaling{}) + require.NoError(t, err) + entry := NewCodecEntry(abi.Arguments{{Name: "Field1", Type: type1}}, nil, nil) + require.NoError(t, entry.Init()) + checked := reflect.New(entry.CheckedType()) + iChecked := reflect.Indirect(checked) + anySliceValue := [3]int16{2, 3, 30} + iChecked.FieldByName("Field1").Set(reflect.ValueOf(anySliceValue)) + native, err := entry.ToNative(checked) + require.NoError(t, err) + iNative := reflect.Indirect(native) + assert.Equal(t, anySliceValue, iNative.FieldByName("Field1").Interface()) + }) + + t.Run("Not return values makes struct{}", func(t *testing.T) { + entry := NewCodecEntry(abi.Arguments{}, nil, nil) + require.NoError(t, entry.Init()) + assert.Equal(t, reflect.TypeOf(struct{}{}), entry.CheckedType()) + native, err := entry.ToNative(reflect.ValueOf(&struct{}{})) + require.NoError(t, err) + assert.Equal(t, &struct{}{}, native.Interface()) + }) + + t.Run("Address works", func(t *testing.T) { + address, err := abi.NewType("address", "", []abi.ArgumentMarshaling{}) + require.NoError(t, err) + entry := NewCodecEntry(abi.Arguments{{Name: "foo", Type: address}}, nil, nil) + require.NoError(t, entry.Init()) + + checked := reflect.New(entry.CheckedType()) + iChecked := reflect.Indirect(checked) + anyAddr := common.Address{1, 2, 3} + iChecked.FieldByName("Foo").Set(reflect.ValueOf(anyAddr)) + + native, err := entry.ToNative(checked) + require.NoError(t, err) + iNative := reflect.Indirect(native) + assert.Equal(t, anyAddr, iNative.FieldByName("Foo").Interface()) + assertHaveSameStructureAndNames(t, iNative.Type(), entry.CheckedType()) + }) + + t.Run("Multiple unnamed parameters are not supported", func(t *testing.T) { + anyType, err := abi.NewType("int16[3]", "", []abi.ArgumentMarshaling{}) + require.NoError(t, err) + entry := NewCodecEntry(abi.Arguments{{Name: "", Type: anyType}, {Name: "", Type: anyType}}, nil, nil) + assert.True(t, errors.Is(entry.Init(), commontypes.ErrInvalidType)) + }) + + t.Run("Multiple abi arguments with the same name returns an error", func(t *testing.T) { + anyType, err := abi.NewType("int16[3]", "", []abi.ArgumentMarshaling{}) + require.NoError(t, err) + entry := NewCodecEntry(abi.Arguments{{Name: "Name", Type: anyType}, {Name: "Name", Type: anyType}}, nil, nil) + assert.True(t, errors.Is(entry.Init(), commontypes.ErrInvalidConfig)) + }) + + t.Run("Indexed basic types leave their native and checked types as-is", func(t *testing.T) { + anyType, err := abi.NewType("int16", "", []abi.ArgumentMarshaling{}) + require.NoError(t, err) + entry := NewCodecEntry(abi.Arguments{{Name: "Name", Type: anyType, Indexed: true}}, nil, nil) + require.NoError(t, entry.Init()) + checkedField, ok := entry.CheckedType().FieldByName("Name") + require.True(t, ok) + assert.Equal(t, reflect.TypeOf(int16(0)), checkedField.Type) + native, err := entry.ToNative(reflect.New(entry.CheckedType())) + require.NoError(t, err) + iNative := reflect.Indirect(native) + assertHaveSameStructureAndNames(t, iNative.Type(), entry.CheckedType()) + }) + + t.Run("Indexed non basic types change to hash", func(t *testing.T) { + anyType, err := abi.NewType("string", "", []abi.ArgumentMarshaling{}) + require.NoError(t, err) + entry := NewCodecEntry(abi.Arguments{{Name: "Name", Type: anyType, Indexed: true}}, nil, nil) + require.NoError(t, entry.Init()) + nativeField, ok := entry.CheckedType().FieldByName("Name") + require.True(t, ok) + assert.Equal(t, reflect.TypeOf(common.Hash{}), nativeField.Type) + native, err := entry.ToNative(reflect.New(entry.CheckedType())) + require.NoError(t, err) + assertHaveSameStructureAndNames(t, native.Type().Elem(), entry.CheckedType()) + }) + + t.Run("Too many indexed items returns an error", func(t *testing.T) { + anyType, err := abi.NewType("int16", "", []abi.ArgumentMarshaling{}) + require.NoError(t, err) + entry := NewCodecEntry( + abi.Arguments{ + {Name: "Name1", Type: anyType, Indexed: true}, + {Name: "Name2", Type: anyType, Indexed: true}, + {Name: "Name3", Type: anyType, Indexed: true}, + {Name: "Name4", Type: anyType, Indexed: true}, + }, nil, nil) + require.True(t, errors.Is(entry.Init(), commontypes.ErrInvalidConfig)) + }) + + // TODO: when the TODO on + // https://github.com/ethereum/go-ethereum/blob/release/1.12/accounts/abi/topics.go#L78 + // is removed, remove this test. + t.Run("Using unsupported types by go-ethereum returns an error", func(t *testing.T) { + anyType, err := abi.NewType("int256[2]", "", []abi.ArgumentMarshaling{}) + require.NoError(t, err) + entry := NewCodecEntry(abi.Arguments{{Name: "Name", Type: anyType, Indexed: true}}, nil, nil) + assert.True(t, errors.Is(entry.Init(), commontypes.ErrInvalidConfig)) + }) + + t.Run("Modifier returns provided modifier", func(t *testing.T) { + anyType, err := abi.NewType("int16", "", []abi.ArgumentMarshaling{}) + require.NoError(t, err) + mod := codec.NewRenamer(map[string]string{"Name": "RenamedName"}) + entry := NewCodecEntry(abi.Arguments{{Name: "Name", Type: anyType, Indexed: true}}, nil, mod) + assert.Equal(t, mod, entry.Modifier()) + }) + + t.Run("EncodingPrefix returns provided prefix", func(t *testing.T) { + anyType, err := abi.NewType("int16", "", []abi.ArgumentMarshaling{}) + require.NoError(t, err) + prefix := []byte{1, 2, 3} + entry := NewCodecEntry(abi.Arguments{{Name: "Name", Type: anyType, Indexed: true}}, prefix, nil) + assert.Equal(t, prefix, entry.EncodingPrefix()) + }) +} + +// sized and bi must be the same pointer. +func setAndVerifyLimit(t *testing.T, sbi SizedBigInt, bi *big.Int, field reflect.Value) { + require.Same(t, reflect.NewAt(reflect.TypeOf(big.Int{}), reflect.ValueOf(sbi).UnsafePointer()).Interface(), bi) + field.Set(reflect.ValueOf(sbi)) + assert.NoError(t, sbi.Verify()) + bi.Add(bi, big.NewInt(1)) + assert.IsType(t, commontypes.ErrInvalidType, sbi.Verify()) +} + +// verifying the same structure allows us to use unsafe pointers to cast between them. +// This is done for perf and simplicity in mapping the two structures. +// [reflect.NewAt]'s use is the same as (*native)(unsafe.Pointer(checked)) +// See the safe usecase 1 from [unsafe.Pointer], as this is a subset of that. +// This also verifies field names are the same. +func assertHaveSameStructureAndNames(t *testing.T, t1, t2 reflect.Type) { + require.Equal(t, t1.Kind(), t2.Kind()) + + switch t1.Kind() { + case reflect.Array: + require.Equal(t, t1.Len(), t2.Len()) + assertHaveSameStructureAndNames(t, t1.Elem(), t2.Elem()) + case reflect.Slice, reflect.Pointer: + assertHaveSameStructureAndNames(t, t1.Elem(), t2.Elem()) + case reflect.Struct: + numFields := t1.NumField() + require.Equal(t, numFields, t2.NumField()) + for i := 0; i < numFields; i++ { + require.Equal(t, t1.Field(i).Name, t2.Field(i).Name) + assertHaveSameStructureAndNames(t, t1.Field(i).Type, t2.Field(i).Type) + } + default: + require.Equal(t, t1, t2) + } +} diff --git a/core/services/relay/evm/types/gen/bytes.go.tmpl b/core/services/relay/evm/types/gen/bytes.go.tmpl new file mode 100644 index 00000000000..3c06529b0f3 --- /dev/null +++ b/core/services/relay/evm/types/gen/bytes.go.tmpl @@ -0,0 +1,14 @@ +package types + +import "reflect" + +{{ range . }} +type bytes{{.Size}} [{{.Size}}]byte +func init() { + typeMap["bytes{{.Size}}"] = &ABIEncodingType { + native: reflect.TypeOf([{{.Size}}]byte{}), + checked: reflect.TypeOf(bytes{{.Size}}{}), + } +} + +{{ end }} \ No newline at end of file diff --git a/core/services/relay/evm/types/gen/ints.go.tmpl b/core/services/relay/evm/types/gen/ints.go.tmpl new file mode 100644 index 00000000000..b5f0fa363ca --- /dev/null +++ b/core/services/relay/evm/types/gen/ints.go.tmpl @@ -0,0 +1,74 @@ +package types + +import ( + "math/big" + "reflect" + + "github.com/fxamacker/cbor/v2" + + "github.com/smartcontractkit/chainlink-common/pkg/codec" + "github.com/smartcontractkit/chainlink-common/pkg/types" +) + +type SizedBigInt interface { + Verify() error + private() +} + +var sizedBigIntType = reflect.TypeOf((*SizedBigInt)(nil)).Elem() +func SizedBigIntType() reflect.Type { + return sizedBigIntType +} + +{{ range . }} +type {{.Prefix}}int{{.Size}} big.Int +func (i *{{.Prefix}}int{{.Size}}) UnmarshalCBOR(input []byte) error { + bi := (*big.Int)(i) + if err := cbor.Unmarshal(input, bi); err != nil { + return err + } + + return i.Verify() +} + +func (i *{{.Prefix}}int{{.Size}}) MarshalCBOR() ([]byte, error) { + return cbor.Marshal((*big.Int)(i)) +} + +func (i *{{.Prefix}}int{{.Size}}) UnmarshalText(input []byte) error { + bi := (*big.Int)(i) + if _, ok := bi.SetString(string(input), 10); !ok { + return types.ErrInvalidType + } + + return i.Verify() +} + +func (i *{{.Prefix}}int{{.Size}}) MarshalText() ([]byte, error) { + bi := (*big.Int)(i) + return []byte(bi.String()), nil +} + +func (i *{{.Prefix}}int{{.Size}}) Verify() error { + bi := (*big.Int)(i) + {{ if .Signed }} + if !codec.FitsInNBitsSigned({{.Size}}, bi) { + return types.ErrInvalidType + } + {{ else }} + if bi.BitLen() > {{.Size}} || bi.Sign() < 0 { + return types.ErrInvalidType + } + {{ end }} + return nil +} + +func (i *{{.Prefix}}int{{.Size}}) private() {} + +func init() { + typeMap["{{.Prefix}}int{{.Size}}"] = &ABIEncodingType { + native: reflect.TypeOf((*big.Int)(nil)), + checked: reflect.TypeOf((*{{.Prefix}}int{{.Size}})(nil)), + } +} +{{ end }} \ No newline at end of file diff --git a/core/services/relay/evm/types/gen/main.go b/core/services/relay/evm/types/gen/main.go new file mode 100644 index 00000000000..84e7c008ee0 --- /dev/null +++ b/core/services/relay/evm/types/gen/main.go @@ -0,0 +1,73 @@ +package main + +import ( + "bytes" + _ "embed" + "go/format" + "os" + "text/template" +) + +func main() { + genInts() + genBytes() +} + +func genBytes() { + byteTypes := [33]ByteType{} + for i := 1; i < 33; i++ { + byteTypes[i-1].Size = i + } + mustRunTemplate("bytes", bytesTemplate, "byte_types_gen.go", byteTypes) +} + +func genInts() { + var intTypes []*IntType + + // 8, 16, 32, and 64 bits have their own type in go that is used by abi. + // The test use *big.Int + for i := 24; i <= 256; i += 8 { + if i == 32 || i == 64 { + continue + } + + signed := &IntType{Size: i, Signed: true} + unsigned := &IntType{Prefix: "u", Size: i} + intTypes = append(intTypes, signed, unsigned) + } + mustRunTemplate("ints", intsTemplate, "int_types_gen.go", intTypes) +} + +func mustRunTemplate(name, rawTemplate, outputFile string, input any) { + t := template.Must(template.New(name).Parse(rawTemplate)) + + br := bytes.Buffer{} + if err := t.Execute(&br, input); err != nil { + panic(err) + } + + res, err := format.Source(br.Bytes()) + if err != nil { + panic(err) + } + + if err = os.WriteFile(outputFile, res, 0600); err != nil { + panic(err) + } +} + +type IntType struct { + Prefix string + Size int + Signed bool +} + +type ByteType struct { + Size int +} + +//go:embed bytes.go.tmpl +var bytesTemplate string + +//go:embed ints.go.tmpl +var intsTemplate string diff --git a/core/services/relay/evm/types/int_types_gen.go b/core/services/relay/evm/types/int_types_gen.go new file mode 100644 index 00000000000..1ff82d5691d --- /dev/null +++ b/core/services/relay/evm/types/int_types_gen.go @@ -0,0 +1,2710 @@ +package types + +import ( + "math/big" + "reflect" + + "github.com/fxamacker/cbor/v2" + + "github.com/smartcontractkit/chainlink-common/pkg/codec" + "github.com/smartcontractkit/chainlink-common/pkg/types" +) + +type SizedBigInt interface { + Verify() error + private() +} + +var sizedBigIntType = reflect.TypeOf((*SizedBigInt)(nil)).Elem() + +func SizedBigIntType() reflect.Type { + return sizedBigIntType +} + +type int24 big.Int + +func (i *int24) UnmarshalCBOR(input []byte) error { + bi := (*big.Int)(i) + if err := cbor.Unmarshal(input, bi); err != nil { + return err + } + + return i.Verify() +} + +func (i *int24) MarshalCBOR() ([]byte, error) { + return cbor.Marshal((*big.Int)(i)) +} + +func (i *int24) UnmarshalText(input []byte) error { + bi := (*big.Int)(i) + if _, ok := bi.SetString(string(input), 10); !ok { + return types.ErrInvalidType + } + + return i.Verify() +} + +func (i *int24) MarshalText() ([]byte, error) { + bi := (*big.Int)(i) + return []byte(bi.String()), nil +} + +func (i *int24) Verify() error { + bi := (*big.Int)(i) + + if !codec.FitsInNBitsSigned(24, bi) { + return types.ErrInvalidType + } + + return nil +} + +func (i *int24) private() {} + +func init() { + typeMap["int24"] = &ABIEncodingType{ + native: reflect.TypeOf((*big.Int)(nil)), + checked: reflect.TypeOf((*int24)(nil)), + } +} + +type uint24 big.Int + +func (i *uint24) UnmarshalCBOR(input []byte) error { + bi := (*big.Int)(i) + if err := cbor.Unmarshal(input, bi); err != nil { + return err + } + + return i.Verify() +} + +func (i *uint24) MarshalCBOR() ([]byte, error) { + return cbor.Marshal((*big.Int)(i)) +} + +func (i *uint24) UnmarshalText(input []byte) error { + bi := (*big.Int)(i) + if _, ok := bi.SetString(string(input), 10); !ok { + return types.ErrInvalidType + } + + return i.Verify() +} + +func (i *uint24) MarshalText() ([]byte, error) { + bi := (*big.Int)(i) + return []byte(bi.String()), nil +} + +func (i *uint24) Verify() error { + bi := (*big.Int)(i) + + if bi.BitLen() > 24 || bi.Sign() < 0 { + return types.ErrInvalidType + } + + return nil +} + +func (i *uint24) private() {} + +func init() { + typeMap["uint24"] = &ABIEncodingType{ + native: reflect.TypeOf((*big.Int)(nil)), + checked: reflect.TypeOf((*uint24)(nil)), + } +} + +type int40 big.Int + +func (i *int40) UnmarshalCBOR(input []byte) error { + bi := (*big.Int)(i) + if err := cbor.Unmarshal(input, bi); err != nil { + return err + } + + return i.Verify() +} + +func (i *int40) MarshalCBOR() ([]byte, error) { + return cbor.Marshal((*big.Int)(i)) +} + +func (i *int40) UnmarshalText(input []byte) error { + bi := (*big.Int)(i) + if _, ok := bi.SetString(string(input), 10); !ok { + return types.ErrInvalidType + } + + return i.Verify() +} + +func (i *int40) MarshalText() ([]byte, error) { + bi := (*big.Int)(i) + return []byte(bi.String()), nil +} + +func (i *int40) Verify() error { + bi := (*big.Int)(i) + + if !codec.FitsInNBitsSigned(40, bi) { + return types.ErrInvalidType + } + + return nil +} + +func (i *int40) private() {} + +func init() { + typeMap["int40"] = &ABIEncodingType{ + native: reflect.TypeOf((*big.Int)(nil)), + checked: reflect.TypeOf((*int40)(nil)), + } +} + +type uint40 big.Int + +func (i *uint40) UnmarshalCBOR(input []byte) error { + bi := (*big.Int)(i) + if err := cbor.Unmarshal(input, bi); err != nil { + return err + } + + return i.Verify() +} + +func (i *uint40) MarshalCBOR() ([]byte, error) { + return cbor.Marshal((*big.Int)(i)) +} + +func (i *uint40) UnmarshalText(input []byte) error { + bi := (*big.Int)(i) + if _, ok := bi.SetString(string(input), 10); !ok { + return types.ErrInvalidType + } + + return i.Verify() +} + +func (i *uint40) MarshalText() ([]byte, error) { + bi := (*big.Int)(i) + return []byte(bi.String()), nil +} + +func (i *uint40) Verify() error { + bi := (*big.Int)(i) + + if bi.BitLen() > 40 || bi.Sign() < 0 { + return types.ErrInvalidType + } + + return nil +} + +func (i *uint40) private() {} + +func init() { + typeMap["uint40"] = &ABIEncodingType{ + native: reflect.TypeOf((*big.Int)(nil)), + checked: reflect.TypeOf((*uint40)(nil)), + } +} + +type int48 big.Int + +func (i *int48) UnmarshalCBOR(input []byte) error { + bi := (*big.Int)(i) + if err := cbor.Unmarshal(input, bi); err != nil { + return err + } + + return i.Verify() +} + +func (i *int48) MarshalCBOR() ([]byte, error) { + return cbor.Marshal((*big.Int)(i)) +} + +func (i *int48) UnmarshalText(input []byte) error { + bi := (*big.Int)(i) + if _, ok := bi.SetString(string(input), 10); !ok { + return types.ErrInvalidType + } + + return i.Verify() +} + +func (i *int48) MarshalText() ([]byte, error) { + bi := (*big.Int)(i) + return []byte(bi.String()), nil +} + +func (i *int48) Verify() error { + bi := (*big.Int)(i) + + if !codec.FitsInNBitsSigned(48, bi) { + return types.ErrInvalidType + } + + return nil +} + +func (i *int48) private() {} + +func init() { + typeMap["int48"] = &ABIEncodingType{ + native: reflect.TypeOf((*big.Int)(nil)), + checked: reflect.TypeOf((*int48)(nil)), + } +} + +type uint48 big.Int + +func (i *uint48) UnmarshalCBOR(input []byte) error { + bi := (*big.Int)(i) + if err := cbor.Unmarshal(input, bi); err != nil { + return err + } + + return i.Verify() +} + +func (i *uint48) MarshalCBOR() ([]byte, error) { + return cbor.Marshal((*big.Int)(i)) +} + +func (i *uint48) UnmarshalText(input []byte) error { + bi := (*big.Int)(i) + if _, ok := bi.SetString(string(input), 10); !ok { + return types.ErrInvalidType + } + + return i.Verify() +} + +func (i *uint48) MarshalText() ([]byte, error) { + bi := (*big.Int)(i) + return []byte(bi.String()), nil +} + +func (i *uint48) Verify() error { + bi := (*big.Int)(i) + + if bi.BitLen() > 48 || bi.Sign() < 0 { + return types.ErrInvalidType + } + + return nil +} + +func (i *uint48) private() {} + +func init() { + typeMap["uint48"] = &ABIEncodingType{ + native: reflect.TypeOf((*big.Int)(nil)), + checked: reflect.TypeOf((*uint48)(nil)), + } +} + +type int56 big.Int + +func (i *int56) UnmarshalCBOR(input []byte) error { + bi := (*big.Int)(i) + if err := cbor.Unmarshal(input, bi); err != nil { + return err + } + + return i.Verify() +} + +func (i *int56) MarshalCBOR() ([]byte, error) { + return cbor.Marshal((*big.Int)(i)) +} + +func (i *int56) UnmarshalText(input []byte) error { + bi := (*big.Int)(i) + if _, ok := bi.SetString(string(input), 10); !ok { + return types.ErrInvalidType + } + + return i.Verify() +} + +func (i *int56) MarshalText() ([]byte, error) { + bi := (*big.Int)(i) + return []byte(bi.String()), nil +} + +func (i *int56) Verify() error { + bi := (*big.Int)(i) + + if !codec.FitsInNBitsSigned(56, bi) { + return types.ErrInvalidType + } + + return nil +} + +func (i *int56) private() {} + +func init() { + typeMap["int56"] = &ABIEncodingType{ + native: reflect.TypeOf((*big.Int)(nil)), + checked: reflect.TypeOf((*int56)(nil)), + } +} + +type uint56 big.Int + +func (i *uint56) UnmarshalCBOR(input []byte) error { + bi := (*big.Int)(i) + if err := cbor.Unmarshal(input, bi); err != nil { + return err + } + + return i.Verify() +} + +func (i *uint56) MarshalCBOR() ([]byte, error) { + return cbor.Marshal((*big.Int)(i)) +} + +func (i *uint56) UnmarshalText(input []byte) error { + bi := (*big.Int)(i) + if _, ok := bi.SetString(string(input), 10); !ok { + return types.ErrInvalidType + } + + return i.Verify() +} + +func (i *uint56) MarshalText() ([]byte, error) { + bi := (*big.Int)(i) + return []byte(bi.String()), nil +} + +func (i *uint56) Verify() error { + bi := (*big.Int)(i) + + if bi.BitLen() > 56 || bi.Sign() < 0 { + return types.ErrInvalidType + } + + return nil +} + +func (i *uint56) private() {} + +func init() { + typeMap["uint56"] = &ABIEncodingType{ + native: reflect.TypeOf((*big.Int)(nil)), + checked: reflect.TypeOf((*uint56)(nil)), + } +} + +type int72 big.Int + +func (i *int72) UnmarshalCBOR(input []byte) error { + bi := (*big.Int)(i) + if err := cbor.Unmarshal(input, bi); err != nil { + return err + } + + return i.Verify() +} + +func (i *int72) MarshalCBOR() ([]byte, error) { + return cbor.Marshal((*big.Int)(i)) +} + +func (i *int72) UnmarshalText(input []byte) error { + bi := (*big.Int)(i) + if _, ok := bi.SetString(string(input), 10); !ok { + return types.ErrInvalidType + } + + return i.Verify() +} + +func (i *int72) MarshalText() ([]byte, error) { + bi := (*big.Int)(i) + return []byte(bi.String()), nil +} + +func (i *int72) Verify() error { + bi := (*big.Int)(i) + + if !codec.FitsInNBitsSigned(72, bi) { + return types.ErrInvalidType + } + + return nil +} + +func (i *int72) private() {} + +func init() { + typeMap["int72"] = &ABIEncodingType{ + native: reflect.TypeOf((*big.Int)(nil)), + checked: reflect.TypeOf((*int72)(nil)), + } +} + +type uint72 big.Int + +func (i *uint72) UnmarshalCBOR(input []byte) error { + bi := (*big.Int)(i) + if err := cbor.Unmarshal(input, bi); err != nil { + return err + } + + return i.Verify() +} + +func (i *uint72) MarshalCBOR() ([]byte, error) { + return cbor.Marshal((*big.Int)(i)) +} + +func (i *uint72) UnmarshalText(input []byte) error { + bi := (*big.Int)(i) + if _, ok := bi.SetString(string(input), 10); !ok { + return types.ErrInvalidType + } + + return i.Verify() +} + +func (i *uint72) MarshalText() ([]byte, error) { + bi := (*big.Int)(i) + return []byte(bi.String()), nil +} + +func (i *uint72) Verify() error { + bi := (*big.Int)(i) + + if bi.BitLen() > 72 || bi.Sign() < 0 { + return types.ErrInvalidType + } + + return nil +} + +func (i *uint72) private() {} + +func init() { + typeMap["uint72"] = &ABIEncodingType{ + native: reflect.TypeOf((*big.Int)(nil)), + checked: reflect.TypeOf((*uint72)(nil)), + } +} + +type int80 big.Int + +func (i *int80) UnmarshalCBOR(input []byte) error { + bi := (*big.Int)(i) + if err := cbor.Unmarshal(input, bi); err != nil { + return err + } + + return i.Verify() +} + +func (i *int80) MarshalCBOR() ([]byte, error) { + return cbor.Marshal((*big.Int)(i)) +} + +func (i *int80) UnmarshalText(input []byte) error { + bi := (*big.Int)(i) + if _, ok := bi.SetString(string(input), 10); !ok { + return types.ErrInvalidType + } + + return i.Verify() +} + +func (i *int80) MarshalText() ([]byte, error) { + bi := (*big.Int)(i) + return []byte(bi.String()), nil +} + +func (i *int80) Verify() error { + bi := (*big.Int)(i) + + if !codec.FitsInNBitsSigned(80, bi) { + return types.ErrInvalidType + } + + return nil +} + +func (i *int80) private() {} + +func init() { + typeMap["int80"] = &ABIEncodingType{ + native: reflect.TypeOf((*big.Int)(nil)), + checked: reflect.TypeOf((*int80)(nil)), + } +} + +type uint80 big.Int + +func (i *uint80) UnmarshalCBOR(input []byte) error { + bi := (*big.Int)(i) + if err := cbor.Unmarshal(input, bi); err != nil { + return err + } + + return i.Verify() +} + +func (i *uint80) MarshalCBOR() ([]byte, error) { + return cbor.Marshal((*big.Int)(i)) +} + +func (i *uint80) UnmarshalText(input []byte) error { + bi := (*big.Int)(i) + if _, ok := bi.SetString(string(input), 10); !ok { + return types.ErrInvalidType + } + + return i.Verify() +} + +func (i *uint80) MarshalText() ([]byte, error) { + bi := (*big.Int)(i) + return []byte(bi.String()), nil +} + +func (i *uint80) Verify() error { + bi := (*big.Int)(i) + + if bi.BitLen() > 80 || bi.Sign() < 0 { + return types.ErrInvalidType + } + + return nil +} + +func (i *uint80) private() {} + +func init() { + typeMap["uint80"] = &ABIEncodingType{ + native: reflect.TypeOf((*big.Int)(nil)), + checked: reflect.TypeOf((*uint80)(nil)), + } +} + +type int88 big.Int + +func (i *int88) UnmarshalCBOR(input []byte) error { + bi := (*big.Int)(i) + if err := cbor.Unmarshal(input, bi); err != nil { + return err + } + + return i.Verify() +} + +func (i *int88) MarshalCBOR() ([]byte, error) { + return cbor.Marshal((*big.Int)(i)) +} + +func (i *int88) UnmarshalText(input []byte) error { + bi := (*big.Int)(i) + if _, ok := bi.SetString(string(input), 10); !ok { + return types.ErrInvalidType + } + + return i.Verify() +} + +func (i *int88) MarshalText() ([]byte, error) { + bi := (*big.Int)(i) + return []byte(bi.String()), nil +} + +func (i *int88) Verify() error { + bi := (*big.Int)(i) + + if !codec.FitsInNBitsSigned(88, bi) { + return types.ErrInvalidType + } + + return nil +} + +func (i *int88) private() {} + +func init() { + typeMap["int88"] = &ABIEncodingType{ + native: reflect.TypeOf((*big.Int)(nil)), + checked: reflect.TypeOf((*int88)(nil)), + } +} + +type uint88 big.Int + +func (i *uint88) UnmarshalCBOR(input []byte) error { + bi := (*big.Int)(i) + if err := cbor.Unmarshal(input, bi); err != nil { + return err + } + + return i.Verify() +} + +func (i *uint88) MarshalCBOR() ([]byte, error) { + return cbor.Marshal((*big.Int)(i)) +} + +func (i *uint88) UnmarshalText(input []byte) error { + bi := (*big.Int)(i) + if _, ok := bi.SetString(string(input), 10); !ok { + return types.ErrInvalidType + } + + return i.Verify() +} + +func (i *uint88) MarshalText() ([]byte, error) { + bi := (*big.Int)(i) + return []byte(bi.String()), nil +} + +func (i *uint88) Verify() error { + bi := (*big.Int)(i) + + if bi.BitLen() > 88 || bi.Sign() < 0 { + return types.ErrInvalidType + } + + return nil +} + +func (i *uint88) private() {} + +func init() { + typeMap["uint88"] = &ABIEncodingType{ + native: reflect.TypeOf((*big.Int)(nil)), + checked: reflect.TypeOf((*uint88)(nil)), + } +} + +type int96 big.Int + +func (i *int96) UnmarshalCBOR(input []byte) error { + bi := (*big.Int)(i) + if err := cbor.Unmarshal(input, bi); err != nil { + return err + } + + return i.Verify() +} + +func (i *int96) MarshalCBOR() ([]byte, error) { + return cbor.Marshal((*big.Int)(i)) +} + +func (i *int96) UnmarshalText(input []byte) error { + bi := (*big.Int)(i) + if _, ok := bi.SetString(string(input), 10); !ok { + return types.ErrInvalidType + } + + return i.Verify() +} + +func (i *int96) MarshalText() ([]byte, error) { + bi := (*big.Int)(i) + return []byte(bi.String()), nil +} + +func (i *int96) Verify() error { + bi := (*big.Int)(i) + + if !codec.FitsInNBitsSigned(96, bi) { + return types.ErrInvalidType + } + + return nil +} + +func (i *int96) private() {} + +func init() { + typeMap["int96"] = &ABIEncodingType{ + native: reflect.TypeOf((*big.Int)(nil)), + checked: reflect.TypeOf((*int96)(nil)), + } +} + +type uint96 big.Int + +func (i *uint96) UnmarshalCBOR(input []byte) error { + bi := (*big.Int)(i) + if err := cbor.Unmarshal(input, bi); err != nil { + return err + } + + return i.Verify() +} + +func (i *uint96) MarshalCBOR() ([]byte, error) { + return cbor.Marshal((*big.Int)(i)) +} + +func (i *uint96) UnmarshalText(input []byte) error { + bi := (*big.Int)(i) + if _, ok := bi.SetString(string(input), 10); !ok { + return types.ErrInvalidType + } + + return i.Verify() +} + +func (i *uint96) MarshalText() ([]byte, error) { + bi := (*big.Int)(i) + return []byte(bi.String()), nil +} + +func (i *uint96) Verify() error { + bi := (*big.Int)(i) + + if bi.BitLen() > 96 || bi.Sign() < 0 { + return types.ErrInvalidType + } + + return nil +} + +func (i *uint96) private() {} + +func init() { + typeMap["uint96"] = &ABIEncodingType{ + native: reflect.TypeOf((*big.Int)(nil)), + checked: reflect.TypeOf((*uint96)(nil)), + } +} + +type int104 big.Int + +func (i *int104) UnmarshalCBOR(input []byte) error { + bi := (*big.Int)(i) + if err := cbor.Unmarshal(input, bi); err != nil { + return err + } + + return i.Verify() +} + +func (i *int104) MarshalCBOR() ([]byte, error) { + return cbor.Marshal((*big.Int)(i)) +} + +func (i *int104) UnmarshalText(input []byte) error { + bi := (*big.Int)(i) + if _, ok := bi.SetString(string(input), 10); !ok { + return types.ErrInvalidType + } + + return i.Verify() +} + +func (i *int104) MarshalText() ([]byte, error) { + bi := (*big.Int)(i) + return []byte(bi.String()), nil +} + +func (i *int104) Verify() error { + bi := (*big.Int)(i) + + if !codec.FitsInNBitsSigned(104, bi) { + return types.ErrInvalidType + } + + return nil +} + +func (i *int104) private() {} + +func init() { + typeMap["int104"] = &ABIEncodingType{ + native: reflect.TypeOf((*big.Int)(nil)), + checked: reflect.TypeOf((*int104)(nil)), + } +} + +type uint104 big.Int + +func (i *uint104) UnmarshalCBOR(input []byte) error { + bi := (*big.Int)(i) + if err := cbor.Unmarshal(input, bi); err != nil { + return err + } + + return i.Verify() +} + +func (i *uint104) MarshalCBOR() ([]byte, error) { + return cbor.Marshal((*big.Int)(i)) +} + +func (i *uint104) UnmarshalText(input []byte) error { + bi := (*big.Int)(i) + if _, ok := bi.SetString(string(input), 10); !ok { + return types.ErrInvalidType + } + + return i.Verify() +} + +func (i *uint104) MarshalText() ([]byte, error) { + bi := (*big.Int)(i) + return []byte(bi.String()), nil +} + +func (i *uint104) Verify() error { + bi := (*big.Int)(i) + + if bi.BitLen() > 104 || bi.Sign() < 0 { + return types.ErrInvalidType + } + + return nil +} + +func (i *uint104) private() {} + +func init() { + typeMap["uint104"] = &ABIEncodingType{ + native: reflect.TypeOf((*big.Int)(nil)), + checked: reflect.TypeOf((*uint104)(nil)), + } +} + +type int112 big.Int + +func (i *int112) UnmarshalCBOR(input []byte) error { + bi := (*big.Int)(i) + if err := cbor.Unmarshal(input, bi); err != nil { + return err + } + + return i.Verify() +} + +func (i *int112) MarshalCBOR() ([]byte, error) { + return cbor.Marshal((*big.Int)(i)) +} + +func (i *int112) UnmarshalText(input []byte) error { + bi := (*big.Int)(i) + if _, ok := bi.SetString(string(input), 10); !ok { + return types.ErrInvalidType + } + + return i.Verify() +} + +func (i *int112) MarshalText() ([]byte, error) { + bi := (*big.Int)(i) + return []byte(bi.String()), nil +} + +func (i *int112) Verify() error { + bi := (*big.Int)(i) + + if !codec.FitsInNBitsSigned(112, bi) { + return types.ErrInvalidType + } + + return nil +} + +func (i *int112) private() {} + +func init() { + typeMap["int112"] = &ABIEncodingType{ + native: reflect.TypeOf((*big.Int)(nil)), + checked: reflect.TypeOf((*int112)(nil)), + } +} + +type uint112 big.Int + +func (i *uint112) UnmarshalCBOR(input []byte) error { + bi := (*big.Int)(i) + if err := cbor.Unmarshal(input, bi); err != nil { + return err + } + + return i.Verify() +} + +func (i *uint112) MarshalCBOR() ([]byte, error) { + return cbor.Marshal((*big.Int)(i)) +} + +func (i *uint112) UnmarshalText(input []byte) error { + bi := (*big.Int)(i) + if _, ok := bi.SetString(string(input), 10); !ok { + return types.ErrInvalidType + } + + return i.Verify() +} + +func (i *uint112) MarshalText() ([]byte, error) { + bi := (*big.Int)(i) + return []byte(bi.String()), nil +} + +func (i *uint112) Verify() error { + bi := (*big.Int)(i) + + if bi.BitLen() > 112 || bi.Sign() < 0 { + return types.ErrInvalidType + } + + return nil +} + +func (i *uint112) private() {} + +func init() { + typeMap["uint112"] = &ABIEncodingType{ + native: reflect.TypeOf((*big.Int)(nil)), + checked: reflect.TypeOf((*uint112)(nil)), + } +} + +type int120 big.Int + +func (i *int120) UnmarshalCBOR(input []byte) error { + bi := (*big.Int)(i) + if err := cbor.Unmarshal(input, bi); err != nil { + return err + } + + return i.Verify() +} + +func (i *int120) MarshalCBOR() ([]byte, error) { + return cbor.Marshal((*big.Int)(i)) +} + +func (i *int120) UnmarshalText(input []byte) error { + bi := (*big.Int)(i) + if _, ok := bi.SetString(string(input), 10); !ok { + return types.ErrInvalidType + } + + return i.Verify() +} + +func (i *int120) MarshalText() ([]byte, error) { + bi := (*big.Int)(i) + return []byte(bi.String()), nil +} + +func (i *int120) Verify() error { + bi := (*big.Int)(i) + + if !codec.FitsInNBitsSigned(120, bi) { + return types.ErrInvalidType + } + + return nil +} + +func (i *int120) private() {} + +func init() { + typeMap["int120"] = &ABIEncodingType{ + native: reflect.TypeOf((*big.Int)(nil)), + checked: reflect.TypeOf((*int120)(nil)), + } +} + +type uint120 big.Int + +func (i *uint120) UnmarshalCBOR(input []byte) error { + bi := (*big.Int)(i) + if err := cbor.Unmarshal(input, bi); err != nil { + return err + } + + return i.Verify() +} + +func (i *uint120) MarshalCBOR() ([]byte, error) { + return cbor.Marshal((*big.Int)(i)) +} + +func (i *uint120) UnmarshalText(input []byte) error { + bi := (*big.Int)(i) + if _, ok := bi.SetString(string(input), 10); !ok { + return types.ErrInvalidType + } + + return i.Verify() +} + +func (i *uint120) MarshalText() ([]byte, error) { + bi := (*big.Int)(i) + return []byte(bi.String()), nil +} + +func (i *uint120) Verify() error { + bi := (*big.Int)(i) + + if bi.BitLen() > 120 || bi.Sign() < 0 { + return types.ErrInvalidType + } + + return nil +} + +func (i *uint120) private() {} + +func init() { + typeMap["uint120"] = &ABIEncodingType{ + native: reflect.TypeOf((*big.Int)(nil)), + checked: reflect.TypeOf((*uint120)(nil)), + } +} + +type int128 big.Int + +func (i *int128) UnmarshalCBOR(input []byte) error { + bi := (*big.Int)(i) + if err := cbor.Unmarshal(input, bi); err != nil { + return err + } + + return i.Verify() +} + +func (i *int128) MarshalCBOR() ([]byte, error) { + return cbor.Marshal((*big.Int)(i)) +} + +func (i *int128) UnmarshalText(input []byte) error { + bi := (*big.Int)(i) + if _, ok := bi.SetString(string(input), 10); !ok { + return types.ErrInvalidType + } + + return i.Verify() +} + +func (i *int128) MarshalText() ([]byte, error) { + bi := (*big.Int)(i) + return []byte(bi.String()), nil +} + +func (i *int128) Verify() error { + bi := (*big.Int)(i) + + if !codec.FitsInNBitsSigned(128, bi) { + return types.ErrInvalidType + } + + return nil +} + +func (i *int128) private() {} + +func init() { + typeMap["int128"] = &ABIEncodingType{ + native: reflect.TypeOf((*big.Int)(nil)), + checked: reflect.TypeOf((*int128)(nil)), + } +} + +type uint128 big.Int + +func (i *uint128) UnmarshalCBOR(input []byte) error { + bi := (*big.Int)(i) + if err := cbor.Unmarshal(input, bi); err != nil { + return err + } + + return i.Verify() +} + +func (i *uint128) MarshalCBOR() ([]byte, error) { + return cbor.Marshal((*big.Int)(i)) +} + +func (i *uint128) UnmarshalText(input []byte) error { + bi := (*big.Int)(i) + if _, ok := bi.SetString(string(input), 10); !ok { + return types.ErrInvalidType + } + + return i.Verify() +} + +func (i *uint128) MarshalText() ([]byte, error) { + bi := (*big.Int)(i) + return []byte(bi.String()), nil +} + +func (i *uint128) Verify() error { + bi := (*big.Int)(i) + + if bi.BitLen() > 128 || bi.Sign() < 0 { + return types.ErrInvalidType + } + + return nil +} + +func (i *uint128) private() {} + +func init() { + typeMap["uint128"] = &ABIEncodingType{ + native: reflect.TypeOf((*big.Int)(nil)), + checked: reflect.TypeOf((*uint128)(nil)), + } +} + +type int136 big.Int + +func (i *int136) UnmarshalCBOR(input []byte) error { + bi := (*big.Int)(i) + if err := cbor.Unmarshal(input, bi); err != nil { + return err + } + + return i.Verify() +} + +func (i *int136) MarshalCBOR() ([]byte, error) { + return cbor.Marshal((*big.Int)(i)) +} + +func (i *int136) UnmarshalText(input []byte) error { + bi := (*big.Int)(i) + if _, ok := bi.SetString(string(input), 10); !ok { + return types.ErrInvalidType + } + + return i.Verify() +} + +func (i *int136) MarshalText() ([]byte, error) { + bi := (*big.Int)(i) + return []byte(bi.String()), nil +} + +func (i *int136) Verify() error { + bi := (*big.Int)(i) + + if !codec.FitsInNBitsSigned(136, bi) { + return types.ErrInvalidType + } + + return nil +} + +func (i *int136) private() {} + +func init() { + typeMap["int136"] = &ABIEncodingType{ + native: reflect.TypeOf((*big.Int)(nil)), + checked: reflect.TypeOf((*int136)(nil)), + } +} + +type uint136 big.Int + +func (i *uint136) UnmarshalCBOR(input []byte) error { + bi := (*big.Int)(i) + if err := cbor.Unmarshal(input, bi); err != nil { + return err + } + + return i.Verify() +} + +func (i *uint136) MarshalCBOR() ([]byte, error) { + return cbor.Marshal((*big.Int)(i)) +} + +func (i *uint136) UnmarshalText(input []byte) error { + bi := (*big.Int)(i) + if _, ok := bi.SetString(string(input), 10); !ok { + return types.ErrInvalidType + } + + return i.Verify() +} + +func (i *uint136) MarshalText() ([]byte, error) { + bi := (*big.Int)(i) + return []byte(bi.String()), nil +} + +func (i *uint136) Verify() error { + bi := (*big.Int)(i) + + if bi.BitLen() > 136 || bi.Sign() < 0 { + return types.ErrInvalidType + } + + return nil +} + +func (i *uint136) private() {} + +func init() { + typeMap["uint136"] = &ABIEncodingType{ + native: reflect.TypeOf((*big.Int)(nil)), + checked: reflect.TypeOf((*uint136)(nil)), + } +} + +type int144 big.Int + +func (i *int144) UnmarshalCBOR(input []byte) error { + bi := (*big.Int)(i) + if err := cbor.Unmarshal(input, bi); err != nil { + return err + } + + return i.Verify() +} + +func (i *int144) MarshalCBOR() ([]byte, error) { + return cbor.Marshal((*big.Int)(i)) +} + +func (i *int144) UnmarshalText(input []byte) error { + bi := (*big.Int)(i) + if _, ok := bi.SetString(string(input), 10); !ok { + return types.ErrInvalidType + } + + return i.Verify() +} + +func (i *int144) MarshalText() ([]byte, error) { + bi := (*big.Int)(i) + return []byte(bi.String()), nil +} + +func (i *int144) Verify() error { + bi := (*big.Int)(i) + + if !codec.FitsInNBitsSigned(144, bi) { + return types.ErrInvalidType + } + + return nil +} + +func (i *int144) private() {} + +func init() { + typeMap["int144"] = &ABIEncodingType{ + native: reflect.TypeOf((*big.Int)(nil)), + checked: reflect.TypeOf((*int144)(nil)), + } +} + +type uint144 big.Int + +func (i *uint144) UnmarshalCBOR(input []byte) error { + bi := (*big.Int)(i) + if err := cbor.Unmarshal(input, bi); err != nil { + return err + } + + return i.Verify() +} + +func (i *uint144) MarshalCBOR() ([]byte, error) { + return cbor.Marshal((*big.Int)(i)) +} + +func (i *uint144) UnmarshalText(input []byte) error { + bi := (*big.Int)(i) + if _, ok := bi.SetString(string(input), 10); !ok { + return types.ErrInvalidType + } + + return i.Verify() +} + +func (i *uint144) MarshalText() ([]byte, error) { + bi := (*big.Int)(i) + return []byte(bi.String()), nil +} + +func (i *uint144) Verify() error { + bi := (*big.Int)(i) + + if bi.BitLen() > 144 || bi.Sign() < 0 { + return types.ErrInvalidType + } + + return nil +} + +func (i *uint144) private() {} + +func init() { + typeMap["uint144"] = &ABIEncodingType{ + native: reflect.TypeOf((*big.Int)(nil)), + checked: reflect.TypeOf((*uint144)(nil)), + } +} + +type int152 big.Int + +func (i *int152) UnmarshalCBOR(input []byte) error { + bi := (*big.Int)(i) + if err := cbor.Unmarshal(input, bi); err != nil { + return err + } + + return i.Verify() +} + +func (i *int152) MarshalCBOR() ([]byte, error) { + return cbor.Marshal((*big.Int)(i)) +} + +func (i *int152) UnmarshalText(input []byte) error { + bi := (*big.Int)(i) + if _, ok := bi.SetString(string(input), 10); !ok { + return types.ErrInvalidType + } + + return i.Verify() +} + +func (i *int152) MarshalText() ([]byte, error) { + bi := (*big.Int)(i) + return []byte(bi.String()), nil +} + +func (i *int152) Verify() error { + bi := (*big.Int)(i) + + if !codec.FitsInNBitsSigned(152, bi) { + return types.ErrInvalidType + } + + return nil +} + +func (i *int152) private() {} + +func init() { + typeMap["int152"] = &ABIEncodingType{ + native: reflect.TypeOf((*big.Int)(nil)), + checked: reflect.TypeOf((*int152)(nil)), + } +} + +type uint152 big.Int + +func (i *uint152) UnmarshalCBOR(input []byte) error { + bi := (*big.Int)(i) + if err := cbor.Unmarshal(input, bi); err != nil { + return err + } + + return i.Verify() +} + +func (i *uint152) MarshalCBOR() ([]byte, error) { + return cbor.Marshal((*big.Int)(i)) +} + +func (i *uint152) UnmarshalText(input []byte) error { + bi := (*big.Int)(i) + if _, ok := bi.SetString(string(input), 10); !ok { + return types.ErrInvalidType + } + + return i.Verify() +} + +func (i *uint152) MarshalText() ([]byte, error) { + bi := (*big.Int)(i) + return []byte(bi.String()), nil +} + +func (i *uint152) Verify() error { + bi := (*big.Int)(i) + + if bi.BitLen() > 152 || bi.Sign() < 0 { + return types.ErrInvalidType + } + + return nil +} + +func (i *uint152) private() {} + +func init() { + typeMap["uint152"] = &ABIEncodingType{ + native: reflect.TypeOf((*big.Int)(nil)), + checked: reflect.TypeOf((*uint152)(nil)), + } +} + +type int160 big.Int + +func (i *int160) UnmarshalCBOR(input []byte) error { + bi := (*big.Int)(i) + if err := cbor.Unmarshal(input, bi); err != nil { + return err + } + + return i.Verify() +} + +func (i *int160) MarshalCBOR() ([]byte, error) { + return cbor.Marshal((*big.Int)(i)) +} + +func (i *int160) UnmarshalText(input []byte) error { + bi := (*big.Int)(i) + if _, ok := bi.SetString(string(input), 10); !ok { + return types.ErrInvalidType + } + + return i.Verify() +} + +func (i *int160) MarshalText() ([]byte, error) { + bi := (*big.Int)(i) + return []byte(bi.String()), nil +} + +func (i *int160) Verify() error { + bi := (*big.Int)(i) + + if !codec.FitsInNBitsSigned(160, bi) { + return types.ErrInvalidType + } + + return nil +} + +func (i *int160) private() {} + +func init() { + typeMap["int160"] = &ABIEncodingType{ + native: reflect.TypeOf((*big.Int)(nil)), + checked: reflect.TypeOf((*int160)(nil)), + } +} + +type uint160 big.Int + +func (i *uint160) UnmarshalCBOR(input []byte) error { + bi := (*big.Int)(i) + if err := cbor.Unmarshal(input, bi); err != nil { + return err + } + + return i.Verify() +} + +func (i *uint160) MarshalCBOR() ([]byte, error) { + return cbor.Marshal((*big.Int)(i)) +} + +func (i *uint160) UnmarshalText(input []byte) error { + bi := (*big.Int)(i) + if _, ok := bi.SetString(string(input), 10); !ok { + return types.ErrInvalidType + } + + return i.Verify() +} + +func (i *uint160) MarshalText() ([]byte, error) { + bi := (*big.Int)(i) + return []byte(bi.String()), nil +} + +func (i *uint160) Verify() error { + bi := (*big.Int)(i) + + if bi.BitLen() > 160 || bi.Sign() < 0 { + return types.ErrInvalidType + } + + return nil +} + +func (i *uint160) private() {} + +func init() { + typeMap["uint160"] = &ABIEncodingType{ + native: reflect.TypeOf((*big.Int)(nil)), + checked: reflect.TypeOf((*uint160)(nil)), + } +} + +type int168 big.Int + +func (i *int168) UnmarshalCBOR(input []byte) error { + bi := (*big.Int)(i) + if err := cbor.Unmarshal(input, bi); err != nil { + return err + } + + return i.Verify() +} + +func (i *int168) MarshalCBOR() ([]byte, error) { + return cbor.Marshal((*big.Int)(i)) +} + +func (i *int168) UnmarshalText(input []byte) error { + bi := (*big.Int)(i) + if _, ok := bi.SetString(string(input), 10); !ok { + return types.ErrInvalidType + } + + return i.Verify() +} + +func (i *int168) MarshalText() ([]byte, error) { + bi := (*big.Int)(i) + return []byte(bi.String()), nil +} + +func (i *int168) Verify() error { + bi := (*big.Int)(i) + + if !codec.FitsInNBitsSigned(168, bi) { + return types.ErrInvalidType + } + + return nil +} + +func (i *int168) private() {} + +func init() { + typeMap["int168"] = &ABIEncodingType{ + native: reflect.TypeOf((*big.Int)(nil)), + checked: reflect.TypeOf((*int168)(nil)), + } +} + +type uint168 big.Int + +func (i *uint168) UnmarshalCBOR(input []byte) error { + bi := (*big.Int)(i) + if err := cbor.Unmarshal(input, bi); err != nil { + return err + } + + return i.Verify() +} + +func (i *uint168) MarshalCBOR() ([]byte, error) { + return cbor.Marshal((*big.Int)(i)) +} + +func (i *uint168) UnmarshalText(input []byte) error { + bi := (*big.Int)(i) + if _, ok := bi.SetString(string(input), 10); !ok { + return types.ErrInvalidType + } + + return i.Verify() +} + +func (i *uint168) MarshalText() ([]byte, error) { + bi := (*big.Int)(i) + return []byte(bi.String()), nil +} + +func (i *uint168) Verify() error { + bi := (*big.Int)(i) + + if bi.BitLen() > 168 || bi.Sign() < 0 { + return types.ErrInvalidType + } + + return nil +} + +func (i *uint168) private() {} + +func init() { + typeMap["uint168"] = &ABIEncodingType{ + native: reflect.TypeOf((*big.Int)(nil)), + checked: reflect.TypeOf((*uint168)(nil)), + } +} + +type int176 big.Int + +func (i *int176) UnmarshalCBOR(input []byte) error { + bi := (*big.Int)(i) + if err := cbor.Unmarshal(input, bi); err != nil { + return err + } + + return i.Verify() +} + +func (i *int176) MarshalCBOR() ([]byte, error) { + return cbor.Marshal((*big.Int)(i)) +} + +func (i *int176) UnmarshalText(input []byte) error { + bi := (*big.Int)(i) + if _, ok := bi.SetString(string(input), 10); !ok { + return types.ErrInvalidType + } + + return i.Verify() +} + +func (i *int176) MarshalText() ([]byte, error) { + bi := (*big.Int)(i) + return []byte(bi.String()), nil +} + +func (i *int176) Verify() error { + bi := (*big.Int)(i) + + if !codec.FitsInNBitsSigned(176, bi) { + return types.ErrInvalidType + } + + return nil +} + +func (i *int176) private() {} + +func init() { + typeMap["int176"] = &ABIEncodingType{ + native: reflect.TypeOf((*big.Int)(nil)), + checked: reflect.TypeOf((*int176)(nil)), + } +} + +type uint176 big.Int + +func (i *uint176) UnmarshalCBOR(input []byte) error { + bi := (*big.Int)(i) + if err := cbor.Unmarshal(input, bi); err != nil { + return err + } + + return i.Verify() +} + +func (i *uint176) MarshalCBOR() ([]byte, error) { + return cbor.Marshal((*big.Int)(i)) +} + +func (i *uint176) UnmarshalText(input []byte) error { + bi := (*big.Int)(i) + if _, ok := bi.SetString(string(input), 10); !ok { + return types.ErrInvalidType + } + + return i.Verify() +} + +func (i *uint176) MarshalText() ([]byte, error) { + bi := (*big.Int)(i) + return []byte(bi.String()), nil +} + +func (i *uint176) Verify() error { + bi := (*big.Int)(i) + + if bi.BitLen() > 176 || bi.Sign() < 0 { + return types.ErrInvalidType + } + + return nil +} + +func (i *uint176) private() {} + +func init() { + typeMap["uint176"] = &ABIEncodingType{ + native: reflect.TypeOf((*big.Int)(nil)), + checked: reflect.TypeOf((*uint176)(nil)), + } +} + +type int184 big.Int + +func (i *int184) UnmarshalCBOR(input []byte) error { + bi := (*big.Int)(i) + if err := cbor.Unmarshal(input, bi); err != nil { + return err + } + + return i.Verify() +} + +func (i *int184) MarshalCBOR() ([]byte, error) { + return cbor.Marshal((*big.Int)(i)) +} + +func (i *int184) UnmarshalText(input []byte) error { + bi := (*big.Int)(i) + if _, ok := bi.SetString(string(input), 10); !ok { + return types.ErrInvalidType + } + + return i.Verify() +} + +func (i *int184) MarshalText() ([]byte, error) { + bi := (*big.Int)(i) + return []byte(bi.String()), nil +} + +func (i *int184) Verify() error { + bi := (*big.Int)(i) + + if !codec.FitsInNBitsSigned(184, bi) { + return types.ErrInvalidType + } + + return nil +} + +func (i *int184) private() {} + +func init() { + typeMap["int184"] = &ABIEncodingType{ + native: reflect.TypeOf((*big.Int)(nil)), + checked: reflect.TypeOf((*int184)(nil)), + } +} + +type uint184 big.Int + +func (i *uint184) UnmarshalCBOR(input []byte) error { + bi := (*big.Int)(i) + if err := cbor.Unmarshal(input, bi); err != nil { + return err + } + + return i.Verify() +} + +func (i *uint184) MarshalCBOR() ([]byte, error) { + return cbor.Marshal((*big.Int)(i)) +} + +func (i *uint184) UnmarshalText(input []byte) error { + bi := (*big.Int)(i) + if _, ok := bi.SetString(string(input), 10); !ok { + return types.ErrInvalidType + } + + return i.Verify() +} + +func (i *uint184) MarshalText() ([]byte, error) { + bi := (*big.Int)(i) + return []byte(bi.String()), nil +} + +func (i *uint184) Verify() error { + bi := (*big.Int)(i) + + if bi.BitLen() > 184 || bi.Sign() < 0 { + return types.ErrInvalidType + } + + return nil +} + +func (i *uint184) private() {} + +func init() { + typeMap["uint184"] = &ABIEncodingType{ + native: reflect.TypeOf((*big.Int)(nil)), + checked: reflect.TypeOf((*uint184)(nil)), + } +} + +type int192 big.Int + +func (i *int192) UnmarshalCBOR(input []byte) error { + bi := (*big.Int)(i) + if err := cbor.Unmarshal(input, bi); err != nil { + return err + } + + return i.Verify() +} + +func (i *int192) MarshalCBOR() ([]byte, error) { + return cbor.Marshal((*big.Int)(i)) +} + +func (i *int192) UnmarshalText(input []byte) error { + bi := (*big.Int)(i) + if _, ok := bi.SetString(string(input), 10); !ok { + return types.ErrInvalidType + } + + return i.Verify() +} + +func (i *int192) MarshalText() ([]byte, error) { + bi := (*big.Int)(i) + return []byte(bi.String()), nil +} + +func (i *int192) Verify() error { + bi := (*big.Int)(i) + + if !codec.FitsInNBitsSigned(192, bi) { + return types.ErrInvalidType + } + + return nil +} + +func (i *int192) private() {} + +func init() { + typeMap["int192"] = &ABIEncodingType{ + native: reflect.TypeOf((*big.Int)(nil)), + checked: reflect.TypeOf((*int192)(nil)), + } +} + +type uint192 big.Int + +func (i *uint192) UnmarshalCBOR(input []byte) error { + bi := (*big.Int)(i) + if err := cbor.Unmarshal(input, bi); err != nil { + return err + } + + return i.Verify() +} + +func (i *uint192) MarshalCBOR() ([]byte, error) { + return cbor.Marshal((*big.Int)(i)) +} + +func (i *uint192) UnmarshalText(input []byte) error { + bi := (*big.Int)(i) + if _, ok := bi.SetString(string(input), 10); !ok { + return types.ErrInvalidType + } + + return i.Verify() +} + +func (i *uint192) MarshalText() ([]byte, error) { + bi := (*big.Int)(i) + return []byte(bi.String()), nil +} + +func (i *uint192) Verify() error { + bi := (*big.Int)(i) + + if bi.BitLen() > 192 || bi.Sign() < 0 { + return types.ErrInvalidType + } + + return nil +} + +func (i *uint192) private() {} + +func init() { + typeMap["uint192"] = &ABIEncodingType{ + native: reflect.TypeOf((*big.Int)(nil)), + checked: reflect.TypeOf((*uint192)(nil)), + } +} + +type int200 big.Int + +func (i *int200) UnmarshalCBOR(input []byte) error { + bi := (*big.Int)(i) + if err := cbor.Unmarshal(input, bi); err != nil { + return err + } + + return i.Verify() +} + +func (i *int200) MarshalCBOR() ([]byte, error) { + return cbor.Marshal((*big.Int)(i)) +} + +func (i *int200) UnmarshalText(input []byte) error { + bi := (*big.Int)(i) + if _, ok := bi.SetString(string(input), 10); !ok { + return types.ErrInvalidType + } + + return i.Verify() +} + +func (i *int200) MarshalText() ([]byte, error) { + bi := (*big.Int)(i) + return []byte(bi.String()), nil +} + +func (i *int200) Verify() error { + bi := (*big.Int)(i) + + if !codec.FitsInNBitsSigned(200, bi) { + return types.ErrInvalidType + } + + return nil +} + +func (i *int200) private() {} + +func init() { + typeMap["int200"] = &ABIEncodingType{ + native: reflect.TypeOf((*big.Int)(nil)), + checked: reflect.TypeOf((*int200)(nil)), + } +} + +type uint200 big.Int + +func (i *uint200) UnmarshalCBOR(input []byte) error { + bi := (*big.Int)(i) + if err := cbor.Unmarshal(input, bi); err != nil { + return err + } + + return i.Verify() +} + +func (i *uint200) MarshalCBOR() ([]byte, error) { + return cbor.Marshal((*big.Int)(i)) +} + +func (i *uint200) UnmarshalText(input []byte) error { + bi := (*big.Int)(i) + if _, ok := bi.SetString(string(input), 10); !ok { + return types.ErrInvalidType + } + + return i.Verify() +} + +func (i *uint200) MarshalText() ([]byte, error) { + bi := (*big.Int)(i) + return []byte(bi.String()), nil +} + +func (i *uint200) Verify() error { + bi := (*big.Int)(i) + + if bi.BitLen() > 200 || bi.Sign() < 0 { + return types.ErrInvalidType + } + + return nil +} + +func (i *uint200) private() {} + +func init() { + typeMap["uint200"] = &ABIEncodingType{ + native: reflect.TypeOf((*big.Int)(nil)), + checked: reflect.TypeOf((*uint200)(nil)), + } +} + +type int208 big.Int + +func (i *int208) UnmarshalCBOR(input []byte) error { + bi := (*big.Int)(i) + if err := cbor.Unmarshal(input, bi); err != nil { + return err + } + + return i.Verify() +} + +func (i *int208) MarshalCBOR() ([]byte, error) { + return cbor.Marshal((*big.Int)(i)) +} + +func (i *int208) UnmarshalText(input []byte) error { + bi := (*big.Int)(i) + if _, ok := bi.SetString(string(input), 10); !ok { + return types.ErrInvalidType + } + + return i.Verify() +} + +func (i *int208) MarshalText() ([]byte, error) { + bi := (*big.Int)(i) + return []byte(bi.String()), nil +} + +func (i *int208) Verify() error { + bi := (*big.Int)(i) + + if !codec.FitsInNBitsSigned(208, bi) { + return types.ErrInvalidType + } + + return nil +} + +func (i *int208) private() {} + +func init() { + typeMap["int208"] = &ABIEncodingType{ + native: reflect.TypeOf((*big.Int)(nil)), + checked: reflect.TypeOf((*int208)(nil)), + } +} + +type uint208 big.Int + +func (i *uint208) UnmarshalCBOR(input []byte) error { + bi := (*big.Int)(i) + if err := cbor.Unmarshal(input, bi); err != nil { + return err + } + + return i.Verify() +} + +func (i *uint208) MarshalCBOR() ([]byte, error) { + return cbor.Marshal((*big.Int)(i)) +} + +func (i *uint208) UnmarshalText(input []byte) error { + bi := (*big.Int)(i) + if _, ok := bi.SetString(string(input), 10); !ok { + return types.ErrInvalidType + } + + return i.Verify() +} + +func (i *uint208) MarshalText() ([]byte, error) { + bi := (*big.Int)(i) + return []byte(bi.String()), nil +} + +func (i *uint208) Verify() error { + bi := (*big.Int)(i) + + if bi.BitLen() > 208 || bi.Sign() < 0 { + return types.ErrInvalidType + } + + return nil +} + +func (i *uint208) private() {} + +func init() { + typeMap["uint208"] = &ABIEncodingType{ + native: reflect.TypeOf((*big.Int)(nil)), + checked: reflect.TypeOf((*uint208)(nil)), + } +} + +type int216 big.Int + +func (i *int216) UnmarshalCBOR(input []byte) error { + bi := (*big.Int)(i) + if err := cbor.Unmarshal(input, bi); err != nil { + return err + } + + return i.Verify() +} + +func (i *int216) MarshalCBOR() ([]byte, error) { + return cbor.Marshal((*big.Int)(i)) +} + +func (i *int216) UnmarshalText(input []byte) error { + bi := (*big.Int)(i) + if _, ok := bi.SetString(string(input), 10); !ok { + return types.ErrInvalidType + } + + return i.Verify() +} + +func (i *int216) MarshalText() ([]byte, error) { + bi := (*big.Int)(i) + return []byte(bi.String()), nil +} + +func (i *int216) Verify() error { + bi := (*big.Int)(i) + + if !codec.FitsInNBitsSigned(216, bi) { + return types.ErrInvalidType + } + + return nil +} + +func (i *int216) private() {} + +func init() { + typeMap["int216"] = &ABIEncodingType{ + native: reflect.TypeOf((*big.Int)(nil)), + checked: reflect.TypeOf((*int216)(nil)), + } +} + +type uint216 big.Int + +func (i *uint216) UnmarshalCBOR(input []byte) error { + bi := (*big.Int)(i) + if err := cbor.Unmarshal(input, bi); err != nil { + return err + } + + return i.Verify() +} + +func (i *uint216) MarshalCBOR() ([]byte, error) { + return cbor.Marshal((*big.Int)(i)) +} + +func (i *uint216) UnmarshalText(input []byte) error { + bi := (*big.Int)(i) + if _, ok := bi.SetString(string(input), 10); !ok { + return types.ErrInvalidType + } + + return i.Verify() +} + +func (i *uint216) MarshalText() ([]byte, error) { + bi := (*big.Int)(i) + return []byte(bi.String()), nil +} + +func (i *uint216) Verify() error { + bi := (*big.Int)(i) + + if bi.BitLen() > 216 || bi.Sign() < 0 { + return types.ErrInvalidType + } + + return nil +} + +func (i *uint216) private() {} + +func init() { + typeMap["uint216"] = &ABIEncodingType{ + native: reflect.TypeOf((*big.Int)(nil)), + checked: reflect.TypeOf((*uint216)(nil)), + } +} + +type int224 big.Int + +func (i *int224) UnmarshalCBOR(input []byte) error { + bi := (*big.Int)(i) + if err := cbor.Unmarshal(input, bi); err != nil { + return err + } + + return i.Verify() +} + +func (i *int224) MarshalCBOR() ([]byte, error) { + return cbor.Marshal((*big.Int)(i)) +} + +func (i *int224) UnmarshalText(input []byte) error { + bi := (*big.Int)(i) + if _, ok := bi.SetString(string(input), 10); !ok { + return types.ErrInvalidType + } + + return i.Verify() +} + +func (i *int224) MarshalText() ([]byte, error) { + bi := (*big.Int)(i) + return []byte(bi.String()), nil +} + +func (i *int224) Verify() error { + bi := (*big.Int)(i) + + if !codec.FitsInNBitsSigned(224, bi) { + return types.ErrInvalidType + } + + return nil +} + +func (i *int224) private() {} + +func init() { + typeMap["int224"] = &ABIEncodingType{ + native: reflect.TypeOf((*big.Int)(nil)), + checked: reflect.TypeOf((*int224)(nil)), + } +} + +type uint224 big.Int + +func (i *uint224) UnmarshalCBOR(input []byte) error { + bi := (*big.Int)(i) + if err := cbor.Unmarshal(input, bi); err != nil { + return err + } + + return i.Verify() +} + +func (i *uint224) MarshalCBOR() ([]byte, error) { + return cbor.Marshal((*big.Int)(i)) +} + +func (i *uint224) UnmarshalText(input []byte) error { + bi := (*big.Int)(i) + if _, ok := bi.SetString(string(input), 10); !ok { + return types.ErrInvalidType + } + + return i.Verify() +} + +func (i *uint224) MarshalText() ([]byte, error) { + bi := (*big.Int)(i) + return []byte(bi.String()), nil +} + +func (i *uint224) Verify() error { + bi := (*big.Int)(i) + + if bi.BitLen() > 224 || bi.Sign() < 0 { + return types.ErrInvalidType + } + + return nil +} + +func (i *uint224) private() {} + +func init() { + typeMap["uint224"] = &ABIEncodingType{ + native: reflect.TypeOf((*big.Int)(nil)), + checked: reflect.TypeOf((*uint224)(nil)), + } +} + +type int232 big.Int + +func (i *int232) UnmarshalCBOR(input []byte) error { + bi := (*big.Int)(i) + if err := cbor.Unmarshal(input, bi); err != nil { + return err + } + + return i.Verify() +} + +func (i *int232) MarshalCBOR() ([]byte, error) { + return cbor.Marshal((*big.Int)(i)) +} + +func (i *int232) UnmarshalText(input []byte) error { + bi := (*big.Int)(i) + if _, ok := bi.SetString(string(input), 10); !ok { + return types.ErrInvalidType + } + + return i.Verify() +} + +func (i *int232) MarshalText() ([]byte, error) { + bi := (*big.Int)(i) + return []byte(bi.String()), nil +} + +func (i *int232) Verify() error { + bi := (*big.Int)(i) + + if !codec.FitsInNBitsSigned(232, bi) { + return types.ErrInvalidType + } + + return nil +} + +func (i *int232) private() {} + +func init() { + typeMap["int232"] = &ABIEncodingType{ + native: reflect.TypeOf((*big.Int)(nil)), + checked: reflect.TypeOf((*int232)(nil)), + } +} + +type uint232 big.Int + +func (i *uint232) UnmarshalCBOR(input []byte) error { + bi := (*big.Int)(i) + if err := cbor.Unmarshal(input, bi); err != nil { + return err + } + + return i.Verify() +} + +func (i *uint232) MarshalCBOR() ([]byte, error) { + return cbor.Marshal((*big.Int)(i)) +} + +func (i *uint232) UnmarshalText(input []byte) error { + bi := (*big.Int)(i) + if _, ok := bi.SetString(string(input), 10); !ok { + return types.ErrInvalidType + } + + return i.Verify() +} + +func (i *uint232) MarshalText() ([]byte, error) { + bi := (*big.Int)(i) + return []byte(bi.String()), nil +} + +func (i *uint232) Verify() error { + bi := (*big.Int)(i) + + if bi.BitLen() > 232 || bi.Sign() < 0 { + return types.ErrInvalidType + } + + return nil +} + +func (i *uint232) private() {} + +func init() { + typeMap["uint232"] = &ABIEncodingType{ + native: reflect.TypeOf((*big.Int)(nil)), + checked: reflect.TypeOf((*uint232)(nil)), + } +} + +type int240 big.Int + +func (i *int240) UnmarshalCBOR(input []byte) error { + bi := (*big.Int)(i) + if err := cbor.Unmarshal(input, bi); err != nil { + return err + } + + return i.Verify() +} + +func (i *int240) MarshalCBOR() ([]byte, error) { + return cbor.Marshal((*big.Int)(i)) +} + +func (i *int240) UnmarshalText(input []byte) error { + bi := (*big.Int)(i) + if _, ok := bi.SetString(string(input), 10); !ok { + return types.ErrInvalidType + } + + return i.Verify() +} + +func (i *int240) MarshalText() ([]byte, error) { + bi := (*big.Int)(i) + return []byte(bi.String()), nil +} + +func (i *int240) Verify() error { + bi := (*big.Int)(i) + + if !codec.FitsInNBitsSigned(240, bi) { + return types.ErrInvalidType + } + + return nil +} + +func (i *int240) private() {} + +func init() { + typeMap["int240"] = &ABIEncodingType{ + native: reflect.TypeOf((*big.Int)(nil)), + checked: reflect.TypeOf((*int240)(nil)), + } +} + +type uint240 big.Int + +func (i *uint240) UnmarshalCBOR(input []byte) error { + bi := (*big.Int)(i) + if err := cbor.Unmarshal(input, bi); err != nil { + return err + } + + return i.Verify() +} + +func (i *uint240) MarshalCBOR() ([]byte, error) { + return cbor.Marshal((*big.Int)(i)) +} + +func (i *uint240) UnmarshalText(input []byte) error { + bi := (*big.Int)(i) + if _, ok := bi.SetString(string(input), 10); !ok { + return types.ErrInvalidType + } + + return i.Verify() +} + +func (i *uint240) MarshalText() ([]byte, error) { + bi := (*big.Int)(i) + return []byte(bi.String()), nil +} + +func (i *uint240) Verify() error { + bi := (*big.Int)(i) + + if bi.BitLen() > 240 || bi.Sign() < 0 { + return types.ErrInvalidType + } + + return nil +} + +func (i *uint240) private() {} + +func init() { + typeMap["uint240"] = &ABIEncodingType{ + native: reflect.TypeOf((*big.Int)(nil)), + checked: reflect.TypeOf((*uint240)(nil)), + } +} + +type int248 big.Int + +func (i *int248) UnmarshalCBOR(input []byte) error { + bi := (*big.Int)(i) + if err := cbor.Unmarshal(input, bi); err != nil { + return err + } + + return i.Verify() +} + +func (i *int248) MarshalCBOR() ([]byte, error) { + return cbor.Marshal((*big.Int)(i)) +} + +func (i *int248) UnmarshalText(input []byte) error { + bi := (*big.Int)(i) + if _, ok := bi.SetString(string(input), 10); !ok { + return types.ErrInvalidType + } + + return i.Verify() +} + +func (i *int248) MarshalText() ([]byte, error) { + bi := (*big.Int)(i) + return []byte(bi.String()), nil +} + +func (i *int248) Verify() error { + bi := (*big.Int)(i) + + if !codec.FitsInNBitsSigned(248, bi) { + return types.ErrInvalidType + } + + return nil +} + +func (i *int248) private() {} + +func init() { + typeMap["int248"] = &ABIEncodingType{ + native: reflect.TypeOf((*big.Int)(nil)), + checked: reflect.TypeOf((*int248)(nil)), + } +} + +type uint248 big.Int + +func (i *uint248) UnmarshalCBOR(input []byte) error { + bi := (*big.Int)(i) + if err := cbor.Unmarshal(input, bi); err != nil { + return err + } + + return i.Verify() +} + +func (i *uint248) MarshalCBOR() ([]byte, error) { + return cbor.Marshal((*big.Int)(i)) +} + +func (i *uint248) UnmarshalText(input []byte) error { + bi := (*big.Int)(i) + if _, ok := bi.SetString(string(input), 10); !ok { + return types.ErrInvalidType + } + + return i.Verify() +} + +func (i *uint248) MarshalText() ([]byte, error) { + bi := (*big.Int)(i) + return []byte(bi.String()), nil +} + +func (i *uint248) Verify() error { + bi := (*big.Int)(i) + + if bi.BitLen() > 248 || bi.Sign() < 0 { + return types.ErrInvalidType + } + + return nil +} + +func (i *uint248) private() {} + +func init() { + typeMap["uint248"] = &ABIEncodingType{ + native: reflect.TypeOf((*big.Int)(nil)), + checked: reflect.TypeOf((*uint248)(nil)), + } +} + +type int256 big.Int + +func (i *int256) UnmarshalCBOR(input []byte) error { + bi := (*big.Int)(i) + if err := cbor.Unmarshal(input, bi); err != nil { + return err + } + + return i.Verify() +} + +func (i *int256) MarshalCBOR() ([]byte, error) { + return cbor.Marshal((*big.Int)(i)) +} + +func (i *int256) UnmarshalText(input []byte) error { + bi := (*big.Int)(i) + if _, ok := bi.SetString(string(input), 10); !ok { + return types.ErrInvalidType + } + + return i.Verify() +} + +func (i *int256) MarshalText() ([]byte, error) { + bi := (*big.Int)(i) + return []byte(bi.String()), nil +} + +func (i *int256) Verify() error { + bi := (*big.Int)(i) + + if !codec.FitsInNBitsSigned(256, bi) { + return types.ErrInvalidType + } + + return nil +} + +func (i *int256) private() {} + +func init() { + typeMap["int256"] = &ABIEncodingType{ + native: reflect.TypeOf((*big.Int)(nil)), + checked: reflect.TypeOf((*int256)(nil)), + } +} + +type uint256 big.Int + +func (i *uint256) UnmarshalCBOR(input []byte) error { + bi := (*big.Int)(i) + if err := cbor.Unmarshal(input, bi); err != nil { + return err + } + + return i.Verify() +} + +func (i *uint256) MarshalCBOR() ([]byte, error) { + return cbor.Marshal((*big.Int)(i)) +} + +func (i *uint256) UnmarshalText(input []byte) error { + bi := (*big.Int)(i) + if _, ok := bi.SetString(string(input), 10); !ok { + return types.ErrInvalidType + } + + return i.Verify() +} + +func (i *uint256) MarshalText() ([]byte, error) { + bi := (*big.Int)(i) + return []byte(bi.String()), nil +} + +func (i *uint256) Verify() error { + bi := (*big.Int)(i) + + if bi.BitLen() > 256 || bi.Sign() < 0 { + return types.ErrInvalidType + } + + return nil +} + +func (i *uint256) private() {} + +func init() { + typeMap["uint256"] = &ABIEncodingType{ + native: reflect.TypeOf((*big.Int)(nil)), + checked: reflect.TypeOf((*uint256)(nil)), + } +} diff --git a/core/services/relay/evm/types/int_types_test.go b/core/services/relay/evm/types/int_types_test.go new file mode 100644 index 00000000000..6930287c869 --- /dev/null +++ b/core/services/relay/evm/types/int_types_test.go @@ -0,0 +1,54 @@ +package types + +import ( + "errors" + "fmt" + "math/big" + "reflect" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink-common/pkg/types" +) + +func TestIntTypes(t *testing.T) { + t.Parallel() + for i := 24; i <= 256; i += 8 { + if i == 64 || i == 32 { + continue + } + t.Run(fmt.Sprintf("int%v", i), func(t *testing.T) { + tpe, ok := GetAbiEncodingType(fmt.Sprintf("int%v", i)) + require.True(t, ok) + minVal := new(big.Int).Neg(new(big.Int).Exp(big.NewInt(2), big.NewInt(int64(i-1)), nil)) + maxVal := new(big.Int).Sub(new(big.Int).Exp(big.NewInt(2), big.NewInt(int64(i-1)), nil), big.NewInt(1)) + assertBigIntBounds(t, tpe, minVal, maxVal) + }) + + t.Run(fmt.Sprintf("uint%v", i), func(t *testing.T) { + tep, ok := GetAbiEncodingType(fmt.Sprintf("uint%v", i)) + require.True(t, ok) + minVal := big.NewInt(0) + maxVal := new(big.Int).Sub(new(big.Int).Exp(big.NewInt(2), big.NewInt(int64(i)), nil), big.NewInt(1)) + assertBigIntBounds(t, tep, minVal, maxVal) + }) + } +} + +func assertBigIntBounds(t *testing.T, tpe *ABIEncodingType, min, max *big.Int) { + t.Helper() + assert.Equal(t, reflect.TypeOf(min), tpe.native) + assert.True(t, tpe.checked.ConvertibleTo(reflect.TypeOf(min))) + minMinusOne := new(big.Int).Sub(min, big.NewInt(1)) + maxPlusOne := new(big.Int).Add(max, big.NewInt(1)) + sbi := reflect.ValueOf(min).Convert(tpe.checked).Interface().(SizedBigInt) + assert.NoError(t, sbi.Verify()) + sbi = reflect.ValueOf(max).Convert(tpe.checked).Interface().(SizedBigInt) + assert.NoError(t, sbi.Verify()) + sbi = reflect.ValueOf(minMinusOne).Convert(tpe.checked).Interface().(SizedBigInt) + assert.True(t, errors.Is(types.ErrInvalidType, sbi.Verify())) + sbi = reflect.ValueOf(maxPlusOne).Convert(tpe.checked).Interface().(SizedBigInt) + assert.True(t, errors.Is(types.ErrInvalidType, sbi.Verify())) +} diff --git a/core/services/relay/evm/types/size_helper.go b/core/services/relay/evm/types/size_helper.go new file mode 100644 index 00000000000..921b8a28315 --- /dev/null +++ b/core/services/relay/evm/types/size_helper.go @@ -0,0 +1,69 @@ +package types + +import ( + "github.com/ethereum/go-ethereum/accounts/abi" + + commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" +) + +func GetMaxSize(n int, args abi.Arguments) (int, error) { + size := 0 + for _, arg := range args { + tmp := arg.Type + argSize, _, err := getTypeSize(n, &tmp, true, false) + if err != nil { + return 0, err + } + size += argSize + } + + return size, nil +} + +func getTypeSize(n int, t *abi.Type, dynamicTypeAllowed bool, isNested bool) (int, bool, error) { + // See https://docs.soliditylang.org/en/latest/abi-spec.html#formal-specification-of-the-encoding + switch t.T { + case abi.ArrayTy: + elmSize, _, err := getTypeSize(n, t.Elem, false, true) + return t.Size * elmSize, false, err + case abi.SliceTy: + if !dynamicTypeAllowed { + return 0, false, commontypes.ErrInvalidType + } + elmSize, _, err := getTypeSize(n, t.Elem, false, true) + return 32 /*header*/ + 32 /*footer*/ + elmSize*n, true, err + case abi.BytesTy, abi.StringTy: + if !dynamicTypeAllowed { + return 0, false, commontypes.ErrInvalidType + } + totalSize := (n + 31) / 32 * 32 // strings and bytes are padded to 32 bytes + return 32 /*header*/ + 32 /*footer*/ + totalSize, true, nil + case abi.TupleTy: + return getTupleSize(n, t, isNested) + default: + // types are padded to 32 bytes + return 32, false, nil + } +} + +func getTupleSize(n int, t *abi.Type, isNested bool) (int, bool, error) { + // No header or footer, because if the tuple is dynamically sized we would need to know the inner slice sizes + // so it would return error for that element. + size := 0 + dynamic := false + for _, elm := range t.TupleElems { + argSize, dynamicArg, err := getTypeSize(n, elm, !isNested, true) + if err != nil { + return 0, false, err + } + dynamic = dynamic || dynamicArg + size += argSize + } + + if dynamic { + // offset for the element needs to be included there are dynamic elements + size += 32 + } + + return size, dynamic, nil +} diff --git a/core/services/relay/evm/types/size_helper_test.go b/core/services/relay/evm/types/size_helper_test.go new file mode 100644 index 00000000000..202269a4536 --- /dev/null +++ b/core/services/relay/evm/types/size_helper_test.go @@ -0,0 +1,263 @@ +package types_test + +import ( + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" + + "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/types" +) + +const anyNumElements = 10 + +func TestGetMaxSize(t *testing.T) { + t.Run("Basic types all encode to 32 bytes", func(t *testing.T) { + args := abi.Arguments{ + {Name: "I8", Type: mustType(t, "int8")}, + {Name: "I80", Type: mustType(t, "int80")}, + {Name: "I256", Type: mustType(t, "int256")}, + {Name: "B3", Type: mustType(t, "bytes3")}, + {Name: "B32", Type: mustType(t, "bytes32")}, + {Name: "TF", Type: mustType(t, "bool")}, + } + + runSizeTest(t, anyNumElements, args, int8(9), big.NewInt(3), big.NewInt(200), [3]byte{1, 3, 4}, make32Bytes(1), true) + }) + + t.Run("Slices of basic types all encode to 32 bytes each + header and footer", func(t *testing.T) { + args := abi.Arguments{ + {Name: "I8", Type: mustType(t, "int8[]")}, + {Name: "I80", Type: mustType(t, "int80[]")}, + {Name: "I256", Type: mustType(t, "int256[]")}, + {Name: "B3", Type: mustType(t, "bytes3[]")}, + {Name: "B32", Type: mustType(t, "bytes32[]")}, + {Name: "TF", Type: mustType(t, "bool[]")}, + } + + i8 := []int8{9, 2, 1, 3, 5, 6, 2, 1, 2, 3} + i80 := []*big.Int{big.NewInt(9), big.NewInt(2), big.NewInt(1), big.NewInt(3), big.NewInt(5), big.NewInt(6), big.NewInt(2), big.NewInt(1), big.NewInt(2), big.NewInt(3)} + i256 := []*big.Int{big.NewInt(119), big.NewInt(112), big.NewInt(1), big.NewInt(3), big.NewInt(5), big.NewInt(6), big.NewInt(2), big.NewInt(1), big.NewInt(2), big.NewInt(3)} + b3 := [][3]byte{{1, 2, 3}, {1, 2, 3}, {1, 2, 3}, {1, 2, 3}, {1, 2, 3}, {1, 2, 3}, {1, 2, 3}, {1, 2, 3}, {1, 2, 3}, {1, 2, 3}} + b32 := [][32]byte{make32Bytes(1), make32Bytes(2), make32Bytes(3), make32Bytes(4), make32Bytes(5), make32Bytes(6), make32Bytes(7), make32Bytes(8), make32Bytes(9), make32Bytes(10)} + tf := []bool{true, false, true, false, true, false, true, false, true, false} + runSizeTest(t, anyNumElements, args, i8, i80, i256, b3, b32, tf) + }) + + t.Run("Arrays of basic types all encode to 32 bytes each", func(t *testing.T) { + args := abi.Arguments{ + {Name: "I8", Type: mustType(t, "int8[3]")}, + {Name: "I80", Type: mustType(t, "int80[3]")}, + {Name: "I256", Type: mustType(t, "int256[3]")}, + {Name: "B3", Type: mustType(t, "bytes3[3]")}, + {Name: "B32", Type: mustType(t, "bytes32[3]")}, + {Name: "TF", Type: mustType(t, "bool[3]")}, + } + + i8 := [3]int8{9, 2, 1} + i80 := [3]*big.Int{big.NewInt(9), big.NewInt(2), big.NewInt(1)} + i256 := [3]*big.Int{big.NewInt(119), big.NewInt(112), big.NewInt(1)} + b3 := [3][3]byte{{1, 2, 3}, {1, 2, 3}, {1, 2, 3}} + b32 := [3][32]byte{make32Bytes(1), make32Bytes(2), make32Bytes(3)} + tf := [3]bool{true, false, true} + runSizeTest(t, anyNumElements, args, i8, i80, i256, b3, b32, tf) + }) + + t.Run("Tuples are a sum of their elements", func(t *testing.T) { + tuple1 := []abi.ArgumentMarshaling{ + {Name: "I8", Type: "int8"}, + {Name: "I80", Type: "int80"}, + {Name: "I256", Type: "int256"}, + {Name: "B3", Type: "bytes3"}, + {Name: "B32", Type: "bytes32"}, + {Name: "TF", Type: "bool"}, + } + t1, err := abi.NewType("tuple", "", tuple1) + require.NoError(t, err) + + tuple2 := []abi.ArgumentMarshaling{ + {Name: "I80", Type: "int80"}, + {Name: "TF", Type: "bool"}, + } + t2, err := abi.NewType("tuple", "", tuple2) + require.NoError(t, err) + + args := abi.Arguments{ + {Name: "t1", Type: t1}, + {Name: "t2", Type: t2}, + } + arg1 := struct { + I8 int8 + I80 *big.Int + I256 *big.Int + B3 [3]byte + B32 [32]byte + TF bool + }{ + int8(9), big.NewInt(3), big.NewInt(200), [3]byte{1, 3, 4}, make32Bytes(1), true, + } + + arg2 := struct { + I80 *big.Int + TF bool + }{ + big.NewInt(3), true, + } + runSizeTest(t, anyNumElements, args, arg1, arg2) + }) + + t.Run("Slices of tuples are a sum of their elements with header and footer", func(t *testing.T) { + tuple1 := []abi.ArgumentMarshaling{ + {Name: "I80", Type: "int80"}, + {Name: "TF", Type: "bool"}, + } + t1, err := abi.NewType("tuple[]", "", tuple1) + require.NoError(t, err) + + args := abi.Arguments{ + {Name: "t1", Type: t1}, + } + arg1 := []struct { + I80 *big.Int + TF bool + }{ + {big.NewInt(1), true}, + {big.NewInt(2), true}, + {big.NewInt(3), true}, + {big.NewInt(4), false}, + {big.NewInt(5), true}, + {big.NewInt(6), true}, + {big.NewInt(7), true}, + {big.NewInt(8), false}, + {big.NewInt(9), true}, + {big.NewInt(10), true}, + } + runSizeTest(t, anyNumElements, args, arg1) + }) + + t.Run("Arrays of tuples are a sum of their elements", func(t *testing.T) { + tuple1 := []abi.ArgumentMarshaling{ + {Name: "I80", Type: "int80"}, + {Name: "TF", Type: "bool"}, + } + t1, err := abi.NewType("tuple[3]", "", tuple1) + require.NoError(t, err) + + args := abi.Arguments{ + {Name: "t1", Type: t1}, + } + arg1 := []struct { + I80 *big.Int + TF bool + }{ + {big.NewInt(1), true}, + {big.NewInt(2), true}, + {big.NewInt(3), true}, + } + runSizeTest(t, anyNumElements, args, arg1) + + }) + + t.Run("Bytes pack themselves", func(t *testing.T) { + args := abi.Arguments{{Name: "B", Type: mustType(t, "bytes")}} + t.Run("No padding needed", func(t *testing.T) { + padded := []byte("12345789022345678903234567890412345678905123456789061234") + runSizeTest(t, 64, args, padded) + }) + t.Run("Padding needed", func(t *testing.T) { + needsPadding := []byte("12345789022345678903234567890412345678905123456") + runSizeTest(t, 56, args, needsPadding) + }) + }) + + t.Run("Strings pack themselves", func(t *testing.T) { + args := abi.Arguments{{Name: "B", Type: mustType(t, "string")}} + t.Run("No padding needed", func(t *testing.T) { + padded := "12345789022345678903234567890412345678905123456789061234" + runSizeTest(t, 64, args, padded) + }) + t.Run("Padding needed", func(t *testing.T) { + needsPadding := "12345789022345678903234567890412345678905123456" + runSizeTest(t, 56, args, needsPadding) + }) + }) + + t.Run("Nested dynamic types return errors", func(t *testing.T) { + t.Run("Slice in slice", func(t *testing.T) { + args := abi.Arguments{{Name: "B", Type: mustType(t, "int32[][]")}} + _, err := types.GetMaxSize(anyNumElements, args) + assert.IsType(t, commontypes.ErrInvalidType, err) + }) + t.Run("Slice in array", func(t *testing.T) { + args := abi.Arguments{{Name: "B", Type: mustType(t, "int32[][2]")}} + _, err := types.GetMaxSize(anyNumElements, args) + assert.IsType(t, commontypes.ErrInvalidType, err) + }) + }) + + t.Run("Slices in a top level tuple works as-if they are the sized element", func(t *testing.T) { + tuple1 := []abi.ArgumentMarshaling{ + {Name: "I80", Type: "int80[]"}, + {Name: "TF", Type: "bool[]"}, + } + t1, err := abi.NewType("tuple", "", tuple1) + require.NoError(t, err) + args := abi.Arguments{{Name: "tuple", Type: t1}} + + arg1 := struct { + I80 []*big.Int + TF []bool + }{ + I80: []*big.Int{big.NewInt(1), big.NewInt(2), big.NewInt(3), big.NewInt(4), big.NewInt(5), big.NewInt(6), big.NewInt(7), big.NewInt(8), big.NewInt(9), big.NewInt(10)}, + TF: []bool{true, true, true, false, true, true, true, false, true, true}, + } + + runSizeTest(t, anyNumElements, args, arg1) + }) + + t.Run("Nested dynamic tuples return errors", func(t *testing.T) { + tuple1 := []abi.ArgumentMarshaling{ + {Name: "I8", Type: "int8"}, + {Name: "I80", Type: "int80"}, + {Name: "I256", Type: "int256"}, + {Name: "B3", Type: "bytes3"}, + {Name: "B32", Type: "bytes32"}, + {Name: "TF", Type: "bool[]"}, + } + + tuple2 := []abi.ArgumentMarshaling{ + {Name: "I80", Type: "int80"}, + {Name: "T1", Type: "tuple", Components: tuple1}, + } + t2, err := abi.NewType("tuple", "", tuple2) + require.NoError(t, err) + + args := abi.Arguments{{Name: "t2", Type: t2}} + _, err = types.GetMaxSize(anyNumElements, args) + assert.IsType(t, commontypes.ErrInvalidType, err) + }) +} + +func runSizeTest(t *testing.T, n int, args abi.Arguments, params ...any) { + + actual, err := types.GetMaxSize(n, args) + require.NoError(t, err) + + expected, err := args.Pack(params...) + require.NoError(t, err) + assert.Equal(t, len(expected), actual) +} + +func mustType(t *testing.T, name string) abi.Type { + aType, err := abi.NewType(name, "", []abi.ArgumentMarshaling{}) + require.NoError(t, err) + return aType +} + +func make32Bytes(firstByte byte) [32]byte { + return [32]byte{firstByte, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3} +} diff --git a/core/services/relay/evm/types/types.go b/core/services/relay/evm/types/types.go index 129ccb4a5e6..0697605edab 100644 --- a/core/services/relay/evm/types/types.go +++ b/core/services/relay/evm/types/types.go @@ -1,6 +1,7 @@ package types import ( + "bytes" "context" "encoding/json" "errors" @@ -8,50 +9,108 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/lib/pq" - - "gopkg.in/guregu/null.v2" - ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + "gopkg.in/guregu/null.v2" + "github.com/smartcontractkit/chainlink-common/pkg/codec" "github.com/smartcontractkit/chainlink-common/pkg/services" "github.com/smartcontractkit/chainlink-common/pkg/types" - commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils/big" ) type ChainReaderConfig struct { - // ChainContractReaders key is contract name - ChainContractReaders map[string]ChainContractReader `json:"chainContractReaders"` + // Contracts key is contract name + Contracts map[string]ChainContractReader `json:"contracts" toml:"contracts"` +} + +type CodecConfig struct { + // Configs key is the type's name for the codec + Configs map[string]ChainCodecConfig `json:"configs" toml:"configs"` +} + +type ChainCodecConfig struct { + TypeABI string `json:"typeAbi" toml:"typeABI"` + ModifierConfigs codec.ModifiersConfig `toml:"modifierConfigs,omitempty"` } type ChainContractReader struct { - ContractABI string `json:"contractABI"` - // ChainReaderDefinitions key is chainAgnostic read name. - ChainReaderDefinitions map[string]ChainReaderDefinition `json:"chainReaderDefinitions"` + ContractABI string `json:"contractABI" toml:"contractABI"` + // key is genericName from config + Configs map[string]*ChainReaderDefinition `json:"configs" toml:"configs"` +} + +type ChainReaderDefinition chainReaderDefinitionFields + +// chainReaderDefinitionFields has the fields for ChainReaderDefinition but no methods. +// This is necessary because package json recognizes the text encoding methods used for TOML, +// and would infinitely recurse on itself. +type chainReaderDefinitionFields struct { + CacheEnabled bool `json:"cacheEnabled,omitempty"` + // chain specific contract method name or event type. + ChainSpecificName string `json:"chainSpecificName"` + ReadType ReadType `json:"readType,omitempty"` + InputModifications codec.ModifiersConfig `json:"inputModifications,omitempty"` + OutputModifications codec.ModifiersConfig `json:"outputModifications,omitempty"` + + // EventInputFields allows you to choose which indexed fields are expected from the input + EventInputFields []string `json:"eventInputFields,omitempty"` +} + +func (d *ChainReaderDefinition) MarshalText() ([]byte, error) { + var b bytes.Buffer + e := json.NewEncoder(&b) + e.SetIndent("", " ") + if err := e.Encode((*chainReaderDefinitionFields)(d)); err != nil { + return nil, err + } + return b.Bytes(), nil } -type ChainReaderDefinition struct { - ChainSpecificName string `json:"chainSpecificName"` // chain specific contract method name or event type. - Params map[string]any `json:"params"` - ReturnValues []string `json:"returnValues"` - CacheEnabled bool `json:"cacheEnabled"` - ReadType ReadType `json:"readType"` +func (d *ChainReaderDefinition) UnmarshalText(b []byte) error { + return json.Unmarshal(b, (*chainReaderDefinitionFields)(d)) } -type ReadType int64 +type ReadType int const ( - Method ReadType = 0 - Event ReadType = 1 + Method ReadType = iota + Event ) +func (r ReadType) String() string { + switch r { + case Method: + return "method" + case Event: + return "event" + } + return fmt.Sprintf("ReadType(%d)", r) +} + +func (r ReadType) MarshalText() ([]byte, error) { + return []byte(r.String()), nil +} + +func (r *ReadType) UnmarshalText(text []byte) error { + switch string(text) { + case "method": + *r = Method + return nil + case "event": + *r = Event + return nil + } + return fmt.Errorf("unrecognized ReadType: %s", string(text)) +} + type RelayConfig struct { ChainID *big.Big `json:"chainID"` FromBlock uint64 `json:"fromBlock"` EffectiveTransmitterID null.String `json:"effectiveTransmitterID"` ConfigContractAddress *common.Address `json:"configContractAddress"` ChainReader *ChainReaderConfig `json:"chainReader"` + Codec *CodecConfig `json:"codec"` // Contract-specific SendingKeys pq.StringArray `json:"sendingKeys"` @@ -60,15 +119,15 @@ type RelayConfig struct { FeedID *common.Hash `json:"feedID"` } +var ErrBadRelayConfig = errors.New("bad relay config") + type RelayOpts struct { // TODO BCF-2508 -- should anyone ever get the raw config bytes that are embedded in args? if not, // make this private and wrap the arg fields with funcs on RelayOpts - commontypes.RelayArgs + types.RelayArgs c *RelayConfig } -var ErrBadRelayConfig = errors.New("bad relay config") - func NewRelayOpts(args types.RelayArgs) *RelayOpts { return &RelayOpts{ RelayArgs: args, @@ -98,9 +157,9 @@ type ConfigPoller interface { Replay(ctx context.Context, fromBlock int64) error } -// TODO(FUN-668): Migrate this fully into commontypes.FunctionsProvider +// TODO(FUN-668): Migrate this fully into types.FunctionsProvider type FunctionsProvider interface { - commontypes.FunctionsProvider + types.FunctionsProvider LogPollerWrapper() LogPollerWrapper } diff --git a/go.mod b/go.mod index a5aa4eb23dc..b8180998e92 100644 --- a/go.mod +++ b/go.mod @@ -65,12 +65,12 @@ require ( github.com/shopspring/decimal v1.3.1 github.com/smartcontractkit/caigo v0.0.0-20230621050857-b29a4ca8c704 github.com/smartcontractkit/chainlink-automation v1.0.2-0.20240118014648-1ab6a88c9429 - github.com/smartcontractkit/chainlink-common v0.1.7-0.20240118012339-4864e2306bb1 - github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20231218175426-6e0427c661e5 + github.com/smartcontractkit/chainlink-common v0.1.7-0.20240119143538-04c7f63ad53a + github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240120192246-4bb04c997ca0 github.com/smartcontractkit/chainlink-data-streams v0.0.0-20231204152908-a6e3fe8ff2a1 - github.com/smartcontractkit/chainlink-feeds v0.0.0-20231127231053-2232d3a6766d - github.com/smartcontractkit/chainlink-solana v1.0.3-0.20231219140448-151a4725f312 - github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20231219014050-0c4a7831293a + github.com/smartcontractkit/chainlink-feeds v0.0.0-20240119021347-3c541a78cdb8 + github.com/smartcontractkit/chainlink-solana v1.0.3-0.20240122152632-38444d2ad8ba + github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20240119162652-3a7274645007 github.com/smartcontractkit/chainlink-vrf v0.0.0-20231120191722-fef03814f868 github.com/smartcontractkit/libocr v0.0.0-20240112202000-6359502d2ff1 github.com/smartcontractkit/tdh2/go/ocr2/decryptionplugin v0.0.0-20230906073235-9e478e5e19f1 @@ -106,7 +106,6 @@ require ( ) require ( - cloud.google.com/go/compute v1.23.3 // indirect contrib.go.opencensus.io/exporter/stackdriver v0.13.5 // indirect cosmossdk.io/api v0.3.1 // indirect cosmossdk.io/core v0.5.1 // indirect @@ -313,10 +312,9 @@ require ( golang.org/x/arch v0.6.0 // indirect golang.org/x/mod v0.14.0 // indirect golang.org/x/net v0.19.0 // indirect - golang.org/x/oauth2 v0.15.0 // indirect golang.org/x/sys v0.15.0 // indirect golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect - google.golang.org/appengine v1.6.8 // indirect + google.golang.org/api v0.149.0 // indirect google.golang.org/genproto v0.0.0-20231030173426-d783a09b4405 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17 // indirect diff --git a/go.sum b/go.sum index 5578bf8ff31..0eccd2bba0b 100644 --- a/go.sum +++ b/go.sum @@ -1153,18 +1153,18 @@ github.com/smartcontractkit/caigo v0.0.0-20230621050857-b29a4ca8c704 h1:T3lFWumv github.com/smartcontractkit/caigo v0.0.0-20230621050857-b29a4ca8c704/go.mod h1:2QuJdEouTWjh5BDy5o/vgGXQtR4Gz8yH1IYB5eT7u4M= github.com/smartcontractkit/chainlink-automation v1.0.2-0.20240118014648-1ab6a88c9429 h1:xkejUBZhcBpBrTSfxc91Iwzadrb6SXw8ks69bHIQ9Ww= github.com/smartcontractkit/chainlink-automation v1.0.2-0.20240118014648-1ab6a88c9429/go.mod h1:wJmVvDf4XSjsahWtfUq3wvIAYEAuhr7oxmxYnEL/LGQ= -github.com/smartcontractkit/chainlink-common v0.1.7-0.20240118012339-4864e2306bb1 h1:3cWO2/lFVDul5SVTgl4/RX/GXcT8Zq5NGMPeNEz09tY= -github.com/smartcontractkit/chainlink-common v0.1.7-0.20240118012339-4864e2306bb1/go.mod h1:f+0ei9N4PlTJHu7pbGzEjTnBUr45syPdGFu5+31lS5Q= -github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20231218175426-6e0427c661e5 h1:kBnmjv3fxU7krVIqZFvo1m4F6qBc4vPURQFX/mcChhI= -github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20231218175426-6e0427c661e5/go.mod h1:EoM7wQ81mov7wsUzG4zEnnr0EH0POEo/I0hRDg433TU= +github.com/smartcontractkit/chainlink-common v0.1.7-0.20240119143538-04c7f63ad53a h1:lgM0yPo0KqSntLY4Y42RAH3avdv+Kyne8n+VM7cwlxo= +github.com/smartcontractkit/chainlink-common v0.1.7-0.20240119143538-04c7f63ad53a/go.mod h1:05rRF84QKlIOF5LfTBPkHdw4UpBI2G3zxRcuZ65bPjk= +github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240120192246-4bb04c997ca0 h1:NALwENz6vQ972DuD9AZjqRjyNSxH9ptNapizQGLI+2s= +github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240120192246-4bb04c997ca0/go.mod h1:NcVAT/GETDBvIoAej5K6OYqAtDOkF6vO5pYw/hLuYVU= github.com/smartcontractkit/chainlink-data-streams v0.0.0-20231204152908-a6e3fe8ff2a1 h1:xYqRgZO0nMSO8CBCMR0r3WA+LZ4kNL8a6bnbyk/oBtQ= github.com/smartcontractkit/chainlink-data-streams v0.0.0-20231204152908-a6e3fe8ff2a1/go.mod h1:GuPvyXryvbiUZIHmPeLBz4L+yJKeyGUjrDfd1KNne+o= -github.com/smartcontractkit/chainlink-feeds v0.0.0-20231127231053-2232d3a6766d h1:w4MsbOtNk6nD/mcXLstHWk9hB6g7QLtcAfhPjhwvOaQ= -github.com/smartcontractkit/chainlink-feeds v0.0.0-20231127231053-2232d3a6766d/go.mod h1:YPAfLNowdBwiKiYOwgwtbJHi8AJWbcxkbOY0ItAvkfc= -github.com/smartcontractkit/chainlink-solana v1.0.3-0.20231219140448-151a4725f312 h1:ziqC+WW/2/UI6w3DShy7HGzJMWWLIYHT5ev2Qaa3h6I= -github.com/smartcontractkit/chainlink-solana v1.0.3-0.20231219140448-151a4725f312/go.mod h1:vqnojBNdzHNI6asWezJlottUiVEXudMEGf2Mz5R+xps= -github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20231219014050-0c4a7831293a h1:atCXqF8e5U2zfEaA87cKJs+K1MAbOVh3V05gEd60fR0= -github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20231219014050-0c4a7831293a/go.mod h1:YWKpf+hO9XMlzIWQT8yGoky3aeFLzMUVsjbs80LD77M= +github.com/smartcontractkit/chainlink-feeds v0.0.0-20240119021347-3c541a78cdb8 h1:1BcjXuviSAKttOX7BZoVHRZZGfxqoA2+AL8tykmkdoc= +github.com/smartcontractkit/chainlink-feeds v0.0.0-20240119021347-3c541a78cdb8/go.mod h1:vy1L7NybTy2F/Yv7BOh+oZBa1MACD6gzd1+DkcSkfp8= +github.com/smartcontractkit/chainlink-solana v1.0.3-0.20240122152632-38444d2ad8ba h1:6rnQrD8NaLfLOPHszW1hbpviqpU8011gzdZk6wKP1xY= +github.com/smartcontractkit/chainlink-solana v1.0.3-0.20240122152632-38444d2ad8ba/go.mod h1:OZfzyayUdwsVBqxvbEMqwUntQT8HbFbgyqoudvwfVN0= +github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20240119162652-3a7274645007 h1:KwB0H2P/gxJgt823Ku1fTcFLDKMj6zsP3wbQGlBOm4U= +github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20240119162652-3a7274645007/go.mod h1:EbZAlb/2K6mKr26u3+3cLBe/caJaqCHw786On94C43g= github.com/smartcontractkit/chainlink-vrf v0.0.0-20231120191722-fef03814f868 h1:FFdvEzlYwcuVHkdZ8YnZR/XomeMGbz5E2F2HZI3I3w8= github.com/smartcontractkit/chainlink-vrf v0.0.0-20231120191722-fef03814f868/go.mod h1:Kn1Hape05UzFZ7bOUnm3GVsHzP0TNrVmpfXYNHdqGGs= github.com/smartcontractkit/go-plugin v0.0.0-20231003134350-e49dad63b306 h1:ko88+ZznniNJZbZPWAvHQU8SwKAdHngdDZ+pvVgB5ss= @@ -1664,7 +1664,6 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= diff --git a/integration-tests/actions/ocr2_helpers.go b/integration-tests/actions/ocr2_helpers.go index 37db348815b..829d85a8498 100644 --- a/integration-tests/actions/ocr2_helpers.go +++ b/integration-tests/actions/ocr2_helpers.go @@ -22,6 +22,7 @@ import ( "github.com/smartcontractkit/chainlink-testing-framework/blockchain" ctfClient "github.com/smartcontractkit/chainlink-testing-framework/client" + "github.com/smartcontractkit/chainlink/v2/core/services/job" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/chaintype" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/testhelpers" @@ -376,6 +377,7 @@ func StartNewOCR2Round( timeout time.Duration, logger zerolog.Logger, ) error { + time.Sleep(5 * time.Second) for i := 0; i < len(ocrInstances); i++ { err := ocrInstances[i].RequestNewRound() if err != nil { @@ -383,9 +385,12 @@ func StartNewOCR2Round( } ocrRound := contracts.NewOffchainAggregatorV2RoundConfirmer(ocrInstances[i], big.NewInt(roundNumber), timeout, logger) client.AddHeaderEventSubscription(ocrInstances[i].Address(), ocrRound) - err = client.WaitForEvents() + err = ocrRound.Wait() // wait for OCR Round to complete if err != nil { - return fmt.Errorf("failed to wait for event subscriptions of OCR instance %d: %w", i+1, err) + return fmt.Errorf("failed to wait for OCR Round %d to complete instance %d", roundNumber, i) + } + if !ocrRound.Complete() { + return fmt.Errorf("failed to complete OCR Round %d for ocr instance %d", roundNumber, i) } } return nil diff --git a/integration-tests/actions/ocr2_helpers_local.go b/integration-tests/actions/ocr2_helpers_local.go index 4a08921b8d1..8a0a02c050f 100644 --- a/integration-tests/actions/ocr2_helpers_local.go +++ b/integration-tests/actions/ocr2_helpers_local.go @@ -18,7 +18,9 @@ import ( "golang.org/x/sync/errgroup" "gopkg.in/guregu/null.v4" + "github.com/smartcontractkit/chainlink-common/pkg/codec" "github.com/smartcontractkit/chainlink-testing-framework/docker/test_env" + evmtypes "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/types" "github.com/smartcontractkit/chainlink/v2/core/services/job" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/chaintype" @@ -38,6 +40,7 @@ func CreateOCRv2JobsLocal( mockAdapterValue int, // Value to get from the mock server when querying the path chainId uint64, // EVM chain ID forwardingAllowed bool, + enableChainReaderAndCodec bool, ) error { // Collect P2P ID bootstrapP2PIds, err := bootstrapNode.MustReadP2PKeys() @@ -125,6 +128,43 @@ func CreateOCRv2JobsLocal( P2PV2Bootstrappers: pq.StringArray{p2pV2Bootstrapper}, // bootstrap node key and address @bootstrap:6690 }, } + if enableChainReaderAndCodec { + ocrSpec.OCR2OracleSpec.RelayConfig["chainReader"] = evmtypes.ChainReaderConfig{ + Contracts: map[string]evmtypes.ChainContractReader{ + "median": { + ContractABI: `[{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"requester","type":"address"},{"indexed":false,"internalType":"bytes32","name":"configDigest","type":"bytes32"},{"indexed":false,"internalType":"uint32","name":"epoch","type":"uint32"},{"indexed":false,"internalType":"uint8","name":"round","type":"uint8"}],"name":"RoundRequested","type":"event"},{"inputs":[],"name":"latestTransmissionDetails","outputs":[{"internalType":"bytes32","name":"configDigest","type":"bytes32"},{"internalType":"uint32","name":"epoch","type":"uint32"},{"internalType":"uint8","name":"round","type":"uint8"},{"internalType":"int192","name":"latestAnswer_","type":"int192"},{"internalType":"uint64","name":"latestTimestamp_","type":"uint64"}],"stateMutability":"view","type":"function"}]`, + Configs: map[string]*evmtypes.ChainReaderDefinition{ + "LatestTransmissionDetails": { + ChainSpecificName: "latestTransmissionDetails", + OutputModifications: codec.ModifiersConfig{ + &codec.EpochToTimeModifierConfig{ + Fields: []string{"LatestTimestamp_"}, + }, + &codec.RenameModifierConfig{ + Fields: map[string]string{ + "LatestAnswer_": "LatestAnswer", + "LatestTimestamp_": "LatestTimestamp", + }, + }, + }, + }, + "LatestRoundRequested": { + ChainSpecificName: "RoundRequested", + ReadType: evmtypes.Event, + }, + }, + }, + }, + } + ocrSpec.OCR2OracleSpec.RelayConfig["codec"] = evmtypes.CodecConfig{ + Configs: map[string]evmtypes.ChainCodecConfig{ + "MedianReport": { + TypeABI: `[{"Name": "Timestamp","Type": "uint32"},{"Name": "Observers","Type": "bytes32"},{"Name": "Observations","Type": "int192[]"},{"Name": "JuelsPerFeeCoin","Type": "int192"}]`, + }, + }, + } + } + _, err = chainlinkNode.MustCreateJob(ocrSpec) if err != nil { return fmt.Errorf("creating OCR task job on OCR node have failed: %w", err) diff --git a/integration-tests/client/chainlink_models.go b/integration-tests/client/chainlink_models.go index e6e1de25e41..9268968800d 100644 --- a/integration-tests/client/chainlink_models.go +++ b/integration-tests/client/chainlink_models.go @@ -6,6 +6,7 @@ import ( "text/template" "time" + "github.com/pelletier/go-toml/v2" "gopkg.in/guregu/null.v4" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils/big" @@ -1027,6 +1028,12 @@ func (o *OCR2TaskJobSpec) String() (string, error) { if o.OCR2OracleSpec.FeedID != nil { feedID = o.OCR2OracleSpec.FeedID.Hex() } + relayConfig, err := toml.Marshal(struct { + RelayConfig job.JSONConfig `toml:"relayConfig"` + }{RelayConfig: o.OCR2OracleSpec.RelayConfig}) + if err != nil { + return "", fmt.Errorf("failed to marshal relay config: %w", err) + } specWrap := struct { Name string JobType string @@ -1036,7 +1043,7 @@ func (o *OCR2TaskJobSpec) String() (string, error) { FeedID string Relay string PluginType string - RelayConfig map[string]interface{} + RelayConfig string PluginConfig map[string]interface{} P2PV2Bootstrappers []string OCRKeyBundleID string @@ -1056,7 +1063,7 @@ func (o *OCR2TaskJobSpec) String() (string, error) { FeedID: feedID, Relay: string(o.OCR2OracleSpec.Relay), PluginType: string(o.OCR2OracleSpec.PluginType), - RelayConfig: o.OCR2OracleSpec.RelayConfig, + RelayConfig: string(relayConfig), PluginConfig: o.OCR2OracleSpec.PluginConfig, P2PV2Bootstrappers: o.OCR2OracleSpec.P2PV2Bootstrappers, OCRKeyBundleID: o.OCR2OracleSpec.OCRKeyBundleID.String, @@ -1071,37 +1078,37 @@ func (o *OCR2TaskJobSpec) String() (string, error) { type = "{{ .JobType }}" name = "{{.Name}}" forwardingAllowed = {{.ForwardingAllowed}} -{{if .MaxTaskDuration}} +{{- if .MaxTaskDuration}} maxTaskDuration = "{{ .MaxTaskDuration }}" {{end}} -{{if .PluginType}} +{{- if .PluginType}} pluginType = "{{ .PluginType }}" {{end}} relay = "{{.Relay}}" schemaVersion = 1 contractID = "{{.ContractID}}" -{{if .FeedID}} +{{- if .FeedID}} feedID = "{{.FeedID}}" {{end}} -{{if eq .JobType "offchainreporting2" }} +{{- if eq .JobType "offchainreporting2" }} ocrKeyBundleID = "{{.OCRKeyBundleID}}" {{end}} -{{if eq .JobType "offchainreporting2" }} +{{- if eq .JobType "offchainreporting2" }} transmitterID = "{{.TransmitterID}}" {{end}} -{{if .BlockchainTimeout}} +{{- if .BlockchainTimeout}} blockchainTimeout = "{{.BlockchainTimeout}}" {{end}} -{{if .ContractConfirmations}} +{{- if .ContractConfirmations}} contractConfigConfirmations = {{.ContractConfirmations}} {{end}} -{{if .TrackerPollInterval}} +{{- if .TrackerPollInterval}} contractConfigTrackerPollInterval = "{{.TrackerPollInterval}}" {{end}} -{{if .TrackerSubscribeInterval}} +{{- if .TrackerSubscribeInterval}} contractConfigTrackerSubscribeInterval = "{{.TrackerSubscribeInterval}}" {{end}} -{{if .P2PV2Bootstrappers}} +{{- if .P2PV2Bootstrappers}} p2pv2Bootstrappers = [{{range .P2PV2Bootstrappers}}"{{.}}",{{end}}]{{end}} -{{if .MonitoringEndpoint}} +{{- if .MonitoringEndpoint}} monitoringEndpoint = "{{.MonitoringEndpoint}}" {{end}} -{{if .ObservationSource}} +{{- if .ObservationSource}} observationSource = """ {{.ObservationSource}} """{{end}} @@ -1109,8 +1116,7 @@ observationSource = """ [pluginConfig]{{range $key, $value := .PluginConfig}} {{$key}} = {{$value}}{{end}} {{end}} -[relayConfig]{{range $key, $value := .RelayConfig}} -{{$key}} = {{$value}}{{end}} +{{.RelayConfig}} ` return MarshallTemplate(specWrap, "OCR2 Job", ocr2TemplateString) } diff --git a/integration-tests/client/chainlink_models_test.go b/integration-tests/client/chainlink_models_test.go new file mode 100644 index 00000000000..5dbac2c27c4 --- /dev/null +++ b/integration-tests/client/chainlink_models_test.go @@ -0,0 +1,135 @@ +package client + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink-common/pkg/codec" + "github.com/smartcontractkit/chainlink/v2/core/services/job" + evmtypes "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/types" +) + +func TestOCR2TaskJobSpec_String(t *testing.T) { + for _, tt := range []struct { + name string + spec OCR2TaskJobSpec + exp string + }{ + { + name: "chain-reader-codec", + spec: OCR2TaskJobSpec{ + OCR2OracleSpec: job.OCR2OracleSpec{ + RelayConfig: map[string]interface{}{ + "chainID": 1337, + "fromBlock": 42, + "chainReader": evmtypes.ChainReaderConfig{ + Contracts: map[string]evmtypes.ChainContractReader{ + "median": { + ContractABI: `[ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "requester", + "type": "address" + } + ], + "name": "RoundRequested", + "type": "event" + } +] +`, + Configs: map[string]*evmtypes.ChainReaderDefinition{ + "LatestTransmissionDetails": { + ChainSpecificName: "latestTransmissionDetails", + OutputModifications: codec.ModifiersConfig{ + &codec.EpochToTimeModifierConfig{ + Fields: []string{"LatestTimestamp_"}, + }, + &codec.RenameModifierConfig{ + Fields: map[string]string{ + "LatestAnswer_": "LatestAnswer", + "LatestTimestamp_": "LatestTimestamp", + }, + }, + }, + }, + "LatestRoundRequested": { + ChainSpecificName: "RoundRequested", + ReadType: evmtypes.Event, + }, + }, + }, + }, + }, + "codec": evmtypes.CodecConfig{ + Configs: map[string]evmtypes.ChainCodecConfig{ + "MedianReport": { + TypeABI: `[ + { + "Name": "Timestamp", + "Type": "uint32" + } +] +`, + }, + }, + }, + }, + PluginConfig: map[string]interface{}{"juelsPerFeeCoinSource": ` // data source 1 + ds1 [type=bridge name="%s"]; + ds1_parse [type=jsonparse path="data"]; + ds1_multiply [type=multiply times=2]; + + // data source 2 + ds2 [type=http method=GET url="%s"]; + ds2_parse [type=jsonparse path="data"]; + ds2_multiply [type=multiply times=2]; + + ds1 -> ds1_parse -> ds1_multiply -> answer1; + ds2 -> ds2_parse -> ds2_multiply -> answer1; + + answer1 [type=median index=0]; +`, + }, + }, + }, + exp: ` +type = "" +name = "" +forwardingAllowed = false +relay = "" +schemaVersion = 1 +contractID = "" + +[relayConfig] +chainID = 1337 +fromBlock = 42 + +[relayConfig.chainReader] +[relayConfig.chainReader.contracts] +[relayConfig.chainReader.contracts.median] +contractABI = "[\n {\n \"anonymous\": false,\n \"inputs\": [\n {\n \"indexed\": true,\n \"internalType\": \"address\",\n \"name\": \"requester\",\n \"type\": \"address\"\n }\n ],\n \"name\": \"RoundRequested\",\n \"type\": \"event\"\n }\n]\n" + +[relayConfig.chainReader.contracts.median.configs] +LatestRoundRequested = "{\n \"chainSpecificName\": \"RoundRequested\",\n \"readType\": \"event\"\n}\n" +LatestTransmissionDetails = "{\n \"chainSpecificName\": \"latestTransmissionDetails\",\n \"output_modifications\": [\n {\n \"Fields\": [\n \"LatestTimestamp_\"\n ],\n \"Type\": \"epoch to time\"\n },\n {\n \"Fields\": {\n \"LatestAnswer_\": \"LatestAnswer\",\n \"LatestTimestamp_\": \"LatestTimestamp\"\n },\n \"Type\": \"rename\"\n }\n ]\n}\n" + +[relayConfig.codec] +[relayConfig.codec.configs] +[relayConfig.codec.configs.MedianReport] +typeABI = "[\n {\n \"Name\": \"Timestamp\",\n \"Type\": \"uint32\"\n }\n]\n" + +`, + }, + } { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.spec.String() + require.NoError(t, err) + require.Equal(t, tt.exp, got) + }) + } +} diff --git a/integration-tests/docker/test_env/cl_node.go b/integration-tests/docker/test_env/cl_node.go index fefc9ca8838..1cf3bc05416 100644 --- a/integration-tests/docker/test_env/cl_node.go +++ b/integration-tests/docker/test_env/cl_node.go @@ -440,6 +440,7 @@ func (n *ClNode) getContainerRequest(secrets string) ( AlwaysPullImage: n.AlwaysPullImage, Image: fmt.Sprintf("%s:%s", n.ContainerImage, n.ContainerVersion), ExposedPorts: []string{"6688/tcp"}, + Env: n.ContainerEnvs, Entrypoint: []string{"chainlink", "-c", configPath, "-s", secretsPath, diff --git a/integration-tests/go.mod b/integration-tests/go.mod index 53135cfea7c..f559c517f0b 100644 --- a/integration-tests/go.mod +++ b/integration-tests/go.mod @@ -25,7 +25,7 @@ require ( github.com/segmentio/ksuid v1.0.4 github.com/slack-go/slack v0.12.2 github.com/smartcontractkit/chainlink-automation v1.0.2-0.20240118014648-1ab6a88c9429 - github.com/smartcontractkit/chainlink-common v0.1.7-0.20240118012339-4864e2306bb1 + github.com/smartcontractkit/chainlink-common v0.1.7-0.20240119143538-04c7f63ad53a github.com/smartcontractkit/chainlink-testing-framework v1.22.6 github.com/smartcontractkit/chainlink-vrf v0.0.0-20231120191722-fef03814f868 github.com/smartcontractkit/chainlink/v2 v2.0.0-00010101000000-000000000000 @@ -364,11 +364,11 @@ require ( github.com/shopspring/decimal v1.3.1 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/smartcontractkit/caigo v0.0.0-20230621050857-b29a4ca8c704 // indirect - github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20231218175426-6e0427c661e5 // indirect + github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240120192246-4bb04c997ca0 // indirect github.com/smartcontractkit/chainlink-data-streams v0.0.0-20231204152908-a6e3fe8ff2a1 // indirect - github.com/smartcontractkit/chainlink-feeds v0.0.0-20231127231053-2232d3a6766d // indirect - github.com/smartcontractkit/chainlink-solana v1.0.3-0.20231219140448-151a4725f312 // indirect - github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20231219014050-0c4a7831293a // indirect + github.com/smartcontractkit/chainlink-feeds v0.0.0-20240119021347-3c541a78cdb8 // indirect + github.com/smartcontractkit/chainlink-solana v1.0.3-0.20240122152632-38444d2ad8ba // indirect + github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20240119162652-3a7274645007 // indirect github.com/smartcontractkit/tdh2/go/ocr2/decryptionplugin v0.0.0-20230906073235-9e478e5e19f1 // indirect github.com/smartcontractkit/wsrpc v0.7.2 // indirect github.com/soheilhy/cmux v0.1.5 // indirect diff --git a/integration-tests/go.sum b/integration-tests/go.sum index 09c9979d578..ab678c43b5b 100644 --- a/integration-tests/go.sum +++ b/integration-tests/go.sum @@ -1486,18 +1486,18 @@ github.com/smartcontractkit/caigo v0.0.0-20230621050857-b29a4ca8c704 h1:T3lFWumv github.com/smartcontractkit/caigo v0.0.0-20230621050857-b29a4ca8c704/go.mod h1:2QuJdEouTWjh5BDy5o/vgGXQtR4Gz8yH1IYB5eT7u4M= github.com/smartcontractkit/chainlink-automation v1.0.2-0.20240118014648-1ab6a88c9429 h1:xkejUBZhcBpBrTSfxc91Iwzadrb6SXw8ks69bHIQ9Ww= github.com/smartcontractkit/chainlink-automation v1.0.2-0.20240118014648-1ab6a88c9429/go.mod h1:wJmVvDf4XSjsahWtfUq3wvIAYEAuhr7oxmxYnEL/LGQ= -github.com/smartcontractkit/chainlink-common v0.1.7-0.20240118012339-4864e2306bb1 h1:3cWO2/lFVDul5SVTgl4/RX/GXcT8Zq5NGMPeNEz09tY= -github.com/smartcontractkit/chainlink-common v0.1.7-0.20240118012339-4864e2306bb1/go.mod h1:f+0ei9N4PlTJHu7pbGzEjTnBUr45syPdGFu5+31lS5Q= -github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20231218175426-6e0427c661e5 h1:kBnmjv3fxU7krVIqZFvo1m4F6qBc4vPURQFX/mcChhI= -github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20231218175426-6e0427c661e5/go.mod h1:EoM7wQ81mov7wsUzG4zEnnr0EH0POEo/I0hRDg433TU= +github.com/smartcontractkit/chainlink-common v0.1.7-0.20240119143538-04c7f63ad53a h1:lgM0yPo0KqSntLY4Y42RAH3avdv+Kyne8n+VM7cwlxo= +github.com/smartcontractkit/chainlink-common v0.1.7-0.20240119143538-04c7f63ad53a/go.mod h1:05rRF84QKlIOF5LfTBPkHdw4UpBI2G3zxRcuZ65bPjk= +github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240120192246-4bb04c997ca0 h1:NALwENz6vQ972DuD9AZjqRjyNSxH9ptNapizQGLI+2s= +github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240120192246-4bb04c997ca0/go.mod h1:NcVAT/GETDBvIoAej5K6OYqAtDOkF6vO5pYw/hLuYVU= github.com/smartcontractkit/chainlink-data-streams v0.0.0-20231204152908-a6e3fe8ff2a1 h1:xYqRgZO0nMSO8CBCMR0r3WA+LZ4kNL8a6bnbyk/oBtQ= github.com/smartcontractkit/chainlink-data-streams v0.0.0-20231204152908-a6e3fe8ff2a1/go.mod h1:GuPvyXryvbiUZIHmPeLBz4L+yJKeyGUjrDfd1KNne+o= -github.com/smartcontractkit/chainlink-feeds v0.0.0-20231127231053-2232d3a6766d h1:w4MsbOtNk6nD/mcXLstHWk9hB6g7QLtcAfhPjhwvOaQ= -github.com/smartcontractkit/chainlink-feeds v0.0.0-20231127231053-2232d3a6766d/go.mod h1:YPAfLNowdBwiKiYOwgwtbJHi8AJWbcxkbOY0ItAvkfc= -github.com/smartcontractkit/chainlink-solana v1.0.3-0.20231219140448-151a4725f312 h1:ziqC+WW/2/UI6w3DShy7HGzJMWWLIYHT5ev2Qaa3h6I= -github.com/smartcontractkit/chainlink-solana v1.0.3-0.20231219140448-151a4725f312/go.mod h1:vqnojBNdzHNI6asWezJlottUiVEXudMEGf2Mz5R+xps= -github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20231219014050-0c4a7831293a h1:atCXqF8e5U2zfEaA87cKJs+K1MAbOVh3V05gEd60fR0= -github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20231219014050-0c4a7831293a/go.mod h1:YWKpf+hO9XMlzIWQT8yGoky3aeFLzMUVsjbs80LD77M= +github.com/smartcontractkit/chainlink-feeds v0.0.0-20240119021347-3c541a78cdb8 h1:1BcjXuviSAKttOX7BZoVHRZZGfxqoA2+AL8tykmkdoc= +github.com/smartcontractkit/chainlink-feeds v0.0.0-20240119021347-3c541a78cdb8/go.mod h1:vy1L7NybTy2F/Yv7BOh+oZBa1MACD6gzd1+DkcSkfp8= +github.com/smartcontractkit/chainlink-solana v1.0.3-0.20240122152632-38444d2ad8ba h1:6rnQrD8NaLfLOPHszW1hbpviqpU8011gzdZk6wKP1xY= +github.com/smartcontractkit/chainlink-solana v1.0.3-0.20240122152632-38444d2ad8ba/go.mod h1:OZfzyayUdwsVBqxvbEMqwUntQT8HbFbgyqoudvwfVN0= +github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20240119162652-3a7274645007 h1:KwB0H2P/gxJgt823Ku1fTcFLDKMj6zsP3wbQGlBOm4U= +github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20240119162652-3a7274645007/go.mod h1:EbZAlb/2K6mKr26u3+3cLBe/caJaqCHw786On94C43g= github.com/smartcontractkit/chainlink-testing-framework v1.22.6 h1:5kWMlo99RY/ys4EWGMPsEg1sfY67f5YQogI8PohdRvw= github.com/smartcontractkit/chainlink-testing-framework v1.22.6/go.mod h1:FBRC6elqaqO8jiMZMfa3UKrvwzZhMGUtYqVTGYmfFrA= github.com/smartcontractkit/chainlink-vrf v0.0.0-20231120191722-fef03814f868 h1:FFdvEzlYwcuVHkdZ8YnZR/XomeMGbz5E2F2HZI3I3w8= @@ -2189,8 +2189,8 @@ google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz513 google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= -google.golang.org/api v0.147.0 h1:Can3FaQo9LlVqxJCodNmeZW/ib3/qKAY3rFeXiHo5gc= -google.golang.org/api v0.147.0/go.mod h1:pQ/9j83DcmPd/5C9e2nFOdjjNkDZ1G+zkbK2uvdkJMs= +google.golang.org/api v0.149.0 h1:b2CqT6kG+zqJIVKRQ3ELJVLN1PwHZ6DJ3dW8yl82rgY= +google.golang.org/api v0.149.0/go.mod h1:Mwn1B7JTXrzXtnvmzQE2BD6bYZQ8DShKZDZbeN9I7qI= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= diff --git a/integration-tests/smoke/forwarders_ocr2_test.go b/integration-tests/smoke/forwarders_ocr2_test.go index 90dc20a3898..f1a01643f5e 100644 --- a/integration-tests/smoke/forwarders_ocr2_test.go +++ b/integration-tests/smoke/forwarders_ocr2_test.go @@ -77,7 +77,7 @@ func TestForwarderOCR2Basic(t *testing.T) { err = env.EVMClient.WaitForEvents() require.NoError(t, err, "Error waiting for events") - err = actions.CreateOCRv2JobsLocal(ocrInstances, bootstrapNode, workerNodes, env.MockAdapter, "ocr2", 5, env.EVMClient.GetChainID().Uint64(), true) + err = actions.CreateOCRv2JobsLocal(ocrInstances, bootstrapNode, workerNodes, env.MockAdapter, "ocr2", 5, env.EVMClient.GetChainID().Uint64(), true, false) require.NoError(t, err, "Error creating OCRv2 jobs with forwarders") err = env.EVMClient.WaitForEvents() require.NoError(t, err, "Error waiting for events") diff --git a/integration-tests/smoke/ocr2_test.go b/integration-tests/smoke/ocr2_test.go index 2603b6e9e6a..f45c324faed 100644 --- a/integration-tests/smoke/ocr2_test.go +++ b/integration-tests/smoke/ocr2_test.go @@ -12,23 +12,29 @@ import ( "github.com/smartcontractkit/chainlink-testing-framework/logging" "github.com/smartcontractkit/chainlink-testing-framework/utils/testcontext" + "github.com/smartcontractkit/chainlink/v2/core/config/env" + "github.com/smartcontractkit/chainlink/integration-tests/actions" "github.com/smartcontractkit/chainlink/integration-tests/contracts" "github.com/smartcontractkit/chainlink/integration-tests/docker/test_env" "github.com/smartcontractkit/chainlink/integration-tests/types/config/node" - "github.com/smartcontractkit/chainlink/v2/core/config/env" ) // Tests a basic OCRv2 median feed func TestOCRv2Basic(t *testing.T) { t.Parallel() + noMedianPlugin := map[string]string{string(env.MedianPluginCmd): ""} + medianPlugin := map[string]string{string(env.MedianPluginCmd): "chainlink-feeds"} for _, test := range []struct { - name string - env map[string]string + name string + env map[string]string + chainReaderAndCodec bool }{ - {"legacy", map[string]string{string(env.MedianPluginCmd): ""}}, - {"plugins", map[string]string{string(env.MedianPluginCmd): "chainlink-feeds"}}, + {"legacy", noMedianPlugin, false}, + {"legacy-chain-reader", noMedianPlugin, true}, + {"plugins", medianPlugin, false}, + {"plugins-chain-reader", medianPlugin, true}, } { test := test t.Run(test.name, func(t *testing.T) { @@ -80,7 +86,7 @@ func TestOCRv2Basic(t *testing.T) { aggregatorContracts, err := actions.DeployOCRv2Contracts(1, linkToken, env.ContractDeployer, transmitters, env.EVMClient, ocrOffchainOptions) require.NoError(t, err, "Error deploying OCRv2 aggregator contracts") - err = actions.CreateOCRv2JobsLocal(aggregatorContracts, bootstrapNode, workerNodes, env.MockAdapter, "ocr2", 5, env.EVMClient.GetChainID().Uint64(), false) + err = actions.CreateOCRv2JobsLocal(aggregatorContracts, bootstrapNode, workerNodes, env.MockAdapter, "ocr2", 5, env.EVMClient.GetChainID().Uint64(), false, test.chainReaderAndCodec) require.NoError(t, err, "Error creating OCRv2 jobs") ocrv2Config, err := actions.BuildMedianOCR2ConfigLocal(workerNodes, ocrOffchainOptions) @@ -161,7 +167,7 @@ func TestOCRv2Request(t *testing.T) { aggregatorContracts, err := actions.DeployOCRv2Contracts(1, linkToken, env.ContractDeployer, transmitters, env.EVMClient, ocrOffchainOptions) require.NoError(t, err, "Error deploying OCRv2 aggregator contracts") - err = actions.CreateOCRv2JobsLocal(aggregatorContracts, bootstrapNode, workerNodes, env.MockAdapter, "ocr2", 5, env.EVMClient.GetChainID().Uint64(), false) + err = actions.CreateOCRv2JobsLocal(aggregatorContracts, bootstrapNode, workerNodes, env.MockAdapter, "ocr2", 5, env.EVMClient.GetChainID().Uint64(), false, false) require.NoError(t, err, "Error creating OCRv2 jobs") ocrv2Config, err := actions.BuildMedianOCR2ConfigLocal(workerNodes, ocrOffchainOptions) @@ -236,7 +242,7 @@ func TestOCRv2JobReplacement(t *testing.T) { aggregatorContracts, err := actions.DeployOCRv2Contracts(1, linkToken, env.ContractDeployer, transmitters, env.EVMClient, ocrOffchainOptions) require.NoError(t, err, "Error deploying OCRv2 aggregator contracts") - err = actions.CreateOCRv2JobsLocal(aggregatorContracts, bootstrapNode, workerNodes, env.MockAdapter, "ocr2", 5, env.EVMClient.GetChainID().Uint64(), false) + err = actions.CreateOCRv2JobsLocal(aggregatorContracts, bootstrapNode, workerNodes, env.MockAdapter, "ocr2", 5, env.EVMClient.GetChainID().Uint64(), false, false) require.NoError(t, err, "Error creating OCRv2 jobs") ocrv2Config, err := actions.BuildMedianOCR2ConfigLocal(workerNodes, ocrOffchainOptions) @@ -272,7 +278,7 @@ func TestOCRv2JobReplacement(t *testing.T) { err = actions.DeleteBridges(nodeClients) require.NoError(t, err) - err = actions.CreateOCRv2JobsLocal(aggregatorContracts, bootstrapNode, workerNodes, env.MockAdapter, "ocr2", 15, env.EVMClient.GetChainID().Uint64(), false) + err = actions.CreateOCRv2JobsLocal(aggregatorContracts, bootstrapNode, workerNodes, env.MockAdapter, "ocr2", 15, env.EVMClient.GetChainID().Uint64(), false, false) require.NoError(t, err, "Error creating OCRv2 jobs") err = actions.WatchNewOCR2Round(3, aggregatorContracts, env.EVMClient, time.Minute*3, l) diff --git a/plugins/medianpoc/plugin_test.go b/plugins/medianpoc/plugin_test.go index ba06d5ea462..d4470eef70a 100644 --- a/plugins/medianpoc/plugin_test.go +++ b/plugins/medianpoc/plugin_test.go @@ -12,6 +12,7 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/types" "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" + "github.com/smartcontractkit/chainlink/v2/core/logger" ) @@ -75,6 +76,10 @@ func (p provider) ChainReader() types.ChainReader { return nil } +func (p provider) Codec() types.Codec { + return nil +} + func TestNewPlugin(t *testing.T) { lggr := logger.TestLogger(t) p := NewPlugin(lggr) diff --git a/sonar-project.properties b/sonar-project.properties index c40b5f361e1..b6f3f1567c6 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -3,7 +3,7 @@ sonar.sources=. sonar.python.version=3.8 # Full exclusions from the static analysis -sonar.exclusions=**/node_modules/**/*,**/mocks/**/*, **/testdata/**/*, **/contracts/typechain/**/*, **/contracts/artifacts/**/*, **/contracts/cache/**/*, **/contracts/scripts/**/*, **/generated/**/*, **/fixtures/**/*, **/docs/**/*, **/tools/**/*, **/*.pb.go, **/*report.xml, **/*.config.ts, **/*.txt, **/*.abi, **/*.bin, **/*_codecgen.go +sonar.exclusions=**/node_modules/**/*,**/mocks/**/*, **/testdata/**/*, **/contracts/typechain/**/*, **/contracts/artifacts/**/*, **/contracts/cache/**/*, **/contracts/scripts/**/*, **/generated/**/*, **/fixtures/**/*, **/docs/**/*, **/tools/**/*, **/*.pb.go, **/*report.xml, **/*.config.ts, **/*.txt, **/*.abi, **/*.bin, **/*_codecgen.go, core/services/relay/evm/types/*_gen.go, core/services/relay/evm/types/gen/main.go, core/services/relay/evm/testfiles/* # Coverage exclusions sonar.coverage.exclusions=**/*.test.ts, **/*_test.go, **/contracts/test/**/*, **/contracts/**/tests/**/*, **/core/**/testutils/**/*, **/core/**/mocks/**/*, **/core/**/cltest/**/*, **/integration-tests/**/*, **/generated/**/*, **/core/scripts**/* , **/*.pb.go, ./plugins/**/*, **/main.go, **/0195_add_not_null_to_evm_chain_id_in_job_specs.go # Duplication exclusions diff --git a/tools/bin/go_core_fuzz b/tools/bin/go_core_fuzz new file mode 100755 index 00000000000..3ea7d9bb0cb --- /dev/null +++ b/tools/bin/go_core_fuzz @@ -0,0 +1,36 @@ +#!/usr/bin/env bash +set -o pipefail +set +e + +SCRIPT_PATH=`dirname "$0"`; SCRIPT_PATH=`eval "cd \"$SCRIPT_PATH\" && pwd"` +OUTPUT_FILE=${OUTPUT_FILE:-"./output.txt"} +USE_TEE="${USE_TEE:-true}" + +echo "Failed tests and panics: ---------------------" +echo "" +GO_LDFLAGS=$(bash tools/bin/ldflags) +use_tee() { + if [ "$USE_TEE" = "true" ]; then + tee "$@" + else + cat > "$@" + fi +} +go test -json -ldflags "$GO_LDFLAGS" github.com/smartcontractkit/chainlink/v2/core/services/relay/evm -fuzz . -fuzztime 12s | use_tee $OUTPUT_FILE +EXITCODE=${PIPESTATUS[0]} + +# Assert no known sensitive strings present in test logger output +printf "\n----------------------------------------------\n\n" +echo "Beginning check of output logs for sensitive strings" +$SCRIPT_PATH/scrub_logs $OUTPUT_FILE +if [[ $? != 0 ]]; then + exit 1 +fi + +echo "Exit code: $EXITCODE" +if [[ $EXITCODE != 0 ]]; then + echo "Encountered test failures." +else + echo "All tests passed!" +fi +exit $EXITCODE From c45ff8999f44f9dae71194bc39bb8fc7dd446163 Mon Sep 17 00:00:00 2001 From: chainchad <96362174+chainchad@users.noreply.github.com> Date: Mon, 22 Jan 2024 17:10:00 -0500 Subject: [PATCH 6/7] Always checkout repo in workflow to support argo sync and rename image tag (#11846) * Always checkout repo in workflow to support argo sync * Add PR number as an image tag suffix. To avoid collisions. * Revert "Add PR number as an image tag suffix." This reverts commit f994e8d2e6adc06ba12a747b28b225dc49f96889. --- .github/workflows/build-publish-pr.yml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-publish-pr.yml b/.github/workflows/build-publish-pr.yml index fd5533c1b5b..25ffa394551 100644 --- a/.github/workflows/build-publish-pr.yml +++ b/.github/workflows/build-publish-pr.yml @@ -20,6 +20,9 @@ jobs: env: ECR_IMAGE_NAME: crib-chainlink-untrusted steps: + - name: Checkout repository + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - name: Git Short SHA shell: bash env: @@ -36,10 +39,6 @@ jobs: AWS_REGION: ${{ secrets.AWS_REGION }} AWS_ROLE_TO_ASSUME: ${{ secrets.AWS_OIDC_IAM_ROLE_PUBLISH_PR_ARN }} - - name: Checkout repository - if: steps.check-image.outputs.exists == 'false' - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - - name: Build and publish chainlink image if: steps.check-image.outputs.exists == 'false' uses: ./.github/actions/build-sign-publish-chainlink From 388e7794dffc9f228e04aec264ff5bcac7908520 Mon Sep 17 00:00:00 2001 From: Bolek <1416262+bolekk@users.noreply.github.com> Date: Mon, 22 Jan 2024 19:06:34 -0800 Subject: [PATCH 7/7] [Functions] Add extra validations for offchain heartbeats (#11783) 1. Add AllowedHeartbeatInitiators list to node's config and validate senders of incoming requests against it (same logic as in Gateway). 2. Validate Sender value in nodes' reponses to make sure it matches the expected node. Extend an integration test to cover this change. 3. Validate age of incoming requests against RequestTimeoutSec from job config to avoid processing ones that already timed out. 4. Disallow null-byte suffixes in message fields to avoid any potential confusion with default padding. --- core/services/functions/connector_handler.go | 83 +++++++++++-------- .../functions/connector_handler_test.go | 38 +++++++-- core/services/gateway/api/message.go | 10 +++ core/services/gateway/api/message_test.go | 18 +++- core/services/gateway/connectionmanager.go | 4 + .../gateway_integration_test.go | 77 +++++++++++++---- .../ocr2/plugins/functions/config/config.go | 1 + .../services/ocr2/plugins/functions/plugin.go | 15 ++-- .../ocr2/plugins/functions/plugin_test.go | 12 ++- 9 files changed, 193 insertions(+), 65 deletions(-) diff --git a/core/services/functions/connector_handler.go b/core/services/functions/connector_handler.go index 1594dc6eb56..18df644c876 100644 --- a/core/services/functions/connector_handler.go +++ b/core/services/functions/connector_handler.go @@ -6,6 +6,7 @@ import ( "crypto/ecdsa" "encoding/json" "fmt" + "strings" "sync" "time" @@ -25,34 +26,34 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/services/gateway/connector" hc "github.com/smartcontractkit/chainlink/v2/core/services/gateway/handlers/common" "github.com/smartcontractkit/chainlink/v2/core/services/gateway/handlers/functions" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/functions/config" "github.com/smartcontractkit/chainlink/v2/core/services/s4" ) type functionsConnectorHandler struct { services.StateMachine - connector connector.GatewayConnector - signerKey *ecdsa.PrivateKey - nodeAddress string - storage s4.Storage - allowlist functions.OnchainAllowlist - rateLimiter *hc.RateLimiter - subscriptions functions.OnchainSubscriptions - minimumBalance assets.Link - listener FunctionsListener - offchainTransmitter OffchainTransmitter - heartbeatRequests map[RequestID]*HeartbeatResponse - orderedRequests []RequestID - mu sync.Mutex - chStop services.StopChan - shutdownWaitGroup sync.WaitGroup - lggr logger.Logger + connector connector.GatewayConnector + signerKey *ecdsa.PrivateKey + nodeAddress string + storage s4.Storage + allowlist functions.OnchainAllowlist + rateLimiter *hc.RateLimiter + subscriptions functions.OnchainSubscriptions + minimumBalance assets.Link + listener FunctionsListener + offchainTransmitter OffchainTransmitter + allowedHeartbeatInitiators map[string]struct{} + heartbeatRequests map[RequestID]*HeartbeatResponse + requestTimeoutSec uint32 + orderedRequests []RequestID + mu sync.Mutex + chStop services.StopChan + shutdownWaitGroup sync.WaitGroup + lggr logger.Logger } -const ( - HeartbeatRequestTimeoutSec = 240 - HeartbeatCacheSize = 1000 -) +const HeartbeatCacheSize = 1000 var ( _ connector.Signer = &functionsConnectorHandler{} @@ -71,23 +72,29 @@ func InternalId(sender []byte, requestId []byte) RequestID { return RequestID(crypto.Keccak256Hash(append(sender, requestId...)).Bytes()) } -func NewFunctionsConnectorHandler(nodeAddress string, signerKey *ecdsa.PrivateKey, storage s4.Storage, allowlist functions.OnchainAllowlist, rateLimiter *hc.RateLimiter, subscriptions functions.OnchainSubscriptions, listener FunctionsListener, offchainTransmitter OffchainTransmitter, minimumBalance assets.Link, lggr logger.Logger) (*functionsConnectorHandler, error) { +func NewFunctionsConnectorHandler(pluginConfig *config.PluginConfig, signerKey *ecdsa.PrivateKey, storage s4.Storage, allowlist functions.OnchainAllowlist, rateLimiter *hc.RateLimiter, subscriptions functions.OnchainSubscriptions, listener FunctionsListener, offchainTransmitter OffchainTransmitter, lggr logger.Logger) (*functionsConnectorHandler, error) { if signerKey == nil || storage == nil || allowlist == nil || rateLimiter == nil || subscriptions == nil || listener == nil || offchainTransmitter == nil { return nil, fmt.Errorf("all dependencies must be non-nil") } + allowedHeartbeatInitiators := make(map[string]struct{}) + for _, initiator := range pluginConfig.AllowedHeartbeatInitiators { + allowedHeartbeatInitiators[strings.ToLower(initiator)] = struct{}{} + } return &functionsConnectorHandler{ - nodeAddress: nodeAddress, - signerKey: signerKey, - storage: storage, - allowlist: allowlist, - rateLimiter: rateLimiter, - subscriptions: subscriptions, - minimumBalance: minimumBalance, - listener: listener, - offchainTransmitter: offchainTransmitter, - heartbeatRequests: make(map[RequestID]*HeartbeatResponse), - chStop: make(services.StopChan), - lggr: lggr.Named("FunctionsConnectorHandler"), + nodeAddress: pluginConfig.GatewayConnectorConfig.NodeAddress, + signerKey: signerKey, + storage: storage, + allowlist: allowlist, + rateLimiter: rateLimiter, + subscriptions: subscriptions, + minimumBalance: pluginConfig.MinimumSubscriptionBalance, + listener: listener, + offchainTransmitter: offchainTransmitter, + allowedHeartbeatInitiators: allowedHeartbeatInitiators, + heartbeatRequests: make(map[RequestID]*HeartbeatResponse), + requestTimeoutSec: pluginConfig.RequestTimeoutSec, + chStop: make(services.StopChan), + lggr: lggr.Named("FunctionsConnectorHandler"), }, nil } @@ -211,6 +218,10 @@ func (h *functionsConnectorHandler) handleHeartbeat(ctx context.Context, gateway h.sendResponseAndLog(ctx, gatewayId, requestBody, internalErrorResponse(fmt.Sprintf("failed to unmarshal request: %v", err))) return } + if _, ok := h.allowedHeartbeatInitiators[requestBody.Sender]; !ok { + h.sendResponseAndLog(ctx, gatewayId, requestBody, internalErrorResponse("sender not allowed to send heartbeat requests")) + return + } if !bytes.Equal(request.RequestInitiator, fromAddr.Bytes()) { h.sendResponseAndLog(ctx, gatewayId, requestBody, internalErrorResponse("RequestInitiator doesn't match sender")) return @@ -219,6 +230,10 @@ func (h *functionsConnectorHandler) handleHeartbeat(ctx context.Context, gateway h.sendResponseAndLog(ctx, gatewayId, requestBody, internalErrorResponse("SubscriptionOwner doesn't match sender")) return } + if request.Timestamp < uint64(time.Now().Unix())-uint64(h.requestTimeoutSec) { + h.sendResponseAndLog(ctx, gatewayId, requestBody, internalErrorResponse("Request is too old")) + return + } internalId := InternalId(fromAddr.Bytes(), request.RequestId) request.RequestId = internalId[:] @@ -250,7 +265,7 @@ func internalErrorResponse(internalError string) HeartbeatResponse { func (h *functionsConnectorHandler) handleOffchainRequest(request *OffchainRequest) { defer h.shutdownWaitGroup.Done() stopCtx, _ := h.chStop.NewCtx() - ctx, cancel := context.WithTimeout(stopCtx, time.Duration(HeartbeatRequestTimeoutSec)*time.Second) + ctx, cancel := context.WithTimeout(stopCtx, time.Duration(h.requestTimeoutSec)*time.Second) defer cancel() err := h.listener.HandleOffchainRequest(ctx, request) if err != nil { diff --git a/core/services/functions/connector_handler_test.go b/core/services/functions/connector_handler_test.go index 409f9cdcc56..9a5a9042693 100644 --- a/core/services/functions/connector_handler_test.go +++ b/core/services/functions/connector_handler_test.go @@ -10,6 +10,7 @@ import ( "time" geth_common "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" "github.com/onsi/gomega" "github.com/smartcontractkit/chainlink-common/pkg/assets" @@ -19,9 +20,11 @@ import ( sfmocks "github.com/smartcontractkit/chainlink/v2/core/services/functions/mocks" "github.com/smartcontractkit/chainlink/v2/core/services/gateway/api" "github.com/smartcontractkit/chainlink/v2/core/services/gateway/common" + gwconnector "github.com/smartcontractkit/chainlink/v2/core/services/gateway/connector" gcmocks "github.com/smartcontractkit/chainlink/v2/core/services/gateway/connector/mocks" hc "github.com/smartcontractkit/chainlink/v2/core/services/gateway/handlers/common" gfmocks "github.com/smartcontractkit/chainlink/v2/core/services/gateway/handlers/functions/mocks" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/functions/config" "github.com/smartcontractkit/chainlink/v2/core/services/s4" s4mocks "github.com/smartcontractkit/chainlink/v2/core/services/s4/mocks" @@ -30,7 +33,7 @@ import ( "github.com/stretchr/testify/require" ) -func newOffchainRequest(t *testing.T, sender []byte) (*api.Message, functions.RequestID) { +func newOffchainRequest(t *testing.T, sender []byte, ageSec uint64) (*api.Message, functions.RequestID) { requestId := make([]byte, 32) _, err := rand.Read(requestId) require.NoError(t, err) @@ -39,6 +42,7 @@ func newOffchainRequest(t *testing.T, sender []byte) (*api.Message, functions.Re RequestInitiator: sender, SubscriptionId: 1, SubscriptionOwner: sender, + Timestamp: uint64(time.Now().Unix()) - ageSec, } internalId := functions.InternalId(request.RequestInitiator, request.RequestId) @@ -74,7 +78,15 @@ func TestFunctionsConnectorHandler(t *testing.T) { allowlist.On("Close", mock.Anything).Return(nil) subscriptions.On("Start", mock.Anything).Return(nil) subscriptions.On("Close", mock.Anything).Return(nil) - handler, err := functions.NewFunctionsConnectorHandler(addr.Hex(), privateKey, storage, allowlist, rateLimiter, subscriptions, listener, offchainTransmitter, *assets.NewLinkFromJuels(100), logger) + config := &config.PluginConfig{ + GatewayConnectorConfig: &gwconnector.ConnectorConfig{ + NodeAddress: addr.Hex(), + }, + MinimumSubscriptionBalance: *assets.NewLinkFromJuels(100), + RequestTimeoutSec: 1_000, + AllowedHeartbeatInitiators: []string{crypto.PubkeyToAddress(privateKey.PublicKey).Hex()}, + } + handler, err := functions.NewFunctionsConnectorHandler(config, privateKey, storage, allowlist, rateLimiter, subscriptions, listener, offchainTransmitter, logger) require.NoError(t, err) handler.SetConnector(connector) @@ -257,7 +269,7 @@ func TestFunctionsConnectorHandler(t *testing.T) { t.Run("heartbeat success", func(t *testing.T) { ctx := testutils.Context(t) - msg, internalId := newOffchainRequest(t, addr.Bytes()) + msg, internalId := newOffchainRequest(t, addr.Bytes(), 0) require.NoError(t, msg.Sign(privateKey)) // first call to trigger the request @@ -292,7 +304,7 @@ func TestFunctionsConnectorHandler(t *testing.T) { t.Run("heartbeat internal error", func(t *testing.T) { ctx := testutils.Context(t) - msg, _ := newOffchainRequest(t, addr.Bytes()) + msg, _ := newOffchainRequest(t, addr.Bytes(), 0) require.NoError(t, msg.Sign(privateKey)) // first call to trigger the request @@ -319,7 +331,23 @@ func TestFunctionsConnectorHandler(t *testing.T) { t.Run("heartbeat sender address doesn't match", func(t *testing.T) { ctx := testutils.Context(t) - msg, _ := newOffchainRequest(t, geth_common.BytesToAddress([]byte("0x1234")).Bytes()) + msg, _ := newOffchainRequest(t, geth_common.BytesToAddress([]byte("0x1234")).Bytes(), 0) + require.NoError(t, msg.Sign(privateKey)) + + var response functions.HeartbeatResponse + allowlist.On("Allow", addr).Return(true).Once() + connector.On("SendToGateway", mock.Anything, "gw1", mock.Anything).Run(func(args mock.Arguments) { + respMsg, ok := args[2].(*api.Message) + require.True(t, ok) + require.NoError(t, json.Unmarshal(respMsg.Body.Payload, &response)) + require.Equal(t, functions.RequestStateInternalError, response.Status) + }).Return(nil).Once() + handler.HandleGatewayMessage(ctx, "gw1", msg) + }) + + t.Run("heartbeat request too old", func(t *testing.T) { + ctx := testutils.Context(t) + msg, _ := newOffchainRequest(t, addr.Bytes(), 10_000) require.NoError(t, msg.Sign(privateKey)) var response functions.HeartbeatResponse diff --git a/core/services/gateway/api/message.go b/core/services/gateway/api/message.go index c01d3bb9f2e..5e6c8e49247 100644 --- a/core/services/gateway/api/message.go +++ b/core/services/gateway/api/message.go @@ -20,6 +20,7 @@ const ( MessageMethodMaxLen = 64 MessageDonIdMaxLen = 64 MessageReceiverLen = 2 + 2*20 + NullChar = "\x00" ) /* @@ -56,12 +57,21 @@ func (m *Message) Validate() error { if len(m.Body.MessageId) == 0 || len(m.Body.MessageId) > MessageIdMaxLen { return errors.New("invalid message ID length") } + if strings.HasSuffix(m.Body.MessageId, NullChar) { + return errors.New("message ID ending with null bytes") + } if len(m.Body.Method) == 0 || len(m.Body.Method) > MessageMethodMaxLen { return errors.New("invalid method name length") } + if strings.HasSuffix(m.Body.Method, NullChar) { + return errors.New("method name ending with null bytes") + } if len(m.Body.DonId) == 0 || len(m.Body.DonId) > MessageDonIdMaxLen { return errors.New("invalid DON ID length") } + if strings.HasSuffix(m.Body.DonId, NullChar) { + return errors.New("DON ID ending with null bytes") + } if len(m.Body.Receiver) != 0 && len(m.Body.Receiver) != MessageReceiverLen { return errors.New("invalid Receiver length") } diff --git a/core/services/gateway/api/message_test.go b/core/services/gateway/api/message_test.go index a0835ea24bb..1f292db26b9 100644 --- a/core/services/gateway/api/message_test.go +++ b/core/services/gateway/api/message_test.go @@ -31,22 +31,38 @@ func TestMessage_Validate(t *testing.T) { // missing message ID msg.Body.MessageId = "" require.Error(t, msg.Validate()) + // message ID ending with null bytes + msg.Body.MessageId = "myid\x00\x00" + require.Error(t, msg.Validate()) msg.Body.MessageId = "abcd" + require.NoError(t, msg.Validate()) // missing DON ID msg.Body.DonId = "" require.Error(t, msg.Validate()) + // DON ID ending with null bytes + msg.Body.DonId = "mydon\x00\x00" + require.Error(t, msg.Validate()) msg.Body.DonId = "donA" + require.NoError(t, msg.Validate()) - // method too long + // method name too long msg.Body.Method = string(bytes.Repeat([]byte("a"), api.MessageMethodMaxLen+1)) require.Error(t, msg.Validate()) + // empty method name + msg.Body.Method = "" + require.Error(t, msg.Validate()) + // method name ending with null bytes + msg.Body.Method = "method\x00" + require.Error(t, msg.Validate()) msg.Body.Method = "request" + require.NoError(t, msg.Validate()) // incorrect receiver msg.Body.Receiver = "blah" require.Error(t, msg.Validate()) msg.Body.Receiver = "0x0000000000000000000000000000000000000000" + require.NoError(t, msg.Validate()) // invalid signature msg.Signature = "0x00" diff --git a/core/services/gateway/connectionmanager.go b/core/services/gateway/connectionmanager.go index 9f88b51e7b5..e5f7fb13afb 100644 --- a/core/services/gateway/connectionmanager.go +++ b/core/services/gateway/connectionmanager.go @@ -287,6 +287,10 @@ func (m *donConnectionManager) readLoop(nodeAddress string, nodeState *nodeState m.lggr.Errorw("message validation error when reading from node", "nodeAddress", nodeAddress, "err", err) break } + if msg.Body.Sender != nodeAddress { + m.lggr.Errorw("message sender mismatch when reading from node", "nodeAddress", nodeAddress, "sender", msg.Body.Sender) + break + } err = m.handler.HandleNodeMessage(ctx, msg, nodeAddress) if err != nil { m.lggr.Error("error when calling HandleNodeMessage ", err) diff --git a/core/services/gateway/integration_tests/gateway_integration_test.go b/core/services/gateway/integration_tests/gateway_integration_test.go index 9e4900efeee..a2064b7a591 100644 --- a/core/services/gateway/integration_tests/gateway_integration_test.go +++ b/core/services/gateway/integration_tests/gateway_integration_test.go @@ -5,6 +5,7 @@ import ( "context" "crypto/ecdsa" "fmt" + "io" "net/http" "strings" "sync/atomic" @@ -36,18 +37,18 @@ Path = "/node" Port = 0 HandshakeTimeoutMillis = 2_000 MaxRequestBytes = 20_000 -ReadTimeoutMillis = 100 -RequestTimeoutMillis = 100 -WriteTimeoutMillis = 100 +ReadTimeoutMillis = 1000 +RequestTimeoutMillis = 1000 +WriteTimeoutMillis = 1000 [UserServerConfig] Path = "/user" Port = 0 ContentTypeHeader = "application/jsonrpc" MaxRequestBytes = 20_000 -ReadTimeoutMillis = 100 -RequestTimeoutMillis = 100 -WriteTimeoutMillis = 100 +ReadTimeoutMillis = 1000 +RequestTimeoutMillis = 1000 +WriteTimeoutMillis = 1000 [[Dons]] DonId = "test_don" @@ -72,6 +73,13 @@ Id = "test_gateway" URL = "%s" ` +const ( + messageId1 = "123" + messageId2 = "456" + + nodeResponsePayload = `{"response":"correct response"}` +) + func parseGatewayConfig(t *testing.T, tomlConfig string) *config.GatewayConfig { var cfg config.GatewayConfig err := toml.Unmarshal([]byte(tomlConfig), &cfg) @@ -94,6 +102,21 @@ type client struct { func (c *client) HandleGatewayMessage(ctx context.Context, gatewayId string, msg *api.Message) { c.done.Store(true) + // send back user's message without re-signing - should be ignored by the Gateway + _ = c.connector.SendToGateway(ctx, gatewayId, msg) + // send back a correct response + responseMsg := &api.Message{Body: api.MessageBody{ + MessageId: msg.Body.MessageId, + Method: "test", + DonId: "test_don", + Receiver: msg.Body.Sender, + Payload: []byte(nodeResponsePayload), + }} + err := responseMsg.Sign(c.privateKey) + if err != nil { + panic(err) + } + _ = c.connector.SendToGateway(ctx, gatewayId, responseMsg) } func (c *client) Sign(data ...[]byte) ([]byte, error) { @@ -111,7 +134,9 @@ func (*client) Close() error { func TestIntegration_Gateway_NoFullNodes_BasicConnectionAndMessage(t *testing.T) { t.Parallel() - nodeKeys := common.NewTestNodes(t, 1)[0] + testWallets := common.NewTestNodes(t, 2) + nodeKeys := testWallets[0] + userKeys := testWallets[1] // Verify that addresses in config are case-insensitive nodeKeys.Address = strings.ToUpper(nodeKeys.Address) @@ -132,17 +157,39 @@ func TestIntegration_Gateway_NoFullNodes_BasicConnectionAndMessage(t *testing.T) client.connector = connector servicetest.Run(t, connector) - // Send requests until one of them reaches Connector + // Send requests until one of them reaches Connector (i.e. the node) gomega.NewGomegaWithT(t).Eventually(func() bool { - msg := &api.Message{Body: api.MessageBody{MessageId: "123", Method: "test", DonId: "test_don"}} - require.NoError(t, msg.Sign(nodeKeys.PrivateKey)) - codec := api.JsonRPCCodec{} - rawMsg, err := codec.EncodeRequest(msg) - require.NoError(t, err) - req, err := http.NewRequestWithContext(testutils.Context(t), "POST", userUrl, bytes.NewBuffer(rawMsg)) - require.NoError(t, err) + req := newHttpRequestObject(t, messageId1, userUrl, userKeys.PrivateKey) httpClient := &http.Client{} _, _ = httpClient.Do(req) // could initially return error if Gateway is not fully initialized yet return client.done.Load() }, testutils.WaitTimeout(t), testutils.TestInterval).Should(gomega.Equal(true)) + + // Send another request and validate that response has correct content and sender + req := newHttpRequestObject(t, messageId2, userUrl, userKeys.PrivateKey) + httpClient := &http.Client{} + resp, err := httpClient.Do(req) + require.NoError(t, err) + require.Equal(t, http.StatusOK, resp.StatusCode) + + rawResp, err := io.ReadAll(resp.Body) + require.NoError(t, err) + codec := api.JsonRPCCodec{} + respMsg, err := codec.DecodeResponse(rawResp) + require.NoError(t, err) + require.NoError(t, respMsg.Validate()) + require.Equal(t, strings.ToLower(nodeKeys.Address), respMsg.Body.Sender) + require.Equal(t, messageId2, respMsg.Body.MessageId) + require.Equal(t, nodeResponsePayload, string(respMsg.Body.Payload)) +} + +func newHttpRequestObject(t *testing.T, messageId string, userUrl string, signerKey *ecdsa.PrivateKey) *http.Request { + msg := &api.Message{Body: api.MessageBody{MessageId: messageId, Method: "test", DonId: "test_don"}} + require.NoError(t, msg.Sign(signerKey)) + codec := api.JsonRPCCodec{} + rawMsg, err := codec.EncodeRequest(msg) + require.NoError(t, err) + req, err := http.NewRequestWithContext(testutils.Context(t), "POST", userUrl, bytes.NewBuffer(rawMsg)) + require.NoError(t, err) + return req } diff --git a/core/services/ocr2/plugins/functions/config/config.go b/core/services/ocr2/plugins/functions/config/config.go index 13e02042506..38af7b8587f 100644 --- a/core/services/ocr2/plugins/functions/config/config.go +++ b/core/services/ocr2/plugins/functions/config/config.go @@ -41,6 +41,7 @@ type PluginConfig struct { MaxRequestSizesList []uint32 `json:"maxRequestSizesList"` MaxSecretsSizesList []uint32 `json:"maxSecretsSizesList"` MinimumSubscriptionBalance assets.Link `json:"minimumSubscriptionBalance"` + AllowedHeartbeatInitiators []string `json:"allowedHeartbeatInitiators"` GatewayConnectorConfig *connector.ConnectorConfig `json:"gatewayConnectorConfig"` OnchainAllowlist *functions.OnchainAllowlistConfig `json:"onchainAllowlist"` OnchainSubscriptions *functions.OnchainSubscriptionsConfig `json:"onchainSubscriptions"` diff --git a/core/services/ocr2/plugins/functions/plugin.go b/core/services/ocr2/plugins/functions/plugin.go index c6cfa946aba..fd72b6fd38e 100644 --- a/core/services/ocr2/plugins/functions/plugin.go +++ b/core/services/ocr2/plugins/functions/plugin.go @@ -13,7 +13,6 @@ import ( "github.com/smartcontractkit/libocr/commontypes" libocr2 "github.com/smartcontractkit/libocr/offchainreporting2plus" - "github.com/smartcontractkit/chainlink-common/pkg/assets" "github.com/smartcontractkit/chainlink-common/pkg/utils/mailbox" "github.com/smartcontractkit/chainlink/v2/core/bridges" @@ -152,7 +151,7 @@ func NewFunctionsServices(functionsOracleArgs, thresholdOracleArgs, s4OracleArgs return nil, errors.Wrap(err, "failed to create a OnchainSubscriptions") } connectorLogger := conf.Logger.Named("GatewayConnector").With("jobName", conf.Job.PipelineSpec.JobName) - connector, err2 := NewConnector(pluginConfig.GatewayConnectorConfig, conf.EthKeystore, conf.Chain.ID(), s4Storage, allowlist, rateLimiter, subscriptions, functionsListener, offchainTransmitter, pluginConfig.MinimumSubscriptionBalance, connectorLogger) + connector, err2 := NewConnector(&pluginConfig, conf.EthKeystore, conf.Chain.ID(), s4Storage, allowlist, rateLimiter, subscriptions, functionsListener, offchainTransmitter, connectorLogger) if err2 != nil { return nil, errors.Wrap(err, "failed to create a GatewayConnector") } @@ -179,24 +178,26 @@ func NewFunctionsServices(functionsOracleArgs, thresholdOracleArgs, s4OracleArgs return allServices, nil } -func NewConnector(gwcCfg *connector.ConnectorConfig, ethKeystore keystore.Eth, chainID *big.Int, s4Storage s4.Storage, allowlist gwFunctions.OnchainAllowlist, rateLimiter *hc.RateLimiter, subscriptions gwFunctions.OnchainSubscriptions, listener functions.FunctionsListener, offchainTransmitter functions.OffchainTransmitter, minimumBalance assets.Link, lggr logger.Logger) (connector.GatewayConnector, error) { +func NewConnector(pluginConfig *config.PluginConfig, ethKeystore keystore.Eth, chainID *big.Int, s4Storage s4.Storage, allowlist gwFunctions.OnchainAllowlist, rateLimiter *hc.RateLimiter, subscriptions gwFunctions.OnchainSubscriptions, listener functions.FunctionsListener, offchainTransmitter functions.OffchainTransmitter, lggr logger.Logger) (connector.GatewayConnector, error) { enabledKeys, err := ethKeystore.EnabledKeysForChain(chainID) if err != nil { return nil, err } - configuredNodeAddress := common.HexToAddress(gwcCfg.NodeAddress) + configuredNodeAddress := common.HexToAddress(pluginConfig.GatewayConnectorConfig.NodeAddress) idx := slices.IndexFunc(enabledKeys, func(key ethkey.KeyV2) bool { return key.Address == configuredNodeAddress }) if idx == -1 { return nil, errors.New("key for configured node address not found") } signerKey := enabledKeys[idx].ToEcdsaPrivKey() - nodeAddress := enabledKeys[idx].ID() + if enabledKeys[idx].ID() != pluginConfig.GatewayConnectorConfig.NodeAddress { + return nil, errors.New("node address mismatch") + } - handler, err := functions.NewFunctionsConnectorHandler(nodeAddress, signerKey, s4Storage, allowlist, rateLimiter, subscriptions, listener, offchainTransmitter, minimumBalance, lggr) + handler, err := functions.NewFunctionsConnectorHandler(pluginConfig, signerKey, s4Storage, allowlist, rateLimiter, subscriptions, listener, offchainTransmitter, lggr) if err != nil { return nil, err } - connector, err := connector.NewGatewayConnector(gwcCfg, handler, handler, utils.NewRealClock(), lggr) + connector, err := connector.NewGatewayConnector(pluginConfig.GatewayConnectorConfig, handler, handler, utils.NewRealClock(), lggr) if err != nil { return nil, err } diff --git a/core/services/ocr2/plugins/functions/plugin_test.go b/core/services/ocr2/plugins/functions/plugin_test.go index d77fabcc437..6d3f57b086c 100644 --- a/core/services/ocr2/plugins/functions/plugin_test.go +++ b/core/services/ocr2/plugins/functions/plugin_test.go @@ -8,7 +8,6 @@ import ( "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" - "github.com/smartcontractkit/chainlink-common/pkg/assets" "github.com/smartcontractkit/chainlink/v2/core/logger" sfmocks "github.com/smartcontractkit/chainlink/v2/core/services/functions/mocks" "github.com/smartcontractkit/chainlink/v2/core/services/gateway/connector" @@ -17,6 +16,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/ethkey" ksmocks "github.com/smartcontractkit/chainlink/v2/core/services/keystore/mocks" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/functions" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/functions/config" s4mocks "github.com/smartcontractkit/chainlink/v2/core/services/s4/mocks" ) @@ -39,7 +39,10 @@ func TestNewConnector_Success(t *testing.T) { listener := sfmocks.NewFunctionsListener(t) offchainTransmitter := sfmocks.NewOffchainTransmitter(t) ethKeystore.On("EnabledKeysForChain", mock.Anything).Return([]ethkey.KeyV2{keyV2}, nil) - _, err = functions.NewConnector(gwcCfg, ethKeystore, chainID, s4Storage, allowlist, rateLimiter, subscriptions, listener, offchainTransmitter, *assets.NewLinkFromJuels(0), logger.TestLogger(t)) + config := &config.PluginConfig{ + GatewayConnectorConfig: gwcCfg, + } + _, err = functions.NewConnector(config, ethKeystore, chainID, s4Storage, allowlist, rateLimiter, subscriptions, listener, offchainTransmitter, logger.TestLogger(t)) require.NoError(t, err) } @@ -64,6 +67,9 @@ func TestNewConnector_NoKeyForConfiguredAddress(t *testing.T) { listener := sfmocks.NewFunctionsListener(t) offchainTransmitter := sfmocks.NewOffchainTransmitter(t) ethKeystore.On("EnabledKeysForChain", mock.Anything).Return([]ethkey.KeyV2{{Address: common.HexToAddress(addresses[1])}}, nil) - _, err = functions.NewConnector(gwcCfg, ethKeystore, chainID, s4Storage, allowlist, rateLimiter, subscriptions, listener, offchainTransmitter, *assets.NewLinkFromJuels(0), logger.TestLogger(t)) + config := &config.PluginConfig{ + GatewayConnectorConfig: gwcCfg, + } + _, err = functions.NewConnector(config, ethKeystore, chainID, s4Storage, allowlist, rateLimiter, subscriptions, listener, offchainTransmitter, logger.TestLogger(t)) require.Error(t, err) }