Skip to content

Commit f75c78b

Browse files
fix(mcms/transfer): change to datastore ref type (#113)
For the transfer to timelock changeset, the input type contains evm specific type which should be avoided. This commit changes the type into a generic datastore type. Before ``` type Config struct { ContractsByChain map[uint64][]common.Address `json:"contractsByChain"` ``` After ``` type Config struct { // ContractsByChain lists contracts as datastore refs for every supported chain family. ContractsByChain map[uint64][]refkey.RefKey `json:"contractsByChain,omitempty"` ```
1 parent 5fe27c1 commit f75c78b

11 files changed

Lines changed: 247 additions & 90 deletions

File tree

mcms/changesets/transfer-to-timelock/changeset.go

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,17 @@ func (Changeset) Apply(env cldf.Environment, input Input) (cldf.ChangesetOutput,
6868
var agg sequenceutils.OnChainOutput
6969

7070
for _, chainSelector := range maputil.SortedMapKeys(input.Cfg.ContractsByChain) {
71-
contracts := input.Cfg.ContractsByChain[chainSelector]
71+
contracts, normErr := normalizeContractRefs(chainSelector, input.Cfg.ContractsByChain[chainSelector])
72+
if normErr != nil {
73+
return buildOutput(env, input.MCMS, agg, fmt.Errorf("chain selector %d: %w", chainSelector, normErr))
74+
}
75+
76+
chainInput := ChainInput{
77+
ChainSelector: chainSelector,
78+
Contracts: contracts,
79+
OnlyAcceptOwnership: input.Cfg.OnlyAcceptOwnership,
80+
MCMS: input.MCMS,
81+
}
7282

7383
seq, seqErr := Registry.SequenceForChainSelector(chainSelector)
7484
if seqErr != nil {
@@ -80,12 +90,7 @@ func (Changeset) Apply(env cldf.Environment, input Input) (cldf.ChangesetOutput,
8090
env.OperationsBundle,
8191
deps,
8292
seq,
83-
ChainInput{
84-
ChainSelector: chainSelector,
85-
Contracts: contracts,
86-
OnlyAcceptOwnership: input.Cfg.OnlyAcceptOwnership,
87-
MCMS: input.MCMS,
88-
},
93+
chainInput,
8994
agg,
9095
)
9196
if mergeErr != nil {
@@ -134,13 +139,20 @@ func groupByFamily(input Input) (map[string][]ChainInput, error) {
134139
if len(contracts) == 0 {
135140
return nil, fmt.Errorf("chain %d: no contracts provided", chainSelector)
136141
}
142+
143+
normalized, err := normalizeContractRefs(chainSelector, contracts)
144+
if err != nil {
145+
return nil, fmt.Errorf("chain %d: %w", chainSelector, err)
146+
}
147+
137148
family, err := chainselectors.GetSelectorFamily(chainSelector)
138149
if err != nil {
139150
return nil, fmt.Errorf("chain selector %d: %w", chainSelector, err)
140151
}
152+
141153
byFamily[family] = append(byFamily[family], ChainInput{
142154
ChainSelector: chainSelector,
143-
Contracts: contracts,
155+
Contracts: normalized,
144156
OnlyAcceptOwnership: input.Cfg.OnlyAcceptOwnership,
145157
MCMS: input.MCMS,
146158
})

mcms/changesets/transfer-to-timelock/changeset_test.go

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import (
55
"testing"
66
"time"
77

8-
"github.com/ethereum/go-ethereum/common"
98
chainselectors "github.com/smartcontractkit/chain-selectors"
109
cldf_chain "github.com/smartcontractkit/chainlink-deployments-framework/chain"
1110
"github.com/smartcontractkit/chainlink-deployments-framework/datastore"
@@ -15,6 +14,8 @@ import (
1514
mcmstypes "github.com/smartcontractkit/mcms/types"
1615
"github.com/stretchr/testify/require"
1716

17+
"github.com/smartcontractkit/cld-changesets/datastore/refkey"
18+
"github.com/smartcontractkit/cld-changesets/internal/semvers"
1819
transfertotimelock "github.com/smartcontractkit/cld-changesets/mcms/changesets/transfer-to-timelock"
1920
)
2021

@@ -43,15 +44,19 @@ func testMCMSInput() *cldf.MCMSTimelockProposalInput {
4344
}
4445
}
4546

47+
func testContractRef(selector uint64) refkey.RefKey {
48+
return refkey.New(selector, "LinkToken", &semvers.V1_0_0, "")
49+
}
50+
4651
func TestChangeset_VerifyPreconditions_NoDatastore(t *testing.T) {
4752
t.Parallel()
4853

4954
env := testEnvironment(t, nil)
5055
input := transfertotimelock.Input{
5156
MCMS: testMCMSInput(),
5257
Cfg: transfertotimelock.Config{
53-
ContractsByChain: map[uint64][]common.Address{
54-
chainselectors.TEST_90000001.Selector: {common.HexToAddress("0x1")},
58+
ContractsByChain: map[uint64][]refkey.RefKey{
59+
chainselectors.TEST_90000001.Selector: {testContractRef(chainselectors.TEST_90000001.Selector)},
5560
},
5661
},
5762
}
@@ -66,8 +71,8 @@ func TestChangeset_VerifyPreconditions_NoMCMSInput(t *testing.T) {
6671
env := testEnvironment(t, datastore.NewMemoryDataStore().Seal())
6772
input := transfertotimelock.Input{
6873
Cfg: transfertotimelock.Config{
69-
ContractsByChain: map[uint64][]common.Address{
70-
chainselectors.TEST_90000001.Selector: {common.HexToAddress("0x1")},
74+
ContractsByChain: map[uint64][]refkey.RefKey{
75+
chainselectors.TEST_90000001.Selector: {testContractRef(chainselectors.TEST_90000001.Selector)},
7176
},
7277
},
7378
}
@@ -96,7 +101,7 @@ func TestChangeset_VerifyPreconditions_EmptyContractsForChain(t *testing.T) {
96101
input := transfertotimelock.Input{
97102
MCMS: testMCMSInput(),
98103
Cfg: transfertotimelock.Config{
99-
ContractsByChain: map[uint64][]common.Address{
104+
ContractsByChain: map[uint64][]refkey.RefKey{
100105
selector: {},
101106
},
102107
},
@@ -114,8 +119,8 @@ func TestChangeset_VerifyPreconditions_UnsupportedChainFamily(t *testing.T) {
114119
input := transfertotimelock.Input{
115120
MCMS: testMCMSInput(),
116121
Cfg: transfertotimelock.Config{
117-
ContractsByChain: map[uint64][]common.Address{
118-
selector: {common.HexToAddress("0x1")},
122+
ContractsByChain: map[uint64][]refkey.RefKey{
123+
selector: {testContractRef(selector)},
119124
},
120125
},
121126
}
@@ -130,8 +135,8 @@ func TestChangeset_Apply_NoMCMSInput(t *testing.T) {
130135
env := testEnvironment(t, datastore.NewMemoryDataStore().Seal())
131136
input := transfertotimelock.Input{
132137
Cfg: transfertotimelock.Config{
133-
ContractsByChain: map[uint64][]common.Address{
134-
chainselectors.TEST_90000001.Selector: {common.HexToAddress("0x1")},
138+
ContractsByChain: map[uint64][]refkey.RefKey{
139+
chainselectors.TEST_90000001.Selector: {testContractRef(chainselectors.TEST_90000001.Selector)},
135140
},
136141
},
137142
}

mcms/changesets/transfer-to-timelock/doc.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,18 @@
77
// sequence (plus MCMS readers when building timelock proposals):
88
//
99
// import (
10-
// "github.com/ethereum/go-ethereum/common"
11-
//
1210
// transfertotimelock "github.com/smartcontractkit/cld-changesets/mcms/changesets/transfer-to-timelock"
11+
// "github.com/smartcontractkit/cld-changesets/datastore/refkey"
1312
// _ "github.com/smartcontractkit/cld-changesets/mcms/evm/readers"
1413
// _ "github.com/smartcontractkit/cld-changesets/mcms/evm/transfer-to-timelock"
1514
// )
1615
//
1716
// rt.Exec(runtime.ChangesetTask(transfertotimelock.Changeset{}, transfertotimelock.Input{
1817
// Cfg: transfertotimelock.Config{
19-
// ContractsByChain: map[uint64][]common.Address{
20-
// chainSelector: {common.HexToAddress("0x...")},
18+
// ContractsByChain: map[uint64][]refkey.RefKey{
19+
// chainSelector: {
20+
// refkey.New(chainSelector, contractType, version, qualifier),
21+
// },
2122
// },
2223
// },
2324
// MCMS: mcmsInput,
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package transfertotimelock
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/smartcontractkit/cld-changesets/datastore/refkey"
7+
)
8+
9+
func normalizeContractRefs(chainSelector uint64, refs []refkey.RefKey) ([]refkey.RefKey, error) {
10+
normalized := make([]refkey.RefKey, len(refs))
11+
for i, ref := range refs {
12+
if ref.ChainSelector != 0 && ref.ChainSelector != chainSelector {
13+
return nil, fmt.Errorf(
14+
"contracts[%d]: ref chain selector %d does not match chain %d",
15+
i,
16+
ref.ChainSelector,
17+
chainSelector,
18+
)
19+
}
20+
21+
ref.ChainSelector = chainSelector
22+
normalized[i] = ref
23+
}
24+
25+
return normalized, nil
26+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package transfertotimelock
2+
3+
import (
4+
"testing"
5+
6+
chainselectors "github.com/smartcontractkit/chain-selectors"
7+
"github.com/stretchr/testify/require"
8+
9+
"github.com/smartcontractkit/cld-changesets/datastore/refkey"
10+
"github.com/smartcontractkit/cld-changesets/internal/semvers"
11+
)
12+
13+
func TestNormalizeContractRefs_fillsChainSelector(t *testing.T) {
14+
t.Parallel()
15+
16+
selector := chainselectors.TEST_90000001.Selector
17+
ref := refkey.RefKey{
18+
Type: "LinkToken",
19+
Version: &semvers.V1_0_0,
20+
Qualifier: "",
21+
}
22+
23+
got, err := normalizeContractRefs(selector, []refkey.RefKey{ref})
24+
require.NoError(t, err)
25+
require.Len(t, got, 1)
26+
require.Equal(t, selector, got[0].ChainSelector)
27+
}
28+
29+
func TestNormalizeContractRefs_rejectsMismatchedChainSelector(t *testing.T) {
30+
t.Parallel()
31+
32+
selector := chainselectors.TEST_90000001.Selector
33+
other := chainselectors.TEST_90000002.Selector
34+
35+
_, err := normalizeContractRefs(selector, []refkey.RefKey{
36+
refkey.New(other, "LinkToken", &semvers.V1_0_0, ""),
37+
})
38+
require.ErrorContains(t, err, "does not match chain")
39+
}

mcms/changesets/transfer-to-timelock/types.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
11
package transfertotimelock
22

33
import (
4-
"github.com/ethereum/go-ethereum/common"
54
"github.com/smartcontractkit/chainlink-deployments-framework/chain"
65
"github.com/smartcontractkit/chainlink-deployments-framework/changeset/sequenceutils"
76
cldfdatastore "github.com/smartcontractkit/chainlink-deployments-framework/datastore"
87
cldf "github.com/smartcontractkit/chainlink-deployments-framework/deployment"
98
"github.com/smartcontractkit/chainlink-deployments-framework/operations"
9+
10+
"github.com/smartcontractkit/cld-changesets/datastore/refkey"
1011
)
1112

1213
// Config selects ownable contracts to transfer to the MCMS timelock per chain.
1314
type Config struct {
14-
ContractsByChain map[uint64][]common.Address `json:"contractsByChain"`
15+
// ContractsByChain lists contracts as datastore refs for every supported chain family.
16+
ContractsByChain map[uint64][]refkey.RefKey `json:"contractsByChain,omitempty"`
1517
// OnlyAcceptOwnership skips the on-chain transferOwnership step and only
1618
// builds accept-ownership operations for the MCMS proposal.
1719
OnlyAcceptOwnership bool `json:"onlyAcceptOwnership,omitempty"`
@@ -23,7 +25,7 @@ type Input = sequenceutils.WithMCMS[Config]
2325
// ChainInput is the per-chain request passed to a family sequence.
2426
type ChainInput struct {
2527
ChainSelector uint64 `json:"chainSelector"`
26-
Contracts []common.Address `json:"contracts"`
28+
Contracts []refkey.RefKey `json:"contracts,omitempty"`
2729
OnlyAcceptOwnership bool `json:"onlyAcceptOwnership,omitempty"`
2830
MCMS *cldf.MCMSTimelockProposalInput `json:"mcms,omitempty"`
2931
}

mcms/evm/transfer-to-timelock/addresses.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,31 @@ import (
44
"fmt"
55

66
"github.com/ethereum/go-ethereum/common"
7+
cldf "github.com/smartcontractkit/chainlink-deployments-framework/deployment"
8+
9+
"github.com/smartcontractkit/cld-changesets/datastore/refkey"
710
)
811

12+
func resolveEVMAddress(env cldf.Environment, chainSelector uint64, ref refkey.RefKey) (common.Address, error) {
13+
if ref.ChainSelector != 0 && ref.ChainSelector != chainSelector {
14+
return common.Address{}, fmt.Errorf(
15+
"ref chain selector %d does not match chain %d",
16+
ref.ChainSelector,
17+
chainSelector,
18+
)
19+
}
20+
if ref.ChainSelector == 0 {
21+
ref.ChainSelector = chainSelector
22+
}
23+
24+
resolved, err := ref.Resolve(env)
25+
if err != nil {
26+
return common.Address{}, err
27+
}
28+
29+
return parseEVMAddress(resolved.Address, "contract")
30+
}
31+
932
func parseEVMAddress(addr string, label string) (common.Address, error) {
1033
if !common.IsHexAddress(addr) {
1134
return common.Address{}, fmt.Errorf("invalid %s address %q", label, addr)

mcms/evm/transfer-to-timelock/changeset_test.go

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
mcmstypes "github.com/smartcontractkit/mcms/types"
2121
"github.com/stretchr/testify/require"
2222

23+
"github.com/smartcontractkit/cld-changesets/datastore/refkey"
2324
"github.com/smartcontractkit/cld-changesets/internal/semvers"
2425
"github.com/smartcontractkit/cld-changesets/mcms/changesets/deploy"
2526
transfertotimelock "github.com/smartcontractkit/cld-changesets/mcms/changesets/transfer-to-timelock"
@@ -39,8 +40,8 @@ func TestChangeset_TransferOwnershipToTimelock(t *testing.T) {
3940
err := rt.Exec(
4041
runtime.ChangesetTask(transfertotimelock.Changeset{}, transfertotimelock.Input{
4142
Cfg: transfertotimelock.Config{
42-
ContractsByChain: map[uint64][]common.Address{
43-
selector: {linkToken.Address()},
43+
ContractsByChain: map[uint64][]refkey.RefKey{
44+
selector: {linkTokenRef(selector)},
4445
},
4546
},
4647
MCMS: &cldf.MCMSTimelockProposalInput{
@@ -80,8 +81,8 @@ func TestChangeset_OnlyAcceptOwnership(t *testing.T) {
8081
runtime.ChangesetTask(transfertotimelock.Changeset{}, transfertotimelock.Input{
8182
Cfg: transfertotimelock.Config{
8283
OnlyAcceptOwnership: true,
83-
ContractsByChain: map[uint64][]common.Address{
84-
selector: {linkToken.Address()},
84+
ContractsByChain: map[uint64][]refkey.RefKey{
85+
selector: {linkTokenRef(selector)},
8586
},
8687
},
8788
MCMS: &cldf.MCMSTimelockProposalInput{
@@ -154,6 +155,12 @@ func newTransferToTimelockTestEnv(
154155
return rt, chain, timelockAddr, linkToken
155156
}
156157

158+
func linkTokenRef(selector uint64) refkey.RefKey {
159+
tv := cldf.NewTypeAndVersion(linkcontracts.LinkToken, semvers.V1_0_0)
160+
161+
return refkey.New(selector, datastore.ContractType(tv.Type.String()), &semvers.V1_0_0, "")
162+
}
163+
157164
func loadLinkTokenFromDataStore(t *testing.T, chain cldfevm.Chain, ds datastore.DataStore) *link_token.LinkToken {
158165
t.Helper()
159166

mcms/evm/transfer-to-timelock/sequence.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,12 @@ func runEVMTransferToTimelock(
4747
}
4848

4949
var transactions []mcmstypes.Transaction
50-
for _, contract := range in.Contracts {
50+
for i, ref := range in.Contracts {
51+
contract, err := resolveEVMAddress(env, in.ChainSelector, ref)
52+
if err != nil {
53+
return sequenceutils.OnChainOutput{}, fmt.Errorf("contracts[%d]: %w", i, err)
54+
}
55+
5156
txs, err := transferContractToTimelock(b, chain, timelock, contract, in)
5257
if err != nil {
5358
return sequenceutils.OnChainOutput{}, fmt.Errorf("contract %s: %w", contract.Hex(), err)

0 commit comments

Comments
 (0)