Skip to content

Commit 4b75d9b

Browse files
authored
feat: add datastore contract metadata changeset (#74)
This pull request introduces a new changeset and supporting operation for deleting contract metadata entries in the Catalog service or from local datastore files CLD-2303
1 parent 11308c8 commit 4b75d9b

3 files changed

Lines changed: 228 additions & 0 deletions

File tree

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package changesets
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
7+
cldfdatastore "github.com/smartcontractkit/chainlink-deployments-framework/datastore"
8+
cldf "github.com/smartcontractkit/chainlink-deployments-framework/deployment"
9+
cldfops "github.com/smartcontractkit/chainlink-deployments-framework/operations"
10+
11+
"github.com/smartcontractkit/cld-changesets/catalog/operations"
12+
)
13+
14+
// DeleteContractMetadataChangeset deletes contract metadata entries from the Catalog service.
15+
type DeleteContractMetadataChangeset struct{}
16+
17+
type DeleteContractMetadataChangesetInput struct {
18+
ContractMetadataKeys []cldfdatastore.ContractMetadataKey `json:"contractMetadataKeys"`
19+
}
20+
21+
// VerifyPreconditions ensures the input is valid.
22+
func (DeleteContractMetadataChangeset) VerifyPreconditions(e cldf.Environment, input DeleteContractMetadataChangesetInput) error {
23+
if len(input.ContractMetadataKeys) == 0 {
24+
return errors.New("missing contract metadata keys input")
25+
}
26+
if e.DataStore == nil {
27+
return errors.New("missing datastore in environment")
28+
}
29+
30+
for _, key := range input.ContractMetadataKeys {
31+
_, err := e.DataStore.ContractMetadata().Get(key)
32+
if err != nil {
33+
if errors.Is(err, cldfdatastore.ErrContractMetadataNotFound) {
34+
return fmt.Errorf("contract metadata entry for chain selector %v and address %v does not exist",
35+
key.ChainSelector(), key.Address())
36+
}
37+
38+
return fmt.Errorf("failed to retrieve contract metadata entry for chain selector %v and address %v: %w",
39+
key.ChainSelector(), key.Address(), err)
40+
}
41+
}
42+
43+
return nil
44+
}
45+
46+
// Apply executes the changeset, staging the contract metadata entries to be deleted from the Catalog service or local datastore files.
47+
func (DeleteContractMetadataChangeset) Apply(e cldf.Environment, input DeleteContractMetadataChangesetInput) (cldf.ChangesetOutput, error) {
48+
deps := operations.DeleteContractMetadataDeps{DataStore: e.DataStore}
49+
opInput := operations.DeleteContractMetadataInput{ContractMetadataKeys: input.ContractMetadataKeys}
50+
51+
report, err := cldfops.ExecuteOperation(e.OperationsBundle, operations.DeleteContractMetadataOp, deps, opInput)
52+
out := cldf.ChangesetOutput{
53+
DataStore: report.Output.DataStore,
54+
Reports: []cldfops.Report[any, any]{report.ToGenericReport()},
55+
}
56+
if err != nil {
57+
return out, err
58+
}
59+
60+
return out, nil
61+
}
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
package changesets
2+
3+
import (
4+
"fmt"
5+
"testing"
6+
7+
"github.com/stretchr/testify/require"
8+
9+
cldfdatastore "github.com/smartcontractkit/chainlink-deployments-framework/datastore"
10+
cldf "github.com/smartcontractkit/chainlink-deployments-framework/deployment"
11+
cldfoperations "github.com/smartcontractkit/chainlink-deployments-framework/operations"
12+
cldflogger "github.com/smartcontractkit/chainlink-deployments-framework/pkg/logger"
13+
)
14+
15+
func TestDeleteContractMetadataChangeset_VerifyPreconditions(t *testing.T) {
16+
t.Parallel()
17+
18+
contractMetadata1 := cldfdatastore.ContractMetadata{Address: "0x01", ChainSelector: 1234, Metadata: "value1"}
19+
contractMetadata2 := cldfdatastore.ContractMetadata{Address: "0x02", ChainSelector: 1234, Metadata: "value2"}
20+
21+
tests := []struct {
22+
name string
23+
env cldf.Environment
24+
input DeleteContractMetadataChangesetInput
25+
wantErr string
26+
}{
27+
{
28+
name: "success: valid preconditions",
29+
env: cldf.Environment{
30+
DataStore: testDataStoreWithContractMetadata(t, contractMetadata1, contractMetadata2).Seal(),
31+
},
32+
input: DeleteContractMetadataChangesetInput{
33+
ContractMetadataKeys: []cldfdatastore.ContractMetadataKey{contractMetadata1.Key(), contractMetadata2.Key()},
34+
},
35+
},
36+
{
37+
name: "failure: missing datastore",
38+
env: cldf.Environment{},
39+
input: DeleteContractMetadataChangesetInput{
40+
ContractMetadataKeys: []cldfdatastore.ContractMetadataKey{contractMetadata1.Key()},
41+
},
42+
wantErr: "missing datastore in environment",
43+
},
44+
{
45+
name: "failure: no contract metadata keys given",
46+
env: cldf.Environment{
47+
DataStore: cldfdatastore.NewMemoryDataStore().Seal(),
48+
},
49+
input: DeleteContractMetadataChangesetInput{ContractMetadataKeys: []cldfdatastore.ContractMetadataKey{}},
50+
wantErr: "missing contract metadata keys input",
51+
},
52+
{
53+
name: "failure: contract metadata entry does not exist",
54+
env: cldf.Environment{
55+
DataStore: cldfdatastore.NewMemoryDataStore().Seal(),
56+
},
57+
input: DeleteContractMetadataChangesetInput{ContractMetadataKeys: []cldfdatastore.ContractMetadataKey{contractMetadata2.Key()}},
58+
wantErr: fmt.Sprintf("contract metadata entry for chain selector %v and address %v does not exist", contractMetadata2.ChainSelector, contractMetadata2.Address),
59+
},
60+
}
61+
62+
for _, tt := range tests {
63+
t.Run(tt.name, func(t *testing.T) {
64+
t.Parallel()
65+
66+
err := DeleteContractMetadataChangeset{}.VerifyPreconditions(tt.env, tt.input)
67+
if tt.wantErr == "" {
68+
require.NoError(t, err)
69+
} else {
70+
require.ErrorContains(t, err, tt.wantErr)
71+
}
72+
})
73+
}
74+
}
75+
76+
func TestDeleteContractMetadataChangeset_Apply(t *testing.T) {
77+
t.Parallel()
78+
79+
contractMetadata1 := cldfdatastore.ContractMetadata{Address: "0x01", ChainSelector: 1234, Metadata: "value1"}
80+
contractMetadata2 := cldfdatastore.ContractMetadata{Address: "0x02", ChainSelector: 5678, Metadata: "value2"}
81+
82+
tests := []struct {
83+
name string
84+
env cldf.Environment
85+
input DeleteContractMetadataChangesetInput
86+
wantDeletedKeys []string
87+
wantErr string
88+
}{
89+
{
90+
name: "success: stages two contract metadata entries for deletion",
91+
env: cldf.Environment{
92+
DataStore: testDataStoreWithContractMetadata(t, contractMetadata1, contractMetadata2).Seal(),
93+
OperationsBundle: cldfoperations.NewBundle(t.Context, cldflogger.Test(t), cldfoperations.NewMemoryReporter()),
94+
},
95+
input: DeleteContractMetadataChangesetInput{
96+
ContractMetadataKeys: []cldfdatastore.ContractMetadataKey{contractMetadata1.Key(), contractMetadata2.Key()},
97+
},
98+
wantDeletedKeys: []string{contractMetadata1.Key().String(), contractMetadata2.Key().String()},
99+
},
100+
}
101+
102+
for _, tt := range tests {
103+
t.Run(tt.name, func(t *testing.T) {
104+
t.Parallel()
105+
106+
got, err := DeleteContractMetadataChangeset{}.Apply(tt.env, tt.input)
107+
108+
if tt.wantErr == "" {
109+
require.NoError(t, err)
110+
require.Len(t, got.Reports, 1)
111+
memDS := got.DataStore.(*cldfdatastore.MemoryDataStore)
112+
require.ElementsMatch(t, tt.wantDeletedKeys, memDS.ContractMetadataStore.DeletedRemoteKeys)
113+
} else {
114+
require.ErrorContains(t, err, tt.wantErr)
115+
}
116+
})
117+
}
118+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package operations
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/Masterminds/semver/v3"
7+
cldfdatastore "github.com/smartcontractkit/chainlink-deployments-framework/datastore"
8+
cldfops "github.com/smartcontractkit/chainlink-deployments-framework/operations"
9+
)
10+
11+
// DeleteContractMetadataDeps holds non-serializable dependencies for the DeleteContractMetadataOp operation.
12+
type DeleteContractMetadataDeps struct {
13+
DataStore cldfdatastore.DataStore
14+
}
15+
16+
// DeleteContractMetadataInput is the serializable input of a DeleteContractMetadataOp invocation.
17+
type DeleteContractMetadataInput struct {
18+
ContractMetadataKeys []cldfdatastore.ContractMetadataKey
19+
}
20+
21+
// DeleteContractMetadataOutput is the serializable output of a DeleteContractMetadataOp invocation.
22+
type DeleteContractMetadataOutput struct {
23+
DataStore cldfdatastore.MutableDataStore
24+
}
25+
26+
// DeleteContractMetadataOp deletes contract metadata entries from the Catalog service or local datastore files.
27+
var DeleteContractMetadataOp = cldfops.NewOperation(
28+
"datastore-delete-contract-metadata",
29+
semver.MustParse("1.0.0"),
30+
"Delete contract metadata entries from the Catalog service or local datastore files",
31+
func(b cldfops.Bundle, deps DeleteContractMetadataDeps, input DeleteContractMetadataInput) (DeleteContractMetadataOutput, error) {
32+
dataStore := cldfdatastore.NewMemoryDataStore()
33+
err := dataStore.Merge(deps.DataStore)
34+
if err != nil {
35+
return DeleteContractMetadataOutput{}, fmt.Errorf("failed to create memory data store: %w", err)
36+
}
37+
38+
for i, key := range input.ContractMetadataKeys {
39+
err = dataStore.ContractMetadata().RemoteDelete(key)
40+
if err != nil {
41+
return DeleteContractMetadataOutput{}, fmt.Errorf("failed to delete contract metadata entry %d in datastore: %w", i, err)
42+
}
43+
}
44+
45+
b.Logger.Infow("Catalog ContractMetadata successfully staged for deletion")
46+
47+
return DeleteContractMetadataOutput{DataStore: dataStore}, nil
48+
},
49+
)

0 commit comments

Comments
 (0)