From 5f46152bfb99c38b5dc06f592836a31ab66f94be Mon Sep 17 00:00:00 2001 From: Pablo Date: Tue, 21 Apr 2026 18:59:35 -0600 Subject: [PATCH 01/12] feat: port evm state and link views --- go.mod | 55 +-- go.sum | 113 +++--- link/view/link_token.go | 60 +++ link/view/link_token_test.go | 82 +++++ link/view/static_link_token.go | 42 +++ link/view/static_link_token_test.go | 37 ++ mcms/common/view/v1_0/mcms.go | 300 +++++++++++++++ pkg/common/types.go | 35 ++ pkg/common/version.go | 21 ++ pkg/family/evm/state.go | 465 ++++++++++++++++++++++++ pkg/family/evm/state_test.go | 543 ++++++++++++++++++++++++++++ 11 files changed, 1687 insertions(+), 66 deletions(-) create mode 100644 link/view/link_token.go create mode 100644 link/view/link_token_test.go create mode 100644 link/view/static_link_token.go create mode 100644 link/view/static_link_token_test.go create mode 100644 mcms/common/view/v1_0/mcms.go create mode 100644 pkg/common/types.go create mode 100644 pkg/common/version.go create mode 100644 pkg/family/evm/state.go create mode 100644 pkg/family/evm/state_test.go diff --git a/go.mod b/go.mod index a86b2e9..ed131ca 100644 --- a/go.mod +++ b/go.mod @@ -2,9 +2,19 @@ module github.com/smartcontractkit/cld-changesets go 1.25.7 +replace github.com/smartcontractkit/chainlink-deployments-framework => ../chainlink-deployments-framework + require ( github.com/Masterminds/semver/v3 v3.4.0 + github.com/ethereum/go-ethereum v1.17.1 + github.com/gagliardetto/solana-go v1.13.0 + github.com/smartcontractkit/ccip-owner-contracts v0.1.0 + github.com/smartcontractkit/chain-selectors v1.0.97 + github.com/smartcontractkit/chainlink-common v0.11.2-0.20260407150650-8115835abd6e github.com/smartcontractkit/chainlink-deployments-framework v0.94.0 + github.com/smartcontractkit/chainlink-evm v0.3.4-0.20260416173445-80f6efde0a03 + github.com/smartcontractkit/chainlink-evm/gethwrappers v0.0.0-20251022073203-7d8ae8cf67c1 + github.com/smartcontractkit/mcms v0.40.1 github.com/stretchr/testify v1.11.1 gopkg.in/yaml.v3 v3.0.1 ) @@ -27,7 +37,7 @@ require ( github.com/bahlo/generic-list-go v0.2.0 // indirect github.com/benbjohnson/clock v1.3.5 // indirect github.com/beorn7/perks v1.0.1 // indirect - github.com/bits-and-blooms/bitset v1.20.0 // indirect + github.com/bits-and-blooms/bitset v1.24.0 // indirect github.com/blendle/zapdriver v1.3.1 // indirect github.com/block-vision/sui-go-sdk v1.1.4 // indirect github.com/btcsuite/btcd v0.24.2 // indirect @@ -39,8 +49,8 @@ require ( github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/cloudevents/sdk-go/binding/format/protobuf/v2 v2.16.1 // indirect - github.com/cloudevents/sdk-go/v2 v2.16.1 // indirect + github.com/cloudevents/sdk-go/binding/format/protobuf/v2 v2.16.2 // indirect + github.com/cloudevents/sdk-go/v2 v2.16.2 // indirect github.com/cockroachdb/errors v1.11.3 // indirect github.com/cockroachdb/fifo v0.0.0-20240816210425-c5d0cb0b6fc0 // indirect github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect @@ -73,16 +83,14 @@ require ( github.com/emicklei/go-restful/v3 v3.11.0 // indirect github.com/ethereum/c-kzg-4844/v2 v2.1.6 // indirect github.com/ethereum/go-bigmodexpfix v0.0.0-20250911101455-f9e208c548ab // indirect - github.com/ethereum/go-ethereum v1.17.1 // indirect github.com/fatih/color v1.18.0 // indirect github.com/fbsobreira/gotron-sdk v0.0.0-20250403083053-2943ce8c759b // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/ferranbt/fastssz v0.1.4 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect - github.com/fxamacker/cbor/v2 v2.7.0 // indirect + github.com/fxamacker/cbor/v2 v2.9.0 // indirect github.com/gabriel-vasile/mimetype v1.4.13 // indirect github.com/gagliardetto/binary v0.8.0 // indirect - github.com/gagliardetto/solana-go v1.13.0 // indirect github.com/gagliardetto/treeout v0.1.4 // indirect github.com/getsentry/sentry-go v0.27.0 // indirect github.com/go-json-experiment/json v0.0.0-20250223041408-d3c622f1b874 // indirect @@ -108,7 +116,8 @@ require ( github.com/google/gofuzz v1.2.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/gorilla/websocket v1.5.3 // indirect - github.com/grafana/pyroscope-go v1.2.7 // indirect + github.com/grafana/otel-profiling-go v0.5.1 // indirect + github.com/grafana/pyroscope-go v1.2.8 // indirect github.com/grafana/pyroscope-go/godeltaprof v0.1.9 // indirect github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1 // indirect github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.2 // indirect @@ -145,7 +154,7 @@ require ( github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect - github.com/lib/pq v1.10.9 // indirect + github.com/lib/pq v1.11.1 // indirect github.com/logrusorgru/aurora v2.0.3+incompatible // indirect github.com/lufia/plan9stats v0.0.0-20260216142805-b3301c5f2a88 // indirect github.com/magiconair/properties v1.8.10 // indirect @@ -159,7 +168,7 @@ require ( github.com/moby/docker-image-spec v1.3.1 // indirect github.com/moby/go-archive v0.2.0 // indirect github.com/moby/patternmatcher v0.6.0 // indirect - github.com/moby/spdystream v0.5.0 // indirect + github.com/moby/spdystream v0.5.1 // indirect github.com/moby/sys/sequential v0.6.0 // indirect github.com/moby/sys/user v0.4.0 // indirect github.com/moby/sys/userns v0.1.0 // indirect @@ -203,25 +212,23 @@ require ( github.com/shopspring/decimal v1.4.0 // indirect github.com/sigurn/crc16 v0.0.0-20211026045750-20ab5afb07e3 // indirect github.com/sirupsen/logrus v1.9.4 // indirect - github.com/smartcontractkit/chain-selectors v1.0.97 // indirect github.com/smartcontractkit/chainlink-aptos v0.0.0-20260306142855-8d629e752265 // indirect github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20260121163256-85accaf3d28d // indirect github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings v0.0.0-20250912190424-fd2e35d7deb5 // indirect - github.com/smartcontractkit/chainlink-common v0.10.1-0.20260217160002-b56cb5356cc7 // indirect + github.com/smartcontractkit/chainlink-common/keystore v1.0.2 // indirect github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.10 // indirect - github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260226130359-963f935e0396 // indirect + github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260326111235-8c09d1a4491f // indirect github.com/smartcontractkit/chainlink-protos/job-distributor v0.18.0 // indirect github.com/smartcontractkit/chainlink-protos/linking-service/go v0.0.0-20251002192024-d2ad9222409b // indirect github.com/smartcontractkit/chainlink-protos/node-platform v0.0.0-20260205130626-db2a2aab956b // indirect github.com/smartcontractkit/chainlink-sui v0.0.0-20260205175622-33e65031f9a9 // indirect - github.com/smartcontractkit/chainlink-testing-framework/framework v0.15.13 // indirect + github.com/smartcontractkit/chainlink-testing-framework/framework v0.15.18 // indirect github.com/smartcontractkit/chainlink-testing-framework/seth v1.51.5 // indirect github.com/smartcontractkit/chainlink-ton v0.0.0-20260219201907-054376f21418 // indirect github.com/smartcontractkit/chainlink-tron/relayer v0.0.11-0.20250815105909-75499abc4335 // indirect github.com/smartcontractkit/freeport v0.1.3-0.20250828155247-add56fa28aad // indirect github.com/smartcontractkit/grpc-proxy v0.0.0-20240830132753-a7e17fec5ab7 // indirect - github.com/smartcontractkit/libocr v0.0.0-20250912173940-f3ab0246e23d // indirect - github.com/smartcontractkit/mcms v0.39.0 // indirect + github.com/smartcontractkit/libocr v0.0.0-20260130195252-6e18e2a30acc // indirect github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect github.com/spf13/afero v1.15.0 // indirect github.com/spf13/cast v1.10.0 // indirect @@ -250,27 +257,29 @@ require ( github.com/xssnick/tonutils-go v1.14.1 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect github.com/zksync-sdk/zksync2-go v1.1.1-0.20250620124214-2c742ee399c6 // indirect + go.dedis.ch/fixbuf v1.0.3 // indirect + go.dedis.ch/kyber/v3 v3.1.0 // indirect go.mongodb.org/mongo-driver v1.17.2 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 // indirect - go.opentelemetry.io/otel v1.41.0 // indirect + go.opentelemetry.io/otel v1.43.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.12.2 // indirect go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.12.2 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.36.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.36.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.41.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.41.0 // indirect go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.13.0 // indirect go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0 // indirect go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.36.0 // indirect go.opentelemetry.io/otel/log v0.15.0 // indirect - go.opentelemetry.io/otel/metric v1.41.0 // indirect - go.opentelemetry.io/otel/sdk v1.41.0 // indirect + go.opentelemetry.io/otel/metric v1.43.0 // indirect + go.opentelemetry.io/otel/sdk v1.43.0 // indirect go.opentelemetry.io/otel/sdk/log v0.15.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.41.0 // indirect - go.opentelemetry.io/otel/trace v1.41.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.43.0 // indirect + go.opentelemetry.io/otel/trace v1.43.0 // indirect go.opentelemetry.io/proto/otlp v1.9.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/ratelimit v0.3.1 // indirect @@ -282,7 +291,7 @@ require ( golang.org/x/net v0.50.0 // indirect golang.org/x/oauth2 v0.35.0 // indirect golang.org/x/sync v0.19.0 // indirect - golang.org/x/sys v0.41.0 // indirect + golang.org/x/sys v0.42.0 // indirect golang.org/x/term v0.40.0 // indirect golang.org/x/text v0.34.0 // indirect golang.org/x/time v0.14.0 // indirect diff --git a/go.sum b/go.sum index 2670262..c8bdf05 100644 --- a/go.sum +++ b/go.sum @@ -84,8 +84,8 @@ github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/bits-and-blooms/bitset v1.20.0 h1:2F+rfL86jE2d/bmw7OhqUg2Sj/1rURkBn3MdfoPyRVU= -github.com/bits-and-blooms/bitset v1.20.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= +github.com/bits-and-blooms/bitset v1.24.0 h1:H4x4TuulnokZKvHLfzVRTHJfFfnHEeSYJizujEZvmAM= +github.com/bits-and-blooms/bitset v1.24.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= github.com/blendle/zapdriver v1.3.1 h1:C3dydBOWYRiOk+B8X9IVZ5IOe+7cl+tGOexN4QqHfpE= github.com/blendle/zapdriver v1.3.1/go.mod h1:mdXfREi6u5MArG4j9fewC+FGnXaBR+T4Ox4J2u4eHCc= github.com/block-vision/sui-go-sdk v1.1.4 h1:1PPgYxQjo1P9UCgFOPTvDCuGEglRL32NwjKPulR4FQk= @@ -136,10 +136,10 @@ github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWR github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cloudevents/sdk-go/binding/format/protobuf/v2 v2.16.1 h1:nLaJZcVAnaqch3K83AyzHfY2DmQM18/L7jvkmKSfkpI= -github.com/cloudevents/sdk-go/binding/format/protobuf/v2 v2.16.1/go.mod h1:6Q+F2puKpJ6zWv+R02BVnizJICf7++oRT5zwpZQAsbk= -github.com/cloudevents/sdk-go/v2 v2.16.1 h1:G91iUdqvl88BZ1GYYr9vScTj5zzXSyEuqbfE63gbu9Q= -github.com/cloudevents/sdk-go/v2 v2.16.1/go.mod h1:v/kVOaWjNfbvc6tkhhlkhvLapj8Aa8kvXiH5GiOHCKI= +github.com/cloudevents/sdk-go/binding/format/protobuf/v2 v2.16.2 h1:ydUjnKn4RoCeN8rge3F/deT52w2WJMmIC5mHNUq+Ut8= +github.com/cloudevents/sdk-go/binding/format/protobuf/v2 v2.16.2/go.mod h1:Bny999RuVUtNjzTGa9HCHpXjrLGMipJVq5kqVpudBl0= +github.com/cloudevents/sdk-go/v2 v2.16.2 h1:ZYDFrYke4FD+jM8TZTJJO6JhKHzOQl2oqpFK1D+NnQM= +github.com/cloudevents/sdk-go/v2 v2.16.2/go.mod h1:laOcGImm4nVJEU+PHnUrKL56CKmRL65RlQF0kRmW/kg= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= @@ -256,8 +256,8 @@ github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4 github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= -github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= -github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= +github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= +github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= github.com/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9Ufpk2AcUM= github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= github.com/gagliardetto/binary v0.8.0 h1:U9ahc45v9HW0d15LoN++vIXSJyqR/pWw8DDlhd7zvxg= @@ -285,6 +285,7 @@ github.com/go-json-experiment/json v0.0.0-20250223041408-d3c622f1b874/go.mod h1: github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= @@ -363,6 +364,7 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -380,8 +382,10 @@ github.com/gorilla/schema v1.4.1/go.mod h1:Dg5SSm5PV60mhF2NFaTV1xuYYj8tV8NOPRo4F github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/grafana/pyroscope-go v1.2.7 h1:VWBBlqxjyR0Cwk2W6UrE8CdcdD80GOFNutj0Kb1T8ac= -github.com/grafana/pyroscope-go v1.2.7/go.mod h1:o/bpSLiJYYP6HQtvcoVKiE9s5RiNgjYTj1DhiddP2Pc= +github.com/grafana/otel-profiling-go v0.5.1 h1:stVPKAFZSa7eGiqbYuG25VcqYksR6iWvF3YH66t4qL8= +github.com/grafana/otel-profiling-go v0.5.1/go.mod h1:ftN/t5A/4gQI19/8MoWurBEtC6gFw8Dns1sJZ9W4Tls= +github.com/grafana/pyroscope-go v1.2.8 h1:UvCwIhlx9DeV7F6TW/z8q1Mi4PIm3vuUJ2ZlCEvmA4M= +github.com/grafana/pyroscope-go v1.2.8/go.mod h1:SSi59eQ1/zmKoY/BKwa5rSFsJaq+242Bcrr4wPix1g8= github.com/grafana/pyroscope-go/godeltaprof v0.1.9 h1:c1Us8i6eSmkW+Ez05d3co8kasnuOY813tbMN8i/a3Og= github.com/grafana/pyroscope-go/godeltaprof v0.1.9/go.mod h1:2+l7K7twW49Ct4wFluZD3tZ6e0SjanjcUUBPVD/UuGU= github.com/graph-gophers/graphql-go v1.5.0 h1:fDqblo50TEpD0LY7RXk/LFVYEVqo3+tXMNMPSVXA1yc= @@ -532,8 +536,9 @@ github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lib/pq v1.11.1 h1:wuChtj2hfsGmmx3nf1m7xC2XpK6OtelS2shMY+bGMtI= +github.com/lib/pq v1.11.1/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA= github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8= github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= github.com/lufia/plan9stats v0.0.0-20260216142805-b3301c5f2a88 h1:PTw+yKnXcOFCR6+8hHTyWBeQ/P4Nb7dd4/0ohEcWQuM= @@ -583,8 +588,8 @@ github.com/moby/go-archive v0.2.0 h1:zg5QDUM2mi0JIM9fdQZWC7U8+2ZfixfTYoHL7rWUcP8 github.com/moby/go-archive v0.2.0/go.mod h1:mNeivT14o8xU+5q1YnNrkQVpK+dnNe/K6fHqnTg4qPU= github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= -github.com/moby/spdystream v0.5.0 h1:7r0J1Si3QO/kjRitvSLVVFUjxMEb/YLj6S9FF62JBCU= -github.com/moby/spdystream v0.5.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= +github.com/moby/spdystream v0.5.1 h1:9sNYeYZUcci9R6/w7KDaFWEWeV4LStVG78Mpyq/Zm/Y= +github.com/moby/spdystream v0.5.1/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw= github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs= github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU= @@ -633,8 +638,8 @@ github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7J github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= -github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4= -github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog= +github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8= +github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= @@ -730,6 +735,8 @@ github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMB github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w= github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= +github.com/smartcontractkit/ccip-owner-contracts v0.1.0 h1:GiBDtlx7539o7AKlDV+9LsA7vTMPv+0n7ClhSFnZFAk= +github.com/smartcontractkit/ccip-owner-contracts v0.1.0/go.mod h1:NnT6w4Kj42OFFXhSx99LvJZWPpMjmo4+CpDEWfw61xY= github.com/smartcontractkit/chain-selectors v1.0.97 h1:ECOin+SkJv2MUrfqTUu28J0kub04Epds5NPMHERfGjo= github.com/smartcontractkit/chain-selectors v1.0.97/go.mod h1:qy7whtgG5g+7z0jt0nRyii9bLND9m15NZTzuQPkMZ5w= github.com/smartcontractkit/chainlink-aptos v0.0.0-20260306142855-8d629e752265 h1:Q/sYLdOefZUKc/Bxssq1mg8ptQE/AOot2WI+QcLoiVA= @@ -738,14 +745,18 @@ github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20260121163256-8 github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20260121163256-85accaf3d28d/go.mod h1:bgmqE7x9xwmIVr8PqLbC0M5iPm4AV2DBl596lO6S5Sw= github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings v0.0.0-20250912190424-fd2e35d7deb5 h1:Z4t2ZY+ZyGWxtcXvPr11y4o3CGqhg3frJB5jXkCSvWA= github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings v0.0.0-20250912190424-fd2e35d7deb5/go.mod h1:xtZNi6pOKdC3sLvokDvXOhgHzT+cyBqH/gWwvxTxqrg= -github.com/smartcontractkit/chainlink-common v0.10.1-0.20260217160002-b56cb5356cc7 h1:h5cmgzKpKn5N5ItpEDFhRcrtqs36nu9r/dciJub1hos= -github.com/smartcontractkit/chainlink-common v0.10.1-0.20260217160002-b56cb5356cc7/go.mod h1:HXgSKzmZ/bhSx8nHU7hHW6dR+BHSXkdcpFv2T8qJcS8= +github.com/smartcontractkit/chainlink-common v0.11.2-0.20260407150650-8115835abd6e h1:SLnJ0/55Rcn9/9OWrjhvhOTDW9+Bhd63v1tY4dvqtQM= +github.com/smartcontractkit/chainlink-common v0.11.2-0.20260407150650-8115835abd6e/go.mod h1:Ob7ZRLEvPkDwGUjKdDIiHy0Mxu4+UG6oMBkR7Jv/U6o= +github.com/smartcontractkit/chainlink-common/keystore v1.0.2 h1:AWisx4JT3QV8tcgh6J5NCrex+wAgTYpWyHsyNPSXzsQ= +github.com/smartcontractkit/chainlink-common/keystore v1.0.2/go.mod h1:rSkIHdomyak3YnUtXLenl6poIq8q0V3UZPiiyYqPdGA= github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.10 h1:FJAFgXS9oqASnkS03RE1HQwYQQxrO4l46O5JSzxqLgg= github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.10/go.mod h1:oiDa54M0FwxevWwyAX773lwdWvFYYlYHHQV1LQ5HpWY= -github.com/smartcontractkit/chainlink-deployments-framework v0.94.0 h1:z+8phMpBzPXGzPhOOl+AtRJt9vj/y9pU6ZB6/xZ4qa8= -github.com/smartcontractkit/chainlink-deployments-framework v0.94.0/go.mod h1:J0ITsNrymaQzLYXHr1mbS7cS0nBD7lJI6bCLVa3j4Po= -github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260226130359-963f935e0396 h1:03tbcwjyIEjvHba1IWOj1sfThwebm2XNzyFHSuZtlWc= -github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260226130359-963f935e0396/go.mod h1:Jqt53s27Tr0jDl8mdBXg1xhu6F8Fci8JOuq43tgHOM8= +github.com/smartcontractkit/chainlink-evm v0.3.4-0.20260416173445-80f6efde0a03 h1:z+Au1CpZhVYpn7mkmG/mYFBFkdZoqibQ3LngEHm8Fqs= +github.com/smartcontractkit/chainlink-evm v0.3.4-0.20260416173445-80f6efde0a03/go.mod h1:6vCMfxz7cMW0wWseNKtct+b1JJbbRVJJhh/t6pQWN3M= +github.com/smartcontractkit/chainlink-evm/gethwrappers v0.0.0-20251022073203-7d8ae8cf67c1 h1:NTODgwAil7BLoijS7y6KnEuNbQ9v60VUhIR9FcAzIhg= +github.com/smartcontractkit/chainlink-evm/gethwrappers v0.0.0-20251022073203-7d8ae8cf67c1/go.mod h1:oyfOm4k0uqmgZIfxk1elI/59B02shbbJQiiUdPdbMgI= +github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260326111235-8c09d1a4491f h1:8p3vE987AHM3Of1JvnNJXNE/AtWtfNvJhk3TeeAG3Qw= +github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260326111235-8c09d1a4491f/go.mod h1:Jqt53s27Tr0jDl8mdBXg1xhu6F8Fci8JOuq43tgHOM8= github.com/smartcontractkit/chainlink-protos/job-distributor v0.18.0 h1:q+VDPcxWrj5k9QizSYfUOSMnDH3Sd5HvbPguZOgfXTY= github.com/smartcontractkit/chainlink-protos/job-distributor v0.18.0/go.mod h1:/dVVLXrsp+V0AbcYGJo3XMzKg3CkELsweA/TTopCsKE= github.com/smartcontractkit/chainlink-protos/linking-service/go v0.0.0-20251002192024-d2ad9222409b h1:QuI6SmQFK/zyUlVWEf0GMkiUYBPY4lssn26nKSd/bOM= @@ -756,8 +767,8 @@ github.com/smartcontractkit/chainlink-protos/op-catalog v0.0.4 h1:AEnxv4HM3WD1Rb github.com/smartcontractkit/chainlink-protos/op-catalog v0.0.4/go.mod h1:PjZD54vr6rIKEKQj6HNA4hllvYI/QpT+Zefj3tqkFAs= github.com/smartcontractkit/chainlink-sui v0.0.0-20260205175622-33e65031f9a9 h1:KyPROV+v7P8VdiU7JhVuGLcDlEBsURSpQmSCgNBTY+s= github.com/smartcontractkit/chainlink-sui v0.0.0-20260205175622-33e65031f9a9/go.mod h1:KpEWZJMLwbdMHeHQz9rbkES0vRrx4nk6OQXyhlHb9/8= -github.com/smartcontractkit/chainlink-testing-framework/framework v0.15.13 h1:quHuZ/2I7XZ8pdRw5UAwKW/idsPchHN7KnQ69YWzxS4= -github.com/smartcontractkit/chainlink-testing-framework/framework v0.15.13/go.mod h1:BALK9cj8sk12e15UF6uDhifHgIApa+6N11TcQfInEro= +github.com/smartcontractkit/chainlink-testing-framework/framework v0.15.18 h1:ZPYXn3VvaZhWOyVHFBsKC543EJbL2d4PthQbdL3WgHs= +github.com/smartcontractkit/chainlink-testing-framework/framework v0.15.18/go.mod h1:BALK9cj8sk12e15UF6uDhifHgIApa+6N11TcQfInEro= github.com/smartcontractkit/chainlink-testing-framework/seth v1.51.5 h1:RwZXxdIAOyjp6cwc9Quxgr38k8r7ACz+Lxh9o/A6oH0= github.com/smartcontractkit/chainlink-testing-framework/seth v1.51.5/go.mod h1:kHYJnZUqiPF7/xN5273prV+srrLJkS77GbBXHLKQpx0= github.com/smartcontractkit/chainlink-ton v0.0.0-20260219201907-054376f21418 h1:7f92q/Tz/Ns+gjChmWg5mEonrYutZV33k/1x+Xxsb4I= @@ -770,10 +781,10 @@ github.com/smartcontractkit/freeport v0.1.3-0.20250828155247-add56fa28aad h1:lgH github.com/smartcontractkit/freeport v0.1.3-0.20250828155247-add56fa28aad/go.mod h1:T4zH9R8R8lVWKfU7tUvYz2o2jMv1OpGCdpY2j2QZXzU= github.com/smartcontractkit/grpc-proxy v0.0.0-20240830132753-a7e17fec5ab7 h1:12ijqMM9tvYVEm+nR826WsrNi6zCKpwBhuApq127wHs= github.com/smartcontractkit/grpc-proxy v0.0.0-20240830132753-a7e17fec5ab7/go.mod h1:FX7/bVdoep147QQhsOPkYsPEXhGZjeYx6lBSaSXtZOA= -github.com/smartcontractkit/libocr v0.0.0-20250912173940-f3ab0246e23d h1:LokA9PoCNb8mm8mDT52c3RECPMRsGz1eCQORq+J3n74= -github.com/smartcontractkit/libocr v0.0.0-20250912173940-f3ab0246e23d/go.mod h1:Acy3BTBxou83ooMESLO90s8PKSu7RvLCzwSTbxxfOK0= -github.com/smartcontractkit/mcms v0.39.0 h1:ORIpFZnNj24FQUZCktnZCNMUpNmd4kz15/Vddrk/LXc= -github.com/smartcontractkit/mcms v0.39.0/go.mod h1:7YqJPR8w9GiO1L/JjjTrwlSwAZ7i3J7cgOcu88PqtvU= +github.com/smartcontractkit/libocr v0.0.0-20260130195252-6e18e2a30acc h1:8VJgxHEICd0oETMQhce5kqV75kgpKhbBi0YFeVs74TM= +github.com/smartcontractkit/libocr v0.0.0-20260130195252-6e18e2a30acc/go.mod h1:oJkBKVn8zoBQm7Feah9CiuEHyCqAhnp1LJBzrvloQtM= +github.com/smartcontractkit/mcms v0.40.1 h1:r9bU/2GfIf6mHHM4PklSBkfi2Lq4+EGILC81e4IqKz0= +github.com/smartcontractkit/mcms v0.40.1/go.mod h1:7YqJPR8w9GiO1L/JjjTrwlSwAZ7i3J7cgOcu88PqtvU= github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw= github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U= github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I= @@ -869,6 +880,15 @@ github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaD github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= github.com/zksync-sdk/zksync2-go v1.1.1-0.20250620124214-2c742ee399c6 h1:VRdX3Gn/I7ITbzUY4ZNfgn65tdQM9Zhf2b7KP0HZllk= github.com/zksync-sdk/zksync2-go v1.1.1-0.20250620124214-2c742ee399c6/go.mod h1:NWNlQS21isOsSsn+hLRAPpiuv+3P+LcdaZNuRt2T5Yo= +go.dedis.ch/fixbuf v1.0.3 h1:hGcV9Cd/znUxlusJ64eAlExS+5cJDIyTyEG+otu5wQs= +go.dedis.ch/fixbuf v1.0.3/go.mod h1:yzJMt34Wa5xD37V5RTdmp38cz3QhMagdGoem9anUalw= +go.dedis.ch/kyber/v3 v3.0.4/go.mod h1:OzvaEnPvKlyrWyp3kGXlFdp7ap1VC6RkZDTaPikqhsQ= +go.dedis.ch/kyber/v3 v3.0.9/go.mod h1:rhNjUUg6ahf8HEg5HUvVBYoWY4boAafX8tYxX+PS+qg= +go.dedis.ch/kyber/v3 v3.1.0 h1:ghu+kiRgM5JyD9TJ0hTIxTLQlJBR/ehjWvWwYW3XsC0= +go.dedis.ch/kyber/v3 v3.1.0/go.mod h1:kXy7p3STAurkADD+/aZcsznZGKVHEqbtmdIzvPfrs1U= +go.dedis.ch/protobuf v1.0.5/go.mod h1:eIV4wicvi6JK0q/QnfIEGeSFNG0ZeB24kzut5+HaRLo= +go.dedis.ch/protobuf v1.0.7/go.mod h1:pv5ysfkDX/EawiPqcW3ikOxsL5t+BqnV6xHSmE79KI4= +go.dedis.ch/protobuf v1.0.11/go.mod h1:97QR256dnkimeNdfmURz0wAMNVbd1VmLXhG1CrTYrJ4= go.mongodb.org/mongo-driver v1.17.2 h1:gvZyk8352qSfzyZ2UMWcpDpMSGEr1eqE4T793SqyhzM= go.mongodb.org/mongo-driver v1.17.2/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= @@ -877,20 +897,21 @@ go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.6 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0/go.mod h1:fvPi2qXDqFs8M4B4fmJhE92TyQs9Ydjlg3RvfUp+NbQ= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 h1:7iP2uCb7sGddAr30RRS6xjKy7AZ2JtTOPA3oolgVSw8= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0/go.mod h1:c7hN3ddxs/z6q9xwvfLPk+UHlWRQyaeR1LdgfL/66l0= -go.opentelemetry.io/otel v1.41.0 h1:YlEwVsGAlCvczDILpUXpIpPSL/VPugt7zHThEMLce1c= -go.opentelemetry.io/otel v1.41.0/go.mod h1:Yt4UwgEKeT05QbLwbyHXEwhnjxNO6D8L5PQP51/46dE= +go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo= +go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I= +go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.12.2 h1:06ZeJRe5BnYXceSM9Vya83XXVaNGe3H1QqsvqRANQq8= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.12.2/go.mod h1:DvPtKE63knkDVP88qpatBj81JxN+w1bqfVbsbCbj1WY= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.12.2 h1:tPLwQlXbJ8NSOfZc4OkgU5h2A38M4c9kfHSVc4PFQGs= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.12.2/go.mod h1:QTnxBwT/1rBIgAG1goq6xMydfYOBKU6KTiYF4fp5zL8= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.36.0 h1:zwdo1gS2eH26Rg+CoqVQpEK1h8gvt5qyU5Kk5Bixvow= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.36.0/go.mod h1:rUKCPscaRWWcqGT6HnEmYrK+YNe5+Sw64xgQTOJ5b30= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 h1:vl9obrcoWVKp/lwl8tRE33853I8Xru9HFbw/skNeLs8= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0/go.mod h1:GAXRxmLJcVM3u22IjTg74zWBrRCKq8BnOqUVLodpcpw= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.36.0 h1:gAU726w9J8fwr4qRDqu1GYMNNs4gXrU+Pv20/N1UpB4= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.36.0/go.mod h1:RboSDkp7N292rgu+T0MgVt2qgFGu6qa1RpZDOtpL76w= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.41.0 h1:ao6Oe+wSebTlQ1OEht7jlYTzQKE+pnx/iNywFvTbuuI= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.41.0/go.mod h1:u3T6vz0gh/NVzgDgiwkgLxpsSF6PaPmo2il0apGJbls= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0 h1:JgtbA0xkWHnTmYk7YusopJFX6uleBmAuZ8n05NEh8nQ= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0/go.mod h1:179AK5aar5R3eS9FucPy6rggvU0g52cvKId8pv4+v0c= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 h1:EtFWSnwW9hGObjkIdmlnWSydO+Qs8OwzfzXLUPg4xOc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0/go.mod h1:QjUEoiGCPkvFZ/MjK6ZZfNOS6mfVEVKYE99dFhuN2LI= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.41.0 h1:inYW9ZhgqiDqh6BioM7DVHHzEGVq76Db5897WLGZ5Go= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.41.0/go.mod h1:Izur+Wt8gClgMJqO/cZ8wdeeMryJ/xxiOVgFSSfpDTY= go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.13.0 h1:yEX3aC9KDgvYPhuKECHbOlr5GLwH6KTjLJ1sBSkkxkc= @@ -901,18 +922,21 @@ go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.36.0 h1:G8Xec/SgZQricwW go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.36.0/go.mod h1:PD57idA/AiFD5aqoxGxCvT/ILJPeHy3MjqU/NS7KogY= go.opentelemetry.io/otel/log v0.15.0 h1:0VqVnc3MgyYd7QqNVIldC3dsLFKgazR6P3P3+ypkyDY= go.opentelemetry.io/otel/log v0.15.0/go.mod h1:9c/G1zbyZfgu1HmQD7Qj84QMmwTp2QCQsZH1aeoWDE4= -go.opentelemetry.io/otel/metric v1.41.0 h1:rFnDcs4gRzBcsO9tS8LCpgR0dxg4aaxWlJxCno7JlTQ= -go.opentelemetry.io/otel/metric v1.41.0/go.mod h1:xPvCwd9pU0VN8tPZYzDZV/BMj9CM9vs00GuBjeKhJps= -go.opentelemetry.io/otel/sdk v1.41.0 h1:YPIEXKmiAwkGl3Gu1huk1aYWwtpRLeskpV+wPisxBp8= -go.opentelemetry.io/otel/sdk v1.41.0/go.mod h1:ahFdU0G5y8IxglBf0QBJXgSe7agzjE4GiTJ6HT9ud90= +go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM= +go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM= +go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY= +go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E= +go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg= +go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg= go.opentelemetry.io/otel/sdk/log v0.15.0 h1:WgMEHOUt5gjJE93yqfqJOkRflApNif84kxoHWS9VVHE= go.opentelemetry.io/otel/sdk/log v0.15.0/go.mod h1:qDC/FlKQCXfH5hokGsNg9aUBGMJQsrUyeOiW5u+dKBQ= go.opentelemetry.io/otel/sdk/log/logtest v0.13.0 h1:9yio6AFZ3QD9j9oqshV1Ibm9gPLlHNxurno5BreMtIA= go.opentelemetry.io/otel/sdk/log/logtest v0.13.0/go.mod h1:QOGiAJHl+fob8Nu85ifXfuQYmJTFAvcrxL6w5/tu168= -go.opentelemetry.io/otel/sdk/metric v1.41.0 h1:siZQIYBAUd1rlIWQT2uCxWJxcCO7q3TriaMlf08rXw8= -go.opentelemetry.io/otel/sdk/metric v1.41.0/go.mod h1:HNBuSvT7ROaGtGI50ArdRLUnvRTRGniSUZbxiWxSO8Y= -go.opentelemetry.io/otel/trace v1.41.0 h1:Vbk2co6bhj8L59ZJ6/xFTskY+tGAbOnCtQGVVa9TIN0= -go.opentelemetry.io/otel/trace v1.41.0/go.mod h1:U1NU4ULCoxeDKc09yCWdWe+3QoyweJcISEVa1RBzOis= +go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw= +go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A= +go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ= +go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A= +go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0= go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A= go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= @@ -945,6 +969,7 @@ go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190123085648-057139ce5d2b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -1029,6 +1054,7 @@ golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190124100055-b90733256f2e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1074,10 +1100,11 @@ golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= -golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= +golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/telemetry v0.0.0-20260209163413-e7419c687ee4 h1:bTLqdHv7xrGlFbvf5/TXNxy/iUwwdkjhqQTJDjW7aj0= golang.org/x/telemetry v0.0.0-20260209163413-e7419c687ee4/go.mod h1:g5NllXBEermZrmR51cJDQxmJUHUOfRAaNyWBM+R+548= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= diff --git a/link/view/link_token.go b/link/view/link_token.go new file mode 100644 index 0000000..4756eb9 --- /dev/null +++ b/link/view/link_token.go @@ -0,0 +1,60 @@ +package v1_0 + +import ( + "fmt" + "math/big" + + "github.com/ethereum/go-ethereum/common" + + "github.com/smartcontractkit/chainlink-evm/gethwrappers/shared/generated/initial/link_token" + + cldf "github.com/smartcontractkit/chainlink-deployments-framework/deployment" + cldflink "github.com/smartcontractkit/chainlink-deployments-framework/engine/cld/link" + + cldchangesetscommon "github.com/smartcontractkit/cld-changesets/pkg/common" +) + +type LinkTokenView struct { + cldchangesetscommon.ContractMetaData + Decimals uint8 `json:"decimals"` + Supply *big.Int `json:"supply"` + Minters []common.Address `json:"minters"` + Burners []common.Address `json:"burners"` +} + +func GenerateLinkTokenView(lt *link_token.LinkToken) (LinkTokenView, error) { + owner, err := lt.Owner(nil) + if err != nil { + owner = common.Address{} + } + decimals, err := lt.Decimals(nil) + if err != nil { + return LinkTokenView{}, fmt.Errorf("failed to get decimals %s: %w", lt.Address(), err) + } + totalSupply, err := lt.TotalSupply(nil) + if err != nil { + return LinkTokenView{}, fmt.Errorf("failed to get total supply %s: %w", lt.Address(), err) + } + minters, err := lt.GetMinters(nil) + if err != nil { + minters = []common.Address{} + } + burners, err := lt.GetBurners(nil) + if err != nil { + burners = []common.Address{} + } + return LinkTokenView{ + ContractMetaData: cldchangesetscommon.ContractMetaData{ + TypeAndVersion: cldf.TypeAndVersion{ + Type: cldflink.LinkToken, + Version: cldchangesetscommon.Version1_0_0, + }.String(), + Address: lt.Address(), + Owner: owner, + }, + Decimals: decimals, + Supply: totalSupply, + Minters: minters, + Burners: burners, + }, nil +} diff --git a/link/view/link_token_test.go b/link/view/link_token_test.go new file mode 100644 index 0000000..0623889 --- /dev/null +++ b/link/view/link_token_test.go @@ -0,0 +1,82 @@ +package v1_0 + +import ( + "math/big" + "testing" + + chainselectors "github.com/smartcontractkit/chain-selectors" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" + + "github.com/smartcontractkit/chainlink-evm/gethwrappers/shared/generated/initial/link_token" + + cldf_evm "github.com/smartcontractkit/chainlink-deployments-framework/chain/evm" + "github.com/smartcontractkit/chainlink-deployments-framework/engine/test/environment" +) + +func TestLinkTokenView(t *testing.T) { + selector := chainselectors.TEST_90000001.Selector + env, err := environment.New(t.Context(), + environment.WithEVMSimulated(t, []uint64{selector}), + ) + require.NoError(t, err) + + chain := env.BlockChains.EVMChains()[selector] + _, tx, lt, err := link_token.DeployLinkToken(chain.DeployerKey, chain.Client) + require.NoError(t, err) + _, err = chain.Confirm(tx) + require.NoError(t, err) + + testLinkTokenViewWithChain(t, chain, lt) +} + +func TestLinkTokenViewZk(t *testing.T) { + // Timeouts in CI + tests.SkipFlakey(t, "https://smartcontract-it.atlassian.net/browse/CCIP-6427") + + selector := chainselectors.TEST_90000050.Selector + env, err := environment.New(t.Context(), + environment.WithZKSyncContainer(t, []uint64{selector}), + ) + require.NoError(t, err) + + chain := env.BlockChains.EVMChains()[selector] + _, _, lt, err := link_token.DeployLinkTokenZk(nil, chain.ClientZkSyncVM, chain.DeployerKeyZkSyncVM, chain.Client) + require.NoError(t, err) + + testLinkTokenViewWithChain(t, chain, lt) +} + +func testLinkTokenViewWithChain(t *testing.T, chain cldf_evm.Chain, lt *link_token.LinkToken) { + v, err := GenerateLinkTokenView(lt) + require.NoError(t, err) + + assert.Equal(t, v.Owner, chain.DeployerKey.From) + assert.Equal(t, "LinkToken 1.0.0", v.TypeAndVersion) + assert.Equal(t, uint8(18), v.Decimals) + // Initially nothing minted and no minters/burners. + assert.Equal(t, "0", v.Supply.String()) + require.Empty(t, v.Minters) + require.Empty(t, v.Burners) + + // Add some minters + tx, err := lt.GrantMintAndBurnRoles(chain.DeployerKey, chain.DeployerKey.From) + require.NoError(t, err) + _, err = chain.Confirm(tx) + require.NoError(t, err) + tx, err = lt.Mint(chain.DeployerKey, chain.DeployerKey.From, big.NewInt(100)) + require.NoError(t, err) + _, err = chain.Confirm(tx) + require.NoError(t, err) + + v, err = GenerateLinkTokenView(lt) + require.NoError(t, err) + + assert.Equal(t, "100", v.Supply.String()) + require.Len(t, v.Minters, 1) + require.Equal(t, v.Minters[0].String(), chain.DeployerKey.From.String()) + require.Len(t, v.Burners, 1) + require.Equal(t, v.Burners[0].String(), chain.DeployerKey.From.String()) +} diff --git a/link/view/static_link_token.go b/link/view/static_link_token.go new file mode 100644 index 0000000..59e1b52 --- /dev/null +++ b/link/view/static_link_token.go @@ -0,0 +1,42 @@ +package v1_0 + +import ( + "fmt" + "math/big" + + "github.com/smartcontractkit/chainlink-deployments-framework/engine/cld/link" + "github.com/smartcontractkit/chainlink-evm/gethwrappers/generated/link_token_interface" + + cldf "github.com/smartcontractkit/chainlink-deployments-framework/deployment" + + "github.com/smartcontractkit/cld-changesets/pkg/common" +) + +type StaticLinkTokenView struct { + common.ContractMetaData + Decimals uint8 `json:"decimals"` + Supply *big.Int `json:"supply"` +} + +func GenerateStaticLinkTokenView(lt *link_token_interface.LinkToken) (StaticLinkTokenView, error) { + decimals, err := lt.Decimals(nil) + if err != nil { + return StaticLinkTokenView{}, fmt.Errorf("failed to get decimals %s: %w", lt.Address(), err) + } + totalSupply, err := lt.TotalSupply(nil) + if err != nil { + return StaticLinkTokenView{}, fmt.Errorf("failed to get total supply %s: %w", lt.Address(), err) + } + return StaticLinkTokenView{ + ContractMetaData: common.ContractMetaData{ + TypeAndVersion: cldf.TypeAndVersion{ + Type: link.StaticLinkToken, + Version: common.Version1_0_0, + }.String(), + Address: lt.Address(), + // No owner. + }, + Decimals: decimals, + Supply: totalSupply, + }, nil +} diff --git a/link/view/static_link_token_test.go b/link/view/static_link_token_test.go new file mode 100644 index 0000000..350698d --- /dev/null +++ b/link/view/static_link_token_test.go @@ -0,0 +1,37 @@ +package v1_0 + +import ( + "testing" + + "github.com/ethereum/go-ethereum/common" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + chain_selectors "github.com/smartcontractkit/chain-selectors" + + "github.com/smartcontractkit/chainlink-deployments-framework/engine/test/environment" + + "github.com/smartcontractkit/chainlink-evm/gethwrappers/generated/link_token_interface" +) + +func TestStaticLinkTokenView(t *testing.T) { + selector := chain_selectors.TEST_90000001.Selector + env, err := environment.New(t.Context(), + environment.WithEVMSimulated(t, []uint64{selector}), + ) + require.NoError(t, err) + + chain := env.BlockChains.EVMChains()[selector] + _, tx, lt, err := link_token_interface.DeployLinkToken(chain.DeployerKey, chain.Client) + require.NoError(t, err) + _, err = chain.Confirm(tx) + require.NoError(t, err) + v, err := GenerateStaticLinkTokenView(lt) + require.NoError(t, err) + + assert.Equal(t, v.Owner, common.HexToAddress("0x0")) // Ownerless + assert.Equal(t, "StaticLinkToken 1.0.0", v.TypeAndVersion) + assert.Equal(t, uint8(18), v.Decimals) + assert.Equal(t, "1000000000000000000000000000", v.Supply.String()) +} diff --git a/mcms/common/view/v1_0/mcms.go b/mcms/common/view/v1_0/mcms.go new file mode 100644 index 0000000..50406b7 --- /dev/null +++ b/mcms/common/view/v1_0/mcms.go @@ -0,0 +1,300 @@ +package v1_0 + +import ( + "context" + "fmt" + "math/big" + "slices" + + "github.com/ethereum/go-ethereum/common" + "github.com/gagliardetto/solana-go" + owner_helpers "github.com/smartcontractkit/ccip-owner-contracts/pkg/gethwrappers" + mcmsevmsdk "github.com/smartcontractkit/mcms/sdk/evm" + "github.com/smartcontractkit/mcms/sdk/evm/bindings" + mcmssolanasdk "github.com/smartcontractkit/mcms/sdk/solana" + mcmstypes "github.com/smartcontractkit/mcms/types" + + "github.com/smartcontractkit/chainlink-evm/pkg/utils" + + cldcommon "github.com/smartcontractkit/cld-changesets/pkg/common" +) + +type Role struct { + ID common.Hash + Name string +} + +const ( + EXECUTOR_ROLE_STR = "EXECUTOR_ROLE" + BYPASSER_ROLE_STR = "BYPASSER_ROLE" + CANCELLER_ROLE_STR = "CANCELLER_ROLE" + PROPOSER_ROLE_STR = "PROPOSER_ROLE" + ADMIN_ROLE_STR = "ADMIN_ROLE" +) + +// https://github.com/smartcontractkit/ccip-owner-contracts/blob/9d81692b324ce7ea2ef8a75e683889edbc7e2dd0/src/RBACTimelock.sol#L71 +// Just to avoid invoking the Go binding to get these. +var ( + ADMIN_ROLE = Role{ + ID: utils.MustHash(ADMIN_ROLE_STR), + Name: ADMIN_ROLE_STR, + } + PROPOSER_ROLE = Role{ + ID: utils.MustHash(PROPOSER_ROLE_STR), + Name: PROPOSER_ROLE_STR, + } + BYPASSER_ROLE = Role{ + ID: utils.MustHash(BYPASSER_ROLE_STR), + Name: BYPASSER_ROLE_STR, + } + CANCELLER_ROLE = Role{ + ID: utils.MustHash(CANCELLER_ROLE_STR), + Name: CANCELLER_ROLE_STR, + } + EXECUTOR_ROLE = Role{ + ID: utils.MustHash(EXECUTOR_ROLE_STR), + Name: EXECUTOR_ROLE_STR, + } +) + +// --- evm --- + +type MCMSView struct { + cldcommon.ContractMetaData + // Note config is json marshallable. + Config mcmstypes.Config `json:"config"` +} + +func GenerateMCMSView(mcms owner_helpers.ManyChainMultiSig) (MCMSView, error) { + owner, err := mcms.Owner(nil) + if err != nil { + return MCMSView{}, err + } + mcmsConfig, err := mcms.GetConfig(nil) + if err != nil { + return MCMSView{}, err + } + + mapSigners := func(in []owner_helpers.ManyChainMultiSigSigner) []bindings.ManyChainMultiSigSigner { + out := make([]bindings.ManyChainMultiSigSigner, len(in)) + for i, s := range in { + out[i] = bindings.ManyChainMultiSigSigner{Addr: s.Addr, Index: s.Index, Group: s.Group} + } + return out + } + + parsedConfig, err := mcmsevmsdk.NewConfigTransformer().ToConfig(bindings.ManyChainMultiSigConfig{ + Signers: mapSigners(mcmsConfig.Signers), + GroupQuorums: mcmsConfig.GroupQuorums, + GroupParents: mcmsConfig.GroupParents, + }) + if err != nil { + return MCMSView{}, err + } + return MCMSView{ + // Has no type and version on the contract + ContractMetaData: cldcommon.ContractMetaData{ + Owner: owner, + Address: mcms.Address(), + }, + Config: *parsedConfig, + }, nil +} + +type TimelockView struct { + cldcommon.ContractMetaData + MembersByRole map[string][]common.Address `json:"membersByRole"` +} + +func GenerateTimelockView(tl owner_helpers.RBACTimelock) (TimelockView, error) { + membersByRole := make(map[string][]common.Address) + for _, role := range []Role{ADMIN_ROLE, PROPOSER_ROLE, BYPASSER_ROLE, CANCELLER_ROLE, EXECUTOR_ROLE} { + numMembers, err := tl.GetRoleMemberCount(nil, role.ID) + if err != nil { + return TimelockView{}, nil + } + for i := int64(0); i < numMembers.Int64(); i++ { + member, err2 := tl.GetRoleMember(nil, role.ID, big.NewInt(i)) + if err2 != nil { + return TimelockView{}, nil + } + membersByRole[role.Name] = append(membersByRole[role.Name], member) + } + } + return TimelockView{ + // Has no type and version or owner. + ContractMetaData: cldcommon.ContractMetaData{ + Address: tl.Address(), + }, + MembersByRole: membersByRole, + }, nil +} + +type CallProxyView struct { + cldcommon.ContractMetaData +} + +func GenerateCallProxyView(cp owner_helpers.CallProxy) (CallProxyView, error) { + return CallProxyView{ + ContractMetaData: cldcommon.ContractMetaData{ + Address: cp.Address(), + }, + }, nil +} + +type MCMSWithTimelockView struct { + Bypasser MCMSView `json:"bypasser"` + Canceller MCMSView `json:"canceller"` + Proposer MCMSView `json:"proposer"` + Timelock TimelockView `json:"timelock"` + CallProxy CallProxyView `json:"callProxy"` +} + +func GenerateMCMSWithTimelockView( + bypasser owner_helpers.ManyChainMultiSig, + canceller owner_helpers.ManyChainMultiSig, + proposer owner_helpers.ManyChainMultiSig, + timelock owner_helpers.RBACTimelock, + callProxy owner_helpers.CallProxy, +) (MCMSWithTimelockView, error) { + timelockView, err := GenerateTimelockView(timelock) + if err != nil { + return MCMSWithTimelockView{}, err + } + callProxyView, err := GenerateCallProxyView(callProxy) + if err != nil { + return MCMSWithTimelockView{}, err + } + bypasserView, err := GenerateMCMSView(bypasser) + if err != nil { + return MCMSWithTimelockView{}, err + } + proposerView, err := GenerateMCMSView(proposer) + if err != nil { + return MCMSWithTimelockView{}, err + } + cancellerView, err := GenerateMCMSView(canceller) + if err != nil { + return MCMSWithTimelockView{}, err + } + + return MCMSWithTimelockView{ + Timelock: timelockView, + Bypasser: bypasserView, + Proposer: proposerView, + Canceller: cancellerView, + CallProxy: callProxyView, + }, nil +} + +// --- solana --- + +type MCMSWithTimelockViewSolana struct { + Bypasser MCMViewSolana `json:"bypasser"` + Canceller MCMViewSolana `json:"canceller"` + Proposer MCMViewSolana `json:"proposer"` + Timelock TimelockViewSolana `json:"timelock"` +} + +func GenerateMCMSWithTimelockViewSolana( + ctx context.Context, + inspector *mcmssolanasdk.Inspector, + timelockInspector *mcmssolanasdk.TimelockInspector, + mcmProgram solana.PublicKey, + proposerMcmSeed [32]byte, + cancellerMcmSeed [32]byte, + bypasserMcmSeed [32]byte, + timelockProgram solana.PublicKey, + timelockSeed [32]byte, +) (MCMSWithTimelockViewSolana, error) { + timelockView, err := GenerateTimelockViewSolana(ctx, timelockInspector, timelockProgram, timelockSeed) + if err != nil { + return MCMSWithTimelockViewSolana{}, fmt.Errorf("unable to generate timelock view: %w", err) + } + bypasserView, err := GenerateMCMViewSolana(ctx, inspector, mcmProgram, bypasserMcmSeed) + if err != nil { + return MCMSWithTimelockViewSolana{}, fmt.Errorf("unable to generate bypasser mcm view: %w", err) + } + proposerView, err := GenerateMCMViewSolana(ctx, inspector, mcmProgram, proposerMcmSeed) + if err != nil { + return MCMSWithTimelockViewSolana{}, fmt.Errorf("unable to generate proposer mcm view: %w", err) + } + cancellerView, err := GenerateMCMViewSolana(ctx, inspector, mcmProgram, cancellerMcmSeed) + if err != nil { + return MCMSWithTimelockViewSolana{}, fmt.Errorf("unable to generate canceller mcm view: %w", err) + } + + return MCMSWithTimelockViewSolana{ + Timelock: timelockView, + Bypasser: bypasserView, + Proposer: proposerView, + Canceller: cancellerView, + }, nil +} + +type MCMViewSolana struct { + ProgramID solana.PublicKey `json:"programID"` + Seed string `json:"seed"` + Owner solana.PublicKey `json:"owner"` + Config mcmstypes.Config `json:"config"` +} + +func GenerateMCMViewSolana( + ctx context.Context, inspector *mcmssolanasdk.Inspector, programID solana.PublicKey, seed [32]byte, +) (MCMViewSolana, error) { + address := mcmssolanasdk.ContractAddress(programID, mcmssolanasdk.PDASeed(seed)) + config, err := inspector.GetConfig(ctx, address) + if err != nil { + return MCMViewSolana{}, fmt.Errorf("unable to get config from mcm (%v): %w", address, err) + } + + return MCMViewSolana{ + ProgramID: programID, + Seed: string(seed[:]), + Owner: solana.PublicKey{}, // FIXME: needs inspector.GetOwner() in mcms solana sdk + Config: *config, + }, nil +} + +type TimelockViewSolana struct { + ProgramID solana.PublicKey `json:"programID"` + Seed string `json:"seed"` + Owner solana.PublicKey `json:"owner"` + Proposers []string `json:"proposers"` + Executors []string `json:"executors"` + Cancellers []string `json:"cancellers"` + Bypassers []string `json:"bypassers"` +} + +func GenerateTimelockViewSolana( + ctx context.Context, inspector *mcmssolanasdk.TimelockInspector, programID solana.PublicKey, seed [32]byte, +) (TimelockViewSolana, error) { + address := mcmssolanasdk.ContractAddress(programID, mcmssolanasdk.PDASeed(seed)) + + proposers, err := inspector.GetProposers(ctx, address) + if err != nil { + return TimelockViewSolana{}, fmt.Errorf("unable to get proposers from timelock (%v): %w", address, err) + } + executors, err := inspector.GetExecutors(ctx, address) + if err != nil { + return TimelockViewSolana{}, fmt.Errorf("unable to get executors from timelock (%v): %w", address, err) + } + cancellers, err := inspector.GetCancellers(ctx, address) + if err != nil { + return TimelockViewSolana{}, fmt.Errorf("unable to get cancellers from timelock (%v): %w", address, err) + } + bypassers, err := inspector.GetBypassers(ctx, address) + if err != nil { + return TimelockViewSolana{}, fmt.Errorf("unable to get bypassers from timelock (%v): %w", address, err) + } + + return TimelockViewSolana{ + ProgramID: programID, + Seed: string(seed[:]), + Owner: solana.PublicKey{}, // FIXME: needs inspector.GetOwner() in mcms solana sdk + Proposers: slices.Sorted(slices.Values(proposers)), + Executors: slices.Sorted(slices.Values(executors)), + Cancellers: slices.Sorted(slices.Values(cancellers)), + Bypassers: slices.Sorted(slices.Values(bypassers)), + }, nil +} diff --git a/pkg/common/types.go b/pkg/common/types.go new file mode 100644 index 0000000..bff4222 --- /dev/null +++ b/pkg/common/types.go @@ -0,0 +1,35 @@ +package common + +import ( + "fmt" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" +) + +type ContractMetaData struct { + TypeAndVersion string `json:"typeAndVersion,omitempty"` + Address common.Address `json:"address,omitempty"` + Owner common.Address `json:"owner,omitempty"` +} + +func NewContractMetaData(tv Meta, addr common.Address) (ContractMetaData, error) { + tvStr, err := tv.TypeAndVersion(nil) + if err != nil { + return ContractMetaData{}, fmt.Errorf("failed to get type and version addr %s: %w", addr.String(), err) + } + owner, err := tv.Owner(nil) + if err != nil { + return ContractMetaData{}, fmt.Errorf("failed to get owner addr %s: %w", addr.String(), err) + } + return ContractMetaData{ + TypeAndVersion: tvStr, + Address: addr, + Owner: owner, + }, nil +} + +type Meta interface { + TypeAndVersion(opts *bind.CallOpts) (string, error) + Owner(opts *bind.CallOpts) (common.Address, error) +} diff --git a/pkg/common/version.go b/pkg/common/version.go new file mode 100644 index 0000000..b53c72c --- /dev/null +++ b/pkg/common/version.go @@ -0,0 +1,21 @@ +package common + +import ( + "github.com/Masterminds/semver/v3" +) + +var ( + Version0_5_0 = *semver.MustParse("0.5.0") + Version1_0_0 = *semver.MustParse("1.0.0") + Version1_1_0 = *semver.MustParse("1.1.0") + Version1_2_0 = *semver.MustParse("1.2.0") + Version1_5_0 = *semver.MustParse("1.5.0") + Version1_5_1 = *semver.MustParse("1.5.1") + Version1_6_0 = *semver.MustParse("1.6.0") + Version1_6_1 = *semver.MustParse("1.6.1") + Version1_6_1Dev = *semver.MustParse("1.6.1-dev") + Version1_6_2 = *semver.MustParse("1.6.2") + Version1_6_3Dev = *semver.MustParse("1.6.3-dev") + Version1_6_3 = *semver.MustParse("1.6.3") + Version1_7_0 = *semver.MustParse("1.7.0") +) diff --git a/pkg/family/evm/state.go b/pkg/family/evm/state.go new file mode 100644 index 0000000..5992278 --- /dev/null +++ b/pkg/family/evm/state.go @@ -0,0 +1,465 @@ +package state + +import ( + "errors" + "fmt" + "maps" + + "github.com/ethereum/go-ethereum/common" + bindings "github.com/smartcontractkit/ccip-owner-contracts/pkg/gethwrappers" + cldflink "github.com/smartcontractkit/chainlink-deployments-framework/engine/cld/link" + "github.com/smartcontractkit/chainlink-deployments-framework/engine/cld/mcms/proposalutils" + + "github.com/smartcontractkit/chainlink-deployments-framework/datastore" + "github.com/smartcontractkit/chainlink-evm/gethwrappers/generated/link_token_interface" + "github.com/smartcontractkit/chainlink-evm/gethwrappers/shared/generated/initial/link_token" + + cldf_evm "github.com/smartcontractkit/chainlink-deployments-framework/chain/evm" + cldf "github.com/smartcontractkit/chainlink-deployments-framework/deployment" + cldproposalutils "github.com/smartcontractkit/chainlink-deployments-framework/engine/cld/mcms/proposalutils" + + linkview "github.com/smartcontractkit/cld-changesets/link/view" + + "github.com/smartcontractkit/cld-changesets/mcms/common/view/v1_0" + common2 "github.com/smartcontractkit/cld-changesets/pkg/common" +) + +// MCMSWithTimelockState holds the Go bindings +// for a MCMSWithTimelock contract mcms. +// It is public for use in product specific packages. +// Either all fields are nil or all fields are non-nil. +type MCMSWithTimelockState struct { + CancellerMcm *bindings.ManyChainMultiSig + BypasserMcm *bindings.ManyChainMultiSig + ProposerMcm *bindings.ManyChainMultiSig + Timelock *bindings.RBACTimelock + CallProxy *bindings.CallProxy +} + +// Validate checks that all fields are non-nil, ensuring it's ready +// for use generating views or interactions. +func (state MCMSWithTimelockState) Validate() error { + if state.Timelock == nil { + return errors.New("timelock not found") + } + if state.CancellerMcm == nil { + return errors.New("canceller not found") + } + if state.ProposerMcm == nil { + return errors.New("proposer not found") + } + if state.BypasserMcm == nil { + return errors.New("bypasser not found") + } + if state.CallProxy == nil { + return errors.New("call proxy not found") + } + return nil +} + +func (state MCMSWithTimelockState) GenerateMCMSWithTimelockView() (v1_0.MCMSWithTimelockView, error) { + if err := state.Validate(); err != nil { + return v1_0.MCMSWithTimelockView{}, fmt.Errorf("unable to validate McmsWithTimelock state: %w", err) + } + + return v1_0.GenerateMCMSWithTimelockView(*state.BypasserMcm, *state.CancellerMcm, *state.ProposerMcm, + *state.Timelock, *state.CallProxy) +} + +// MaybeLoadMCMSWithTimelockState loads the MCMSWithTimelockState state for each chain in the given environment. +func MaybeLoadMCMSWithTimelockState(env cldf.Environment, chainSelectors []uint64) (map[uint64]*MCMSWithTimelockState, error) { + return MaybeLoadMCMSWithTimelockStateWithQualifier(env, chainSelectors, "") +} + +// MaybeLoadMCMSWithTimelockStateWithQualifier loads the MCMSWithTimelockState state for each chain in the given environment, +// supporting qualifiers for filtering addresses. This uses the merged approach searching both AddressBook and DataStore. +func MaybeLoadMCMSWithTimelockStateWithQualifier(env cldf.Environment, chainSelectors []uint64, qualifier string) (map[uint64]*MCMSWithTimelockState, error) { + result := map[uint64]*MCMSWithTimelockState{} + for _, chainSelector := range chainSelectors { + chain, ok := env.BlockChains.EVMChains()[chainSelector] + if !ok { + return nil, fmt.Errorf("chain %d not found", chainSelector) + } + + // Use merged addresses from both AddressBook and DataStore for backward compatibility + addressesChain, err := AddressesForChain(env, chainSelector, qualifier) + if err != nil { + return nil, err + } + + state, err := MaybeLoadMCMSWithTimelockChainState(chain, addressesChain) + if err != nil { + return nil, err + } + result[chainSelector] = state + } + return result, nil +} + +// AddressesForChain combines addresses from both DataStore and AddressBook making it backward compatible. +// This version supports qualifiers for filtering DataStore addresses. +// When a qualifier is specified, only DataStore addresses with that qualifier are returned (no AddressBook merge) +// to ensure isolation between different deployments. +func AddressesForChain(env cldf.Environment, chainSelector uint64, qualifier string) (map[string]cldf.TypeAndVersion, error) { + // If a qualifier is specified, only use DataStore to ensure isolation between deployments + if qualifier != "" { + if env.DataStore != nil { + return LoadAddressesFromDataStore(env.DataStore, chainSelector, qualifier) + } + return nil, fmt.Errorf("DataStore not available but qualifier %s specified", qualifier) + } + + // For backward compatibility without qualifier, merge both sources + // Start with addresses from AddressBook + addressBookAddresses := make(map[string]cldf.TypeAndVersion) + if addresses, err := env.ExistingAddresses.AddressesForChain(chainSelector); err == nil { + addressBookAddresses = addresses + } else if !errors.Is(err, cldf.ErrChainNotFound) { + return nil, fmt.Errorf("failed to load addresses from AddressBook: %w", err) + } + + // If no DataStore, just return AddressBook addresses + if env.DataStore == nil { + return addressBookAddresses, nil + } + + // Try to load addresses from DataStore (without qualifier for general case) + dataStoreAddresses, err := LoadAddressesFromDataStore(env.DataStore, chainSelector, "") + if err != nil { + // If DataStore has no addresses or returns an error, fall back to AddressBook addresses only + return addressBookAddresses, nil + } + + // Merge the two maps - DataStore addresses take precedence + mergedAddresses := make(map[string]cldf.TypeAndVersion) + + // First add all AddressBook addresses + maps.Copy(mergedAddresses, addressBookAddresses) + + // Then add DataStore addresses (overwriting any conflicts) + maps.Copy(mergedAddresses, dataStoreAddresses) + + return mergedAddresses, nil +} + +// MaybeLoadMCMSWithTimelockStateDataStore loads the MCMSWithTimelockState state for each chain in the given environment from the DataStore. +func MaybeLoadMCMSWithTimelockStateDataStore(env cldf.Environment, chainSelectors []uint64) (map[uint64]*MCMSWithTimelockState, error) { + return MaybeLoadMCMSWithTimelockStateDataStoreWithQualifier(env, chainSelectors, "") +} + +func MaybeLoadMCMSWithTimelockStateDataStoreWithQualifier(env cldf.Environment, chainSelectors []uint64, qualifier string) (map[uint64]*MCMSWithTimelockState, error) { + result := map[uint64]*MCMSWithTimelockState{} + ds := env.DataStore + for _, chainSelector := range chainSelectors { + chain, ok := env.BlockChains.EVMChains()[chainSelector] + if !ok { + return nil, fmt.Errorf("chain %d not found", chainSelector) + } + state, err := GetMCMSWithTimelockState(ds.Addresses(), chain, qualifier) + if err != nil { + return nil, fmt.Errorf("failed to get MCMSWithTimelock state for chain %d, qualifier %s: %w", chainSelector, qualifier, err) + } + result[chainSelector] = state + } + return result, nil +} + +// GetMCMSWithTimelockState loads the MCMSWithTimelockState for a specific chain and qualifier from the DataStore. +// It filters AddressRefs to avoid key collisions that occur when multiple contract types share the same address (e.g. bypasser and canceller). +func GetMCMSWithTimelockState(store datastore.AddressRefStore, chain cldf_evm.Chain, qualifier string) (*MCMSWithTimelockState, error) { + filters := []datastore.FilterFunc[datastore.AddressRefKey, datastore.AddressRef]{datastore.AddressRefByChainSelector(chain.Selector)} + if qualifier != "" { + filters = append(filters, datastore.AddressRefByQualifier(qualifier)) + } + + refs := store.Filter(filters...) + if len(refs) == 0 { + return nil, fmt.Errorf("no addresses found for chain %d", chain.Selector) + } + + return MaybeLoadMCMSWithTimelockChainStateFromRefs(chain, refs) +} + +// LoadAddressesFromDataStore loads addresses from DataStore with optional qualifier. +// This is a public utility function that can be used by other packages to avoid duplication. +// +// Deprecated: Use GetTypeVersionByQualifier instead. +func LoadAddressesFromDataStore(ds datastore.DataStore, chainSelector uint64, qualifier string) (map[string]cldf.TypeAndVersion, error) { + addressesChain, err := GetAddressTypeVersionByQualifier(ds.Addresses(), chainSelector, qualifier) + if err != nil { + return nil, err + } + return addressesChain, nil +} + +// GetAddressTypeVersionByQualifier loads addresses from DataStore for a specific chain and qualifier. +// returns a map of address to TypeAndVersion. +func GetAddressTypeVersionByQualifier(store datastore.AddressRefStore, chainSelector uint64, qualifier string) (map[string]cldf.TypeAndVersion, error) { + addressesChain := make(map[string]cldf.TypeAndVersion) + + // Build filter list starting with chain selector + filters := []datastore.FilterFunc[datastore.AddressRefKey, datastore.AddressRef]{datastore.AddressRefByChainSelector(chainSelector)} + + // Add qualifier filter if provided + if qualifier != "" { + filters = append(filters, datastore.AddressRefByQualifier(qualifier)) + } + + addresses := store.Filter(filters...) + if len(addresses) == 0 { + return nil, fmt.Errorf("no addresses found for chain %d", chainSelector) + } + + for _, addressRef := range addresses { + tv := cldf.TypeAndVersion{ + Type: cldf.ContractType(addressRef.Type), + Version: *addressRef.Version, + } + // Preserve labels from DataStore + if !addressRef.Labels.IsEmpty() { + tv.Labels = cldf.NewLabelSet(addressRef.Labels.List()...) + } + addressesChain[addressRef.Address] = tv + } + return addressesChain, nil +} + +// MaybeLoadMCMSWithTimelockChainState looks for the addresses corresponding to +// contracts deployed with DeployMCMSWithTimelock and loads them into a +// MCMSWithTimelockState struct. If none of the contracts are found, the state struct will be nil. +// An error indicates: +// - Found but was unable to load a contract +// - It only found part of the bundle of contracts +// - If found more than one instance of a contract (we expect one bundle in the given addresses) +func MaybeLoadMCMSWithTimelockChainState(chain cldf_evm.Chain, addresses map[string]cldf.TypeAndVersion) (*MCMSWithTimelockState, error) { + state := MCMSWithTimelockState{} + var ( + // We expect one of each contract on the chain. + timelock = cldf.NewTypeAndVersion(cldproposalutils.RBACTimelock, common2.Version1_0_0) + callProxy = cldf.NewTypeAndVersion(proposalutils.CallProxy, common2.Version1_0_0) + proposer = cldf.NewTypeAndVersion(proposalutils.ProposerManyChainMultisig, common2.Version1_0_0) + canceller = cldf.NewTypeAndVersion(proposalutils.CancellerManyChainMultisig, common2.Version1_0_0) + bypasser = cldf.NewTypeAndVersion(proposalutils.BypasserManyChainMultisig, common2.Version1_0_0) + + // the same contract can have different roles + multichain = cldf.NewTypeAndVersion(proposalutils.ManyChainMultisig, common2.Version1_0_0) + proposerMCMS = cldf.NewTypeAndVersion(proposalutils.ManyChainMultisig, common2.Version1_0_0) + bypasserMCMS = cldf.NewTypeAndVersion(proposalutils.ManyChainMultisig, common2.Version1_0_0) + cancellerMCMS = cldf.NewTypeAndVersion(proposalutils.ManyChainMultisig, common2.Version1_0_0) + ) + + // Convert map keys to a slice + proposerMCMS.Labels.Add(proposalutils.ProposerRole.String()) + bypasserMCMS.Labels.Add(proposalutils.BypasserRole.String()) + cancellerMCMS.Labels.Add(proposalutils.CancellerRole.String()) + wantTypes := []cldf.TypeAndVersion{timelock, proposer, canceller, bypasser, callProxy, + proposerMCMS, bypasserMCMS, cancellerMCMS, + } + + // Ensure we either have the bundle or not. + _, err := cldf.EnsureDeduped(addresses, wantTypes) + if err != nil { + return nil, fmt.Errorf("unable to check MCMS contracts on chain %s error: %w", chain.Name(), err) + } + + for address, tv := range addresses { + switch { + case tv.Type == timelock.Type && tv.Version.String() == timelock.Version.String(): + tl, err := bindings.NewRBACTimelock(common.HexToAddress(address), chain.Client) + if err != nil { + return nil, err + } + state.Timelock = tl + case tv.Type == callProxy.Type && tv.Version.String() == callProxy.Version.String(): + cp, err := bindings.NewCallProxy(common.HexToAddress(address), chain.Client) + if err != nil { + return nil, err + } + state.CallProxy = cp + case tv.Type == proposer.Type && tv.Version.String() == proposer.Version.String(): + mcms, err := bindings.NewManyChainMultiSig(common.HexToAddress(address), chain.Client) + if err != nil { + return nil, err + } + state.ProposerMcm = mcms + case tv.Type == bypasser.Type && tv.Version.String() == bypasser.Version.String(): + mcms, err := bindings.NewManyChainMultiSig(common.HexToAddress(address), chain.Client) + if err != nil { + return nil, err + } + state.BypasserMcm = mcms + case tv.Type == canceller.Type && tv.Version.String() == canceller.Version.String(): + mcms, err := bindings.NewManyChainMultiSig(common.HexToAddress(address), chain.Client) + if err != nil { + return nil, err + } + state.CancellerMcm = mcms + case tv.Type == multichain.Type && tv.Version.String() == multichain.Version.String(): + // Contract of type ManyChainMultiSig must be labeled to assign to the proper state + // field. If a specifically typed contract already occupies the field, then this + // contract will be ignored. + mcms, err := bindings.NewManyChainMultiSig(common.HexToAddress(address), chain.Client) + if err != nil { + return nil, err + } + if tv.Labels.Contains(proposalutils.ProposerRole.String()) && state.ProposerMcm == nil { + state.ProposerMcm = mcms + } + if tv.Labels.Contains(proposalutils.BypasserRole.String()) && state.BypasserMcm == nil { + state.BypasserMcm = mcms + } + if tv.Labels.Contains(proposalutils.CancellerRole.String()) && state.CancellerMcm == nil { + state.CancellerMcm = mcms + } + } + } + return &state, nil +} + +// MaybeLoadMCMSWithTimelockChainStateFromRefs is the DataStore-native equivalent of MaybeLoadMCMSWithTimelockChainState. +// It accepts []datastore.AddressRef directly, to preserve entries when multiple contract types share the same address (e.g. bypasser and canceller). +func MaybeLoadMCMSWithTimelockChainStateFromRefs(chain cldf_evm.Chain, refs []datastore.AddressRef) (*MCMSWithTimelockState, error) { + state := MCMSWithTimelockState{} + var ( + // We expect one of each contract on the chain. + timelock = cldf.NewTypeAndVersion(proposalutils.RBACTimelock, common2.Version1_0_0) + callProxy = cldf.NewTypeAndVersion(proposalutils.CallProxy, common2.Version1_0_0) + proposer = cldf.NewTypeAndVersion(proposalutils.ProposerManyChainMultisig, common2.Version1_0_0) + canceller = cldf.NewTypeAndVersion(proposalutils.CancellerManyChainMultisig, common2.Version1_0_0) + bypasser = cldf.NewTypeAndVersion(proposalutils.BypasserManyChainMultisig, common2.Version1_0_0) + ) + + wantTypes := []cldf.TypeAndVersion{timelock, proposer, canceller, bypasser, callProxy} + + dedupMap := make(map[string]cldf.TypeAndVersion, len(refs)) + for _, ref := range refs { + tv := cldf.TypeAndVersion{ + Type: cldf.ContractType(ref.Type), + Version: *ref.Version, + } + if !ref.Labels.IsEmpty() { + tv.Labels = cldf.NewLabelSet(ref.Labels.List()...) + } + dedupMap[ref.Key().String()] = tv + } + + // Ensure we either have the bundle or not. + _, err := cldf.EnsureDeduped(dedupMap, wantTypes) + if err != nil { + return nil, fmt.Errorf("unable to check MCMS contracts on chain %s error: %w", chain.Name(), err) + } + + for _, ref := range refs { + addr := common.HexToAddress(ref.Address) + tv := cldf.TypeAndVersion{ + Type: cldf.ContractType(ref.Type), + Version: *ref.Version, + } + + switch { + case tv.Type == timelock.Type && tv.Version.String() == timelock.Version.String(): + tl, err := bindings.NewRBACTimelock(addr, chain.Client) + if err != nil { + return nil, err + } + state.Timelock = tl + case tv.Type == callProxy.Type && tv.Version.String() == callProxy.Version.String(): + cp, err := bindings.NewCallProxy(addr, chain.Client) + if err != nil { + return nil, err + } + state.CallProxy = cp + case tv.Type == proposer.Type && tv.Version.String() == proposer.Version.String(): + mcms, err := bindings.NewManyChainMultiSig(addr, chain.Client) + if err != nil { + return nil, err + } + state.ProposerMcm = mcms + case tv.Type == bypasser.Type && tv.Version.String() == bypasser.Version.String(): + mcms, err := bindings.NewManyChainMultiSig(addr, chain.Client) + if err != nil { + return nil, err + } + state.BypasserMcm = mcms + case tv.Type == canceller.Type && tv.Version.String() == canceller.Version.String(): + mcms, err := bindings.NewManyChainMultiSig(addr, chain.Client) + if err != nil { + return nil, err + } + state.CancellerMcm = mcms + } + } + return &state, nil +} + +type LinkTokenState struct { + LinkToken *link_token.LinkToken +} + +func (s LinkTokenState) GenerateLinkView() (linkview.LinkTokenView, error) { + if s.LinkToken == nil { + return linkview.LinkTokenView{}, errors.New("link token not found") + } + return linkview.GenerateLinkTokenView(s.LinkToken) +} + +func MaybeLoadLinkTokenChainState(chain cldf_evm.Chain, addresses map[string]cldf.TypeAndVersion) (*LinkTokenState, error) { + state := LinkTokenState{} + linkToken := cldf.NewTypeAndVersion(cldflink.LinkToken, common2.Version1_0_0) + + // Convert map keys to a slice + wantTypes := []cldf.TypeAndVersion{linkToken} + + // Ensure we either have the bundle or not. + _, err := cldf.EnsureDeduped(addresses, wantTypes) + if err != nil { + return nil, fmt.Errorf("unable to check link token on chain %s error: %w", chain.Name(), err) + } + + for address, tvStr := range addresses { + if tvStr.Type == linkToken.Type && tvStr.Version.String() == linkToken.Version.String() { + lt, err := link_token.NewLinkToken(common.HexToAddress(address), chain.Client) + if err != nil { + return nil, err + } + state.LinkToken = lt + } + } + return &state, nil +} + +type StaticLinkTokenState struct { + StaticLinkToken *link_token_interface.LinkToken +} + +func (s StaticLinkTokenState) GenerateStaticLinkView() (linkview.StaticLinkTokenView, error) { + if s.StaticLinkToken == nil { + return linkview.StaticLinkTokenView{}, errors.New("static link token not found") + } + return linkview.GenerateStaticLinkTokenView(s.StaticLinkToken) +} + +func MaybeLoadStaticLinkTokenState(chain cldf_evm.Chain, addresses map[string]cldf.TypeAndVersion) (*StaticLinkTokenState, error) { + state := StaticLinkTokenState{} + staticLinkToken := cldf.NewTypeAndVersion(cldflink.StaticLinkToken, common2.Version1_0_0) + + // Convert map keys to a slice + wantTypes := []cldf.TypeAndVersion{staticLinkToken} + + // Ensure we either have the bundle or not. + _, err := cldf.EnsureDeduped(addresses, wantTypes) + if err != nil { + return nil, fmt.Errorf("unable to check static link token on chain %s error: %w", chain.Name(), err) + } + + for address, tvStr := range addresses { + if tvStr.Type == staticLinkToken.Type && tvStr.Version.String() == staticLinkToken.Version.String() { + lt, err := link_token_interface.NewLinkToken(common.HexToAddress(address), chain.Client) + if err != nil { + return nil, err + } + state.StaticLinkToken = lt + } + } + return &state, nil +} diff --git a/pkg/family/evm/state_test.go b/pkg/family/evm/state_test.go new file mode 100644 index 0000000..4b8baa0 --- /dev/null +++ b/pkg/family/evm/state_test.go @@ -0,0 +1,543 @@ +package state + +import ( + "encoding/json" + "fmt" + "math/big" + "strings" + "testing" + + "github.com/ethereum/go-ethereum/common" + bindings "github.com/smartcontractkit/ccip-owner-contracts/pkg/gethwrappers" + chain_selectors "github.com/smartcontractkit/chain-selectors" + "github.com/smartcontractkit/mcms/sdk" + mcmstypes "github.com/smartcontractkit/mcms/types" + "github.com/stretchr/testify/require" + + cldf_evm "github.com/smartcontractkit/chainlink-deployments-framework/chain/evm" + "github.com/smartcontractkit/chainlink-deployments-framework/engine/test/environment" + + "github.com/smartcontractkit/chainlink-deployments-framework/datastore" + cldf "github.com/smartcontractkit/chainlink-deployments-framework/deployment" +) + +func TestMCMSWithTimelockState_GenerateMCMSWithTimelockViewV2(t *testing.T) { + selector := chain_selectors.TEST_90000001.Selector + env, err := environment.New(t.Context(), + environment.WithEVMSimulated(t, []uint64{selector}), + ) + require.NoError(t, err) + + chain := env.BlockChains.EVMChains()[selector] + + proposerMcm := deployMCMEvm(t, chain, &mcmstypes.Config{Quorum: 1, Signers: []common.Address{ + common.HexToAddress("0x0000000000000000000000000000000000000001"), + }}) + cancellerMcm := deployMCMEvm(t, chain, &mcmstypes.Config{Quorum: 1, Signers: []common.Address{ + common.HexToAddress("0x0000000000000000000000000000000000000002"), + }}) + bypasserMcm := deployMCMEvm(t, chain, &mcmstypes.Config{Quorum: 1, Signers: []common.Address{ + common.HexToAddress("0x0000000000000000000000000000000000000003"), + }}) + timelock := deployTimelockEvm(t, chain, big.NewInt(1), + common.HexToAddress("0x0000000000000000000000000000000000000004"), + []common.Address{common.HexToAddress("0x0000000000000000000000000000000000000005")}, + []common.Address{common.HexToAddress("0x0000000000000000000000000000000000000006")}, + []common.Address{common.HexToAddress("0x0000000000000000000000000000000000000007")}, + []common.Address{common.HexToAddress("0x0000000000000000000000000000000000000008")}, + ) + callProxy := deployCallProxyEvm(t, chain, + common.HexToAddress("0x0000000000000000000000000000000000000009")) + + tests := []struct { + name string + contracts *MCMSWithTimelockState + want string + wantErr string + }{ + { + name: "success", + contracts: &MCMSWithTimelockState{ + ProposerMcm: proposerMcm, + CancellerMcm: cancellerMcm, + BypasserMcm: bypasserMcm, + Timelock: timelock, + CallProxy: callProxy, + }, + want: fmt.Sprintf(`{ + "proposer": { + "address": "%s", + "owner": "%s", + "config": { + "quorum": 1, + "signers": ["0x0000000000000000000000000000000000000001"], + "groupSigners": [] + } + }, + "canceller": { + "address": "%s", + "owner": "%s", + "config": { + "quorum": 1, + "signers": ["0x0000000000000000000000000000000000000002"], + "groupSigners": [] + } + }, + "bypasser": { + "address": "%s", + "owner": "%s", + "config": { + "quorum": 1, + "signers": ["0x0000000000000000000000000000000000000003"], + "groupSigners": [] + } + }, + "timelock": { + "address": "%s", + "owner": "0x0000000000000000000000000000000000000000", + "membersByRole": { + "ADMIN_ROLE": [ "0x0000000000000000000000000000000000000004" ], + "PROPOSER_ROLE": [ "0x0000000000000000000000000000000000000005" ], + "EXECUTOR_ROLE": [ "0x0000000000000000000000000000000000000006" ], + "CANCELLER_ROLE": [ "0x0000000000000000000000000000000000000007" ], + "BYPASSER_ROLE": [ "0x0000000000000000000000000000000000000008" ] + } + }, + "callProxy": { + "address": "%s", + "owner": "0x0000000000000000000000000000000000000000" + } + }`, evmAddr(proposerMcm.Address()), evmAddr(chain.DeployerKey.From), + evmAddr(cancellerMcm.Address()), evmAddr(chain.DeployerKey.From), + evmAddr(bypasserMcm.Address()), evmAddr(chain.DeployerKey.From), + evmAddr(timelock.Address()), evmAddr(callProxy.Address())), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + state := tt.contracts + + got, err := state.GenerateMCMSWithTimelockView() + + if tt.wantErr == "" { + require.NoError(t, err) + require.JSONEq(t, tt.want, toJSON(t, &got)) + } else { + require.ErrorContains(t, err, tt.wantErr) + } + }) + } +} + +func TestAddressesForChain(t *testing.T) { + chainSelector := chain_selectors.ETHEREUM_MAINNET.Selector + + t.Run("environment with AddressBook only", func(t *testing.T) { + // Create environment with only AddressBook + addressBook := cldf.NewMemoryAddressBook() + err := addressBook.Save(chainSelector, "0x1234567890123456789012345678901234567890", + cldf.NewTypeAndVersion(types.LinkToken, deployment.Version1_0_0)) + require.NoError(t, err) + + env := cldf.Environment{ + ExistingAddresses: addressBook, + DataStore: nil, // No DataStore + } + + // Test the merge function + mergedAddresses, err := AddressesForChain(env, chainSelector, "") + require.NoError(t, err) + + // Should have address from AddressBook only + require.Len(t, mergedAddresses, 1) + require.Contains(t, mergedAddresses, "0x1234567890123456789012345678901234567890") + }) + + t.Run("environment with DataStore only", func(t *testing.T) { + // Create environment with only DataStore + dataStore := datastore.NewMemoryDataStore() + err := dataStore.Addresses().Add(datastore.AddressRef{ + ChainSelector: chainSelector, + Address: "0xABCDEF1234567890123456789012345678901234", + Type: datastore.ContractType(types.RBACTimelock), + Version: &deployment.Version1_0_0, + }) + require.NoError(t, err) + + addressBook := cldf.NewMemoryAddressBook() + + env := cldf.Environment{ + ExistingAddresses: addressBook, + DataStore: dataStore.Seal(), + } + + // Test the merge function + mergedAddresses, err := AddressesForChain(env, chainSelector, "") + require.NoError(t, err) + + // Should have address from DataStore only + require.Len(t, mergedAddresses, 1) + require.Contains(t, mergedAddresses, "0xABCDEF1234567890123456789012345678901234") + }) + t.Run("environment with AddressBook and DataStore without qualifier", func(t *testing.T) { + // Create a mock environment with both AddressBook and DataStore + addressBook := cldf.NewMemoryAddressBook() + err := addressBook.Save(chainSelector, "0x1234567890123456789012345678901234567890", + cldf.NewTypeAndVersion(types.LinkToken, deployment.Version1_0_0)) + require.NoError(t, err) + + dataStore := datastore.NewMemoryDataStore() + err = dataStore.Addresses().Add(datastore.AddressRef{ + ChainSelector: chainSelector, + Address: "0xABCDEF1234567890123456789012345678901234", + Type: datastore.ContractType(types.RBACTimelock), + Version: &deployment.Version1_0_0, + Labels: datastore.NewLabelSet( + "team:core", + "environment:production", + "role:timelock", + ), + }) + require.NoError(t, err) + + env := cldf.Environment{ + ExistingAddresses: addressBook, + DataStore: dataStore.Seal(), + } + + // Test the merge function + mergedAddresses, err := AddressesForChain(env, chainSelector, "") + require.NoError(t, err) + + // Should have addresses from both sources + require.Len(t, mergedAddresses, 2) + require.Contains(t, mergedAddresses, "0x1234567890123456789012345678901234567890") + require.Contains(t, mergedAddresses, "0xABCDEF1234567890123456789012345678901234") + + // Verify that types are correctly preserved + linkTokenTV := mergedAddresses["0x1234567890123456789012345678901234567890"] + require.Equal(t, types.LinkToken, linkTokenTV.Type) + require.Equal(t, deployment.Version1_0_0, linkTokenTV.Version) + + timelockTV := mergedAddresses["0xABCDEF1234567890123456789012345678901234"] + require.Equal(t, types.RBACTimelock, timelockTV.Type) + require.Equal(t, deployment.Version1_0_0, timelockTV.Version) + + // Verify labels are preserved in DataStore + refs := env.DataStore.Addresses().Filter(datastore.AddressRefByChainSelector(chainSelector)) + require.Len(t, refs, 1) + + timelockRef := refs[0] + require.Equal(t, "0xABCDEF1234567890123456789012345678901234", timelockRef.Address) + require.True(t, timelockRef.Labels.Contains("team:core")) + require.True(t, timelockRef.Labels.Contains("environment:production")) + require.True(t, timelockRef.Labels.Contains("role:timelock")) + }) + + t.Run("environment with AddressBook and DataStore with qualifier", func(t *testing.T) { + dataStore := datastore.NewMemoryDataStore() + + // Add contracts with different qualifiers + err := dataStore.Addresses().Add(datastore.AddressRef{ + ChainSelector: chainSelector, + Address: "0x1111111111111111111111111111111111111111", + Type: datastore.ContractType(types.RBACTimelock), + Version: &deployment.Version1_0_0, + Qualifier: "team-a", + Labels: datastore.NewLabelSet( + "team:team-a", + "role:timelock", + ), + }) + require.NoError(t, err) + + err = dataStore.Addresses().Add(datastore.AddressRef{ + ChainSelector: chainSelector, + Address: "0x2222222222222222222222222222222222222222", + Type: datastore.ContractType(types.RBACTimelock), + Version: &deployment.Version1_0_0, + Qualifier: "team-b", + Labels: datastore.NewLabelSet( + "team:team-b", + "role:timelock", + ), + }) + require.NoError(t, err) + + env := cldf.Environment{ + ExistingAddresses: cldf.NewMemoryAddressBook(), + DataStore: dataStore.Seal(), + } + + // Test filtering by qualifier + mergedAddresses, err := AddressesForChain(env, chainSelector, "team-a") + require.NoError(t, err) + + // Should only have team-a contract + require.Len(t, mergedAddresses, 1) + require.Contains(t, mergedAddresses, "0x1111111111111111111111111111111111111111") + require.NotContains(t, mergedAddresses, "0x2222222222222222222222222222222222222222") + + // Verify the correct contract type + timelockTV := mergedAddresses["0x1111111111111111111111111111111111111111"] + require.Equal(t, types.RBACTimelock, timelockTV.Type) + + // Verify labels are preserved for the filtered contract + refs := env.DataStore.Addresses().Filter( + datastore.AddressRefByChainSelector(chainSelector), + datastore.AddressRefByQualifier("team-a"), + ) + require.Len(t, refs, 1) + + teamARef := refs[0] + require.Equal(t, "0x1111111111111111111111111111111111111111", teamARef.Address) + require.Equal(t, "team-a", teamARef.Qualifier) + require.True(t, teamARef.Labels.Contains("team:team-a")) + require.True(t, teamARef.Labels.Contains("role:timelock")) + }) + + t.Run("environment with duplicated addresses in AddressBook and DataStore", func(t *testing.T) { + const ( + duplicateAddress = "0x1234567890123456789012345678901234567890" + uniqueAddress = "0xABCDEF1234567890123456789012345678901234" + ) + + // Create environment with same address in both AddressBook and DataStore + addressBook := cldf.NewMemoryAddressBook() + // Add LinkToken to AddressBook + err := addressBook.Save(chainSelector, duplicateAddress, + cldf.NewTypeAndVersion(types.LinkToken, deployment.Version1_0_0)) + require.NoError(t, err) + + dataStore := datastore.NewMemoryDataStore() + + // Add the SAME address to DataStore but with different type/version and labels + err = dataStore.Addresses().Add(datastore.AddressRef{ + ChainSelector: chainSelector, + Address: duplicateAddress, // Same address as AddressBook + Type: datastore.ContractType(types.RBACTimelock), // Different type from AddressBook LinkToken + Version: &deployment.Version1_6_0, // Different version + Labels: datastore.NewLabelSet( + "team:datastore-team", + "environment:staging", + "override:true", + ), + }) + require.NoError(t, err) + + // Also add a unique DataStore address + err = dataStore.Addresses().Add(datastore.AddressRef{ + ChainSelector: chainSelector, + Address: uniqueAddress, + Type: datastore.ContractType(types.RBACTimelock), + Version: &deployment.Version1_0_0, + Labels: datastore.NewLabelSet( + "team:unique-entry", + "role:timelock", + ), + }) + require.NoError(t, err) + + env := cldf.Environment{ + ExistingAddresses: addressBook, + DataStore: dataStore.Seal(), + } + + // Test the merge function + mergedAddresses, err := AddressesForChain(env, chainSelector, "") + require.NoError(t, err) + + // Should have 2 addresses total (duplicate should be merged, unique should be included) + require.Len(t, mergedAddresses, 2) + require.Contains(t, mergedAddresses, duplicateAddress) + require.Contains(t, mergedAddresses, uniqueAddress) + + // The duplicate address should use DataStore values (DataStore takes precedence) + duplicateTV := mergedAddresses[duplicateAddress] + require.Equal(t, types.RBACTimelock, duplicateTV.Type, "DataStore type should override AddressBook type") + require.Equal(t, deployment.Version1_6_0, duplicateTV.Version, "DataStore version should override AddressBook version") + + // The unique address should have correct type + uniqueTV := mergedAddresses[uniqueAddress] + require.Equal(t, types.RBACTimelock, uniqueTV.Type) + require.Equal(t, deployment.Version1_0_0, uniqueTV.Version) + + // Verify that DataStore labels are preserved for both addresses + refs := env.DataStore.Addresses().Filter(datastore.AddressRefByChainSelector(chainSelector)) + require.Len(t, refs, 2) + + // Find the refs by address + var duplicateRef, uniqueRef *datastore.AddressRef + for i := range refs { + switch refs[i].Address { + case duplicateAddress: + duplicateRef = &refs[i] + case uniqueAddress: + uniqueRef = &refs[i] + } + } + + require.NotNil(t, duplicateRef, "Should find duplicate address in DataStore") + require.NotNil(t, uniqueRef, "Should find unique address in DataStore") + + // Verify labels are preserved for the duplicate address (which should come from DataStore) + require.True(t, duplicateRef.Labels.Contains("team:datastore-team")) + require.True(t, duplicateRef.Labels.Contains("environment:staging")) + require.True(t, duplicateRef.Labels.Contains("override:true")) + + // Verify labels for the unique address + require.True(t, uniqueRef.Labels.Contains("team:unique-entry")) + require.True(t, uniqueRef.Labels.Contains("role:timelock")) + }) +} + +func TestGetMCMSWithTimelockState(t *testing.T) { + selector := chain_selectors.TEST_90000001.Selector + env, err := environment.New(t.Context(), + environment.WithEVMSimulated(t, []uint64{selector}), + ) + require.NoError(t, err) + + chain := env.BlockChains.EVMChains()[selector] + + sharedMcm := deployMCMEvm(t, chain, &mcmstypes.Config{Quorum: 1, Signers: []common.Address{ + common.HexToAddress("0x0000000000000000000000000000000000000001"), + }}) + sharedAddress := strings.ToLower(sharedMcm.Address().Hex()) + + timelock := deployTimelockEvm(t, chain, big.NewInt(1), + common.HexToAddress("0x0000000000000000000000000000000000000004"), + []common.Address{common.HexToAddress("0x0000000000000000000000000000000000000005")}, + []common.Address{common.HexToAddress("0x0000000000000000000000000000000000000006")}, + []common.Address{common.HexToAddress("0x0000000000000000000000000000000000000007")}, + []common.Address{common.HexToAddress("0x0000000000000000000000000000000000000008")}, + ) + callProxy := deployCallProxyEvm(t, chain, + common.HexToAddress("0x0000000000000000000000000000000000000009")) + proposerMcm := deployMCMEvm(t, chain, &mcmstypes.Config{Quorum: 1, Signers: []common.Address{ + common.HexToAddress("0x0000000000000000000000000000000000000002"), + }}) + + // timelock, callProxy, proposer shared by both stores + commonRefs := []datastore.AddressRef{ + {ChainSelector: selector, Address: strings.ToLower(timelock.Address().Hex()), Type: datastore.ContractType(types.RBACTimelock), Version: &deployment.Version1_0_0}, + {ChainSelector: selector, Address: strings.ToLower(callProxy.Address().Hex()), Type: datastore.ContractType(types.CallProxy), Version: &deployment.Version1_0_0}, + {ChainSelector: selector, Address: strings.ToLower(proposerMcm.Address().Hex()), Type: datastore.ContractType(types.ProposerManyChainMultisig), Version: &deployment.Version1_0_0}, + } + + t.Run("shared address for bypasser and canceller", func(t *testing.T) { + // Store DS with bypasser/canceller sharing the same address + store := datastore.NewMemoryDataStore() + for _, ref := range commonRefs { + require.NoError(t, store.Addresses().Add(ref)) + } + require.NoError(t, store.Addresses().Add(datastore.AddressRef{ + ChainSelector: selector, Address: sharedAddress, + Type: datastore.ContractType(types.BypasserManyChainMultisig), Version: &deployment.Version1_0_0, Qualifier: "bypasser", + })) + require.NoError(t, store.Addresses().Add(datastore.AddressRef{ + ChainSelector: selector, Address: sharedAddress, + Type: datastore.ContractType(types.CancellerManyChainMultisig), Version: &deployment.Version1_0_0, Qualifier: "canceller", + })) + + state, err := GetMCMSWithTimelockState(store.Seal().Addresses(), chain, "") + require.NoError(t, err) + + require.NotNil(t, state.Timelock, "timelock should be loaded") + require.NotNil(t, state.CallProxy, "call proxy should be loaded") + require.NotNil(t, state.ProposerMcm, "proposer should be loaded") + require.NotNil(t, state.BypasserMcm, "bypasser should be loaded despite shared address") + require.NotNil(t, state.CancellerMcm, "canceller should be loaded despite shared address") + + require.Equal(t, sharedMcm.Address(), state.BypasserMcm.Address()) + require.Equal(t, sharedMcm.Address(), state.CancellerMcm.Address()) + }) + + t.Run("legacy ManyChainMultisig type is ignored", func(t *testing.T) { + // Store with legacy ManyChainMultisig typed bypasser/canceller + legacyStore := datastore.NewMemoryDataStore() + for _, ref := range commonRefs { + require.NoError(t, legacyStore.Addresses().Add(ref)) + } + require.NoError(t, legacyStore.Addresses().Add(datastore.AddressRef{ + ChainSelector: selector, Address: sharedAddress, + Type: datastore.ContractType(types.ManyChainMultisig), Version: &deployment.Version1_0_0, Qualifier: "bypasser", + Labels: datastore.NewLabelSet(types.BypasserRole.String()), + })) + require.NoError(t, legacyStore.Addresses().Add(datastore.AddressRef{ + ChainSelector: selector, Address: sharedAddress, + Type: datastore.ContractType(types.ManyChainMultisig), Version: &deployment.Version1_0_0, Qualifier: "canceller", + Labels: datastore.NewLabelSet(types.CancellerRole.String()), + })) + + state, err := GetMCMSWithTimelockState(legacyStore.Seal().Addresses(), chain, "") + require.NoError(t, err) + + require.NotNil(t, state.Timelock, "timelock should still load") + require.NotNil(t, state.CallProxy, "callProxy should still load") + require.NotNil(t, state.ProposerMcm, "proposer should still load") + require.Nil(t, state.BypasserMcm, "legacy ManyChainMultisig bypasser should not load") + require.Nil(t, state.CancellerMcm, "legacy ManyChainMultisig canceller should not load") + }) +} + +// ----- helpers ----- + +func toJSON[T any](t *testing.T, value T) string { + t.Helper() + + bytes, err := json.Marshal(value) + require.NoError(t, err) + + return string(bytes) +} + +func deployMCMEvm( + t *testing.T, chain cldf_evm.Chain, config *mcmstypes.Config, +) *bindings.ManyChainMultiSig { + t.Helper() + + _, tx, contract, err := bindings.DeployManyChainMultiSig(chain.DeployerKey, chain.Client) + require.NoError(t, err) + _, err = chain.Confirm(tx) + require.NoError(t, err) + + groupQuorums, groupParents, signerAddresses, signerGroups, err := sdk.ExtractSetConfigInputs(config) + require.NoError(t, err) + tx, err = contract.SetConfig(chain.DeployerKey, signerAddresses, signerGroups, groupQuorums, groupParents, false) + require.NoError(t, err) + _, err = chain.Confirm(tx) + require.NoError(t, err) + + return contract +} + +func deployTimelockEvm( + t *testing.T, chain cldf_evm.Chain, minDelay *big.Int, admin common.Address, + proposers, executors, cancellers, bypassers []common.Address, +) *bindings.RBACTimelock { + t.Helper() + _, tx, contract, err := bindings.DeployRBACTimelock( + chain.DeployerKey, chain.Client, minDelay, admin, proposers, executors, cancellers, bypassers) + require.NoError(t, err) + _, err = chain.Confirm(tx) + require.NoError(t, err) + + return contract +} + +func deployCallProxyEvm( + t *testing.T, chain cldf_evm.Chain, target common.Address, +) *bindings.CallProxy { + t.Helper() + _, tx, contract, err := bindings.DeployCallProxy(chain.DeployerKey, chain.Client, target) + require.NoError(t, err) + _, err = chain.Confirm(tx) + require.NoError(t, err) + + return contract +} + +func evmAddr(addr common.Address) string { + return strings.ToLower(addr.Hex()) +} From 6c0e9db8c267dd09470c207ded6762cd0f29dcc0 Mon Sep 17 00:00:00 2001 From: Pablo Date: Wed, 22 Apr 2026 07:53:02 -0600 Subject: [PATCH 02/12] fix: remove unused function, will be ported in graham's PR --- pkg/family/evm/state.go | 215 ++++++++++------------------------- pkg/family/evm/state_test.go | 72 ++++++------ 2 files changed, 98 insertions(+), 189 deletions(-) diff --git a/pkg/family/evm/state.go b/pkg/family/evm/state.go index 5992278..6063c46 100644 --- a/pkg/family/evm/state.go +++ b/pkg/family/evm/state.go @@ -16,7 +16,6 @@ import ( cldf_evm "github.com/smartcontractkit/chainlink-deployments-framework/chain/evm" cldf "github.com/smartcontractkit/chainlink-deployments-framework/deployment" - cldproposalutils "github.com/smartcontractkit/chainlink-deployments-framework/engine/cld/mcms/proposalutils" linkview "github.com/smartcontractkit/cld-changesets/link/view" @@ -66,36 +65,6 @@ func (state MCMSWithTimelockState) GenerateMCMSWithTimelockView() (v1_0.MCMSWith *state.Timelock, *state.CallProxy) } -// MaybeLoadMCMSWithTimelockState loads the MCMSWithTimelockState state for each chain in the given environment. -func MaybeLoadMCMSWithTimelockState(env cldf.Environment, chainSelectors []uint64) (map[uint64]*MCMSWithTimelockState, error) { - return MaybeLoadMCMSWithTimelockStateWithQualifier(env, chainSelectors, "") -} - -// MaybeLoadMCMSWithTimelockStateWithQualifier loads the MCMSWithTimelockState state for each chain in the given environment, -// supporting qualifiers for filtering addresses. This uses the merged approach searching both AddressBook and DataStore. -func MaybeLoadMCMSWithTimelockStateWithQualifier(env cldf.Environment, chainSelectors []uint64, qualifier string) (map[uint64]*MCMSWithTimelockState, error) { - result := map[uint64]*MCMSWithTimelockState{} - for _, chainSelector := range chainSelectors { - chain, ok := env.BlockChains.EVMChains()[chainSelector] - if !ok { - return nil, fmt.Errorf("chain %d not found", chainSelector) - } - - // Use merged addresses from both AddressBook and DataStore for backward compatibility - addressesChain, err := AddressesForChain(env, chainSelector, qualifier) - if err != nil { - return nil, err - } - - state, err := MaybeLoadMCMSWithTimelockChainState(chain, addressesChain) - if err != nil { - return nil, err - } - result[chainSelector] = state - } - return result, nil -} - // AddressesForChain combines addresses from both DataStore and AddressBook making it backward compatible. // This version supports qualifiers for filtering DataStore addresses. // When a qualifier is specified, only DataStore addresses with that qualifier are returned (no AddressBook merge) @@ -192,130 +161,6 @@ func LoadAddressesFromDataStore(ds datastore.DataStore, chainSelector uint64, qu return addressesChain, nil } -// GetAddressTypeVersionByQualifier loads addresses from DataStore for a specific chain and qualifier. -// returns a map of address to TypeAndVersion. -func GetAddressTypeVersionByQualifier(store datastore.AddressRefStore, chainSelector uint64, qualifier string) (map[string]cldf.TypeAndVersion, error) { - addressesChain := make(map[string]cldf.TypeAndVersion) - - // Build filter list starting with chain selector - filters := []datastore.FilterFunc[datastore.AddressRefKey, datastore.AddressRef]{datastore.AddressRefByChainSelector(chainSelector)} - - // Add qualifier filter if provided - if qualifier != "" { - filters = append(filters, datastore.AddressRefByQualifier(qualifier)) - } - - addresses := store.Filter(filters...) - if len(addresses) == 0 { - return nil, fmt.Errorf("no addresses found for chain %d", chainSelector) - } - - for _, addressRef := range addresses { - tv := cldf.TypeAndVersion{ - Type: cldf.ContractType(addressRef.Type), - Version: *addressRef.Version, - } - // Preserve labels from DataStore - if !addressRef.Labels.IsEmpty() { - tv.Labels = cldf.NewLabelSet(addressRef.Labels.List()...) - } - addressesChain[addressRef.Address] = tv - } - return addressesChain, nil -} - -// MaybeLoadMCMSWithTimelockChainState looks for the addresses corresponding to -// contracts deployed with DeployMCMSWithTimelock and loads them into a -// MCMSWithTimelockState struct. If none of the contracts are found, the state struct will be nil. -// An error indicates: -// - Found but was unable to load a contract -// - It only found part of the bundle of contracts -// - If found more than one instance of a contract (we expect one bundle in the given addresses) -func MaybeLoadMCMSWithTimelockChainState(chain cldf_evm.Chain, addresses map[string]cldf.TypeAndVersion) (*MCMSWithTimelockState, error) { - state := MCMSWithTimelockState{} - var ( - // We expect one of each contract on the chain. - timelock = cldf.NewTypeAndVersion(cldproposalutils.RBACTimelock, common2.Version1_0_0) - callProxy = cldf.NewTypeAndVersion(proposalutils.CallProxy, common2.Version1_0_0) - proposer = cldf.NewTypeAndVersion(proposalutils.ProposerManyChainMultisig, common2.Version1_0_0) - canceller = cldf.NewTypeAndVersion(proposalutils.CancellerManyChainMultisig, common2.Version1_0_0) - bypasser = cldf.NewTypeAndVersion(proposalutils.BypasserManyChainMultisig, common2.Version1_0_0) - - // the same contract can have different roles - multichain = cldf.NewTypeAndVersion(proposalutils.ManyChainMultisig, common2.Version1_0_0) - proposerMCMS = cldf.NewTypeAndVersion(proposalutils.ManyChainMultisig, common2.Version1_0_0) - bypasserMCMS = cldf.NewTypeAndVersion(proposalutils.ManyChainMultisig, common2.Version1_0_0) - cancellerMCMS = cldf.NewTypeAndVersion(proposalutils.ManyChainMultisig, common2.Version1_0_0) - ) - - // Convert map keys to a slice - proposerMCMS.Labels.Add(proposalutils.ProposerRole.String()) - bypasserMCMS.Labels.Add(proposalutils.BypasserRole.String()) - cancellerMCMS.Labels.Add(proposalutils.CancellerRole.String()) - wantTypes := []cldf.TypeAndVersion{timelock, proposer, canceller, bypasser, callProxy, - proposerMCMS, bypasserMCMS, cancellerMCMS, - } - - // Ensure we either have the bundle or not. - _, err := cldf.EnsureDeduped(addresses, wantTypes) - if err != nil { - return nil, fmt.Errorf("unable to check MCMS contracts on chain %s error: %w", chain.Name(), err) - } - - for address, tv := range addresses { - switch { - case tv.Type == timelock.Type && tv.Version.String() == timelock.Version.String(): - tl, err := bindings.NewRBACTimelock(common.HexToAddress(address), chain.Client) - if err != nil { - return nil, err - } - state.Timelock = tl - case tv.Type == callProxy.Type && tv.Version.String() == callProxy.Version.String(): - cp, err := bindings.NewCallProxy(common.HexToAddress(address), chain.Client) - if err != nil { - return nil, err - } - state.CallProxy = cp - case tv.Type == proposer.Type && tv.Version.String() == proposer.Version.String(): - mcms, err := bindings.NewManyChainMultiSig(common.HexToAddress(address), chain.Client) - if err != nil { - return nil, err - } - state.ProposerMcm = mcms - case tv.Type == bypasser.Type && tv.Version.String() == bypasser.Version.String(): - mcms, err := bindings.NewManyChainMultiSig(common.HexToAddress(address), chain.Client) - if err != nil { - return nil, err - } - state.BypasserMcm = mcms - case tv.Type == canceller.Type && tv.Version.String() == canceller.Version.String(): - mcms, err := bindings.NewManyChainMultiSig(common.HexToAddress(address), chain.Client) - if err != nil { - return nil, err - } - state.CancellerMcm = mcms - case tv.Type == multichain.Type && tv.Version.String() == multichain.Version.String(): - // Contract of type ManyChainMultiSig must be labeled to assign to the proper state - // field. If a specifically typed contract already occupies the field, then this - // contract will be ignored. - mcms, err := bindings.NewManyChainMultiSig(common.HexToAddress(address), chain.Client) - if err != nil { - return nil, err - } - if tv.Labels.Contains(proposalutils.ProposerRole.String()) && state.ProposerMcm == nil { - state.ProposerMcm = mcms - } - if tv.Labels.Contains(proposalutils.BypasserRole.String()) && state.BypasserMcm == nil { - state.BypasserMcm = mcms - } - if tv.Labels.Contains(proposalutils.CancellerRole.String()) && state.CancellerMcm == nil { - state.CancellerMcm = mcms - } - } - } - return &state, nil -} - // MaybeLoadMCMSWithTimelockChainStateFromRefs is the DataStore-native equivalent of MaybeLoadMCMSWithTimelockChainState. // It accepts []datastore.AddressRef directly, to preserve entries when multiple contract types share the same address (e.g. bypasser and canceller). func MaybeLoadMCMSWithTimelockChainStateFromRefs(chain cldf_evm.Chain, refs []datastore.AddressRef) (*MCMSWithTimelockState, error) { @@ -463,3 +308,63 @@ func MaybeLoadStaticLinkTokenState(chain cldf_evm.Chain, addresses map[string]cl } return &state, nil } + +// GetAddressTypeVersionByQualifier loads addresses from DataStore for a specific chain and qualifier. +// It returns a map of address to TypeAndVersion. Refs with a nil Version are skipped; if none remain, +// it returns an error. Each address must be a non-zero hex-encoded EVM address (see common.IsHexAddress). +func GetAddressTypeVersionByQualifier(store datastore.AddressRefStore, chainSelector uint64, qualifier string) (map[string]cldf.TypeAndVersion, error) { + addressesChain := make(map[string]cldf.TypeAndVersion) + + // Build filter list starting with chain selector + filters := []datastore.FilterFunc[datastore.AddressRefKey, datastore.AddressRef]{datastore.AddressRefByChainSelector(chainSelector)} + + // Add qualifier filter if provided + if qualifier != "" { + filters = append(filters, datastore.AddressRefByQualifier(qualifier)) + } + + addresses := store.Filter(filters...) + if len(addresses) == 0 { + if qualifier != "" { + return nil, fmt.Errorf("no addresses found for chain %d with qualifier %q", chainSelector, qualifier) + } + + return nil, fmt.Errorf("no addresses found for chain %d", chainSelector) + } + + for _, addressRef := range addresses { + if addressRef.Version == nil { + continue + } + if _, err := parseValidatedEVMAddress(addressRef.Address); err != nil { + return nil, fmt.Errorf("datastore address ref for chain %d type=%s version=%s: %w", + chainSelector, addressRef.Type, addressRef.Version.String(), err) + } + tv := cldf.TypeAndVersion{ + Type: cldf.ContractType(addressRef.Type), + Version: *addressRef.Version, + } + // Preserve labels from DataStore + if !addressRef.Labels.IsEmpty() { + tv.Labels = cldf.NewLabelSet(addressRef.Labels.List()...) + } + addressesChain[addressRef.Address] = tv + } + + if len(addressesChain) == 0 { + return nil, fmt.Errorf("no address refs with a non-nil contract version for chain %d", chainSelector) + } + + return addressesChain, nil +} +func parseValidatedEVMAddress(raw string) (common.Address, error) { + if !common.IsHexAddress(raw) { + return common.Address{}, fmt.Errorf("not a valid hex-encoded EVM address: %q", raw) + } + addr := common.HexToAddress(raw) + if addr == (common.Address{}) { + return common.Address{}, fmt.Errorf("EVM address must not be the zero address: %q", raw) + } + + return addr, nil +} diff --git a/pkg/family/evm/state_test.go b/pkg/family/evm/state_test.go index 4b8baa0..49a9e13 100644 --- a/pkg/family/evm/state_test.go +++ b/pkg/family/evm/state_test.go @@ -15,10 +15,14 @@ import ( "github.com/stretchr/testify/require" cldf_evm "github.com/smartcontractkit/chainlink-deployments-framework/chain/evm" + cldflink "github.com/smartcontractkit/chainlink-deployments-framework/engine/cld/link" + cldfproposalutils "github.com/smartcontractkit/chainlink-deployments-framework/engine/cld/mcms/proposalutils" "github.com/smartcontractkit/chainlink-deployments-framework/engine/test/environment" "github.com/smartcontractkit/chainlink-deployments-framework/datastore" cldf "github.com/smartcontractkit/chainlink-deployments-framework/deployment" + + cldcommon "github.com/smartcontractkit/cld-changesets/pkg/common" ) func TestMCMSWithTimelockState_GenerateMCMSWithTimelockViewV2(t *testing.T) { @@ -136,7 +140,7 @@ func TestAddressesForChain(t *testing.T) { // Create environment with only AddressBook addressBook := cldf.NewMemoryAddressBook() err := addressBook.Save(chainSelector, "0x1234567890123456789012345678901234567890", - cldf.NewTypeAndVersion(types.LinkToken, deployment.Version1_0_0)) + cldf.NewTypeAndVersion(cldflink.LinkToken, cldcommon.Version1_0_0)) require.NoError(t, err) env := cldf.Environment{ @@ -159,8 +163,8 @@ func TestAddressesForChain(t *testing.T) { err := dataStore.Addresses().Add(datastore.AddressRef{ ChainSelector: chainSelector, Address: "0xABCDEF1234567890123456789012345678901234", - Type: datastore.ContractType(types.RBACTimelock), - Version: &deployment.Version1_0_0, + Type: datastore.ContractType(cldfproposalutils.RBACTimelock), + Version: &cldcommon.Version1_0_0, }) require.NoError(t, err) @@ -183,15 +187,15 @@ func TestAddressesForChain(t *testing.T) { // Create a mock environment with both AddressBook and DataStore addressBook := cldf.NewMemoryAddressBook() err := addressBook.Save(chainSelector, "0x1234567890123456789012345678901234567890", - cldf.NewTypeAndVersion(types.LinkToken, deployment.Version1_0_0)) + cldf.NewTypeAndVersion(cldflink.LinkToken, cldcommon.Version1_0_0)) require.NoError(t, err) dataStore := datastore.NewMemoryDataStore() err = dataStore.Addresses().Add(datastore.AddressRef{ ChainSelector: chainSelector, Address: "0xABCDEF1234567890123456789012345678901234", - Type: datastore.ContractType(types.RBACTimelock), - Version: &deployment.Version1_0_0, + Type: datastore.ContractType(cldfproposalutils.RBACTimelock), + Version: &cldcommon.Version1_0_0, Labels: datastore.NewLabelSet( "team:core", "environment:production", @@ -216,12 +220,12 @@ func TestAddressesForChain(t *testing.T) { // Verify that types are correctly preserved linkTokenTV := mergedAddresses["0x1234567890123456789012345678901234567890"] - require.Equal(t, types.LinkToken, linkTokenTV.Type) - require.Equal(t, deployment.Version1_0_0, linkTokenTV.Version) + require.Equal(t, cldflink.LinkToken, linkTokenTV.Type) + require.Equal(t, cldcommon.Version1_0_0, linkTokenTV.Version) timelockTV := mergedAddresses["0xABCDEF1234567890123456789012345678901234"] - require.Equal(t, types.RBACTimelock, timelockTV.Type) - require.Equal(t, deployment.Version1_0_0, timelockTV.Version) + require.Equal(t, cldfproposalutils.RBACTimelock, timelockTV.Type) + require.Equal(t, cldcommon.Version1_0_0, timelockTV.Version) // Verify labels are preserved in DataStore refs := env.DataStore.Addresses().Filter(datastore.AddressRefByChainSelector(chainSelector)) @@ -241,8 +245,8 @@ func TestAddressesForChain(t *testing.T) { err := dataStore.Addresses().Add(datastore.AddressRef{ ChainSelector: chainSelector, Address: "0x1111111111111111111111111111111111111111", - Type: datastore.ContractType(types.RBACTimelock), - Version: &deployment.Version1_0_0, + Type: datastore.ContractType(cldfproposalutils.RBACTimelock), + Version: &cldcommon.Version1_0_0, Qualifier: "team-a", Labels: datastore.NewLabelSet( "team:team-a", @@ -254,8 +258,8 @@ func TestAddressesForChain(t *testing.T) { err = dataStore.Addresses().Add(datastore.AddressRef{ ChainSelector: chainSelector, Address: "0x2222222222222222222222222222222222222222", - Type: datastore.ContractType(types.RBACTimelock), - Version: &deployment.Version1_0_0, + Type: datastore.ContractType(cldfproposalutils.RBACTimelock), + Version: &cldcommon.Version1_0_0, Qualifier: "team-b", Labels: datastore.NewLabelSet( "team:team-b", @@ -280,7 +284,7 @@ func TestAddressesForChain(t *testing.T) { // Verify the correct contract type timelockTV := mergedAddresses["0x1111111111111111111111111111111111111111"] - require.Equal(t, types.RBACTimelock, timelockTV.Type) + require.Equal(t, cldfproposalutils.RBACTimelock, timelockTV.Type) // Verify labels are preserved for the filtered contract refs := env.DataStore.Addresses().Filter( @@ -306,7 +310,7 @@ func TestAddressesForChain(t *testing.T) { addressBook := cldf.NewMemoryAddressBook() // Add LinkToken to AddressBook err := addressBook.Save(chainSelector, duplicateAddress, - cldf.NewTypeAndVersion(types.LinkToken, deployment.Version1_0_0)) + cldf.NewTypeAndVersion(cldflink.LinkToken, cldcommon.Version1_0_0)) require.NoError(t, err) dataStore := datastore.NewMemoryDataStore() @@ -314,9 +318,9 @@ func TestAddressesForChain(t *testing.T) { // Add the SAME address to DataStore but with different type/version and labels err = dataStore.Addresses().Add(datastore.AddressRef{ ChainSelector: chainSelector, - Address: duplicateAddress, // Same address as AddressBook - Type: datastore.ContractType(types.RBACTimelock), // Different type from AddressBook LinkToken - Version: &deployment.Version1_6_0, // Different version + Address: duplicateAddress, // Same address as AddressBook + Type: datastore.ContractType(cldfproposalutils.RBACTimelock), // Different type from AddressBook LinkToken + Version: &cldcommon.Version1_6_0, // Different version Labels: datastore.NewLabelSet( "team:datastore-team", "environment:staging", @@ -329,8 +333,8 @@ func TestAddressesForChain(t *testing.T) { err = dataStore.Addresses().Add(datastore.AddressRef{ ChainSelector: chainSelector, Address: uniqueAddress, - Type: datastore.ContractType(types.RBACTimelock), - Version: &deployment.Version1_0_0, + Type: datastore.ContractType(cldfproposalutils.RBACTimelock), + Version: &cldcommon.Version1_0_0, Labels: datastore.NewLabelSet( "team:unique-entry", "role:timelock", @@ -354,13 +358,13 @@ func TestAddressesForChain(t *testing.T) { // The duplicate address should use DataStore values (DataStore takes precedence) duplicateTV := mergedAddresses[duplicateAddress] - require.Equal(t, types.RBACTimelock, duplicateTV.Type, "DataStore type should override AddressBook type") - require.Equal(t, deployment.Version1_6_0, duplicateTV.Version, "DataStore version should override AddressBook version") + require.Equal(t, cldfproposalutils.RBACTimelock, duplicateTV.Type, "DataStore type should override AddressBook type") + require.Equal(t, cldcommon.Version1_6_0, duplicateTV.Version, "DataStore version should override AddressBook version") // The unique address should have correct type uniqueTV := mergedAddresses[uniqueAddress] - require.Equal(t, types.RBACTimelock, uniqueTV.Type) - require.Equal(t, deployment.Version1_0_0, uniqueTV.Version) + require.Equal(t, cldfproposalutils.RBACTimelock, uniqueTV.Type) + require.Equal(t, cldcommon.Version1_0_0, uniqueTV.Version) // Verify that DataStore labels are preserved for both addresses refs := env.DataStore.Addresses().Filter(datastore.AddressRefByChainSelector(chainSelector)) @@ -420,9 +424,9 @@ func TestGetMCMSWithTimelockState(t *testing.T) { // timelock, callProxy, proposer shared by both stores commonRefs := []datastore.AddressRef{ - {ChainSelector: selector, Address: strings.ToLower(timelock.Address().Hex()), Type: datastore.ContractType(types.RBACTimelock), Version: &deployment.Version1_0_0}, - {ChainSelector: selector, Address: strings.ToLower(callProxy.Address().Hex()), Type: datastore.ContractType(types.CallProxy), Version: &deployment.Version1_0_0}, - {ChainSelector: selector, Address: strings.ToLower(proposerMcm.Address().Hex()), Type: datastore.ContractType(types.ProposerManyChainMultisig), Version: &deployment.Version1_0_0}, + {ChainSelector: selector, Address: strings.ToLower(timelock.Address().Hex()), Type: datastore.ContractType(cldfproposalutils.RBACTimelock), Version: &cldcommon.Version1_0_0}, + {ChainSelector: selector, Address: strings.ToLower(callProxy.Address().Hex()), Type: datastore.ContractType(cldfproposalutils.CallProxy), Version: &cldcommon.Version1_0_0}, + {ChainSelector: selector, Address: strings.ToLower(proposerMcm.Address().Hex()), Type: datastore.ContractType(cldfproposalutils.ProposerManyChainMultisig), Version: &cldcommon.Version1_0_0}, } t.Run("shared address for bypasser and canceller", func(t *testing.T) { @@ -433,11 +437,11 @@ func TestGetMCMSWithTimelockState(t *testing.T) { } require.NoError(t, store.Addresses().Add(datastore.AddressRef{ ChainSelector: selector, Address: sharedAddress, - Type: datastore.ContractType(types.BypasserManyChainMultisig), Version: &deployment.Version1_0_0, Qualifier: "bypasser", + Type: datastore.ContractType(cldfproposalutils.BypasserManyChainMultisig), Version: &cldcommon.Version1_0_0, Qualifier: "bypasser", })) require.NoError(t, store.Addresses().Add(datastore.AddressRef{ ChainSelector: selector, Address: sharedAddress, - Type: datastore.ContractType(types.CancellerManyChainMultisig), Version: &deployment.Version1_0_0, Qualifier: "canceller", + Type: datastore.ContractType(cldfproposalutils.CancellerManyChainMultisig), Version: &cldcommon.Version1_0_0, Qualifier: "canceller", })) state, err := GetMCMSWithTimelockState(store.Seal().Addresses(), chain, "") @@ -461,13 +465,13 @@ func TestGetMCMSWithTimelockState(t *testing.T) { } require.NoError(t, legacyStore.Addresses().Add(datastore.AddressRef{ ChainSelector: selector, Address: sharedAddress, - Type: datastore.ContractType(types.ManyChainMultisig), Version: &deployment.Version1_0_0, Qualifier: "bypasser", - Labels: datastore.NewLabelSet(types.BypasserRole.String()), + Type: datastore.ContractType(cldfproposalutils.ManyChainMultisig), Version: &cldcommon.Version1_0_0, Qualifier: "bypasser", + Labels: datastore.NewLabelSet(cldfproposalutils.BypasserRole.String()), })) require.NoError(t, legacyStore.Addresses().Add(datastore.AddressRef{ ChainSelector: selector, Address: sharedAddress, - Type: datastore.ContractType(types.ManyChainMultisig), Version: &deployment.Version1_0_0, Qualifier: "canceller", - Labels: datastore.NewLabelSet(types.CancellerRole.String()), + Type: datastore.ContractType(cldfproposalutils.ManyChainMultisig), Version: &cldcommon.Version1_0_0, Qualifier: "canceller", + Labels: datastore.NewLabelSet(cldfproposalutils.CancellerRole.String()), })) state, err := GetMCMSWithTimelockState(legacyStore.Seal().Addresses(), chain, "") From 038d5a21ceb40607bfac96b69e32121983ef6fc1 Mon Sep 17 00:00:00 2001 From: Pablo Estrada <139084212+ecPablo@users.noreply.github.com> Date: Wed, 22 Apr 2026 07:55:10 -0600 Subject: [PATCH 03/12] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- pkg/family/evm/state.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/family/evm/state.go b/pkg/family/evm/state.go index 6063c46..c1865f2 100644 --- a/pkg/family/evm/state.go +++ b/pkg/family/evm/state.go @@ -152,7 +152,7 @@ func GetMCMSWithTimelockState(store datastore.AddressRefStore, chain cldf_evm.Ch // LoadAddressesFromDataStore loads addresses from DataStore with optional qualifier. // This is a public utility function that can be used by other packages to avoid duplication. // -// Deprecated: Use GetTypeVersionByQualifier instead. +// Deprecated: Use GetAddressTypeVersionByQualifier instead. func LoadAddressesFromDataStore(ds datastore.DataStore, chainSelector uint64, qualifier string) (map[string]cldf.TypeAndVersion, error) { addressesChain, err := GetAddressTypeVersionByQualifier(ds.Addresses(), chainSelector, qualifier) if err != nil { From 3cde7253da392b7bb86b96c18d7355277e2382ea Mon Sep 17 00:00:00 2001 From: Pablo Estrada <139084212+ecPablo@users.noreply.github.com> Date: Wed, 22 Apr 2026 07:55:26 -0600 Subject: [PATCH 04/12] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- pkg/family/evm/state.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/family/evm/state.go b/pkg/family/evm/state.go index c1865f2..3065967 100644 --- a/pkg/family/evm/state.go +++ b/pkg/family/evm/state.go @@ -119,6 +119,9 @@ func MaybeLoadMCMSWithTimelockStateDataStore(env cldf.Environment, chainSelector func MaybeLoadMCMSWithTimelockStateDataStoreWithQualifier(env cldf.Environment, chainSelectors []uint64, qualifier string) (map[uint64]*MCMSWithTimelockState, error) { result := map[uint64]*MCMSWithTimelockState{} ds := env.DataStore + if ds == nil { + return nil, fmt.Errorf("datastore not available") + } for _, chainSelector := range chainSelectors { chain, ok := env.BlockChains.EVMChains()[chainSelector] if !ok { From d2207a3d35420a6f9de2bbfda19b6bdbf69c5e09 Mon Sep 17 00:00:00 2001 From: Pablo Estrada <139084212+ecPablo@users.noreply.github.com> Date: Wed, 22 Apr 2026 07:56:28 -0600 Subject: [PATCH 05/12] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- mcms/common/view/v1_0/mcms.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mcms/common/view/v1_0/mcms.go b/mcms/common/view/v1_0/mcms.go index 50406b7..fdbc7a2 100644 --- a/mcms/common/view/v1_0/mcms.go +++ b/mcms/common/view/v1_0/mcms.go @@ -111,12 +111,12 @@ func GenerateTimelockView(tl owner_helpers.RBACTimelock) (TimelockView, error) { for _, role := range []Role{ADMIN_ROLE, PROPOSER_ROLE, BYPASSER_ROLE, CANCELLER_ROLE, EXECUTOR_ROLE} { numMembers, err := tl.GetRoleMemberCount(nil, role.ID) if err != nil { - return TimelockView{}, nil + return TimelockView{}, fmt.Errorf("get role member count for role %s (%s): %w", role.Name, role.ID.Hex(), err) } for i := int64(0); i < numMembers.Int64(); i++ { member, err2 := tl.GetRoleMember(nil, role.ID, big.NewInt(i)) if err2 != nil { - return TimelockView{}, nil + return TimelockView{}, fmt.Errorf("get role member for role %s (%s) at index %d: %w", role.Name, role.ID.Hex(), i, err2) } membersByRole[role.Name] = append(membersByRole[role.Name], member) } From 78942046485010fda3aae997901ccfcd119791ef Mon Sep 17 00:00:00 2001 From: Pablo Date: Wed, 22 Apr 2026 09:05:58 -0600 Subject: [PATCH 06/12] fix: update contract types imports --- link/view/link_token.go | 4 +-- link/view/static_link_token.go | 4 +-- pkg/family/evm/state.go | 19 ++++++------ pkg/family/evm/state_test.go | 55 +++++++++++++++++----------------- 4 files changed, 41 insertions(+), 41 deletions(-) diff --git a/link/view/link_token.go b/link/view/link_token.go index 4756eb9..292462b 100644 --- a/link/view/link_token.go +++ b/link/view/link_token.go @@ -5,11 +5,11 @@ import ( "math/big" "github.com/ethereum/go-ethereum/common" + linkcontracts "github.com/smartcontractkit/chainlink-deployments-framework/engine/cld/contracts/link" "github.com/smartcontractkit/chainlink-evm/gethwrappers/shared/generated/initial/link_token" cldf "github.com/smartcontractkit/chainlink-deployments-framework/deployment" - cldflink "github.com/smartcontractkit/chainlink-deployments-framework/engine/cld/link" cldchangesetscommon "github.com/smartcontractkit/cld-changesets/pkg/common" ) @@ -46,7 +46,7 @@ func GenerateLinkTokenView(lt *link_token.LinkToken) (LinkTokenView, error) { return LinkTokenView{ ContractMetaData: cldchangesetscommon.ContractMetaData{ TypeAndVersion: cldf.TypeAndVersion{ - Type: cldflink.LinkToken, + Type: linkcontracts.LinkToken, Version: cldchangesetscommon.Version1_0_0, }.String(), Address: lt.Address(), diff --git a/link/view/static_link_token.go b/link/view/static_link_token.go index 59e1b52..ce1bef1 100644 --- a/link/view/static_link_token.go +++ b/link/view/static_link_token.go @@ -4,7 +4,7 @@ import ( "fmt" "math/big" - "github.com/smartcontractkit/chainlink-deployments-framework/engine/cld/link" + linkcontracts "github.com/smartcontractkit/chainlink-deployments-framework/engine/cld/contracts/link" "github.com/smartcontractkit/chainlink-evm/gethwrappers/generated/link_token_interface" cldf "github.com/smartcontractkit/chainlink-deployments-framework/deployment" @@ -30,7 +30,7 @@ func GenerateStaticLinkTokenView(lt *link_token_interface.LinkToken) (StaticLink return StaticLinkTokenView{ ContractMetaData: common.ContractMetaData{ TypeAndVersion: cldf.TypeAndVersion{ - Type: link.StaticLinkToken, + Type: linkcontracts.StaticLinkToken, Version: common.Version1_0_0, }.String(), Address: lt.Address(), diff --git a/pkg/family/evm/state.go b/pkg/family/evm/state.go index 3065967..8863691 100644 --- a/pkg/family/evm/state.go +++ b/pkg/family/evm/state.go @@ -7,10 +7,9 @@ import ( "github.com/ethereum/go-ethereum/common" bindings "github.com/smartcontractkit/ccip-owner-contracts/pkg/gethwrappers" - cldflink "github.com/smartcontractkit/chainlink-deployments-framework/engine/cld/link" - "github.com/smartcontractkit/chainlink-deployments-framework/engine/cld/mcms/proposalutils" - "github.com/smartcontractkit/chainlink-deployments-framework/datastore" + linkcontracts "github.com/smartcontractkit/chainlink-deployments-framework/engine/cld/contracts/link" + mcmscontracts "github.com/smartcontractkit/chainlink-deployments-framework/engine/cld/contracts/mcms" "github.com/smartcontractkit/chainlink-evm/gethwrappers/generated/link_token_interface" "github.com/smartcontractkit/chainlink-evm/gethwrappers/shared/generated/initial/link_token" @@ -170,11 +169,11 @@ func MaybeLoadMCMSWithTimelockChainStateFromRefs(chain cldf_evm.Chain, refs []da state := MCMSWithTimelockState{} var ( // We expect one of each contract on the chain. - timelock = cldf.NewTypeAndVersion(proposalutils.RBACTimelock, common2.Version1_0_0) - callProxy = cldf.NewTypeAndVersion(proposalutils.CallProxy, common2.Version1_0_0) - proposer = cldf.NewTypeAndVersion(proposalutils.ProposerManyChainMultisig, common2.Version1_0_0) - canceller = cldf.NewTypeAndVersion(proposalutils.CancellerManyChainMultisig, common2.Version1_0_0) - bypasser = cldf.NewTypeAndVersion(proposalutils.BypasserManyChainMultisig, common2.Version1_0_0) + timelock = cldf.NewTypeAndVersion(mcmscontracts.RBACTimelock, common2.Version1_0_0) + callProxy = cldf.NewTypeAndVersion(mcmscontracts.CallProxy, common2.Version1_0_0) + proposer = cldf.NewTypeAndVersion(mcmscontracts.ProposerManyChainMultisig, common2.Version1_0_0) + canceller = cldf.NewTypeAndVersion(mcmscontracts.CancellerManyChainMultisig, common2.Version1_0_0) + bypasser = cldf.NewTypeAndVersion(mcmscontracts.BypasserManyChainMultisig, common2.Version1_0_0) ) wantTypes := []cldf.TypeAndVersion{timelock, proposer, canceller, bypasser, callProxy} @@ -253,7 +252,7 @@ func (s LinkTokenState) GenerateLinkView() (linkview.LinkTokenView, error) { func MaybeLoadLinkTokenChainState(chain cldf_evm.Chain, addresses map[string]cldf.TypeAndVersion) (*LinkTokenState, error) { state := LinkTokenState{} - linkToken := cldf.NewTypeAndVersion(cldflink.LinkToken, common2.Version1_0_0) + linkToken := cldf.NewTypeAndVersion(linkcontracts.LinkToken, common2.Version1_0_0) // Convert map keys to a slice wantTypes := []cldf.TypeAndVersion{linkToken} @@ -289,7 +288,7 @@ func (s StaticLinkTokenState) GenerateStaticLinkView() (linkview.StaticLinkToken func MaybeLoadStaticLinkTokenState(chain cldf_evm.Chain, addresses map[string]cldf.TypeAndVersion) (*StaticLinkTokenState, error) { state := StaticLinkTokenState{} - staticLinkToken := cldf.NewTypeAndVersion(cldflink.StaticLinkToken, common2.Version1_0_0) + staticLinkToken := cldf.NewTypeAndVersion(linkcontracts.StaticLinkToken, common2.Version1_0_0) // Convert map keys to a slice wantTypes := []cldf.TypeAndVersion{staticLinkToken} diff --git a/pkg/family/evm/state_test.go b/pkg/family/evm/state_test.go index 49a9e13..3943040 100644 --- a/pkg/family/evm/state_test.go +++ b/pkg/family/evm/state_test.go @@ -15,8 +15,9 @@ import ( "github.com/stretchr/testify/require" cldf_evm "github.com/smartcontractkit/chainlink-deployments-framework/chain/evm" - cldflink "github.com/smartcontractkit/chainlink-deployments-framework/engine/cld/link" - cldfproposalutils "github.com/smartcontractkit/chainlink-deployments-framework/engine/cld/mcms/proposalutils" + linkcontracts "github.com/smartcontractkit/chainlink-deployments-framework/engine/cld/contracts/link" + mcmscontracts "github.com/smartcontractkit/chainlink-deployments-framework/engine/cld/contracts/mcms" + "github.com/smartcontractkit/chainlink-deployments-framework/engine/test/environment" "github.com/smartcontractkit/chainlink-deployments-framework/datastore" @@ -140,7 +141,7 @@ func TestAddressesForChain(t *testing.T) { // Create environment with only AddressBook addressBook := cldf.NewMemoryAddressBook() err := addressBook.Save(chainSelector, "0x1234567890123456789012345678901234567890", - cldf.NewTypeAndVersion(cldflink.LinkToken, cldcommon.Version1_0_0)) + cldf.NewTypeAndVersion(linkcontracts.LinkToken, cldcommon.Version1_0_0)) require.NoError(t, err) env := cldf.Environment{ @@ -163,7 +164,7 @@ func TestAddressesForChain(t *testing.T) { err := dataStore.Addresses().Add(datastore.AddressRef{ ChainSelector: chainSelector, Address: "0xABCDEF1234567890123456789012345678901234", - Type: datastore.ContractType(cldfproposalutils.RBACTimelock), + Type: datastore.ContractType(mcmscontracts.RBACTimelock), Version: &cldcommon.Version1_0_0, }) require.NoError(t, err) @@ -187,14 +188,14 @@ func TestAddressesForChain(t *testing.T) { // Create a mock environment with both AddressBook and DataStore addressBook := cldf.NewMemoryAddressBook() err := addressBook.Save(chainSelector, "0x1234567890123456789012345678901234567890", - cldf.NewTypeAndVersion(cldflink.LinkToken, cldcommon.Version1_0_0)) + cldf.NewTypeAndVersion(linkcontracts.LinkToken, cldcommon.Version1_0_0)) require.NoError(t, err) dataStore := datastore.NewMemoryDataStore() err = dataStore.Addresses().Add(datastore.AddressRef{ ChainSelector: chainSelector, Address: "0xABCDEF1234567890123456789012345678901234", - Type: datastore.ContractType(cldfproposalutils.RBACTimelock), + Type: datastore.ContractType(mcmscontracts.RBACTimelock), Version: &cldcommon.Version1_0_0, Labels: datastore.NewLabelSet( "team:core", @@ -220,11 +221,11 @@ func TestAddressesForChain(t *testing.T) { // Verify that types are correctly preserved linkTokenTV := mergedAddresses["0x1234567890123456789012345678901234567890"] - require.Equal(t, cldflink.LinkToken, linkTokenTV.Type) + require.Equal(t, linkcontracts.LinkToken, linkTokenTV.Type) require.Equal(t, cldcommon.Version1_0_0, linkTokenTV.Version) timelockTV := mergedAddresses["0xABCDEF1234567890123456789012345678901234"] - require.Equal(t, cldfproposalutils.RBACTimelock, timelockTV.Type) + require.Equal(t, mcmscontracts.RBACTimelock, timelockTV.Type) require.Equal(t, cldcommon.Version1_0_0, timelockTV.Version) // Verify labels are preserved in DataStore @@ -245,7 +246,7 @@ func TestAddressesForChain(t *testing.T) { err := dataStore.Addresses().Add(datastore.AddressRef{ ChainSelector: chainSelector, Address: "0x1111111111111111111111111111111111111111", - Type: datastore.ContractType(cldfproposalutils.RBACTimelock), + Type: datastore.ContractType(mcmscontracts.RBACTimelock), Version: &cldcommon.Version1_0_0, Qualifier: "team-a", Labels: datastore.NewLabelSet( @@ -258,7 +259,7 @@ func TestAddressesForChain(t *testing.T) { err = dataStore.Addresses().Add(datastore.AddressRef{ ChainSelector: chainSelector, Address: "0x2222222222222222222222222222222222222222", - Type: datastore.ContractType(cldfproposalutils.RBACTimelock), + Type: datastore.ContractType(mcmscontracts.RBACTimelock), Version: &cldcommon.Version1_0_0, Qualifier: "team-b", Labels: datastore.NewLabelSet( @@ -284,7 +285,7 @@ func TestAddressesForChain(t *testing.T) { // Verify the correct contract type timelockTV := mergedAddresses["0x1111111111111111111111111111111111111111"] - require.Equal(t, cldfproposalutils.RBACTimelock, timelockTV.Type) + require.Equal(t, mcmscontracts.RBACTimelock, timelockTV.Type) // Verify labels are preserved for the filtered contract refs := env.DataStore.Addresses().Filter( @@ -310,7 +311,7 @@ func TestAddressesForChain(t *testing.T) { addressBook := cldf.NewMemoryAddressBook() // Add LinkToken to AddressBook err := addressBook.Save(chainSelector, duplicateAddress, - cldf.NewTypeAndVersion(cldflink.LinkToken, cldcommon.Version1_0_0)) + cldf.NewTypeAndVersion(linkcontracts.LinkToken, cldcommon.Version1_0_0)) require.NoError(t, err) dataStore := datastore.NewMemoryDataStore() @@ -318,9 +319,9 @@ func TestAddressesForChain(t *testing.T) { // Add the SAME address to DataStore but with different type/version and labels err = dataStore.Addresses().Add(datastore.AddressRef{ ChainSelector: chainSelector, - Address: duplicateAddress, // Same address as AddressBook - Type: datastore.ContractType(cldfproposalutils.RBACTimelock), // Different type from AddressBook LinkToken - Version: &cldcommon.Version1_6_0, // Different version + Address: duplicateAddress, // Same address as AddressBook + Type: datastore.ContractType(mcmscontracts.RBACTimelock), // Different type from AddressBook LinkToken + Version: &cldcommon.Version1_6_0, // Different version Labels: datastore.NewLabelSet( "team:datastore-team", "environment:staging", @@ -333,7 +334,7 @@ func TestAddressesForChain(t *testing.T) { err = dataStore.Addresses().Add(datastore.AddressRef{ ChainSelector: chainSelector, Address: uniqueAddress, - Type: datastore.ContractType(cldfproposalutils.RBACTimelock), + Type: datastore.ContractType(mcmscontracts.RBACTimelock), Version: &cldcommon.Version1_0_0, Labels: datastore.NewLabelSet( "team:unique-entry", @@ -358,12 +359,12 @@ func TestAddressesForChain(t *testing.T) { // The duplicate address should use DataStore values (DataStore takes precedence) duplicateTV := mergedAddresses[duplicateAddress] - require.Equal(t, cldfproposalutils.RBACTimelock, duplicateTV.Type, "DataStore type should override AddressBook type") + require.Equal(t, mcmscontracts.RBACTimelock, duplicateTV.Type, "DataStore type should override AddressBook type") require.Equal(t, cldcommon.Version1_6_0, duplicateTV.Version, "DataStore version should override AddressBook version") // The unique address should have correct type uniqueTV := mergedAddresses[uniqueAddress] - require.Equal(t, cldfproposalutils.RBACTimelock, uniqueTV.Type) + require.Equal(t, mcmscontracts.RBACTimelock, uniqueTV.Type) require.Equal(t, cldcommon.Version1_0_0, uniqueTV.Version) // Verify that DataStore labels are preserved for both addresses @@ -424,9 +425,9 @@ func TestGetMCMSWithTimelockState(t *testing.T) { // timelock, callProxy, proposer shared by both stores commonRefs := []datastore.AddressRef{ - {ChainSelector: selector, Address: strings.ToLower(timelock.Address().Hex()), Type: datastore.ContractType(cldfproposalutils.RBACTimelock), Version: &cldcommon.Version1_0_0}, - {ChainSelector: selector, Address: strings.ToLower(callProxy.Address().Hex()), Type: datastore.ContractType(cldfproposalutils.CallProxy), Version: &cldcommon.Version1_0_0}, - {ChainSelector: selector, Address: strings.ToLower(proposerMcm.Address().Hex()), Type: datastore.ContractType(cldfproposalutils.ProposerManyChainMultisig), Version: &cldcommon.Version1_0_0}, + {ChainSelector: selector, Address: strings.ToLower(timelock.Address().Hex()), Type: datastore.ContractType(mcmscontracts.RBACTimelock), Version: &cldcommon.Version1_0_0}, + {ChainSelector: selector, Address: strings.ToLower(callProxy.Address().Hex()), Type: datastore.ContractType(mcmscontracts.CallProxy), Version: &cldcommon.Version1_0_0}, + {ChainSelector: selector, Address: strings.ToLower(proposerMcm.Address().Hex()), Type: datastore.ContractType(mcmscontracts.ProposerManyChainMultisig), Version: &cldcommon.Version1_0_0}, } t.Run("shared address for bypasser and canceller", func(t *testing.T) { @@ -437,11 +438,11 @@ func TestGetMCMSWithTimelockState(t *testing.T) { } require.NoError(t, store.Addresses().Add(datastore.AddressRef{ ChainSelector: selector, Address: sharedAddress, - Type: datastore.ContractType(cldfproposalutils.BypasserManyChainMultisig), Version: &cldcommon.Version1_0_0, Qualifier: "bypasser", + Type: datastore.ContractType(mcmscontracts.BypasserManyChainMultisig), Version: &cldcommon.Version1_0_0, Qualifier: "bypasser", })) require.NoError(t, store.Addresses().Add(datastore.AddressRef{ ChainSelector: selector, Address: sharedAddress, - Type: datastore.ContractType(cldfproposalutils.CancellerManyChainMultisig), Version: &cldcommon.Version1_0_0, Qualifier: "canceller", + Type: datastore.ContractType(mcmscontracts.CancellerManyChainMultisig), Version: &cldcommon.Version1_0_0, Qualifier: "canceller", })) state, err := GetMCMSWithTimelockState(store.Seal().Addresses(), chain, "") @@ -465,13 +466,13 @@ func TestGetMCMSWithTimelockState(t *testing.T) { } require.NoError(t, legacyStore.Addresses().Add(datastore.AddressRef{ ChainSelector: selector, Address: sharedAddress, - Type: datastore.ContractType(cldfproposalutils.ManyChainMultisig), Version: &cldcommon.Version1_0_0, Qualifier: "bypasser", - Labels: datastore.NewLabelSet(cldfproposalutils.BypasserRole.String()), + Type: datastore.ContractType(mcmscontracts.ManyChainMultisig), Version: &cldcommon.Version1_0_0, Qualifier: "bypasser", + Labels: datastore.NewLabelSet(mcmscontracts.BypasserRole.String()), })) require.NoError(t, legacyStore.Addresses().Add(datastore.AddressRef{ ChainSelector: selector, Address: sharedAddress, - Type: datastore.ContractType(cldfproposalutils.ManyChainMultisig), Version: &cldcommon.Version1_0_0, Qualifier: "canceller", - Labels: datastore.NewLabelSet(cldfproposalutils.CancellerRole.String()), + Type: datastore.ContractType(mcmscontracts.ManyChainMultisig), Version: &cldcommon.Version1_0_0, Qualifier: "canceller", + Labels: datastore.NewLabelSet(mcmscontracts.CancellerRole.String()), })) state, err := GetMCMSWithTimelockState(legacyStore.Seal().Addresses(), chain, "") From 1abb5213dfd7cb893f1f07949729dc59f756f086 Mon Sep 17 00:00:00 2001 From: Pablo Estrada <139084212+ecPablo@users.noreply.github.com> Date: Wed, 22 Apr 2026 09:32:37 -0600 Subject: [PATCH 07/12] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- pkg/family/evm/state.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pkg/family/evm/state.go b/pkg/family/evm/state.go index 8863691..e307f95 100644 --- a/pkg/family/evm/state.go +++ b/pkg/family/evm/state.go @@ -180,6 +180,9 @@ func MaybeLoadMCMSWithTimelockChainStateFromRefs(chain cldf_evm.Chain, refs []da dedupMap := make(map[string]cldf.TypeAndVersion, len(refs)) for _, ref := range refs { + if ref.Version == nil { + return nil, fmt.Errorf("invalid MCMS ref on chain %s: nil version for address %s type %s", chain.Name(), ref.Address, ref.Type) + } tv := cldf.TypeAndVersion{ Type: cldf.ContractType(ref.Type), Version: *ref.Version, @@ -197,6 +200,9 @@ func MaybeLoadMCMSWithTimelockChainStateFromRefs(chain cldf_evm.Chain, refs []da } for _, ref := range refs { + if ref.Version == nil { + return nil, fmt.Errorf("invalid MCMS ref on chain %s: nil version for address %s type %s", chain.Name(), ref.Address, ref.Type) + } addr := common.HexToAddress(ref.Address) tv := cldf.TypeAndVersion{ Type: cldf.ContractType(ref.Type), From 34bb0a788ad660f33239c9b6fd6024f6b6617b26 Mon Sep 17 00:00:00 2001 From: Pablo Estrada <139084212+ecPablo@users.noreply.github.com> Date: Wed, 22 Apr 2026 09:44:37 -0600 Subject: [PATCH 08/12] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- pkg/family/evm/state.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/family/evm/state.go b/pkg/family/evm/state.go index e307f95..d4f4217 100644 --- a/pkg/family/evm/state.go +++ b/pkg/family/evm/state.go @@ -145,7 +145,7 @@ func GetMCMSWithTimelockState(store datastore.AddressRefStore, chain cldf_evm.Ch refs := store.Filter(filters...) if len(refs) == 0 { - return nil, fmt.Errorf("no addresses found for chain %d", chain.Selector) + return nil, fmt.Errorf("no addresses found for chain %d with qualifier %q", chain.Selector, qualifier) } return MaybeLoadMCMSWithTimelockChainStateFromRefs(chain, refs) From 96757da1cfcb02e32b8d82e1f3977817b5450247 Mon Sep 17 00:00:00 2001 From: Pablo Estrada <139084212+ecPablo@users.noreply.github.com> Date: Wed, 22 Apr 2026 09:45:28 -0600 Subject: [PATCH 09/12] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- link/view/link_token.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/link/view/link_token.go b/link/view/link_token.go index 292462b..ea2f62c 100644 --- a/link/view/link_token.go +++ b/link/view/link_token.go @@ -1,4 +1,4 @@ -package v1_0 +package view import ( "fmt" From 0d11917e21decf97b0766451ba78471792f045fa Mon Sep 17 00:00:00 2001 From: Pablo Date: Wed, 22 Apr 2026 14:23:31 -0600 Subject: [PATCH 10/12] fix: go mod --- go.mod | 10 ++++++---- go.sum | 12 ++++++++---- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/go.mod b/go.mod index e3846de..38f6ae2 100644 --- a/go.mod +++ b/go.mod @@ -5,9 +5,14 @@ go 1.25.7 require ( github.com/Masterminds/semver/v3 v3.4.0 github.com/aptos-labs/aptos-go-sdk v1.12.0 + github.com/ethereum/go-ethereum v1.17.1 github.com/gagliardetto/solana-go v1.13.0 + github.com/smartcontractkit/ccip-owner-contracts v0.1.0 github.com/smartcontractkit/chain-selectors v1.0.97 - github.com/smartcontractkit/chainlink-deployments-framework v0.97.0 + github.com/smartcontractkit/chainlink-common v0.10.1-0.20260217160002-b56cb5356cc7 + github.com/smartcontractkit/chainlink-deployments-framework v0.98.0 + github.com/smartcontractkit/chainlink-evm v0.3.3 + github.com/smartcontractkit/chainlink-evm/gethwrappers v0.0.0-20260421142741-9c7fbaf7c828 github.com/smartcontractkit/mcms v0.40.1 github.com/stretchr/testify v1.11.1 gopkg.in/yaml.v3 v3.0.1 @@ -76,7 +81,6 @@ require ( github.com/emicklei/go-restful/v3 v3.11.0 // indirect github.com/ethereum/c-kzg-4844/v2 v2.1.6 // indirect github.com/ethereum/go-bigmodexpfix v0.0.0-20250911101455-f9e208c548ab // indirect - github.com/ethereum/go-ethereum v1.17.1 // indirect github.com/fatih/color v1.18.0 // indirect github.com/fbsobreira/gotron-sdk v0.0.0-20250403083053-2943ce8c759b // indirect github.com/felixge/httpsnoop v1.0.4 // indirect @@ -205,11 +209,9 @@ require ( github.com/shopspring/decimal v1.4.0 // indirect github.com/sigurn/crc16 v0.0.0-20211026045750-20ab5afb07e3 // indirect github.com/sirupsen/logrus v1.9.4 // indirect - github.com/smartcontractkit/ccip-owner-contracts v0.1.0 // indirect github.com/smartcontractkit/chainlink-aptos v0.0.0-20260306142855-8d629e752265 // indirect github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20260121163256-85accaf3d28d // indirect github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings v0.0.0-20250912190424-fd2e35d7deb5 // indirect - github.com/smartcontractkit/chainlink-common v0.10.1-0.20260217160002-b56cb5356cc7 // indirect github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.10 // indirect github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260226130359-963f935e0396 // indirect github.com/smartcontractkit/chainlink-protos/job-distributor v0.18.0 // indirect diff --git a/go.sum b/go.sum index 65357c0..4cc5e17 100644 --- a/go.sum +++ b/go.sum @@ -633,8 +633,8 @@ github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7J github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= -github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4= -github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog= +github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8= +github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= @@ -744,8 +744,12 @@ github.com/smartcontractkit/chainlink-common v0.10.1-0.20260217160002-b56cb5356c github.com/smartcontractkit/chainlink-common v0.10.1-0.20260217160002-b56cb5356cc7/go.mod h1:HXgSKzmZ/bhSx8nHU7hHW6dR+BHSXkdcpFv2T8qJcS8= github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.10 h1:FJAFgXS9oqASnkS03RE1HQwYQQxrO4l46O5JSzxqLgg= github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.10/go.mod h1:oiDa54M0FwxevWwyAX773lwdWvFYYlYHHQV1LQ5HpWY= -github.com/smartcontractkit/chainlink-deployments-framework v0.97.0 h1:LC8SJ4WW3FBHTDhAtpEOqQnZk+s3Qm8+pnGEI3dnkvw= -github.com/smartcontractkit/chainlink-deployments-framework v0.97.0/go.mod h1:24dwRW1PYolrlxSth///ddG3auGqR+50xaJiXfUHhkg= +github.com/smartcontractkit/chainlink-deployments-framework v0.98.0 h1:Ov/KOEtubOHXX8oa9UtARhHmkQNCOIjWNt+Zi0AuzHM= +github.com/smartcontractkit/chainlink-deployments-framework v0.98.0/go.mod h1:24dwRW1PYolrlxSth///ddG3auGqR+50xaJiXfUHhkg= +github.com/smartcontractkit/chainlink-evm v0.3.3 h1:JqwyJEtnNEUaoQQPoOBTT4sn2lpdIZHtf0Hr0M60YDw= +github.com/smartcontractkit/chainlink-evm v0.3.3/go.mod h1:q0ZBvaoisNaqC8NcMYWNPTjee88nQktDEeJMQHq3hVI= +github.com/smartcontractkit/chainlink-evm/gethwrappers v0.0.0-20260421142741-9c7fbaf7c828 h1:BmsFk/TSHL6dPPR86GTqgSrUXLSINNFC6cfpFRrQX+4= +github.com/smartcontractkit/chainlink-evm/gethwrappers v0.0.0-20260421142741-9c7fbaf7c828/go.mod h1:a260YnLyWq2NHLUN5cSVyMGk9nhO6RguCaTI2rsVqyA= github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260226130359-963f935e0396 h1:03tbcwjyIEjvHba1IWOj1sfThwebm2XNzyFHSuZtlWc= github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260226130359-963f935e0396/go.mod h1:Jqt53s27Tr0jDl8mdBXg1xhu6F8Fci8JOuq43tgHOM8= github.com/smartcontractkit/chainlink-protos/job-distributor v0.18.0 h1:q+VDPcxWrj5k9QizSYfUOSMnDH3Sd5HvbPguZOgfXTY= From 60d7fe8ad1b5e8e0f40e9955cb5df35a7b7e0a1a Mon Sep 17 00:00:00 2001 From: Pablo Date: Wed, 22 Apr 2026 15:40:27 -0600 Subject: [PATCH 11/12] fix: unit tests --- link/view/{ => v1_0}/link_token.go | 3 +- link/view/{ => v1_0}/link_token_test.go | 5 ++ link/view/{ => v1_0}/static_link_token.go | 1 + .../view/{ => v1_0}/static_link_token_test.go | 2 + mcms/common/view/v1_0/mcms.go | 9 ++- pkg/common/types.go | 1 + pkg/family/evm/state.go | 61 +++++++++++-------- pkg/family/evm/state_test.go | 22 +++++++ pkg/family/solana/state.go | 24 ++++---- 9 files changed, 89 insertions(+), 39 deletions(-) rename link/view/{ => v1_0}/link_token.go (99%) rename link/view/{ => v1_0}/link_token_test.go (98%) rename link/view/{ => v1_0}/static_link_token.go (99%) rename link/view/{ => v1_0}/static_link_token_test.go (98%) diff --git a/link/view/link_token.go b/link/view/v1_0/link_token.go similarity index 99% rename from link/view/link_token.go rename to link/view/v1_0/link_token.go index ea2f62c..79412f6 100644 --- a/link/view/link_token.go +++ b/link/view/v1_0/link_token.go @@ -1,4 +1,4 @@ -package view +package v1_0 import ( "fmt" @@ -43,6 +43,7 @@ func GenerateLinkTokenView(lt *link_token.LinkToken) (LinkTokenView, error) { if err != nil { burners = []common.Address{} } + return LinkTokenView{ ContractMetaData: cldchangesetscommon.ContractMetaData{ TypeAndVersion: cldf.TypeAndVersion{ diff --git a/link/view/link_token_test.go b/link/view/v1_0/link_token_test.go similarity index 98% rename from link/view/link_token_test.go rename to link/view/v1_0/link_token_test.go index 0623889..cac8484 100644 --- a/link/view/link_token_test.go +++ b/link/view/v1_0/link_token_test.go @@ -17,6 +17,8 @@ import ( ) func TestLinkTokenView(t *testing.T) { + t.Parallel() + selector := chainselectors.TEST_90000001.Selector env, err := environment.New(t.Context(), environment.WithEVMSimulated(t, []uint64{selector}), @@ -35,6 +37,7 @@ func TestLinkTokenView(t *testing.T) { func TestLinkTokenViewZk(t *testing.T) { // Timeouts in CI tests.SkipFlakey(t, "https://smartcontract-it.atlassian.net/browse/CCIP-6427") + t.Parallel() selector := chainselectors.TEST_90000050.Selector env, err := environment.New(t.Context(), @@ -50,6 +53,8 @@ func TestLinkTokenViewZk(t *testing.T) { } func testLinkTokenViewWithChain(t *testing.T, chain cldf_evm.Chain, lt *link_token.LinkToken) { + t.Helper() + v, err := GenerateLinkTokenView(lt) require.NoError(t, err) diff --git a/link/view/static_link_token.go b/link/view/v1_0/static_link_token.go similarity index 99% rename from link/view/static_link_token.go rename to link/view/v1_0/static_link_token.go index ce1bef1..14ae1db 100644 --- a/link/view/static_link_token.go +++ b/link/view/v1_0/static_link_token.go @@ -27,6 +27,7 @@ func GenerateStaticLinkTokenView(lt *link_token_interface.LinkToken) (StaticLink if err != nil { return StaticLinkTokenView{}, fmt.Errorf("failed to get total supply %s: %w", lt.Address(), err) } + return StaticLinkTokenView{ ContractMetaData: common.ContractMetaData{ TypeAndVersion: cldf.TypeAndVersion{ diff --git a/link/view/static_link_token_test.go b/link/view/v1_0/static_link_token_test.go similarity index 98% rename from link/view/static_link_token_test.go rename to link/view/v1_0/static_link_token_test.go index 350698d..92a99db 100644 --- a/link/view/static_link_token_test.go +++ b/link/view/v1_0/static_link_token_test.go @@ -16,6 +16,8 @@ import ( ) func TestStaticLinkTokenView(t *testing.T) { + t.Parallel() + selector := chain_selectors.TEST_90000001.Selector env, err := environment.New(t.Context(), environment.WithEVMSimulated(t, []uint64{selector}), diff --git a/mcms/common/view/v1_0/mcms.go b/mcms/common/view/v1_0/mcms.go index fdbc7a2..12a30c1 100644 --- a/mcms/common/view/v1_0/mcms.go +++ b/mcms/common/view/v1_0/mcms.go @@ -25,7 +25,8 @@ type Role struct { } const ( - EXECUTOR_ROLE_STR = "EXECUTOR_ROLE" + EXECUTOR_ROLE_STR = "EXECUTOR_ROLE" + //nolint:gosec // Public role identifier constant, not a credential. BYPASSER_ROLE_STR = "BYPASSER_ROLE" CANCELLER_ROLE_STR = "CANCELLER_ROLE" PROPOSER_ROLE_STR = "PROPOSER_ROLE" @@ -80,6 +81,7 @@ func GenerateMCMSView(mcms owner_helpers.ManyChainMultiSig) (MCMSView, error) { for i, s := range in { out[i] = bindings.ManyChainMultiSigSigner{Addr: s.Addr, Index: s.Index, Group: s.Group} } + return out } @@ -91,6 +93,7 @@ func GenerateMCMSView(mcms owner_helpers.ManyChainMultiSig) (MCMSView, error) { if err != nil { return MCMSView{}, err } + return MCMSView{ // Has no type and version on the contract ContractMetaData: cldcommon.ContractMetaData{ @@ -113,7 +116,8 @@ func GenerateTimelockView(tl owner_helpers.RBACTimelock) (TimelockView, error) { if err != nil { return TimelockView{}, fmt.Errorf("get role member count for role %s (%s): %w", role.Name, role.ID.Hex(), err) } - for i := int64(0); i < numMembers.Int64(); i++ { + memberCount := numMembers.Int64() + for i := range memberCount { member, err2 := tl.GetRoleMember(nil, role.ID, big.NewInt(i)) if err2 != nil { return TimelockView{}, fmt.Errorf("get role member for role %s (%s) at index %d: %w", role.Name, role.ID.Hex(), i, err2) @@ -121,6 +125,7 @@ func GenerateTimelockView(tl owner_helpers.RBACTimelock) (TimelockView, error) { membersByRole[role.Name] = append(membersByRole[role.Name], member) } } + return TimelockView{ // Has no type and version or owner. ContractMetaData: cldcommon.ContractMetaData{ diff --git a/pkg/common/types.go b/pkg/common/types.go index bff4222..622ccab 100644 --- a/pkg/common/types.go +++ b/pkg/common/types.go @@ -22,6 +22,7 @@ func NewContractMetaData(tv Meta, addr common.Address) (ContractMetaData, error) if err != nil { return ContractMetaData{}, fmt.Errorf("failed to get owner addr %s: %w", addr.String(), err) } + return ContractMetaData{ TypeAndVersion: tvStr, Address: addr, diff --git a/pkg/family/evm/state.go b/pkg/family/evm/state.go index d4f4217..a524a7a 100644 --- a/pkg/family/evm/state.go +++ b/pkg/family/evm/state.go @@ -16,7 +16,7 @@ import ( cldf_evm "github.com/smartcontractkit/chainlink-deployments-framework/chain/evm" cldf "github.com/smartcontractkit/chainlink-deployments-framework/deployment" - linkview "github.com/smartcontractkit/cld-changesets/link/view" + v1_1 "github.com/smartcontractkit/cld-changesets/link/view/v1_0" "github.com/smartcontractkit/cld-changesets/mcms/common/view/v1_0" common2 "github.com/smartcontractkit/cld-changesets/pkg/common" @@ -52,6 +52,7 @@ func (state MCMSWithTimelockState) Validate() error { if state.CallProxy == nil { return errors.New("call proxy not found") } + return nil } @@ -74,16 +75,21 @@ func AddressesForChain(env cldf.Environment, chainSelector uint64, qualifier str if env.DataStore != nil { return LoadAddressesFromDataStore(env.DataStore, chainSelector, qualifier) } + return nil, fmt.Errorf("DataStore not available but qualifier %s specified", qualifier) } // For backward compatibility without qualifier, merge both sources // Start with addresses from AddressBook addressBookAddresses := make(map[string]cldf.TypeAndVersion) - if addresses, err := env.ExistingAddresses.AddressesForChain(chainSelector); err == nil { - addressBookAddresses = addresses - } else if !errors.Is(err, cldf.ErrChainNotFound) { - return nil, fmt.Errorf("failed to load addresses from AddressBook: %w", err) + //nolint:staticcheck // Backward compatibility while callers migrate from AddressBook to DataStore. + existingAddresses := env.ExistingAddresses + if existingAddresses != nil { + if addresses, err := existingAddresses.AddressesForChain(chainSelector); err == nil { + addressBookAddresses = addresses + } else if !errors.Is(err, cldf.ErrChainNotFound) { + return nil, fmt.Errorf("failed to load addresses from AddressBook: %w", err) + } } // If no DataStore, just return AddressBook addresses @@ -92,22 +98,22 @@ func AddressesForChain(env cldf.Environment, chainSelector uint64, qualifier str } // Try to load addresses from DataStore (without qualifier for general case) - dataStoreAddresses, err := LoadAddressesFromDataStore(env.DataStore, chainSelector, "") - if err != nil { - // If DataStore has no addresses or returns an error, fall back to AddressBook addresses only - return addressBookAddresses, nil - } + dataStoreAddresses, dataStoreErr := LoadAddressesFromDataStore(env.DataStore, chainSelector, "") + if dataStoreErr == nil { + // Merge the two maps - DataStore addresses take precedence + mergedAddresses := make(map[string]cldf.TypeAndVersion) - // Merge the two maps - DataStore addresses take precedence - mergedAddresses := make(map[string]cldf.TypeAndVersion) + // First add all AddressBook addresses + maps.Copy(mergedAddresses, addressBookAddresses) - // First add all AddressBook addresses - maps.Copy(mergedAddresses, addressBookAddresses) + // Then add DataStore addresses (overwriting any conflicts) + maps.Copy(mergedAddresses, dataStoreAddresses) - // Then add DataStore addresses (overwriting any conflicts) - maps.Copy(mergedAddresses, dataStoreAddresses) + return mergedAddresses, nil + } - return mergedAddresses, nil + // If DataStore has no addresses or returns an error, fall back to AddressBook addresses only. + return addressBookAddresses, nil } // MaybeLoadMCMSWithTimelockStateDataStore loads the MCMSWithTimelockState state for each chain in the given environment from the DataStore. @@ -119,7 +125,7 @@ func MaybeLoadMCMSWithTimelockStateDataStoreWithQualifier(env cldf.Environment, result := map[uint64]*MCMSWithTimelockState{} ds := env.DataStore if ds == nil { - return nil, fmt.Errorf("datastore not available") + return nil, errors.New("datastore not available") } for _, chainSelector := range chainSelectors { chain, ok := env.BlockChains.EVMChains()[chainSelector] @@ -132,6 +138,7 @@ func MaybeLoadMCMSWithTimelockStateDataStoreWithQualifier(env cldf.Environment, } result[chainSelector] = state } + return result, nil } @@ -160,6 +167,7 @@ func LoadAddressesFromDataStore(ds datastore.DataStore, chainSelector uint64, qu if err != nil { return nil, err } + return addressesChain, nil } @@ -242,6 +250,7 @@ func MaybeLoadMCMSWithTimelockChainStateFromRefs(chain cldf_evm.Chain, refs []da state.CancellerMcm = mcms } } + return &state, nil } @@ -249,11 +258,12 @@ type LinkTokenState struct { LinkToken *link_token.LinkToken } -func (s LinkTokenState) GenerateLinkView() (linkview.LinkTokenView, error) { +func (s LinkTokenState) GenerateLinkView() (v1_1.LinkTokenView, error) { if s.LinkToken == nil { - return linkview.LinkTokenView{}, errors.New("link token not found") + return v1_1.LinkTokenView{}, errors.New("link token not found") } - return linkview.GenerateLinkTokenView(s.LinkToken) + + return v1_1.GenerateLinkTokenView(s.LinkToken) } func MaybeLoadLinkTokenChainState(chain cldf_evm.Chain, addresses map[string]cldf.TypeAndVersion) (*LinkTokenState, error) { @@ -278,6 +288,7 @@ func MaybeLoadLinkTokenChainState(chain cldf_evm.Chain, addresses map[string]cld state.LinkToken = lt } } + return &state, nil } @@ -285,11 +296,12 @@ type StaticLinkTokenState struct { StaticLinkToken *link_token_interface.LinkToken } -func (s StaticLinkTokenState) GenerateStaticLinkView() (linkview.StaticLinkTokenView, error) { +func (s StaticLinkTokenState) GenerateStaticLinkView() (v1_1.StaticLinkTokenView, error) { if s.StaticLinkToken == nil { - return linkview.StaticLinkTokenView{}, errors.New("static link token not found") + return v1_1.StaticLinkTokenView{}, errors.New("static link token not found") } - return linkview.GenerateStaticLinkTokenView(s.StaticLinkToken) + + return v1_1.GenerateStaticLinkTokenView(s.StaticLinkToken) } func MaybeLoadStaticLinkTokenState(chain cldf_evm.Chain, addresses map[string]cldf.TypeAndVersion) (*StaticLinkTokenState, error) { @@ -314,6 +326,7 @@ func MaybeLoadStaticLinkTokenState(chain cldf_evm.Chain, addresses map[string]cl state.StaticLinkToken = lt } } + return &state, nil } diff --git a/pkg/family/evm/state_test.go b/pkg/family/evm/state_test.go index 3943040..a25ba3c 100644 --- a/pkg/family/evm/state_test.go +++ b/pkg/family/evm/state_test.go @@ -27,6 +27,8 @@ import ( ) func TestMCMSWithTimelockState_GenerateMCMSWithTimelockViewV2(t *testing.T) { + t.Parallel() + selector := chain_selectors.TEST_90000001.Selector env, err := environment.New(t.Context(), environment.WithEVMSimulated(t, []uint64{selector}), @@ -120,6 +122,8 @@ func TestMCMSWithTimelockState_GenerateMCMSWithTimelockViewV2(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + t.Parallel() + state := tt.contracts got, err := state.GenerateMCMSWithTimelockView() @@ -135,9 +139,13 @@ func TestMCMSWithTimelockState_GenerateMCMSWithTimelockViewV2(t *testing.T) { } func TestAddressesForChain(t *testing.T) { + t.Parallel() + chainSelector := chain_selectors.ETHEREUM_MAINNET.Selector t.Run("environment with AddressBook only", func(t *testing.T) { + t.Parallel() + // Create environment with only AddressBook addressBook := cldf.NewMemoryAddressBook() err := addressBook.Save(chainSelector, "0x1234567890123456789012345678901234567890", @@ -159,6 +167,8 @@ func TestAddressesForChain(t *testing.T) { }) t.Run("environment with DataStore only", func(t *testing.T) { + t.Parallel() + // Create environment with only DataStore dataStore := datastore.NewMemoryDataStore() err := dataStore.Addresses().Add(datastore.AddressRef{ @@ -185,6 +195,8 @@ func TestAddressesForChain(t *testing.T) { require.Contains(t, mergedAddresses, "0xABCDEF1234567890123456789012345678901234") }) t.Run("environment with AddressBook and DataStore without qualifier", func(t *testing.T) { + t.Parallel() + // Create a mock environment with both AddressBook and DataStore addressBook := cldf.NewMemoryAddressBook() err := addressBook.Save(chainSelector, "0x1234567890123456789012345678901234567890", @@ -240,6 +252,8 @@ func TestAddressesForChain(t *testing.T) { }) t.Run("environment with AddressBook and DataStore with qualifier", func(t *testing.T) { + t.Parallel() + dataStore := datastore.NewMemoryDataStore() // Add contracts with different qualifiers @@ -302,6 +316,8 @@ func TestAddressesForChain(t *testing.T) { }) t.Run("environment with duplicated addresses in AddressBook and DataStore", func(t *testing.T) { + t.Parallel() + const ( duplicateAddress = "0x1234567890123456789012345678901234567890" uniqueAddress = "0xABCDEF1234567890123456789012345678901234" @@ -397,6 +413,8 @@ func TestAddressesForChain(t *testing.T) { } func TestGetMCMSWithTimelockState(t *testing.T) { + t.Parallel() + selector := chain_selectors.TEST_90000001.Selector env, err := environment.New(t.Context(), environment.WithEVMSimulated(t, []uint64{selector}), @@ -431,6 +449,8 @@ func TestGetMCMSWithTimelockState(t *testing.T) { } t.Run("shared address for bypasser and canceller", func(t *testing.T) { + t.Parallel() + // Store DS with bypasser/canceller sharing the same address store := datastore.NewMemoryDataStore() for _, ref := range commonRefs { @@ -459,6 +479,8 @@ func TestGetMCMSWithTimelockState(t *testing.T) { }) t.Run("legacy ManyChainMultisig type is ignored", func(t *testing.T) { + t.Parallel() + // Store with legacy ManyChainMultisig typed bypasser/canceller legacyStore := datastore.NewMemoryDataStore() for _, ref := range commonRefs { diff --git a/pkg/family/solana/state.go b/pkg/family/solana/state.go index cca4e0e..d357d8b 100644 --- a/pkg/family/solana/state.go +++ b/pkg/family/solana/state.go @@ -6,7 +6,7 @@ import ( "github.com/gagliardetto/solana-go" "github.com/smartcontractkit/chainlink-deployments-framework/datastore" cldf "github.com/smartcontractkit/chainlink-deployments-framework/deployment" - "github.com/smartcontractkit/chainlink-deployments-framework/engine/cld/mcms/proposalutils" + mcmscontracts "github.com/smartcontractkit/chainlink-deployments-framework/engine/cld/contracts/mcms" mcmssolanasdk "github.com/smartcontractkit/mcms/sdk/solana" ) @@ -49,17 +49,17 @@ type PDASeed [32]byte func maybeLoadMCMSWithTimelockChainState(refs []datastore.AddressRef) (*MCMSWithTimelockState, error) { state := MCMSWithTimelockState{MCMSWithTimelockPrograms: &MCMSWithTimelockPrograms{}} - mcmProgram := datastore.ContractType(proposalutils.ManyChainMultisigProgram) - timelockProgram := datastore.ContractType(proposalutils.RBACTimelockProgram) - accessControllerProgram := datastore.ContractType(proposalutils.AccessControllerProgram) - proposerMCM := datastore.ContractType(proposalutils.ProposerManyChainMultisig) - cancellerMCM := datastore.ContractType(proposalutils.CancellerManyChainMultisig) - bypasserMCM := datastore.ContractType(proposalutils.BypasserManyChainMultisig) - timelock := datastore.ContractType(proposalutils.RBACTimelock) - proposerAccessControllerAccount := datastore.ContractType(proposalutils.ProposerAccessControllerAccount) - executorAccessControllerAccount := datastore.ContractType(proposalutils.ExecutorAccessControllerAccount) - cancellerAccessControllerAccount := datastore.ContractType(proposalutils.CancellerAccessControllerAccount) - bypasserAccessControllerAccount := datastore.ContractType(proposalutils.BypasserAccessControllerAccount) + mcmProgram := datastore.ContractType(mcmscontracts.ManyChainMultisigProgram) + timelockProgram := datastore.ContractType(mcmscontracts.RBACTimelockProgram) + accessControllerProgram := datastore.ContractType(mcmscontracts.AccessControllerProgram) + proposerMCM := datastore.ContractType(mcmscontracts.ProposerManyChainMultisig) + cancellerMCM := datastore.ContractType(mcmscontracts.CancellerManyChainMultisig) + bypasserMCM := datastore.ContractType(mcmscontracts.BypasserManyChainMultisig) + timelock := datastore.ContractType(mcmscontracts.RBACTimelock) + proposerAccessControllerAccount := datastore.ContractType(mcmscontracts.ProposerAccessControllerAccount) + executorAccessControllerAccount := datastore.ContractType(mcmscontracts.ExecutorAccessControllerAccount) + cancellerAccessControllerAccount := datastore.ContractType(mcmscontracts.CancellerAccessControllerAccount) + bypasserAccessControllerAccount := datastore.ContractType(mcmscontracts.BypasserAccessControllerAccount) for _, ref := range refs { address := ref.Address From 8d76f0538fccec0befdb782b019962b33a3b11eb Mon Sep 17 00:00:00 2001 From: Pablo Date: Wed, 22 Apr 2026 19:30:40 -0600 Subject: [PATCH 12/12] fix: move views into pkg/contracts --- go.mod | 4 +++- go.sum | 8 ++++++-- {link => pkg/contract/link}/view/v1_0/link_token.go | 0 {link => pkg/contract/link}/view/v1_0/link_token_test.go | 0 .../contract/link}/view/v1_0/static_link_token.go | 0 .../contract/link}/view/v1_0/static_link_token_test.go | 0 {mcms/common => pkg/contract/mcms}/view/v1_0/mcms.go | 0 pkg/family/solana/state.go | 2 +- 8 files changed, 10 insertions(+), 4 deletions(-) rename {link => pkg/contract/link}/view/v1_0/link_token.go (100%) rename {link => pkg/contract/link}/view/v1_0/link_token_test.go (100%) rename {link => pkg/contract/link}/view/v1_0/static_link_token.go (100%) rename {link => pkg/contract/link}/view/v1_0/static_link_token_test.go (100%) rename {mcms/common => pkg/contract/mcms}/view/v1_0/mcms.go (100%) diff --git a/go.mod b/go.mod index a75eac1..38f6ae2 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,10 @@ require ( github.com/gagliardetto/solana-go v1.13.0 github.com/smartcontractkit/ccip-owner-contracts v0.1.0 github.com/smartcontractkit/chain-selectors v1.0.97 + github.com/smartcontractkit/chainlink-common v0.10.1-0.20260217160002-b56cb5356cc7 github.com/smartcontractkit/chainlink-deployments-framework v0.98.0 + github.com/smartcontractkit/chainlink-evm v0.3.3 + github.com/smartcontractkit/chainlink-evm/gethwrappers v0.0.0-20260421142741-9c7fbaf7c828 github.com/smartcontractkit/mcms v0.40.1 github.com/stretchr/testify v1.11.1 gopkg.in/yaml.v3 v3.0.1 @@ -209,7 +212,6 @@ require ( github.com/smartcontractkit/chainlink-aptos v0.0.0-20260306142855-8d629e752265 // indirect github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20260121163256-85accaf3d28d // indirect github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings v0.0.0-20250912190424-fd2e35d7deb5 // indirect - github.com/smartcontractkit/chainlink-common v0.10.1-0.20260217160002-b56cb5356cc7 // indirect github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.10 // indirect github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260226130359-963f935e0396 // indirect github.com/smartcontractkit/chainlink-protos/job-distributor v0.18.0 // indirect diff --git a/go.sum b/go.sum index 8be5eb1..4cc5e17 100644 --- a/go.sum +++ b/go.sum @@ -633,8 +633,8 @@ github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7J github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= -github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4= -github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog= +github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8= +github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= @@ -746,6 +746,10 @@ github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.10 h1:FJAFgXS9 github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.10/go.mod h1:oiDa54M0FwxevWwyAX773lwdWvFYYlYHHQV1LQ5HpWY= github.com/smartcontractkit/chainlink-deployments-framework v0.98.0 h1:Ov/KOEtubOHXX8oa9UtARhHmkQNCOIjWNt+Zi0AuzHM= github.com/smartcontractkit/chainlink-deployments-framework v0.98.0/go.mod h1:24dwRW1PYolrlxSth///ddG3auGqR+50xaJiXfUHhkg= +github.com/smartcontractkit/chainlink-evm v0.3.3 h1:JqwyJEtnNEUaoQQPoOBTT4sn2lpdIZHtf0Hr0M60YDw= +github.com/smartcontractkit/chainlink-evm v0.3.3/go.mod h1:q0ZBvaoisNaqC8NcMYWNPTjee88nQktDEeJMQHq3hVI= +github.com/smartcontractkit/chainlink-evm/gethwrappers v0.0.0-20260421142741-9c7fbaf7c828 h1:BmsFk/TSHL6dPPR86GTqgSrUXLSINNFC6cfpFRrQX+4= +github.com/smartcontractkit/chainlink-evm/gethwrappers v0.0.0-20260421142741-9c7fbaf7c828/go.mod h1:a260YnLyWq2NHLUN5cSVyMGk9nhO6RguCaTI2rsVqyA= github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260226130359-963f935e0396 h1:03tbcwjyIEjvHba1IWOj1sfThwebm2XNzyFHSuZtlWc= github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260226130359-963f935e0396/go.mod h1:Jqt53s27Tr0jDl8mdBXg1xhu6F8Fci8JOuq43tgHOM8= github.com/smartcontractkit/chainlink-protos/job-distributor v0.18.0 h1:q+VDPcxWrj5k9QizSYfUOSMnDH3Sd5HvbPguZOgfXTY= diff --git a/link/view/v1_0/link_token.go b/pkg/contract/link/view/v1_0/link_token.go similarity index 100% rename from link/view/v1_0/link_token.go rename to pkg/contract/link/view/v1_0/link_token.go diff --git a/link/view/v1_0/link_token_test.go b/pkg/contract/link/view/v1_0/link_token_test.go similarity index 100% rename from link/view/v1_0/link_token_test.go rename to pkg/contract/link/view/v1_0/link_token_test.go diff --git a/link/view/v1_0/static_link_token.go b/pkg/contract/link/view/v1_0/static_link_token.go similarity index 100% rename from link/view/v1_0/static_link_token.go rename to pkg/contract/link/view/v1_0/static_link_token.go diff --git a/link/view/v1_0/static_link_token_test.go b/pkg/contract/link/view/v1_0/static_link_token_test.go similarity index 100% rename from link/view/v1_0/static_link_token_test.go rename to pkg/contract/link/view/v1_0/static_link_token_test.go diff --git a/mcms/common/view/v1_0/mcms.go b/pkg/contract/mcms/view/v1_0/mcms.go similarity index 100% rename from mcms/common/view/v1_0/mcms.go rename to pkg/contract/mcms/view/v1_0/mcms.go diff --git a/pkg/family/solana/state.go b/pkg/family/solana/state.go index 505bb59..f5146f7 100644 --- a/pkg/family/solana/state.go +++ b/pkg/family/solana/state.go @@ -6,7 +6,7 @@ import ( "github.com/gagliardetto/solana-go" "github.com/smartcontractkit/chainlink-deployments-framework/datastore" cldf "github.com/smartcontractkit/chainlink-deployments-framework/deployment" - "github.com/smartcontractkit/chainlink-deployments-framework/engine/cld/mcms/proposalutils" + mcmscontracts "github.com/smartcontractkit/chainlink-deployments-framework/engine/cld/contracts/mcms" mcmssolanasdk "github.com/smartcontractkit/mcms/sdk/solana" )