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
61 changes: 61 additions & 0 deletions catalog/changesets/delete_contract_metadata.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package changesets

import (
"errors"
"fmt"

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"
)

// DeleteContractMetadataChangeset deletes contract metadata entries from the Catalog service.
type DeleteContractMetadataChangeset struct{}

type DeleteContractMetadataChangesetInput struct {
ContractMetadataKeys []cldfdatastore.ContractMetadataKey `json:"contractMetadataKeys"`
}

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

for _, key := range input.ContractMetadataKeys {
_, err := e.DataStore.ContractMetadata().Get(key)
if err != nil {
if errors.Is(err, cldfdatastore.ErrContractMetadataNotFound) {
return fmt.Errorf("contract metadata entry for chain selector %v and address %v does not exist",
key.ChainSelector(), key.Address())
}

return fmt.Errorf("failed to retrieve contract metadata entry for chain selector %v and address %v: %w",
key.ChainSelector(), key.Address(), err)
}
}

return nil
}

// Apply executes the changeset, staging the contract metadata entries to be deleted from the Catalog service or local datastore files.
func (DeleteContractMetadataChangeset) Apply(e cldf.Environment, input DeleteContractMetadataChangesetInput) (cldf.ChangesetOutput, error) {
deps := operations.DeleteContractMetadataDeps{DataStore: e.DataStore}
opInput := operations.DeleteContractMetadataInput{ContractMetadataKeys: input.ContractMetadataKeys}

report, err := cldfops.ExecuteOperation(e.OperationsBundle, operations.DeleteContractMetadataOp, 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
}
118 changes: 118 additions & 0 deletions catalog/changesets/delete_contract_metadata_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package changesets

import (
"fmt"
"testing"

"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"
)

func TestDeleteContractMetadataChangeset_VerifyPreconditions(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 DeleteContractMetadataChangesetInput
wantErr string
}{
{
name: "success: valid preconditions",
env: cldf.Environment{
DataStore: testDataStoreWithContractMetadata(t, contractMetadata1, contractMetadata2).Seal(),
},
input: DeleteContractMetadataChangesetInput{
ContractMetadataKeys: []cldfdatastore.ContractMetadataKey{contractMetadata1.Key(), contractMetadata2.Key()},
},
},
{
name: "failure: missing datastore",
env: cldf.Environment{},
input: DeleteContractMetadataChangesetInput{
ContractMetadataKeys: []cldfdatastore.ContractMetadataKey{contractMetadata1.Key()},
},
wantErr: "missing datastore in environment",
},
{
name: "failure: no contract metadata keys given",
env: cldf.Environment{
DataStore: cldfdatastore.NewMemoryDataStore().Seal(),
},
input: DeleteContractMetadataChangesetInput{ContractMetadataKeys: []cldfdatastore.ContractMetadataKey{}},
wantErr: "missing contract metadata keys input",
},
{
name: "failure: contract metadata entry does not exist",
env: cldf.Environment{
DataStore: cldfdatastore.NewMemoryDataStore().Seal(),
},
input: DeleteContractMetadataChangesetInput{ContractMetadataKeys: []cldfdatastore.ContractMetadataKey{contractMetadata2.Key()}},
wantErr: fmt.Sprintf("contract metadata entry for chain selector %v and address %v does not exist", contractMetadata2.ChainSelector, contractMetadata2.Address),
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()

err := DeleteContractMetadataChangeset{}.VerifyPreconditions(tt.env, tt.input)
if tt.wantErr == "" {
require.NoError(t, err)
} else {
require.ErrorContains(t, err, tt.wantErr)
}
})
}
}

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

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

tests := []struct {
name string
env cldf.Environment
input DeleteContractMetadataChangesetInput
wantDeletedKeys []string
wantErr string
}{
{
name: "success: stages two contract metadata entries for deletion",
env: cldf.Environment{
DataStore: testDataStoreWithContractMetadata(t, contractMetadata1, contractMetadata2).Seal(),
OperationsBundle: cldfoperations.NewBundle(t.Context, cldflogger.Test(t), cldfoperations.NewMemoryReporter()),
},
input: DeleteContractMetadataChangesetInput{
ContractMetadataKeys: []cldfdatastore.ContractMetadataKey{contractMetadata1.Key(), contractMetadata2.Key()},
},
wantDeletedKeys: []string{contractMetadata1.Key().String(), contractMetadata2.Key().String()},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()

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

if tt.wantErr == "" {
require.NoError(t, err)
require.Len(t, got.Reports, 1)
memDS := got.DataStore.(*cldfdatastore.MemoryDataStore)
require.ElementsMatch(t, tt.wantDeletedKeys, memDS.ContractMetadataStore.DeletedRemoteKeys)
} else {
require.ErrorContains(t, err, tt.wantErr)
}
})
}
}
49 changes: 49 additions & 0 deletions catalog/operations/delete_contract_metadata.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
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"
)

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

// DeleteContractMetadataInput is the serializable input of a DeleteContractMetadataOp invocation.
type DeleteContractMetadataInput struct {
ContractMetadataKeys []cldfdatastore.ContractMetadataKey
}

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

// DeleteContractMetadataOp deletes contract metadata entries from the Catalog service or local datastore files.
var DeleteContractMetadataOp = cldfops.NewOperation(
"datastore-delete-contract-metadata",
semver.MustParse("1.0.0"),
"Delete contract metadata entries from the Catalog service or local datastore files",
func(b cldfops.Bundle, deps DeleteContractMetadataDeps, input DeleteContractMetadataInput) (DeleteContractMetadataOutput, error) {
dataStore := cldfdatastore.NewMemoryDataStore()
err := dataStore.Merge(deps.DataStore)
if err != nil {
return DeleteContractMetadataOutput{}, fmt.Errorf("failed to create memory data store: %w", err)
}

for i, key := range input.ContractMetadataKeys {
err = dataStore.ContractMetadata().RemoteDelete(key)
if err != nil {
return DeleteContractMetadataOutput{}, fmt.Errorf("failed to delete contract metadata entry %d in datastore: %w", i, err)
}
}

b.Logger.Infow("Catalog ContractMetadata successfully staged for deletion")

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