Skip to content
Merged
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_contract_metadata.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"
)

// CreateContractMetadataChangeset creates contract metadata entries in the Catalog service.
type CreateContractMetadataChangeset struct{}

type CreateContractMetadataChangesetInput struct {
ContractMetadata []cldfdatastore.ContractMetadata `json:"contractMetadata"`
}

// VerifyPreconditions ensures the input is valid.
func (CreateContractMetadataChangeset) VerifyPreconditions(e cldf.Environment, input CreateContractMetadataChangesetInput) error {
if len(input.ContractMetadata) == 0 {
return errors.New("missing contract metadata input")
}
if e.DataStore == nil {
return errors.New("missing datastore in environment")
}

uniqContractMetadata := lo.UniqBy(input.ContractMetadata, func(cm cldfdatastore.ContractMetadata) cldfdatastore.ContractMetadataKey {
return cm.Key()
})
if len(uniqContractMetadata) != len(input.ContractMetadata) {
return errors.New("duplicate contract metadata entries found in input")
}

for _, contractMetadata := range input.ContractMetadata {
_, err := e.DataStore.ContractMetadata().Get(contractMetadata.Key())
if err == nil {
return fmt.Errorf("contract metadata for chain selector %v and address %v already exists",
contractMetadata.ChainSelector, contractMetadata.Address)
}
if !errors.Is(err, cldfdatastore.ErrContractMetadataNotFound) {
return fmt.Errorf("failed to retrieve contract metadata for chain selector %v and address %v: %w",
contractMetadata.ChainSelector, contractMetadata.Address, err)
}
}
Comment thread
gustavogama-cll marked this conversation as resolved.

return nil
}

// Apply executes the changeset, adding the contract metadata to the Catalog service.
func (CreateContractMetadataChangeset) Apply(e cldf.Environment, input CreateContractMetadataChangesetInput) (cldf.ChangesetOutput, error) {
deps := operations.CreateContractMetadataDeps{DataStore: e.DataStore}
opInput := operations.CreateContractMetadataInput{ContractMetadata: input.ContractMetadata}

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

return out, nil
}
178 changes: 178 additions & 0 deletions catalog/changesets/create_contract_metadata_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
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 TestCreateContractMetadataChangeset_VerifyPreconditions(t *testing.T) {
t.Parallel()

contractMetadata1 := cldfdatastore.ContractMetadata{Address: "0x01", ChainSelector: 1234, Metadata: "value1"}
contractMetadata2 := cldfdatastore.ContractMetadata{Address: "0x01", ChainSelector: 1234, Metadata: "value2"}

tests := []struct {
name string
env cldf.Environment
input CreateContractMetadataChangesetInput
wantErr string
}{
{
name: "success: valid preconditions",
env: cldf.Environment{DataStore: cldfdatastore.NewMemoryDataStore().Seal()},
input: CreateContractMetadataChangesetInput{
ContractMetadata: []cldfdatastore.ContractMetadata{contractMetadata1},
},
},
{
name: "failure: missing datastore",
env: cldf.Environment{},
input: CreateContractMetadataChangesetInput{
ContractMetadata: []cldfdatastore.ContractMetadata{{}},
},
wantErr: "missing datastore in environment",
},
{
name: "failure: no contract metadata given",
env: cldf.Environment{DataStore: cldfdatastore.NewMemoryDataStore().Seal()},
input: CreateContractMetadataChangesetInput{
ContractMetadata: []cldfdatastore.ContractMetadata{},
},
wantErr: "missing contract metadata input",
},
{
name: "failure: duplicate entries",
env: cldf.Environment{DataStore: cldfdatastore.NewMemoryDataStore().Seal()},
input: CreateContractMetadataChangesetInput{
ContractMetadata: []cldfdatastore.ContractMetadata{contractMetadata1, contractMetadata2},
},
wantErr: "duplicate contract metadata entries found in input",
},
{
name: "failure: contract metadata already exists",
env: cldf.Environment{DataStore: func() cldfdatastore.DataStore {
ds := cldfdatastore.NewMemoryDataStore()
err := ds.ContractMetadata().Add(contractMetadata1)
require.NoError(t, err)

return ds.Seal()
}()},
input: CreateContractMetadataChangesetInput{
ContractMetadata: []cldfdatastore.ContractMetadata{contractMetadata1},
},
wantErr: "contract metadata for chain selector 1234 and address 0x01 already exists",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()

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

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

func TestCreateContractMetadataChangeset_Apply(t *testing.T) {
t.Parallel()

contractMetadata1 := cldfdatastore.ContractMetadata{Address: "0x01", ChainSelector: 1234, Metadata: "value1"}
contractMetadata2 := cldfdatastore.ContractMetadata{Address: "0x02", ChainSelector: 1234, Metadata: "value2"}

tests := []struct {
name string
env cldf.Environment
input CreateContractMetadataChangesetInput
want cldf.ChangesetOutput
wantErr string
}{
{
name: "success: adds two entries to contract metadata",
env: cldf.Environment{
DataStore: testDataStoreWithContractMetadata(t).Seal(),
OperationsBundle: cldfoperations.NewBundle(t.Context, cldflogger.Test(t), cldfoperations.NewMemoryReporter()),
},
Comment thread
gustavogama-cll marked this conversation as resolved.
Comment thread
gustavogama-cll marked this conversation as resolved.
input: CreateContractMetadataChangesetInput{
ContractMetadata: []cldfdatastore.ContractMetadata{contractMetadata1, contractMetadata2},
},
want: cldf.ChangesetOutput{
DataStore: testDataStoreWithContractMetadata(t, contractMetadata1, contractMetadata2),
Reports: []cldfoperations.Report[any, any]{{
Def: cldfoperations.Definition{
ID: "catalog-create-contract-metadata",
Version: semver.MustParse("1.0.0"),
Description: "Add contract metadata entries to the Catalog service",
},
Input: operations.CreateContractMetadataInput{
ContractMetadata: []cldfdatastore.ContractMetadata{contractMetadata1, contractMetadata2},
},
Output: operations.CreateContractMetadataOutput{
DataStore: testDataStoreWithContractMetadata(t, contractMetadata1, contractMetadata2),
},
}},
},
},
{
name: "failure: fails to add second entry",
env: cldf.Environment{
DataStore: testDataStoreWithContractMetadata(t, contractMetadata2).Seal(),
OperationsBundle: cldfoperations.NewBundle(t.Context, cldflogger.Test(t), cldfoperations.NewMemoryReporter()),
},
Comment thread
gustavogama-cll marked this conversation as resolved.
Comment thread
gustavogama-cll marked this conversation as resolved.
input: CreateContractMetadataChangesetInput{
ContractMetadata: []cldfdatastore.ContractMetadata{contractMetadata1, contractMetadata2},
},
wantErr: "failed to create contract metadata entry 1 in catalog store: " +
"a contract metadata record with the supplied key already exists",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()

got, err := CreateContractMetadataChangeset{}.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{})))
} else {
require.ErrorContains(t, err, tt.wantErr)
}
})
}
}

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

func testDataStoreWithContractMetadata(
t *testing.T, metadata ...cldfdatastore.ContractMetadata,
) cldfdatastore.MutableDataStore {
t.Helper()

ds := cldfdatastore.NewMemoryDataStore()
for _, m := range metadata {
err := ds.ContractMetadata().Add(m)
require.NoError(t, err)
}

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

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

// CreateContractMetadataInput is the serializable input of a CreateContractMetadataOp invocation.
type CreateContractMetadataInput struct {
ContractMetadata []cldfdatastore.ContractMetadata
}

// CreateContractMetadataOutput is the serializable output of a CreateContractMetadataOp invocation.
type CreateContractMetadataOutput struct {
DataStore cldfdatastore.MutableDataStore
}
Comment thread
gustavogama-cll marked this conversation as resolved.

// CreateContractMetadataOp creates contract metadata entries in the Catalog service.
var CreateContractMetadataOp = cldfops.NewOperation(
"catalog-create-contract-metadata",
semver.MustParse("1.0.0"),
"Add contract metadata entries to the Catalog service",
func(b cldfops.Bundle, deps CreateContractMetadataDeps, input CreateContractMetadataInput) (CreateContractMetadataOutput, error) {
dataStore := cldfdatastore.NewMemoryDataStore()
err := dataStore.Merge(deps.DataStore)
if err != nil {
return CreateContractMetadataOutput{}, fmt.Errorf("failed to create memory data store: %w", err)
Comment thread
gustavogama-cll marked this conversation as resolved.
Comment thread
gustavogama-cll marked this conversation as resolved.
Comment thread
gustavogama-cll marked this conversation as resolved.
}

for i, item := range input.ContractMetadata {
err = dataStore.ContractMetadata().Add(item)
if err != nil {
return CreateContractMetadataOutput{}, fmt.Errorf("failed to create contract metadata entry %d in catalog store: %w", i, err)
}
}

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

return CreateContractMetadataOutput{DataStore: dataStore}, nil
},
)
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ require (
github.com/deckarep/golang-set/v2 v2.6.0
github.com/ethereum/go-ethereum v1.17.1
github.com/gagliardetto/solana-go v1.13.0
github.com/google/go-cmp v0.7.0
Comment thread
gustavogama-cll marked this conversation as resolved.
github.com/samber/lo v1.52.0
github.com/smartcontractkit/ccip-owner-contracts v0.1.0
github.com/smartcontractkit/chain-selectors v1.0.97
github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20260121163256-85accaf3d28d
Expand Down Expand Up @@ -114,7 +116,6 @@ require (
github.com/golang/protobuf v1.5.4 // indirect
github.com/golang/snappy v1.0.0 // indirect
github.com/google/gnostic-models v0.6.9 // indirect
github.com/google/go-cmp v0.7.0 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/websocket v1.5.3 // indirect
Expand Down Expand Up @@ -204,7 +205,6 @@ require (
github.com/rs/zerolog v1.34.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/sagikazarmark/locafero v0.11.0 // indirect
github.com/samber/lo v1.52.0 // indirect
github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 // indirect
github.com/scylladb/go-reflectx v1.0.1 // indirect
github.com/segmentio/ksuid v1.0.4 // indirect
Expand Down
Loading