Skip to content

Commit f5d228e

Browse files
Allow to hydrate contract from the capability registry view (smartcontractkit#15309)
* Adds `Hydrate()` functionality to `CapabilityRegistryView` * Extracts hydration functionality into a `common` package * Add `UnmarshalJSON` functionality to `CapabilityRegistryView` * Adds test - WIP * Completes tests comparing capability registry views * Refactors to use existing deployment logic * Gets deployed contract from contract sets * Uses uint64 type annotation * Moves implementation to `keystone/test`
1 parent e094909 commit f5d228e

11 files changed

+717
-10
lines changed

deployment/common/view/v1_0/capreg.go

+119-4
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ type CapabilityRegistryView struct {
2626

2727
// MarshalJSON marshals the CapabilityRegistryView to JSON. It includes the Capabilities, Nodes, Nops, and Dons
2828
// and a denormalized summary of the Dons with their associated Nodes and Capabilities, which is useful for a high-level view
29-
func (v CapabilityRegistryView) MarshalJSON() ([]byte, error) {
29+
func (v *CapabilityRegistryView) MarshalJSON() ([]byte, error) {
3030
// Alias to avoid recursive calls
3131
type Alias struct {
3232
types.ContractMetaData
@@ -51,6 +51,36 @@ func (v CapabilityRegistryView) MarshalJSON() ([]byte, error) {
5151
return json.MarshalIndent(&a, "", " ")
5252
}
5353

54+
// UnmarshalJSON unmarshals the CapabilityRegistryView from JSON. Since the CapabilityRegistryView doesn't hold a DonCapabilities field,
55+
// it is not unmarshaled.
56+
func (v *CapabilityRegistryView) UnmarshalJSON(data []byte) error {
57+
// Alias to avoid recursive calls
58+
type Alias struct {
59+
types.ContractMetaData
60+
Capabilities []CapabilityView `json:"capabilities,omitempty"`
61+
Nodes []NodeView `json:"nodes,omitempty"`
62+
Nops []NopView `json:"nops,omitempty"`
63+
Dons []DonView `json:"dons,omitempty"`
64+
DonCapabilities []DonDenormalizedView `json:"don_capabilities_summary,omitempty"`
65+
}
66+
a := Alias{
67+
ContractMetaData: v.ContractMetaData,
68+
Capabilities: v.Capabilities,
69+
Nodes: v.Nodes,
70+
Nops: v.Nops,
71+
Dons: v.Dons,
72+
}
73+
if err := json.Unmarshal(data, &a); err != nil {
74+
return err
75+
}
76+
v.ContractMetaData = a.ContractMetaData
77+
v.Capabilities = a.Capabilities
78+
v.Nodes = a.Nodes
79+
v.Nops = a.Nops
80+
v.Dons = a.Dons
81+
return nil
82+
}
83+
5484
// GenerateCapabilityRegistryView generates a CapRegView from a CapabilitiesRegistry contract.
5585
func GenerateCapabilityRegistryView(capReg *capabilities_registry.CapabilitiesRegistry) (CapabilityRegistryView, error) {
5686
tv, err := types.NewContractMetaData(capReg, capReg.Address())
@@ -112,7 +142,7 @@ type DonDenormalizedView struct {
112142
// Nodes and Capabilities. This is a useful form of the CapabilityRegistryView, but it is not definitive.
113143
// The full CapRegView should be used for the most accurate information as it can contain
114144
// Capabilities and Nodes the are not associated with any Don.
115-
func (v CapabilityRegistryView) DonDenormalizedView() ([]DonDenormalizedView, error) {
145+
func (v *CapabilityRegistryView) DonDenormalizedView() ([]DonDenormalizedView, error) {
116146
var out []DonDenormalizedView
117147
for _, don := range v.Dons {
118148
var nodes []NodeDenormalizedView
@@ -140,6 +170,91 @@ func (v CapabilityRegistryView) DonDenormalizedView() ([]DonDenormalizedView, er
140170
return out, nil
141171
}
142172

173+
func (v *CapabilityRegistryView) NodesToNodesParams() ([]capabilities_registry.CapabilitiesRegistryNodeParams, error) {
174+
var nodesParams []capabilities_registry.CapabilitiesRegistryNodeParams
175+
for _, node := range v.Nodes {
176+
signer, err := hexTo32Bytes(node.Signer)
177+
if err != nil {
178+
return nil, err
179+
}
180+
encryptionPubKey, err := hexTo32Bytes(node.EncryptionPublicKey)
181+
if err != nil {
182+
return nil, err
183+
}
184+
capIDs := make([][32]byte, len(node.CapabilityIDs))
185+
for i, id := range node.CapabilityIDs {
186+
cid, err := hexTo32Bytes(id)
187+
if err != nil {
188+
return nil, err
189+
}
190+
capIDs[i] = cid
191+
}
192+
nodesParams = append(nodesParams, capabilities_registry.CapabilitiesRegistryNodeParams{
193+
Signer: signer,
194+
P2pId: node.P2pId,
195+
EncryptionPublicKey: encryptionPubKey,
196+
NodeOperatorId: node.NodeOperatorID,
197+
HashedCapabilityIds: capIDs,
198+
})
199+
}
200+
201+
return nodesParams, nil
202+
}
203+
204+
func (v *CapabilityRegistryView) CapabilitiesToCapabilitiesParams() []capabilities_registry.CapabilitiesRegistryCapability {
205+
var capabilitiesParams []capabilities_registry.CapabilitiesRegistryCapability
206+
for _, capability := range v.Capabilities {
207+
capabilitiesParams = append(capabilitiesParams, capabilities_registry.CapabilitiesRegistryCapability{
208+
LabelledName: capability.LabelledName,
209+
Version: capability.Version,
210+
CapabilityType: capability.CapabilityType,
211+
ResponseType: capability.ResponseType,
212+
ConfigurationContract: capability.ConfigurationContract,
213+
})
214+
}
215+
return capabilitiesParams
216+
}
217+
218+
func (v *CapabilityRegistryView) NopsToNopsParams() []capabilities_registry.CapabilitiesRegistryNodeOperator {
219+
var nopsParams []capabilities_registry.CapabilitiesRegistryNodeOperator
220+
for _, nop := range v.Nops {
221+
nopsParams = append(nopsParams, capabilities_registry.CapabilitiesRegistryNodeOperator{
222+
Admin: nop.Admin,
223+
Name: nop.Name,
224+
})
225+
}
226+
return nopsParams
227+
}
228+
229+
func (v *CapabilityRegistryView) CapabilityConfigToCapabilityConfigParams(don DonView) ([]capabilities_registry.CapabilitiesRegistryCapabilityConfiguration, error) {
230+
var cfgs []capabilities_registry.CapabilitiesRegistryCapabilityConfiguration
231+
for _, cfg := range don.CapabilityConfigurations {
232+
cid, err := hexTo32Bytes(cfg.ID)
233+
if err != nil {
234+
return nil, err
235+
}
236+
config, err := hex.DecodeString(cfg.Config)
237+
if err != nil {
238+
return nil, err
239+
}
240+
cfgs = append(cfgs, capabilities_registry.CapabilitiesRegistryCapabilityConfiguration{
241+
CapabilityId: cid,
242+
Config: config,
243+
})
244+
}
245+
return cfgs, nil
246+
}
247+
248+
func hexTo32Bytes(val string) ([32]byte, error) {
249+
var out [32]byte
250+
b, err := hex.DecodeString(val)
251+
if err != nil {
252+
return out, err
253+
}
254+
copy(out[:], b)
255+
return out, nil
256+
}
257+
143258
// CapabilityView is a serialization-friendly view of a capability in the capabilities registry.
144259
type CapabilityView struct {
145260
ID string `json:"id"` // hex 32 bytes
@@ -272,7 +387,7 @@ func NewNodeView(n capabilities_registry.INodeInfoProviderNodeInfo) NodeView {
272387
ConfigCount: n.ConfigCount,
273388
WorkflowDONID: n.WorkflowDONId,
274389
Signer: hex.EncodeToString(n.Signer[:]),
275-
P2pId: p2pkey.PeerID(n.P2pId),
390+
P2pId: n.P2pId,
276391
EncryptionPublicKey: hex.EncodeToString(n.EncryptionPublicKey[:]),
277392
},
278393
NodeOperatorID: n.NodeOperatorId,
@@ -328,7 +443,7 @@ func NewNopView(nop capabilities_registry.CapabilitiesRegistryNodeOperator) NopV
328443
}
329444
}
330445

331-
func (v CapabilityRegistryView) nodeDenormalizedView(n NodeView) (NodeDenormalizedView, error) {
446+
func (v *CapabilityRegistryView) nodeDenormalizedView(n NodeView) (NodeDenormalizedView, error) {
332447
nop, err := nodeNop(n, v.Nops)
333448
if err != nil {
334449
return NodeDenormalizedView{}, err

deployment/common/view/v1_0/capreg_test.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@ import (
44
"math/big"
55
"testing"
66

7+
"github.com/stretchr/testify/assert"
8+
79
"github.com/smartcontractkit/chainlink/deployment/common/view/types"
810
cr "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/capabilities_registry"
9-
"github.com/stretchr/testify/assert"
1011
)
1112

1213
func TestCapRegView_Denormalize(t *testing.T) {

deployment/keystone/capability_registry_deployer.go

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"github.com/ethereum/go-ethereum/common"
1010

1111
"github.com/smartcontractkit/chainlink-common/pkg/logger"
12+
1213
"github.com/smartcontractkit/chainlink/deployment"
1314
"github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/capabilities_registry"
1415
)

deployment/keystone/changeset/deploy_registry.go

+3-5
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,9 @@ import (
77
kslib "github.com/smartcontractkit/chainlink/deployment/keystone"
88
)
99

10-
func DeployCapabilityRegistry(env deployment.Environment, config interface{}) (deployment.ChangesetOutput, error) {
11-
registrySelector, ok := config.(uint64)
12-
if !ok {
13-
return deployment.ChangesetOutput{}, deployment.ErrInvalidConfig
14-
}
10+
var _ deployment.ChangeSet[uint64] = DeployCapabilityRegistry
11+
12+
func DeployCapabilityRegistry(env deployment.Environment, registrySelector uint64) (deployment.ChangesetOutput, error) {
1513
chain, ok := env.Chains[registrySelector]
1614
if !ok {
1715
return deployment.ChangesetOutput{}, fmt.Errorf("chain not found in environment")

deployment/keystone/changeset/deploy_registry_test.go

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"github.com/stretchr/testify/require"
99

1010
"github.com/smartcontractkit/chainlink-common/pkg/logger"
11+
1112
"github.com/smartcontractkit/chainlink/deployment/environment/memory"
1213
"github.com/smartcontractkit/chainlink/deployment/keystone/changeset"
1314
)

deployment/keystone/changeset/view_test.go

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"go.uber.org/zap/zapcore"
88

99
"github.com/smartcontractkit/chainlink-common/pkg/logger"
10+
1011
"github.com/smartcontractkit/chainlink/deployment/environment/memory"
1112
)
1213

deployment/keystone/contract_set.go

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"fmt"
55

66
"github.com/smartcontractkit/chainlink-common/pkg/logger"
7+
78
"github.com/smartcontractkit/chainlink/deployment"
89
)
910

deployment/keystone/state.go

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"github.com/ethereum/go-ethereum/common"
77

88
"github.com/smartcontractkit/chainlink-common/pkg/logger"
9+
910
"github.com/smartcontractkit/chainlink/deployment"
1011
common_v1_0 "github.com/smartcontractkit/chainlink/deployment/common/view/v1_0"
1112
"github.com/smartcontractkit/chainlink/deployment/keystone/view"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
package changeset
2+
3+
import (
4+
"fmt"
5+
"testing"
6+
7+
chainsel "github.com/smartcontractkit/chain-selectors"
8+
9+
"github.com/smartcontractkit/chainlink/deployment"
10+
"github.com/smartcontractkit/chainlink/deployment/common/view/v1_0"
11+
"github.com/smartcontractkit/chainlink/deployment/keystone"
12+
"github.com/smartcontractkit/chainlink/deployment/keystone/changeset"
13+
"github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/capabilities_registry"
14+
)
15+
16+
type HydrateConfig struct {
17+
ChainID uint64
18+
}
19+
20+
// HydrateCapabilityRegistry deploys a new capabilities registry contract and hydrates it with the provided data.
21+
func HydrateCapabilityRegistry(t *testing.T, v v1_0.CapabilityRegistryView, env deployment.Environment, cfg HydrateConfig) (*capabilities_registry.CapabilitiesRegistry, error) {
22+
t.Helper()
23+
chainSelector, err := chainsel.SelectorFromChainId(cfg.ChainID)
24+
if err != nil {
25+
return nil, fmt.Errorf("failed to get chain selector from chain id: %w", err)
26+
}
27+
chain, ok := env.Chains[chainSelector]
28+
if !ok {
29+
return nil, fmt.Errorf("chain with id %d not found", cfg.ChainID)
30+
}
31+
changesetOutput, err := changeset.DeployCapabilityRegistry(env, chainSelector)
32+
if err != nil {
33+
return nil, fmt.Errorf("failed to deploy contract: %w", err)
34+
}
35+
36+
resp, err := keystone.GetContractSets(env.Logger, &keystone.GetContractSetsRequest{
37+
Chains: env.Chains,
38+
AddressBook: changesetOutput.AddressBook,
39+
})
40+
if err != nil {
41+
return nil, fmt.Errorf("failed to get contract sets: %w", err)
42+
}
43+
cs, ok := resp.ContractSets[chainSelector]
44+
if !ok {
45+
return nil, fmt.Errorf("failed to get contract set for chain selector: %d, chain ID: %d", chainSelector, cfg.ChainID)
46+
}
47+
48+
deployedContract := cs.CapabilitiesRegistry
49+
50+
nopsParams := v.NopsToNopsParams()
51+
tx, err := deployedContract.AddNodeOperators(chain.DeployerKey, nopsParams)
52+
if _, err = deployment.ConfirmIfNoError(chain, tx, keystone.DecodeErr(capabilities_registry.CapabilitiesRegistryABI, err)); err != nil {
53+
return nil, fmt.Errorf("failed to add node operators: %w", err)
54+
}
55+
56+
capabilitiesParams := v.CapabilitiesToCapabilitiesParams()
57+
tx, err = deployedContract.AddCapabilities(chain.DeployerKey, capabilitiesParams)
58+
if _, err = deployment.ConfirmIfNoError(chain, tx, keystone.DecodeErr(capabilities_registry.CapabilitiesRegistryABI, err)); err != nil {
59+
return nil, fmt.Errorf("failed to add capabilities: %w", err)
60+
}
61+
62+
nodesParams, err := v.NodesToNodesParams()
63+
if err != nil {
64+
return nil, fmt.Errorf("failed to convert nodes to nodes params: %w", err)
65+
}
66+
tx, err = deployedContract.AddNodes(chain.DeployerKey, nodesParams)
67+
if _, err = deployment.ConfirmIfNoError(chain, tx, keystone.DecodeErr(capabilities_registry.CapabilitiesRegistryABI, err)); err != nil {
68+
return nil, fmt.Errorf("failed to add nodes: %w", err)
69+
}
70+
71+
for _, don := range v.Dons {
72+
cfgs, err := v.CapabilityConfigToCapabilityConfigParams(don)
73+
if err != nil {
74+
return nil, fmt.Errorf("failed to convert capability configurations to capability configuration params: %w", err)
75+
}
76+
var peerIds [][32]byte
77+
for _, id := range don.NodeP2PIds {
78+
peerIds = append(peerIds, id)
79+
}
80+
tx, err = deployedContract.AddDON(chain.DeployerKey, peerIds, cfgs, don.IsPublic, don.AcceptsWorkflows, don.F)
81+
if _, err = deployment.ConfirmIfNoError(chain, tx, keystone.DecodeErr(capabilities_registry.CapabilitiesRegistryABI, err)); err != nil {
82+
return nil, fmt.Errorf("failed to add don: %w", err)
83+
}
84+
}
85+
86+
return deployedContract, nil
87+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package changeset
2+
3+
import (
4+
"encoding/json"
5+
"os"
6+
"testing"
7+
8+
"github.com/stretchr/testify/require"
9+
"go.uber.org/zap/zapcore"
10+
11+
chainsel "github.com/smartcontractkit/chain-selectors"
12+
13+
"github.com/smartcontractkit/chainlink/deployment/common/view/v1_0"
14+
"github.com/smartcontractkit/chainlink/deployment/environment/memory"
15+
"github.com/smartcontractkit/chainlink/v2/core/logger"
16+
)
17+
18+
func TestHydrateCapabilityRegistry(t *testing.T) {
19+
b, err := os.ReadFile("testdata/capability_registry_view.json")
20+
require.NoError(t, err)
21+
require.NotEmpty(t, b)
22+
var capabilityRegistryView v1_0.CapabilityRegistryView
23+
require.NoError(t, json.Unmarshal(b, &capabilityRegistryView))
24+
25+
chainID := chainsel.TEST_90000001.EvmChainID
26+
cfg := HydrateConfig{ChainID: chainID}
27+
env := memory.NewMemoryEnvironment(t, logger.TestLogger(t), zapcore.InfoLevel, memory.MemoryEnvironmentConfig{
28+
Bootstraps: 1,
29+
Chains: 1,
30+
Nodes: 4,
31+
})
32+
hydrated, err := HydrateCapabilityRegistry(t, capabilityRegistryView, env, cfg)
33+
require.NoError(t, err)
34+
require.NotNil(t, hydrated)
35+
hydratedCapView, err := v1_0.GenerateCapabilityRegistryView(hydrated)
36+
require.NoError(t, err)
37+
38+
// Setting address/owner values to be the same in order to compare the views
39+
hydratedCapView.Address = capabilityRegistryView.Address
40+
hydratedCapView.Owner = capabilityRegistryView.Owner
41+
b1, err := capabilityRegistryView.MarshalJSON()
42+
require.NoError(t, err)
43+
b2, err := hydratedCapView.MarshalJSON()
44+
require.NoError(t, err)
45+
require.Equal(t, string(b1), string(b2))
46+
}

0 commit comments

Comments
 (0)