Skip to content

Commit a9de479

Browse files
feat: add catalog update address refs changeset (#43)
This pull request introduces a new changeset and supporting operation for updating existing address reference entries in the Catalog service. --- CLD-2302
1 parent c49ce9d commit a9de479

3 files changed

Lines changed: 288 additions & 0 deletions

File tree

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package changesets
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
7+
"github.com/samber/lo"
8+
cldfdatastore "github.com/smartcontractkit/chainlink-deployments-framework/datastore"
9+
cldf "github.com/smartcontractkit/chainlink-deployments-framework/deployment"
10+
cldfops "github.com/smartcontractkit/chainlink-deployments-framework/operations"
11+
12+
"github.com/smartcontractkit/cld-changesets/catalog/operations"
13+
)
14+
15+
// UpdateAddressRefChangeset updates existing address ref entries in the Catalog service.
16+
type UpdateAddressRefChangeset struct{}
17+
18+
type UpdateAddressRefChangesetInput struct {
19+
AddressRefs []cldfdatastore.AddressRef `json:"addressRefs"`
20+
}
21+
22+
// VerifyPreconditions ensures the input is valid.
23+
func (UpdateAddressRefChangeset) VerifyPreconditions(e cldf.Environment, input UpdateAddressRefChangesetInput) error {
24+
if len(input.AddressRefs) == 0 {
25+
return errors.New("missing address refs input")
26+
}
27+
if e.DataStore == nil {
28+
return errors.New("missing datastore in environment")
29+
}
30+
31+
uniqAddressRefs := lo.UniqBy(input.AddressRefs, func(ar cldfdatastore.AddressRef) cldfdatastore.AddressRefKey {
32+
return ar.Key()
33+
})
34+
if len(uniqAddressRefs) != len(input.AddressRefs) {
35+
return errors.New("duplicate address ref entries found in input")
36+
}
37+
38+
for _, addressRef := range input.AddressRefs {
39+
_, err := e.DataStore.Addresses().Get(addressRef.Key())
40+
if errors.Is(err, cldfdatastore.ErrAddressRefNotFound) {
41+
return fmt.Errorf("address ref for chain selector %v, type %v, version %v and qualifier %q does not exist",
42+
addressRef.ChainSelector, addressRef.Type, addressRef.Version, addressRef.Qualifier)
43+
}
44+
if err != nil {
45+
return fmt.Errorf("failed to retrieve address ref for chain selector %v, type %v, version %v and qualifier %q: %w",
46+
addressRef.ChainSelector, addressRef.Type, addressRef.Version, addressRef.Qualifier, err)
47+
}
48+
}
49+
50+
return nil
51+
}
52+
53+
// Apply executes the changeset, updating the address refs in the Catalog service.
54+
func (UpdateAddressRefChangeset) Apply(
55+
e cldf.Environment, input UpdateAddressRefChangesetInput,
56+
) (cldf.ChangesetOutput, error) {
57+
deps := operations.UpdateAddressRefDeps{DataStore: e.DataStore}
58+
opInput := operations.UpdateAddressRefInput{AddressRefs: input.AddressRefs}
59+
60+
report, err := cldfops.ExecuteOperation(e.OperationsBundle, operations.UpdateAddressRefOp, deps, opInput)
61+
out := cldf.ChangesetOutput{
62+
DataStore: report.Output.DataStore,
63+
Reports: []cldfops.Report[any, any]{report.ToGenericReport()},
64+
}
65+
if err != nil {
66+
return out, err
67+
}
68+
69+
return out, nil
70+
}
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
package changesets
2+
3+
import (
4+
"testing"
5+
6+
"github.com/Masterminds/semver/v3"
7+
"github.com/google/go-cmp/cmp"
8+
"github.com/google/go-cmp/cmp/cmpopts"
9+
"github.com/stretchr/testify/require"
10+
11+
cldfdatastore "github.com/smartcontractkit/chainlink-deployments-framework/datastore"
12+
cldf "github.com/smartcontractkit/chainlink-deployments-framework/deployment"
13+
cldfoperations "github.com/smartcontractkit/chainlink-deployments-framework/operations"
14+
cldflogger "github.com/smartcontractkit/chainlink-deployments-framework/pkg/logger"
15+
16+
"github.com/smartcontractkit/cld-changesets/catalog/operations"
17+
)
18+
19+
func TestUpdateAddressRefChangeset_VerifyPreconditions(t *testing.T) {
20+
t.Parallel()
21+
22+
version := semver.MustParse("1.0.0")
23+
addressRef1 := cldfdatastore.AddressRef{Address: "0x01", ChainSelector: 1234, Type: "MyContract", Version: version, Qualifier: "q1"}
24+
addressRef2 := cldfdatastore.AddressRef{Address: "0x02", ChainSelector: 1234, Type: "MyContract", Version: version, Qualifier: "q1"}
25+
26+
tests := []struct {
27+
name string
28+
env cldf.Environment
29+
input UpdateAddressRefChangesetInput
30+
wantErr string
31+
}{
32+
{
33+
name: "success: valid preconditions",
34+
env: cldf.Environment{DataStore: func() cldfdatastore.DataStore {
35+
ds := cldfdatastore.NewMemoryDataStore()
36+
err := ds.Addresses().Add(addressRef1)
37+
require.NoError(t, err)
38+
39+
return ds.Seal()
40+
}()},
41+
input: UpdateAddressRefChangesetInput{
42+
AddressRefs: []cldfdatastore.AddressRef{addressRef1},
43+
},
44+
},
45+
{
46+
name: "failure: missing datastore",
47+
env: cldf.Environment{},
48+
input: UpdateAddressRefChangesetInput{
49+
AddressRefs: []cldfdatastore.AddressRef{addressRef1},
50+
},
51+
wantErr: "missing datastore in environment",
52+
},
53+
{
54+
name: "failure: no address refs given",
55+
env: cldf.Environment{DataStore: cldfdatastore.NewMemoryDataStore().Seal()},
56+
input: UpdateAddressRefChangesetInput{
57+
AddressRefs: []cldfdatastore.AddressRef{},
58+
},
59+
wantErr: "missing address refs input",
60+
},
61+
{
62+
name: "failure: duplicate entries",
63+
env: cldf.Environment{DataStore: cldfdatastore.NewMemoryDataStore().Seal()},
64+
input: UpdateAddressRefChangesetInput{
65+
AddressRefs: []cldfdatastore.AddressRef{addressRef1, addressRef2},
66+
},
67+
wantErr: "duplicate address ref entries found in input",
68+
},
69+
{
70+
name: "failure: address ref does not exist",
71+
env: cldf.Environment{DataStore: cldfdatastore.NewMemoryDataStore().Seal()},
72+
input: UpdateAddressRefChangesetInput{
73+
AddressRefs: []cldfdatastore.AddressRef{addressRef1},
74+
},
75+
wantErr: "address ref for chain selector 1234, type MyContract, version 1.0.0 and qualifier \"q1\" does not exist",
76+
},
77+
}
78+
for _, tt := range tests {
79+
t.Run(tt.name, func(t *testing.T) {
80+
t.Parallel()
81+
82+
err := UpdateAddressRefChangeset{}.VerifyPreconditions(tt.env, tt.input)
83+
84+
if tt.wantErr == "" {
85+
require.NoError(t, err)
86+
} else {
87+
require.ErrorContains(t, err, tt.wantErr)
88+
}
89+
})
90+
}
91+
}
92+
93+
func TestUpdateAddressRefChangeset_Apply(t *testing.T) {
94+
t.Parallel()
95+
96+
version := semver.MustParse("1.0.0")
97+
addressRef1 := cldfdatastore.AddressRef{Address: "0x01", ChainSelector: 1234, Type: "MyContract", Version: version, Qualifier: "q1"}
98+
addressRef2 := cldfdatastore.AddressRef{Address: "0x02", ChainSelector: 5678, Type: "OtherContract", Version: version, Qualifier: "q2"}
99+
addressRef1Updated := cldfdatastore.AddressRef{Address: "0x99", ChainSelector: 1234, Type: "MyContract", Version: version, Qualifier: "q1"}
100+
addressRef2Updated := cldfdatastore.AddressRef{Address: "0x88", ChainSelector: 5678, Type: "OtherContract", Version: version, Qualifier: "q2"}
101+
102+
tests := []struct {
103+
name string
104+
env cldf.Environment
105+
input UpdateAddressRefChangesetInput
106+
want cldf.ChangesetOutput
107+
wantErr string
108+
}{
109+
{
110+
name: "success: updates two entries in address refs",
111+
env: cldf.Environment{
112+
DataStore: testDataStoreWithAddressRefs(t, addressRef1, addressRef2).Seal(),
113+
OperationsBundle: cldfoperations.NewBundle(t.Context, cldflogger.Test(t), cldfoperations.NewMemoryReporter()),
114+
},
115+
input: UpdateAddressRefChangesetInput{
116+
AddressRefs: []cldfdatastore.AddressRef{addressRef1Updated, addressRef2Updated},
117+
},
118+
want: cldf.ChangesetOutput{
119+
DataStore: testDataStoreWithAddressRefs(t, addressRef1Updated, addressRef2Updated),
120+
Reports: []cldfoperations.Report[any, any]{{
121+
Def: cldfoperations.Definition{
122+
ID: "catalog-update-address-ref",
123+
Version: semver.MustParse("1.0.0"),
124+
Description: "Update address ref entries in the Catalog service",
125+
},
126+
Input: operations.UpdateAddressRefInput{
127+
AddressRefs: []cldfdatastore.AddressRef{addressRef1Updated, addressRef2Updated},
128+
},
129+
Output: operations.UpdateAddressRefOutput{
130+
DataStore: testDataStoreWithAddressRefs(t, addressRef1Updated, addressRef2Updated),
131+
},
132+
}},
133+
},
134+
},
135+
{
136+
name: "failure: fails to update entry that does not exist",
137+
env: cldf.Environment{
138+
DataStore: testDataStoreWithAddressRefs(t).Seal(),
139+
OperationsBundle: cldfoperations.NewBundle(t.Context, cldflogger.Test(t), cldfoperations.NewMemoryReporter()),
140+
},
141+
input: UpdateAddressRefChangesetInput{
142+
AddressRefs: []cldfdatastore.AddressRef{addressRef1Updated},
143+
},
144+
wantErr: "failed to update address ref entry 0 in catalog store: " +
145+
"no such address ref can be found for the provided key",
146+
},
147+
}
148+
for _, tt := range tests {
149+
t.Run(tt.name, func(t *testing.T) {
150+
t.Parallel()
151+
152+
got, err := UpdateAddressRefChangeset{}.Apply(tt.env, tt.input)
153+
154+
if tt.wantErr == "" {
155+
require.NoError(t, err)
156+
require.Empty(t,
157+
cmp.Diff(tt.want, got,
158+
cmpopts.IgnoreFields(cldfoperations.Report[any, any]{}, "ID", "Timestamp"),
159+
cmpopts.IgnoreUnexported(cldfdatastore.MemoryAddressRefStore{}, cldfdatastore.MemoryChainMetadataStore{},
160+
cldfdatastore.MemoryContractMetadataStore{}, cldfdatastore.MemoryEnvMetadataStore{},
161+
cldfdatastore.LabelSet{})))
162+
} else {
163+
require.ErrorContains(t, err, tt.wantErr)
164+
}
165+
})
166+
}
167+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package operations
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/Masterminds/semver/v3"
7+
8+
cldfdatastore "github.com/smartcontractkit/chainlink-deployments-framework/datastore"
9+
cldfops "github.com/smartcontractkit/chainlink-deployments-framework/operations"
10+
)
11+
12+
// UpdateAddressRefDeps holds non-serializable dependencies for the
13+
// UpdateAddressRefOp operation.
14+
type UpdateAddressRefDeps struct {
15+
DataStore cldfdatastore.DataStore
16+
}
17+
18+
// UpdateAddressRefInput is the serializable input of an UpdateAddressRefOp invocation.
19+
type UpdateAddressRefInput struct {
20+
AddressRefs []cldfdatastore.AddressRef
21+
}
22+
23+
// UpdateAddressRefOutput is the serializable output of an UpdateAddressRefOp invocation.
24+
type UpdateAddressRefOutput struct {
25+
DataStore cldfdatastore.MutableDataStore
26+
}
27+
28+
// UpdateAddressRefOp updates existing address ref entries in the Catalog service.
29+
var UpdateAddressRefOp = cldfops.NewOperation(
30+
"catalog-update-address-ref",
31+
semver.MustParse("1.0.0"),
32+
"Update address ref entries in the Catalog service",
33+
func(b cldfops.Bundle, deps UpdateAddressRefDeps, input UpdateAddressRefInput) (UpdateAddressRefOutput, error) {
34+
dataStore := cldfdatastore.NewMemoryDataStore()
35+
err := dataStore.Merge(deps.DataStore)
36+
if err != nil {
37+
return UpdateAddressRefOutput{}, fmt.Errorf("failed to create memory data store: %w", err)
38+
}
39+
40+
for i, item := range input.AddressRefs {
41+
err = dataStore.Addresses().Update(item)
42+
if err != nil {
43+
return UpdateAddressRefOutput{}, fmt.Errorf("failed to update address ref entry %d in catalog store: %w", i, err)
44+
}
45+
}
46+
47+
b.Logger.Infow("Catalog AddressRef updated successfully")
48+
49+
return UpdateAddressRefOutput{DataStore: dataStore}, nil
50+
},
51+
)

0 commit comments

Comments
 (0)