Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions catalog/changesets/update_address_ref.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package changesets

import (
"errors"
"fmt"

"github.com/samber/lo"
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"
)

// UpdateAddressRefChangeset updates existing address ref entries in the Catalog service.
type UpdateAddressRefChangeset struct{}

type UpdateAddressRefChangesetInput struct {
AddressRefs []cldfdatastore.AddressRef `json:"addressRefs"`
}

// VerifyPreconditions ensures the input is valid.
func (UpdateAddressRefChangeset) VerifyPreconditions(e cldf.Environment, input UpdateAddressRefChangesetInput) error {
if len(input.AddressRefs) == 0 {
return errors.New("missing address refs input")
}
if e.DataStore == nil {
return errors.New("missing datastore in environment")
}

uniqAddressRefs := lo.UniqBy(input.AddressRefs, func(ar cldfdatastore.AddressRef) cldfdatastore.AddressRefKey {
return ar.Key()
})
if len(uniqAddressRefs) != len(input.AddressRefs) {
return errors.New("duplicate address ref entries found in input")
}

for _, addressRef := range input.AddressRefs {
_, err := e.DataStore.Addresses().Get(addressRef.Key())
if errors.Is(err, cldfdatastore.ErrAddressRefNotFound) {
return fmt.Errorf("address ref for chain selector %v, type %v, version %v and qualifier %q does not exist",
addressRef.ChainSelector, addressRef.Type, addressRef.Version, addressRef.Qualifier)
}
if err != nil {
return fmt.Errorf("failed to retrieve address ref for chain selector %v, type %v, version %v and qualifier %q: %w",
addressRef.ChainSelector, addressRef.Type, addressRef.Version, addressRef.Qualifier, err)
}
}

return nil
}

// Apply executes the changeset, updating the address refs in the Catalog service.
func (UpdateAddressRefChangeset) Apply(
e cldf.Environment, input UpdateAddressRefChangesetInput,
) (cldf.ChangesetOutput, error) {
deps := operations.UpdateAddressRefDeps{DataStore: e.DataStore}
opInput := operations.UpdateAddressRefInput{AddressRefs: input.AddressRefs}

report, err := cldfops.ExecuteOperation(e.OperationsBundle, operations.UpdateAddressRefOp, deps, opInput)
out := cldf.ChangesetOutput{
DataStore: report.Output.DataStore,
Reports: []cldfops.Report[any, any]{report.ToGenericReport()},
}
if err != nil {
return out, err
}
Comment thread
gustavogama-cll marked this conversation as resolved.

return out, nil
}
167 changes: 167 additions & 0 deletions catalog/changesets/update_address_ref_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
package changesets

import (
"testing"

"github.com/Masterminds/semver/v3"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"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"

"github.com/smartcontractkit/cld-changesets/catalog/operations"
)

func TestUpdateAddressRefChangeset_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 UpdateAddressRefChangesetInput
wantErr string
}{
{
name: "success: valid preconditions",
env: cldf.Environment{DataStore: func() cldfdatastore.DataStore {
ds := cldfdatastore.NewMemoryDataStore()
err := ds.Addresses().Add(addressRef1)
require.NoError(t, err)

return ds.Seal()
}()},
input: UpdateAddressRefChangesetInput{
AddressRefs: []cldfdatastore.AddressRef{addressRef1},
},
},
{
name: "failure: missing datastore",
env: cldf.Environment{},
input: UpdateAddressRefChangesetInput{
AddressRefs: []cldfdatastore.AddressRef{addressRef1},
},
wantErr: "missing datastore in environment",
},
{
name: "failure: no address refs given",
env: cldf.Environment{DataStore: cldfdatastore.NewMemoryDataStore().Seal()},
input: UpdateAddressRefChangesetInput{
AddressRefs: []cldfdatastore.AddressRef{},
},
wantErr: "missing address refs input",
},
{
name: "failure: duplicate entries",
env: cldf.Environment{DataStore: cldfdatastore.NewMemoryDataStore().Seal()},
input: UpdateAddressRefChangesetInput{
AddressRefs: []cldfdatastore.AddressRef{addressRef1, addressRef2},
},
wantErr: "duplicate address ref entries found in input",
},
{
name: "failure: address ref does not exist",
env: cldf.Environment{DataStore: cldfdatastore.NewMemoryDataStore().Seal()},
input: UpdateAddressRefChangesetInput{
AddressRefs: []cldfdatastore.AddressRef{addressRef1},
},
wantErr: "address ref for chain selector 1234, type MyContract, version 1.0.0 and qualifier \"q1\" does not exist",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()

err := UpdateAddressRefChangeset{}.VerifyPreconditions(tt.env, tt.input)

if tt.wantErr == "" {
require.NoError(t, err)
} else {
require.ErrorContains(t, err, tt.wantErr)
}
})
}
}

func TestUpdateAddressRefChangeset_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"}
addressRef1Updated := cldfdatastore.AddressRef{Address: "0x99", ChainSelector: 1234, Type: "MyContract", Version: version, Qualifier: "q1"}
addressRef2Updated := cldfdatastore.AddressRef{Address: "0x88", ChainSelector: 5678, Type: "OtherContract", Version: version, Qualifier: "q2"}

tests := []struct {
name string
env cldf.Environment
input UpdateAddressRefChangesetInput
want cldf.ChangesetOutput
wantErr string
}{
{
name: "success: updates two entries in address refs",
env: cldf.Environment{
DataStore: testDataStoreWithAddressRefs(t, addressRef1, addressRef2).Seal(),
OperationsBundle: cldfoperations.NewBundle(t.Context, cldflogger.Test(t), cldfoperations.NewMemoryReporter()),
},
input: UpdateAddressRefChangesetInput{
AddressRefs: []cldfdatastore.AddressRef{addressRef1Updated, addressRef2Updated},
},
want: cldf.ChangesetOutput{
DataStore: testDataStoreWithAddressRefs(t, addressRef1Updated, addressRef2Updated),
Reports: []cldfoperations.Report[any, any]{{
Def: cldfoperations.Definition{
ID: "catalog-update-address-ref",
Version: semver.MustParse("1.0.0"),
Description: "Update address ref entries in the Catalog service",
},
Input: operations.UpdateAddressRefInput{
AddressRefs: []cldfdatastore.AddressRef{addressRef1Updated, addressRef2Updated},
},
Output: operations.UpdateAddressRefOutput{
DataStore: testDataStoreWithAddressRefs(t, addressRef1Updated, addressRef2Updated),
},
}},
},
},
{
name: "failure: fails to update entry that does not exist",
env: cldf.Environment{
DataStore: testDataStoreWithAddressRefs(t).Seal(),
OperationsBundle: cldfoperations.NewBundle(t.Context, cldflogger.Test(t), cldfoperations.NewMemoryReporter()),
},
input: UpdateAddressRefChangesetInput{
AddressRefs: []cldfdatastore.AddressRef{addressRef1Updated},
},
wantErr: "failed to update address ref entry 0 in catalog store: " +
"no such address ref can be found for the provided key",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()

got, err := UpdateAddressRefChangeset{}.Apply(tt.env, tt.input)

if tt.wantErr == "" {
require.NoError(t, err)
require.Empty(t,
cmp.Diff(tt.want, got,
cmpopts.IgnoreFields(cldfoperations.Report[any, any]{}, "ID", "Timestamp"),
cmpopts.IgnoreUnexported(cldfdatastore.MemoryAddressRefStore{}, cldfdatastore.MemoryChainMetadataStore{},
cldfdatastore.MemoryContractMetadataStore{}, cldfdatastore.MemoryEnvMetadataStore{},
cldfdatastore.LabelSet{})))
} else {
require.ErrorContains(t, err, tt.wantErr)
}
})
}
}
51 changes: 51 additions & 0 deletions catalog/operations/update_address_ref.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
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"
)

// UpdateAddressRefDeps holds non-serializable dependencies for the
// UpdateAddressRefOp operation.
type UpdateAddressRefDeps struct {
DataStore cldfdatastore.DataStore
}

// UpdateAddressRefInput is the serializable input of an UpdateAddressRefOp invocation.
type UpdateAddressRefInput struct {
AddressRefs []cldfdatastore.AddressRef
}

// UpdateAddressRefOutput is the serializable output of an UpdateAddressRefOp invocation.
type UpdateAddressRefOutput struct {
DataStore cldfdatastore.MutableDataStore
}

// UpdateAddressRefOp updates existing address ref entries in the Catalog service.
var UpdateAddressRefOp = cldfops.NewOperation(
"catalog-update-address-ref",
semver.MustParse("1.0.0"),
"Update address ref entries in the Catalog service",
func(b cldfops.Bundle, deps UpdateAddressRefDeps, input UpdateAddressRefInput) (UpdateAddressRefOutput, error) {
dataStore := cldfdatastore.NewMemoryDataStore()
err := dataStore.Merge(deps.DataStore)
if err != nil {
return UpdateAddressRefOutput{}, fmt.Errorf("failed to create memory data store: %w", err)
Comment thread
gustavogama-cll marked this conversation as resolved.
}

for i, item := range input.AddressRefs {
err = dataStore.Addresses().Update(item)
if err != nil {
return UpdateAddressRefOutput{}, fmt.Errorf("failed to update address ref entry %d in catalog store: %w", i, err)
}
}

b.Logger.Infow("Catalog AddressRef updated successfully")

return UpdateAddressRefOutput{DataStore: dataStore}, nil
},
)