Skip to content
Draft
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
68 changes: 68 additions & 0 deletions catalog/changesets/create_address_ref.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
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"
)

// CreateAddressRefChangeset creates address ref entries in the Catalog service.
type CreateAddressRefChangeset struct{}

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

// VerifyPreconditions ensures the input is valid.
func (CreateAddressRefChangeset) VerifyPreconditions(e cldf.Environment, input CreateAddressRefChangesetInput) 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 err == nil {
return fmt.Errorf("address ref for chain selector %v, type %v, version %v and qualifier %q already exists",
addressRef.ChainSelector, addressRef.Type, addressRef.Version, addressRef.Qualifier)
}
if !errors.Is(err, cldfdatastore.ErrAddressRefNotFound) {
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, adding the address refs to the Catalog service.
func (CreateAddressRefChangeset) Apply(e cldf.Environment, input CreateAddressRefChangesetInput) (cldf.ChangesetOutput, error) {
deps := operations.CreateAddressRefDeps{DataStore: e.DataStore}
opInput := operations.CreateAddressRefInput{AddressRefs: input.AddressRefs}

report, err := cldfops.ExecuteOperation(e.OperationsBundle, operations.CreateAddressRefOp, 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
}
181 changes: 181 additions & 0 deletions catalog/changesets/create_address_ref_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
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 TestCreateAddressRefChangeset_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 CreateAddressRefChangesetInput
wantErr string
}{
{
name: "success: valid preconditions",
env: cldf.Environment{DataStore: cldfdatastore.NewMemoryDataStore().Seal()},
input: CreateAddressRefChangesetInput{
AddressRefs: []cldfdatastore.AddressRef{addressRef1},
},
},
{
name: "failure: missing datastore",
env: cldf.Environment{},
input: CreateAddressRefChangesetInput{
AddressRefs: []cldfdatastore.AddressRef{addressRef1},
},
wantErr: "missing datastore in environment",
},
{
name: "failure: no address refs given",
env: cldf.Environment{DataStore: cldfdatastore.NewMemoryDataStore().Seal()},
input: CreateAddressRefChangesetInput{
AddressRefs: []cldfdatastore.AddressRef{},
},
wantErr: "missing address refs input",
},
{
name: "failure: duplicate entries",
env: cldf.Environment{DataStore: cldfdatastore.NewMemoryDataStore().Seal()},
input: CreateAddressRefChangesetInput{
AddressRefs: []cldfdatastore.AddressRef{addressRef1, addressRef2},
},
wantErr: "duplicate address ref entries found in input",
},
{
name: "failure: address ref already exists",
env: cldf.Environment{DataStore: func() cldfdatastore.DataStore {
ds := cldfdatastore.NewMemoryDataStore()
err := ds.Addresses().Add(addressRef1)
require.NoError(t, err)

return ds.Seal()
}()},
input: CreateAddressRefChangesetInput{
AddressRefs: []cldfdatastore.AddressRef{addressRef1},
},
wantErr: "address ref for chain selector 1234, type MyContract, version 1.0.0 and qualifier \"q1\" already exists",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()

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

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

func TestCreateAddressRefChangeset_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: 1234, Type: "MyContract", Version: version, Qualifier: "q2"}

tests := []struct {
name string
env cldf.Environment
input CreateAddressRefChangesetInput
want cldf.ChangesetOutput
wantErr string
}{
{
name: "success: adds two entries to address refs",
env: cldf.Environment{
DataStore: testDataStoreWithAddressRefs(t).Seal(),
OperationsBundle: cldfoperations.NewBundle(t.Context, cldflogger.Test(t), cldfoperations.NewMemoryReporter()),
},
input: CreateAddressRefChangesetInput{
AddressRefs: []cldfdatastore.AddressRef{addressRef1, addressRef2},
},
want: cldf.ChangesetOutput{
DataStore: testDataStoreWithAddressRefs(t, addressRef1, addressRef2),
Reports: []cldfoperations.Report[any, any]{{
Def: cldfoperations.Definition{
ID: "catalog-create-address-ref",
Version: semver.MustParse("1.0.0"),
Description: "Add address ref entries to the Catalog service",
},
Input: operations.CreateAddressRefInput{
AddressRefs: []cldfdatastore.AddressRef{addressRef1, addressRef2},
},
Output: operations.CreateAddressRefOutput{
DataStore: testDataStoreWithAddressRefs(t, addressRef1, addressRef2),
},
}},
},
},
{
name: "failure: fails to add second entry",
env: cldf.Environment{
DataStore: testDataStoreWithAddressRefs(t, addressRef2).Seal(),
OperationsBundle: cldfoperations.NewBundle(t.Context, cldflogger.Test(t), cldfoperations.NewMemoryReporter()),
},
input: CreateAddressRefChangesetInput{
AddressRefs: []cldfdatastore.AddressRef{addressRef1, addressRef2},
},
wantErr: "failed to create address ref entry 1 in catalog store: " +
"an address ref with the supplied key already exists",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()

got, err := CreateAddressRefChangeset{}.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)
}
})
}
}

// ----- helpers -----

func testDataStoreWithAddressRefs(
t *testing.T, addressRefs ...cldfdatastore.AddressRef,
) cldfdatastore.MutableDataStore {
t.Helper()

ds := cldfdatastore.NewMemoryDataStore()
for _, ar := range addressRefs {
err := ds.Addresses().Add(ar)
require.NoError(t, err)
}

return ds
}
51 changes: 51 additions & 0 deletions catalog/operations/create_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"
)

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

// CreateAddressRefInput is the serializable input of a CreateAddressRefOp invocation.
type CreateAddressRefInput struct {
AddressRefs []cldfdatastore.AddressRef
}

// CreateAddressRefOutput is the serializable output of a CreateAddressRefOp invocation.
type CreateAddressRefOutput struct {
DataStore cldfdatastore.MutableDataStore
}

// CreateAddressRefOp creates address ref entries in the Catalog service.
var CreateAddressRefOp = cldfops.NewOperation(
"catalog-create-address-ref",
semver.MustParse("1.0.0"),
"Add address ref entries to the Catalog service",
func(b cldfops.Bundle, deps CreateAddressRefDeps, input CreateAddressRefInput) (CreateAddressRefOutput, error) {
dataStore := cldfdatastore.NewMemoryDataStore()
err := dataStore.Merge(deps.DataStore)
if err != nil {
return CreateAddressRefOutput{}, fmt.Errorf("failed to create memory data store: %w", err)
}

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

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

return CreateAddressRefOutput{DataStore: dataStore}, nil
},
)
Loading