diff --git a/catalog/changesets/delete_address_ref.go b/catalog/changesets/delete_address_ref.go new file mode 100644 index 0000000..f8f09c0 --- /dev/null +++ b/catalog/changesets/delete_address_ref.go @@ -0,0 +1,61 @@ +package changesets + +import ( + "errors" + "fmt" + + cldfdatastore "github.com/smartcontractkit/chainlink-deployments-framework/datastore" + cldf "github.com/smartcontractkit/chainlink-deployments-framework/deployment" + cldfops "github.com/smartcontractkit/chainlink-deployments-framework/operations" + + "github.com/smartcontractkit/cld-changesets/catalog/operations" +) + +// DeleteAddressRefChangeset deletes address ref entries from the Catalog service. +type DeleteAddressRefChangeset struct{} + +type DeleteAddressRefChangesetInput struct { + AddressRefKeys []cldfdatastore.AddressRefKey `json:"addressRefKeys"` +} + +// VerifyPreconditions ensures the input is valid. +func (DeleteAddressRefChangeset) VerifyPreconditions(e cldf.Environment, input DeleteAddressRefChangesetInput) error { + if len(input.AddressRefKeys) == 0 { + return errors.New("missing address ref keys input") + } + if e.DataStore == nil { + return errors.New("missing datastore in environment") + } + + for _, key := range input.AddressRefKeys { + _, err := e.DataStore.Addresses().Get(key) + if err != nil { + if errors.Is(err, cldfdatastore.ErrAddressRefNotFound) { + return fmt.Errorf("address ref entry for chain selector %v, type %v, version %v and qualifier %q does not exist", + key.ChainSelector(), key.Type(), key.Version(), key.Qualifier()) + } + + return fmt.Errorf("failed to retrieve address ref entry for chain selector %v, type %v, version %v and qualifier %q: %w", + key.ChainSelector(), key.Type(), key.Version(), key.Qualifier(), err) + } + } + + return nil +} + +// Apply executes the changeset, staging the address refs to be deleted from the Catalog service of local datastore files. +func (DeleteAddressRefChangeset) Apply(e cldf.Environment, input DeleteAddressRefChangesetInput) (cldf.ChangesetOutput, error) { + deps := operations.DeleteAddressRefDeps{DataStore: e.DataStore} + opInput := operations.DeleteAddressRefInput{AddressRefKeys: input.AddressRefKeys} + + report, err := cldfops.ExecuteOperation(e.OperationsBundle, operations.DeleteAddressRefOp, deps, opInput) + out := cldf.ChangesetOutput{ + DataStore: report.Output.DataStore, + Reports: []cldfops.Report[any, any]{report.ToGenericReport()}, + } + if err != nil { + return out, err + } + + return out, nil +} diff --git a/catalog/changesets/detele_address_ref_test.go b/catalog/changesets/detele_address_ref_test.go new file mode 100644 index 0000000..2a7e8ac --- /dev/null +++ b/catalog/changesets/detele_address_ref_test.go @@ -0,0 +1,121 @@ +package changesets + +import ( + "fmt" + "testing" + + "github.com/Masterminds/semver/v3" + "github.com/stretchr/testify/require" + + cldfdatastore "github.com/smartcontractkit/chainlink-deployments-framework/datastore" + cldf "github.com/smartcontractkit/chainlink-deployments-framework/deployment" + cldfoperations "github.com/smartcontractkit/chainlink-deployments-framework/operations" + cldflogger "github.com/smartcontractkit/chainlink-deployments-framework/pkg/logger" +) + +func TestDeleteAddressRefChangeset_VerifyPreconditions(t *testing.T) { + t.Parallel() + + version := semver.MustParse("1.0.0") + addressRef1 := cldfdatastore.AddressRef{Address: "0x01", ChainSelector: 1234, Type: "MyContract", Version: version, Qualifier: "q1"} + addressRef2 := cldfdatastore.AddressRef{Address: "0x02", ChainSelector: 1234, Type: "MyContract", Version: version, Qualifier: "q1"} + + tests := []struct { + name string + env cldf.Environment + input DeleteAddressRefChangesetInput + wantErr string + }{ + { + name: "success: valid preconditions", + env: cldf.Environment{ + DataStore: testDataStoreWithAddressRefs(t, addressRef1).Seal(), + }, + input: DeleteAddressRefChangesetInput{ + AddressRefKeys: []cldfdatastore.AddressRefKey{addressRef1.Key(), addressRef2.Key()}, + }, + }, + { + name: "failure: missing datastore", + env: cldf.Environment{}, + input: DeleteAddressRefChangesetInput{ + AddressRefKeys: []cldfdatastore.AddressRefKey{addressRef1.Key(), addressRef2.Key()}, + }, + wantErr: "missing datastore in environment", + }, + { + name: "failure: no address ref keys given", + env: cldf.Environment{ + DataStore: cldfdatastore.NewMemoryDataStore().Seal(), + }, + input: DeleteAddressRefChangesetInput{AddressRefKeys: []cldfdatastore.AddressRefKey{}}, + wantErr: "missing address ref keys input", + }, + { + name: "failure: address ref entry does not exist", + env: cldf.Environment{ + DataStore: cldfdatastore.NewMemoryDataStore().Seal(), + }, + input: DeleteAddressRefChangesetInput{AddressRefKeys: []cldfdatastore.AddressRefKey{addressRef2.Key()}}, + wantErr: fmt.Sprintf("address ref entry for chain selector %v, type %v, version %v and qualifier %q does not exist", addressRef2.ChainSelector, addressRef2.Type, addressRef2.Version, addressRef2.Qualifier), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + err := DeleteAddressRefChangeset{}.VerifyPreconditions(tt.env, tt.input) + if tt.wantErr == "" { + require.NoError(t, err) + } else { + require.ErrorContains(t, err, tt.wantErr) + } + }) + } +} + +func TestDeleteAddressRefChangeset_Apply(t *testing.T) { + t.Parallel() + + version := semver.MustParse("1.0.0") + addressRef1 := cldfdatastore.AddressRef{Address: "0x01", ChainSelector: 1234, Type: "MyContract", Version: version, Qualifier: "q1"} + addressRef2 := cldfdatastore.AddressRef{Address: "0x02", ChainSelector: 5678, Type: "OtherContract", Version: version, Qualifier: "q2"} + + tests := []struct { + name string + env cldf.Environment + input DeleteAddressRefChangesetInput + wantDeletedKeys []string + wantErr string + }{ + { + name: "success: stages two address ref entries for deletion", + env: cldf.Environment{ + DataStore: testDataStoreWithAddressRefs(t, addressRef1, addressRef2).Seal(), + OperationsBundle: cldfoperations.NewBundle(t.Context, cldflogger.Test(t), cldfoperations.NewMemoryReporter()), + }, + input: DeleteAddressRefChangesetInput{ + AddressRefKeys: []cldfdatastore.AddressRefKey{addressRef1.Key(), addressRef2.Key()}, + }, + wantDeletedKeys: []string{addressRef1.Key().String(), addressRef2.Key().String()}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + got, err := DeleteAddressRefChangeset{}.Apply(tt.env, tt.input) + + if tt.wantErr == "" { + require.NoError(t, err) + require.Len(t, got.Reports, 1) + memDS := got.DataStore.(*cldfdatastore.MemoryDataStore) + require.ElementsMatch(t, tt.wantDeletedKeys, memDS.AddressRefStore.DeletedRemoteKeys) + } else { + require.ErrorContains(t, err, tt.wantErr) + } + }) + } +} diff --git a/catalog/operations/delete_address_ref.go b/catalog/operations/delete_address_ref.go new file mode 100644 index 0000000..3318fc7 --- /dev/null +++ b/catalog/operations/delete_address_ref.go @@ -0,0 +1,49 @@ +package operations + +import ( + "fmt" + + "github.com/Masterminds/semver/v3" + cldfdatastore "github.com/smartcontractkit/chainlink-deployments-framework/datastore" + cldfops "github.com/smartcontractkit/chainlink-deployments-framework/operations" +) + +// DeleteAddressRefDeps holds non-serializable dependencies for the DeleteAddressRefOp operation. +type DeleteAddressRefDeps struct { + DataStore cldfdatastore.DataStore +} + +// DeleteAddressRefInput is the serializable input of a DeleteAddressRefOp invocation. +type DeleteAddressRefInput struct { + AddressRefKeys []cldfdatastore.AddressRefKey +} + +// DeleteAddressRefOutput is the serializable output of a DeleteAddressRefOp invocation. +type DeleteAddressRefOutput struct { + DataStore cldfdatastore.MutableDataStore +} + +// DeleteAddressRefOp deletes address ref entries from the Catalog service or local datastore files. +var DeleteAddressRefOp = cldfops.NewOperation( + "datastore-delete-address-ref", + semver.MustParse("1.0.0"), + "Delete address ref entries from the Catalog service or local datastore files", + func(b cldfops.Bundle, deps DeleteAddressRefDeps, input DeleteAddressRefInput) (DeleteAddressRefOutput, error) { + dataStore := cldfdatastore.NewMemoryDataStore() + err := dataStore.Merge(deps.DataStore) + if err != nil { + return DeleteAddressRefOutput{}, fmt.Errorf("failed to create memory data store: %w", err) + } + + for i, key := range input.AddressRefKeys { + err = dataStore.Addresses().RemoteDelete(key) + if err != nil { + return DeleteAddressRefOutput{}, fmt.Errorf("failed to delete address ref entry %d in datastore: %w", i, err) + } + } + + b.Logger.Infow("Catalog AddressRef successfully staged for deletion") + + return DeleteAddressRefOutput{DataStore: dataStore}, nil + }, +) diff --git a/go.mod b/go.mod index 71343e5..90bdc0f 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/smartcontractkit/cld-changesets go 1.26.2 require ( - github.com/Masterminds/semver/v3 v3.4.0 + github.com/Masterminds/semver/v3 v3.5.0 github.com/aptos-labs/aptos-go-sdk v1.13.0 github.com/deckarep/golang-set/v2 v2.8.0 github.com/ethereum/go-ethereum v1.17.3 @@ -17,7 +17,7 @@ require ( github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20260415165642-49f23e4d76cc github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings v0.0.0-20260415165642-49f23e4d76cc github.com/smartcontractkit/chainlink-common v0.11.2-0.20260506120607-7f10be016c89 - github.com/smartcontractkit/chainlink-deployments-framework v0.105.0 + github.com/smartcontractkit/chainlink-deployments-framework v0.107.0 github.com/smartcontractkit/chainlink-evm v0.3.4-0.20260429160308-91a892a60171 github.com/smartcontractkit/chainlink-evm/gethwrappers v0.0.0-20260421142741-9c7fbaf7c828 github.com/smartcontractkit/chainlink-protos/job-distributor v0.18.0 @@ -239,9 +239,9 @@ require ( github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260505131349-78e491b80735 // 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-20260319180422-b5808c964785 // indirect - github.com/smartcontractkit/chainlink-protos/op-catalog v0.0.4 // indirect + github.com/smartcontractkit/chainlink-protos/op-catalog v0.1.0 // indirect github.com/smartcontractkit/chainlink-sui v0.0.0-20260429183453-39df0198aed8 // indirect - github.com/smartcontractkit/chainlink-testing-framework/framework v0.16.1 // indirect + github.com/smartcontractkit/chainlink-testing-framework/framework v0.16.3 // indirect github.com/smartcontractkit/chainlink-testing-framework/seth v1.51.5 // indirect github.com/smartcontractkit/chainlink-ton v1.0.5-0.20260514223130-48bc90aca745 // indirect github.com/smartcontractkit/chainlink-tron/relayer v0.0.11-0.20260408092456-3c6369888d4a // indirect @@ -317,7 +317,7 @@ require ( golang.org/x/tools v0.45.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 // indirect - google.golang.org/grpc v1.81.0 // indirect + google.golang.org/grpc v1.81.1 // indirect google.golang.org/protobuf v1.36.11 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect diff --git a/go.sum b/go.sum index 8239290..2b4b20d 100644 --- a/go.sum +++ b/go.sum @@ -45,8 +45,8 @@ github.com/DataDog/zstd v1.5.6 h1:LbEglqepa/ipmmQJUDnSsfvA8e8IStVcGaFWDuxvGOY= github.com/DataDog/zstd v1.5.6/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= github.com/GeertJohan/go.incremental v1.0.0/go.mod h1:6fAjUhbVuX1KcMD3c8TEgVUqmo4seqhv0i0kdATSkM0= github.com/GeertJohan/go.rice v1.0.0/go.mod h1:eH6gbSOAUv07dQuZVnBmoDP8mgsM1rtixis4Tib9if0= -github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= -github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= +github.com/Masterminds/semver/v3 v3.5.0 h1:kQceYJfbupGfZOKZQg0kou0DgAKhzDg2NZPAwZ/2OOE= +github.com/Masterminds/semver/v3 v3.5.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= @@ -858,8 +858,8 @@ github.com/smartcontractkit/chainlink-common/keystore v1.1.0 h1:2wzySccgk2fpWusP github.com/smartcontractkit/chainlink-common/keystore v1.1.0/go.mod h1:6JexOOhPhknQ0QMuppFIlOpm6wCp54yZMxai+tWugwY= 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.105.0 h1:Vp4EwkvxcBzgahIZdbWyCExDXLha93cS63xvwd2xwx8= -github.com/smartcontractkit/chainlink-deployments-framework v0.105.0/go.mod h1:xFLOOpIz7vqqno4YngHZlF9MKqk8rnvQa9adVElUXaE= +github.com/smartcontractkit/chainlink-deployments-framework v0.107.0 h1:7fbWOseJ2LwCc09KKfWUBzPKMwPbDit2PEagzwPlKdU= +github.com/smartcontractkit/chainlink-deployments-framework v0.107.0/go.mod h1:ubpvoLoRdru8IQHw3TFr7KthbjYpAwmiRmvvNCf2daA= github.com/smartcontractkit/chainlink-evm v0.3.4-0.20260429160308-91a892a60171 h1:HY+Ekr4+E3xokyNZ0oOj0gjO7B065uqTxa6wcXJfJto= github.com/smartcontractkit/chainlink-evm v0.3.4-0.20260429160308-91a892a60171/go.mod h1:lhxIJe+Qy8duvf20q4D8ewfJzwHfvxK/s4s0exG1zEQ= github.com/smartcontractkit/chainlink-evm/gethwrappers v0.0.0-20260421142741-9c7fbaf7c828 h1:BmsFk/TSHL6dPPR86GTqgSrUXLSINNFC6cfpFRrQX+4= @@ -872,12 +872,12 @@ github.com/smartcontractkit/chainlink-protos/linking-service/go v0.0.0-202510021 github.com/smartcontractkit/chainlink-protos/linking-service/go v0.0.0-20251002192024-d2ad9222409b/go.mod h1:qSTSwX3cBP3FKQwQacdjArqv0g6QnukjV4XuzO6UyoY= github.com/smartcontractkit/chainlink-protos/node-platform v0.0.0-20260319180422-b5808c964785 h1:oli+2uLU6jcrJGCuYFqk3475hiwL17SWlITWLv+tx/w= github.com/smartcontractkit/chainlink-protos/node-platform v0.0.0-20260319180422-b5808c964785/go.mod h1:dkR2uYg9XYJuT1JASkPzWE51jjFkVb86P7a/yXe5/GM= -github.com/smartcontractkit/chainlink-protos/op-catalog v0.0.4 h1:AEnxv4HM3WD1RbQkRiFyb9cJ6YKAcqBp1CpIcFdZfuo= -github.com/smartcontractkit/chainlink-protos/op-catalog v0.0.4/go.mod h1:PjZD54vr6rIKEKQj6HNA4hllvYI/QpT+Zefj3tqkFAs= +github.com/smartcontractkit/chainlink-protos/op-catalog v0.1.0 h1:hGEJFD2X3oNIPXQbtIPxCJyg5CcKglRCYBmESS+gmeQ= +github.com/smartcontractkit/chainlink-protos/op-catalog v0.1.0/go.mod h1:PjZD54vr6rIKEKQj6HNA4hllvYI/QpT+Zefj3tqkFAs= github.com/smartcontractkit/chainlink-sui v0.0.0-20260429183453-39df0198aed8 h1:sWpTYRucOQQ/wXbKj52UE59JMMEq2Aq5g+sMdjYzfRM= github.com/smartcontractkit/chainlink-sui v0.0.0-20260429183453-39df0198aed8/go.mod h1:k1HSbHyPaQWPOj6lXDIAe04EuwbC5ge1nK+cpG2E8hE= -github.com/smartcontractkit/chainlink-testing-framework/framework v0.16.1 h1:wZd5hIQRcQaq3FgW1lg/4ilk68Id6cxYKFNU9iTnugs= -github.com/smartcontractkit/chainlink-testing-framework/framework v0.16.1/go.mod h1:wxgGfrJpzIdC1wyMJEGOfN4H4yPQTZD/DdrMRBxA0io= +github.com/smartcontractkit/chainlink-testing-framework/framework v0.16.3 h1:y3dFPfouGziisDJa0JbY6DE7/JjoSCP/5aebzHcj4jA= +github.com/smartcontractkit/chainlink-testing-framework/framework v0.16.3/go.mod h1:W+X9xaRulD8dD87QOB0njHkBKkmYxB4E0osfpe9808I= 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 v1.0.5-0.20260514223130-48bc90aca745 h1:eieKLvYuzwBPh/FdbUS1gnIanI86zgWby1L10o90g4o= @@ -1444,8 +1444,8 @@ google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8 google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.81.0 h1:W3G9N3KQf3BU+YuCtGKJk0CmxQNbAISICD/9AORxLIw= -google.golang.org/grpc v1.81.0/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I= +google.golang.org/grpc v1.81.1 h1:VnnIIZ88UzOOKLukQi+ImGz8O1Wdp8nAGGnvOfEIWQQ= +google.golang.org/grpc v1.81.1/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=