Skip to content

Commit 0e5b4a0

Browse files
feat: add catalog create address refs changeset
1 parent d29a277 commit 0e5b4a0

3 files changed

Lines changed: 300 additions & 0 deletions

File tree

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
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+
// CreateAddressRefChangeset creates address ref entries in the Catalog service.
16+
type CreateAddressRefChangeset struct{}
17+
18+
type CreateAddressRefChangesetInput struct {
19+
AddressRefs []cldfdatastore.AddressRef `json:"addressRefs"`
20+
}
21+
22+
// VerifyPreconditions ensures the input is valid.
23+
func (CreateAddressRefChangeset) VerifyPreconditions(e cldf.Environment, input CreateAddressRefChangesetInput) 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 err == nil {
41+
return fmt.Errorf("address ref for chain selector %v, type %v, version %v and qualifier %q already exists",
42+
addressRef.ChainSelector, addressRef.Type, addressRef.Version, addressRef.Qualifier)
43+
}
44+
if !errors.Is(err, cldfdatastore.ErrAddressRefNotFound) {
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, adding the address refs to the Catalog service.
54+
func (CreateAddressRefChangeset) Apply(e cldf.Environment, input CreateAddressRefChangesetInput) (cldf.ChangesetOutput, error) {
55+
deps := operations.CreateAddressRefDeps{DataStore: e.DataStore}
56+
opInput := operations.CreateAddressRefInput{AddressRefs: input.AddressRefs}
57+
58+
report, err := cldfops.ExecuteOperation(e.OperationsBundle, operations.CreateAddressRefOp, deps, opInput)
59+
out := cldf.ChangesetOutput{
60+
DataStore: report.Output.DataStore,
61+
Reports: []cldfops.Report[any, any]{report.ToGenericReport()},
62+
}
63+
if err != nil {
64+
return out, err
65+
}
66+
67+
return out, nil
68+
}
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
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 TestCreateAddressRefChangeset_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 CreateAddressRefChangesetInput
30+
wantErr string
31+
}{
32+
{
33+
name: "success: valid preconditions",
34+
env: cldf.Environment{DataStore: cldfdatastore.NewMemoryDataStore().Seal()},
35+
input: CreateAddressRefChangesetInput{
36+
AddressRefs: []cldfdatastore.AddressRef{addressRef1},
37+
},
38+
},
39+
{
40+
name: "failure: missing datastore",
41+
env: cldf.Environment{},
42+
input: CreateAddressRefChangesetInput{
43+
AddressRefs: []cldfdatastore.AddressRef{addressRef1},
44+
},
45+
wantErr: "missing datastore in environment",
46+
},
47+
{
48+
name: "failure: no address refs given",
49+
env: cldf.Environment{DataStore: cldfdatastore.NewMemoryDataStore().Seal()},
50+
input: CreateAddressRefChangesetInput{
51+
AddressRefs: []cldfdatastore.AddressRef{},
52+
},
53+
wantErr: "missing address refs input",
54+
},
55+
{
56+
name: "failure: duplicate entries",
57+
env: cldf.Environment{DataStore: cldfdatastore.NewMemoryDataStore().Seal()},
58+
input: CreateAddressRefChangesetInput{
59+
AddressRefs: []cldfdatastore.AddressRef{addressRef1, addressRef2},
60+
},
61+
wantErr: "duplicate address ref entries found in input",
62+
},
63+
{
64+
name: "failure: address ref already exists",
65+
env: cldf.Environment{DataStore: func() cldfdatastore.DataStore {
66+
ds := cldfdatastore.NewMemoryDataStore()
67+
err := ds.Addresses().Add(addressRef1)
68+
require.NoError(t, err)
69+
70+
return ds.Seal()
71+
}()},
72+
input: CreateAddressRefChangesetInput{
73+
AddressRefs: []cldfdatastore.AddressRef{addressRef1},
74+
},
75+
wantErr: "address ref for chain selector 1234, type MyContract, version 1.0.0 and qualifier \"q1\" already exists",
76+
},
77+
}
78+
for _, tt := range tests {
79+
t.Run(tt.name, func(t *testing.T) {
80+
t.Parallel()
81+
82+
err := CreateAddressRefChangeset{}.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 TestCreateAddressRefChangeset_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: 1234, Type: "MyContract", Version: version, Qualifier: "q2"}
99+
100+
tests := []struct {
101+
name string
102+
env cldf.Environment
103+
input CreateAddressRefChangesetInput
104+
want cldf.ChangesetOutput
105+
wantErr string
106+
}{
107+
{
108+
name: "success: adds two entries to address refs",
109+
env: cldf.Environment{
110+
DataStore: testDataStoreWithAddressRefs(t).Seal(),
111+
OperationsBundle: cldfoperations.NewBundle(t.Context, cldflogger.Test(t), cldfoperations.NewMemoryReporter()),
112+
},
113+
input: CreateAddressRefChangesetInput{
114+
AddressRefs: []cldfdatastore.AddressRef{addressRef1, addressRef2},
115+
},
116+
want: cldf.ChangesetOutput{
117+
DataStore: testDataStoreWithAddressRefs(t, addressRef1, addressRef2),
118+
Reports: []cldfoperations.Report[any, any]{{
119+
Def: cldfoperations.Definition{
120+
ID: "catalog-create-address-ref",
121+
Version: semver.MustParse("1.0.0"),
122+
Description: "Add address ref entries to the Catalog service",
123+
},
124+
Input: operations.CreateAddressRefInput{
125+
AddressRefs: []cldfdatastore.AddressRef{addressRef1, addressRef2},
126+
},
127+
Output: operations.CreateAddressRefOutput{
128+
DataStore: testDataStoreWithAddressRefs(t, addressRef1, addressRef2),
129+
},
130+
}},
131+
},
132+
},
133+
{
134+
name: "failure: fails to add second entry",
135+
env: cldf.Environment{
136+
DataStore: testDataStoreWithAddressRefs(t, addressRef2).Seal(),
137+
OperationsBundle: cldfoperations.NewBundle(t.Context, cldflogger.Test(t), cldfoperations.NewMemoryReporter()),
138+
},
139+
input: CreateAddressRefChangesetInput{
140+
AddressRefs: []cldfdatastore.AddressRef{addressRef1, addressRef2},
141+
},
142+
wantErr: "failed to create address ref entry 1 in catalog store: " +
143+
"an address ref with the supplied key already exists",
144+
},
145+
}
146+
for _, tt := range tests {
147+
t.Run(tt.name, func(t *testing.T) {
148+
t.Parallel()
149+
150+
got, err := CreateAddressRefChangeset{}.Apply(tt.env, tt.input)
151+
152+
if tt.wantErr == "" {
153+
require.NoError(t, err)
154+
require.Empty(t,
155+
cmp.Diff(tt.want, got,
156+
cmpopts.IgnoreFields(cldfoperations.Report[any, any]{}, "ID", "Timestamp"),
157+
cmpopts.IgnoreUnexported(cldfdatastore.MemoryAddressRefStore{}, cldfdatastore.MemoryChainMetadataStore{},
158+
cldfdatastore.MemoryContractMetadataStore{}, cldfdatastore.MemoryEnvMetadataStore{},
159+
cldfdatastore.LabelSet{})))
160+
} else {
161+
require.ErrorContains(t, err, tt.wantErr)
162+
}
163+
})
164+
}
165+
}
166+
167+
// ----- helpers -----
168+
169+
func testDataStoreWithAddressRefs(
170+
t *testing.T, addressRefs ...cldfdatastore.AddressRef,
171+
) cldfdatastore.MutableDataStore {
172+
t.Helper()
173+
174+
ds := cldfdatastore.NewMemoryDataStore()
175+
for _, ar := range addressRefs {
176+
err := ds.Addresses().Add(ar)
177+
require.NoError(t, err)
178+
}
179+
180+
return ds
181+
}
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+
// CreateAddressRefDeps holds non-serializable dependencies for the
13+
// CreateAddressRefOp operation.
14+
type CreateAddressRefDeps struct {
15+
DataStore cldfdatastore.DataStore
16+
}
17+
18+
// CreateAddressRefInput is the serializable input of a CreateAddressRefOp invocation.
19+
type CreateAddressRefInput struct {
20+
AddressRefs []cldfdatastore.AddressRef
21+
}
22+
23+
// CreateAddressRefOutput is the serializable output of a CreateAddressRefOp invocation.
24+
type CreateAddressRefOutput struct {
25+
DataStore cldfdatastore.MutableDataStore
26+
}
27+
28+
// CreateAddressRefOp creates address ref entries in the Catalog service.
29+
var CreateAddressRefOp = cldfops.NewOperation(
30+
"catalog-create-address-ref",
31+
semver.MustParse("1.0.0"),
32+
"Add address ref entries to the Catalog service",
33+
func(b cldfops.Bundle, deps CreateAddressRefDeps, input CreateAddressRefInput) (CreateAddressRefOutput, error) {
34+
dataStore := cldfdatastore.NewMemoryDataStore()
35+
err := dataStore.Merge(deps.DataStore)
36+
if err != nil {
37+
return CreateAddressRefOutput{}, fmt.Errorf("failed to create memory data store: %w", err)
38+
}
39+
40+
for i, item := range input.AddressRefs {
41+
err = dataStore.Addresses().Add(item)
42+
if err != nil {
43+
return CreateAddressRefOutput{}, fmt.Errorf("failed to create address ref entry %d in catalog store: %w", i, err)
44+
}
45+
}
46+
47+
b.Logger.Infow("Catalog AddressRef created successfully")
48+
49+
return CreateAddressRefOutput{DataStore: dataStore}, nil
50+
},
51+
)

0 commit comments

Comments
 (0)