Skip to content

Commit 0408440

Browse files
feat(mcms): add Solana deploy changeset for MCMS with timelock [CLD-2719]
Register Solana in the MCMS deploy registry with an operations-based sequence that deploys programs, initializes accounts/instances, and grants timelock roles, matching the EVM deploy pattern.
1 parent 2da6931 commit 0408440

12 files changed

Lines changed: 1789 additions & 94 deletions

File tree

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// Package datastoretest provides lightweight fakes for datastore interfaces in unit tests.
2+
package datastoretest
3+
4+
import (
5+
cldfdatastore "github.com/smartcontractkit/chainlink-deployments-framework/datastore"
6+
)
7+
8+
// NewDataStore returns a read-only DataStore backed by the given address refs.
9+
func NewDataStore(refs []cldfdatastore.AddressRef) cldfdatastore.DataStore {
10+
return fakeDataStore{store: fakeAddressRefStore{refs: refs}}
11+
}
12+
13+
type fakeAddressRefStore struct {
14+
refs []cldfdatastore.AddressRef
15+
}
16+
17+
var _ cldfdatastore.AddressRefStore = fakeAddressRefStore{}
18+
19+
func (f fakeAddressRefStore) Fetch() ([]cldfdatastore.AddressRef, error) {
20+
return f.refs, nil
21+
}
22+
23+
func (f fakeAddressRefStore) Get(key cldfdatastore.AddressRefKey) (cldfdatastore.AddressRef, error) {
24+
for _, ref := range f.refs {
25+
if ref.Key().Equals(key) {
26+
return ref, nil
27+
}
28+
}
29+
30+
return cldfdatastore.AddressRef{}, cldfdatastore.ErrAddressRefNotFound
31+
}
32+
33+
func (f fakeAddressRefStore) Filter(filters ...cldfdatastore.FilterFunc[cldfdatastore.AddressRefKey, cldfdatastore.AddressRef]) []cldfdatastore.AddressRef {
34+
refs := f.refs
35+
for _, filter := range filters {
36+
refs = filter(refs)
37+
}
38+
39+
return refs
40+
}
41+
42+
type fakeDataStore struct {
43+
store fakeAddressRefStore
44+
}
45+
46+
var _ cldfdatastore.DataStore = fakeDataStore{}
47+
48+
func (f fakeDataStore) Addresses() cldfdatastore.AddressRefStore { return f.store }
49+
50+
func (f fakeDataStore) ChainMetadata() cldfdatastore.ChainMetadataStore { return nil }
51+
52+
func (f fakeDataStore) ContractMetadata() cldfdatastore.ContractMetadataStore { return nil }
53+
54+
func (f fakeDataStore) EnvMetadata() cldfdatastore.EnvMetadataStore { return nil }

mcms/evm/deploy/addresses.go

Lines changed: 27 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
package evmdeploy
44

55
import (
6+
"fmt"
7+
68
"github.com/ethereum/go-ethereum/common"
79
cldfdatastore "github.com/smartcontractkit/chainlink-deployments-framework/datastore"
810
cldf "github.com/smartcontractkit/chainlink-deployments-framework/deployment"
@@ -21,9 +23,9 @@ type deployedAddresses struct {
2123
CallProxy common.Address
2224
}
2325

24-
func loadDeployedAddresses(ds cldfdatastore.DataStore, chainSelector uint64, qualifier string) deployedAddresses {
26+
func loadDeployedAddresses(ds cldfdatastore.DataStore, chainSelector uint64, qualifier string) (deployedAddresses, error) {
2527
if ds == nil {
26-
return deployedAddresses{}
28+
return deployedAddresses{}, nil
2729
}
2830

2931
type lookup struct {
@@ -41,45 +43,46 @@ func loadDeployedAddresses(ds cldfdatastore.DataStore, chainSelector uint64, qua
4143
}
4244

4345
for _, l := range lookups {
44-
if addr, ok := findDeployedAddress(ds.Addresses(), chainSelector, l.contractType, qualifier); ok {
46+
addr, ok, err := findDeployedAddress(ds.Addresses(), chainSelector, l.contractType, qualifier)
47+
if err != nil {
48+
return deployedAddresses{}, err
49+
}
50+
if ok {
4551
*l.dest = addr
4652
}
4753
}
4854

49-
return addrs
55+
return addrs, nil
5056
}
5157

5258
// findDeployedAddress returns a previously deployed contract address for this
53-
// deploy package's version. Legacy datastore entries without a version are
54-
// accepted as a fallback; refs with a different version are ignored so a
55-
// version bump can redeploy.
59+
// deploy package's version. Qualifier is always matched exactly, including "".
60+
// Returns ok=false when no ref matches; an error when multiple refs match.
5661
func findDeployedAddress(
5762
store cldfdatastore.AddressRefStore,
5863
chainSelector uint64,
5964
contractType cldf.ContractType,
6065
qualifier string,
61-
) (common.Address, bool) {
62-
baseFilters := make([]cldfdatastore.FilterFunc[cldfdatastore.AddressRefKey, cldfdatastore.AddressRef], 0, 3)
63-
baseFilters = append(baseFilters,
66+
) (common.Address, bool, error) {
67+
version := semvers.V1_0_0
68+
refs := store.Filter(
6469
cldfdatastore.AddressRefByChainSelector(chainSelector),
6570
cldfdatastore.AddressRefByType(cldfdatastore.ContractType(contractType)),
6671
cldfdatastore.AddressRefByQualifier(qualifier),
72+
cldfdatastore.AddressRefByVersion(&version),
6773
)
68-
69-
version := semvers.V1_0_0
70-
refs := store.Filter(append(baseFilters, cldfdatastore.AddressRefByVersion(&version))...)
71-
if len(refs) > 0 {
72-
return common.HexToAddress(refs[0].Address), true
74+
switch len(refs) {
75+
case 0:
76+
return common.Address{}, false, nil
77+
case 1:
78+
return common.HexToAddress(refs[0].Address), true, nil
79+
default:
80+
return common.Address{}, false, fmt.Errorf(
81+
"%w: chain selector %d contract type %s qualifier %q version %s: found %d refs",
82+
cldfdatastore.ErrAddressRefQueryAmbiguous,
83+
chainSelector, contractType, qualifier, version, len(refs),
84+
)
7385
}
74-
75-
refs = store.Filter(baseFilters...)
76-
for _, ref := range refs {
77-
if ref.Version == nil {
78-
return common.HexToAddress(ref.Address), true
79-
}
80-
}
81-
82-
return common.Address{}, false
8386
}
8487

8588
// addressRefWithLabel attaches an optional label to a deployed contract address ref.

mcms/evm/deploy/addresses_test.go

Lines changed: 32 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"github.com/stretchr/testify/require"
1212

1313
"github.com/smartcontractkit/cld-changesets/internal/semvers"
14+
"github.com/smartcontractkit/cld-changesets/internal/testutil/datastoretest"
1415
)
1516

1617
func TestLoadDeployedAddresses(t *testing.T) {
@@ -21,13 +22,14 @@ func TestLoadDeployedAddresses(t *testing.T) {
2122
v090 := semver.MustParse("0.9.0")
2223

2324
bypasserV100 := common.HexToAddress("0x00000000000000000000000000000000000000b1")
24-
bypasserLegacy := common.HexToAddress("0x00000000000000000000000000000000000000b2")
2525
bypasserOld := common.HexToAddress("0x00000000000000000000000000000000000000b3")
2626
proposerV100 := common.HexToAddress("0x00000000000000000000000000000000000000c1")
2727

2828
t.Run("nil datastore", func(t *testing.T) {
2929
t.Parallel()
30-
require.Equal(t, deployedAddresses{}, loadDeployedAddresses(nil, selector, ""))
30+
addrs, err := loadDeployedAddresses(nil, selector, "")
31+
require.NoError(t, err)
32+
require.Equal(t, deployedAddresses{}, addrs)
3133
})
3234

3335
t.Run("matches v1.0.0", func(t *testing.T) {
@@ -41,7 +43,8 @@ func TestLoadDeployedAddresses(t *testing.T) {
4143
Version: &v100,
4244
}))
4345

44-
addrs := loadDeployedAddresses(ds.Seal(), selector, "")
46+
addrs, err := loadDeployedAddresses(ds.Seal(), selector, "")
47+
require.NoError(t, err)
4548
require.Equal(t, bypasserV100, addrs.Bypasser)
4649
})
4750

@@ -56,63 +59,50 @@ func TestLoadDeployedAddresses(t *testing.T) {
5659
Version: v090,
5760
}))
5861

59-
addrs := loadDeployedAddresses(ds.Seal(), selector, "")
62+
addrs, err := loadDeployedAddresses(ds.Seal(), selector, "")
63+
require.NoError(t, err)
6064
require.Equal(t, common.Address{}, addrs.Bypasser)
6165
})
6266

63-
t.Run("falls back to legacy nil version", func(t *testing.T) {
67+
t.Run("respects qualifier", func(t *testing.T) {
6468
t.Parallel()
6569

66-
store := fakeAddressRefStore{refs: []cldfdatastore.AddressRef{{
70+
ds := cldfdatastore.NewMemoryDataStore()
71+
require.NoError(t, ds.Addresses().Add(cldfdatastore.AddressRef{
6772
ChainSelector: selector,
68-
Address: bypasserLegacy.Hex(),
69-
Type: cldfdatastore.ContractType(mcmscontracts.BypasserManyChainMultisig),
70-
}}}
73+
Address: proposerV100.Hex(),
74+
Type: cldfdatastore.ContractType(mcmscontracts.ProposerManyChainMultisig),
75+
Version: &v100,
76+
Qualifier: "prod",
77+
}))
78+
79+
addrs, err := loadDeployedAddresses(ds.Seal(), selector, "")
80+
require.NoError(t, err)
81+
require.Equal(t, common.Address{}, addrs.Proposer)
7182

72-
got, ok := findDeployedAddress(store, selector, mcmscontracts.BypasserManyChainMultisig, "")
73-
require.True(t, ok)
74-
require.Equal(t, bypasserLegacy, got)
83+
addrs, err = loadDeployedAddresses(ds.Seal(), selector, "prod")
84+
require.NoError(t, err)
85+
require.Equal(t, proposerV100, addrs.Proposer)
7586
})
7687

77-
t.Run("prefers v1.0.0 over legacy nil version", func(t *testing.T) {
88+
t.Run("duplicate refs", func(t *testing.T) {
7889
t.Parallel()
7990

80-
store := fakeAddressRefStore{refs: []cldfdatastore.AddressRef{
91+
_, err := loadDeployedAddresses(datastoretest.NewDataStore([]cldfdatastore.AddressRef{
8192
{
8293
ChainSelector: selector,
83-
Address: bypasserLegacy.Hex(),
94+
Address: bypasserV100.Hex(),
8495
Type: cldfdatastore.ContractType(mcmscontracts.BypasserManyChainMultisig),
96+
Version: &v100,
8597
},
8698
{
8799
ChainSelector: selector,
88-
Address: bypasserV100.Hex(),
100+
Address: bypasserOld.Hex(),
89101
Type: cldfdatastore.ContractType(mcmscontracts.BypasserManyChainMultisig),
90102
Version: &v100,
91103
},
92-
}}
93-
94-
got, ok := findDeployedAddress(store, selector, mcmscontracts.BypasserManyChainMultisig, "")
95-
require.True(t, ok)
96-
require.Equal(t, bypasserV100, got)
97-
})
98-
99-
t.Run("respects qualifier", func(t *testing.T) {
100-
t.Parallel()
101-
102-
ds := cldfdatastore.NewMemoryDataStore()
103-
require.NoError(t, ds.Addresses().Add(cldfdatastore.AddressRef{
104-
ChainSelector: selector,
105-
Address: proposerV100.Hex(),
106-
Type: cldfdatastore.ContractType(mcmscontracts.ProposerManyChainMultisig),
107-
Version: &v100,
108-
Qualifier: "prod",
109-
}))
110-
111-
addrs := loadDeployedAddresses(ds.Seal(), selector, "")
112-
require.Equal(t, common.Address{}, addrs.Proposer)
113-
114-
addrs = loadDeployedAddresses(ds.Seal(), selector, "prod")
115-
require.Equal(t, proposerV100, addrs.Proposer)
104+
}), selector, "")
105+
require.ErrorIs(t, err, cldfdatastore.ErrAddressRefQueryAmbiguous)
116106
})
117107
}
118108

@@ -131,34 +121,8 @@ func TestFindDeployedAddress(t *testing.T) {
131121
Version: &v100,
132122
}))
133123

134-
got, ok := findDeployedAddress(ds.Addresses(), selector, mcmscontracts.RBACTimelock, "")
124+
got, ok, err := findDeployedAddress(ds.Addresses(), selector, mcmscontracts.RBACTimelock, "")
125+
require.NoError(t, err)
135126
require.True(t, ok)
136127
require.Equal(t, addr, got)
137128
}
138-
139-
type fakeAddressRefStore struct {
140-
refs []cldfdatastore.AddressRef
141-
}
142-
143-
func (f fakeAddressRefStore) Fetch() ([]cldfdatastore.AddressRef, error) {
144-
return f.refs, nil
145-
}
146-
147-
func (f fakeAddressRefStore) Get(key cldfdatastore.AddressRefKey) (cldfdatastore.AddressRef, error) {
148-
for _, ref := range f.refs {
149-
if ref.Key().Equals(key) {
150-
return ref, nil
151-
}
152-
}
153-
154-
return cldfdatastore.AddressRef{}, cldfdatastore.ErrAddressRefNotFound
155-
}
156-
157-
func (f fakeAddressRefStore) Filter(filters ...cldfdatastore.FilterFunc[cldfdatastore.AddressRefKey, cldfdatastore.AddressRef]) []cldfdatastore.AddressRef {
158-
refs := f.refs
159-
for _, filter := range filters {
160-
refs = filter(refs)
161-
}
162-
163-
return refs
164-
}

mcms/evm/deploy/sequence.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,11 +54,13 @@ func deployMCMSWithTimelock(
5454

5555
qualifier := qualifierFromConfig(in.Config.Qualifier)
5656

57-
existing := loadDeployedAddresses(deps.DataStore, in.ChainSelector, qualifier)
57+
existing, err := loadDeployedAddresses(deps.DataStore, in.ChainSelector, qualifier)
58+
if err != nil {
59+
return sequenceutils.OnChainOutput{}, fmt.Errorf("load deployed addresses: %w", err)
60+
}
5861

5962
d := &deployer{b: b, chain: chain, config: in.Config, qualifier: qualifier}
6063

61-
var err error
6264
if existing.Bypasser, err = d.deployMCMIfNeeded(mcmscontracts.BypasserManyChainMultisig, in.Config.Bypasser, existing.Bypasser); err != nil {
6365
return d.out, err
6466
}

0 commit comments

Comments
 (0)