Skip to content

Commit 4251a75

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 4251a75

10 files changed

Lines changed: 1321 additions & 82 deletions

File tree

mcms/evm/deploy/addresses.go

Lines changed: 7 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -50,36 +50,25 @@ func loadDeployedAddresses(ds cldfdatastore.DataStore, chainSelector uint64, qua
5050
}
5151

5252
// 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.
53+
// deploy package's version. Qualifier is always matched exactly, including "".
5654
func findDeployedAddress(
5755
store cldfdatastore.AddressRefStore,
5856
chainSelector uint64,
5957
contractType cldf.ContractType,
6058
qualifier string,
6159
) (common.Address, bool) {
62-
baseFilters := make([]cldfdatastore.FilterFunc[cldfdatastore.AddressRefKey, cldfdatastore.AddressRef], 0, 3)
63-
baseFilters = append(baseFilters,
60+
version := semvers.V1_0_0
61+
refs := store.Filter(
6462
cldfdatastore.AddressRefByChainSelector(chainSelector),
6563
cldfdatastore.AddressRefByType(cldfdatastore.ContractType(contractType)),
6664
cldfdatastore.AddressRefByQualifier(qualifier),
65+
cldfdatastore.AddressRefByVersion(&version),
6766
)
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
73-
}
74-
75-
refs = store.Filter(baseFilters...)
76-
for _, ref := range refs {
77-
if ref.Version == nil {
78-
return common.HexToAddress(ref.Address), true
79-
}
67+
if len(refs) != 1 {
68+
return common.Address{}, false
8069
}
8170

82-
return common.Address{}, false
71+
return common.HexToAddress(refs[0].Address), true
8372
}
8473

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

mcms/evm/deploy/addresses_test.go

Lines changed: 0 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ func TestLoadDeployedAddresses(t *testing.T) {
2121
v090 := semver.MustParse("0.9.0")
2222

2323
bypasserV100 := common.HexToAddress("0x00000000000000000000000000000000000000b1")
24-
bypasserLegacy := common.HexToAddress("0x00000000000000000000000000000000000000b2")
2524
bypasserOld := common.HexToAddress("0x00000000000000000000000000000000000000b3")
2625
proposerV100 := common.HexToAddress("0x00000000000000000000000000000000000000c1")
2726

@@ -60,42 +59,6 @@ func TestLoadDeployedAddresses(t *testing.T) {
6059
require.Equal(t, common.Address{}, addrs.Bypasser)
6160
})
6261

63-
t.Run("falls back to legacy nil version", func(t *testing.T) {
64-
t.Parallel()
65-
66-
store := fakeAddressRefStore{refs: []cldfdatastore.AddressRef{{
67-
ChainSelector: selector,
68-
Address: bypasserLegacy.Hex(),
69-
Type: cldfdatastore.ContractType(mcmscontracts.BypasserManyChainMultisig),
70-
}}}
71-
72-
got, ok := findDeployedAddress(store, selector, mcmscontracts.BypasserManyChainMultisig, "")
73-
require.True(t, ok)
74-
require.Equal(t, bypasserLegacy, got)
75-
})
76-
77-
t.Run("prefers v1.0.0 over legacy nil version", func(t *testing.T) {
78-
t.Parallel()
79-
80-
store := fakeAddressRefStore{refs: []cldfdatastore.AddressRef{
81-
{
82-
ChainSelector: selector,
83-
Address: bypasserLegacy.Hex(),
84-
Type: cldfdatastore.ContractType(mcmscontracts.BypasserManyChainMultisig),
85-
},
86-
{
87-
ChainSelector: selector,
88-
Address: bypasserV100.Hex(),
89-
Type: cldfdatastore.ContractType(mcmscontracts.BypasserManyChainMultisig),
90-
Version: &v100,
91-
},
92-
}}
93-
94-
got, ok := findDeployedAddress(store, selector, mcmscontracts.BypasserManyChainMultisig, "")
95-
require.True(t, ok)
96-
require.Equal(t, bypasserV100, got)
97-
})
98-
9962
t.Run("respects qualifier", func(t *testing.T) {
10063
t.Parallel()
10164

@@ -135,30 +98,3 @@ func TestFindDeployedAddress(t *testing.T) {
13598
require.True(t, ok)
13699
require.Equal(t, addr, got)
137100
}
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/solana/deploy/addresses.go

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
package soldeploy
2+
3+
import (
4+
"fmt"
5+
6+
solanago "github.com/gagliardetto/solana-go"
7+
cldfdatastore "github.com/smartcontractkit/chainlink-deployments-framework/datastore"
8+
cldf "github.com/smartcontractkit/chainlink-deployments-framework/deployment"
9+
mcmscontracts "github.com/smartcontractkit/chainlink-deployments-framework/engine/cld/contracts/mcms"
10+
11+
"github.com/smartcontractkit/cld-changesets/internal/semvers"
12+
legacysolana "github.com/smartcontractkit/cld-changesets/legacy/pkg/family/solana"
13+
)
14+
15+
// deployedAddresses holds the on-chain state of an MCMS+timelock deployment on one
16+
// Solana chain. Zero values mean the corresponding program or account has not yet
17+
// been deployed/initialized.
18+
type deployedAddresses struct {
19+
AccessControllerProgram solanago.PublicKey
20+
ProposerAccessControllerAccount solanago.PublicKey
21+
ExecutorAccessControllerAccount solanago.PublicKey
22+
CancellerAccessControllerAccount solanago.PublicKey
23+
BypasserAccessControllerAccount solanago.PublicKey
24+
McmProgram solanago.PublicKey
25+
ProposerMCMSeed legacysolana.PDASeed
26+
CancellerMCMSeed legacysolana.PDASeed
27+
BypasserMCMSeed legacysolana.PDASeed
28+
TimelockProgram solanago.PublicKey
29+
TimelockSeed legacysolana.PDASeed
30+
}
31+
32+
func (d deployedAddresses) hasProposerMCM() bool {
33+
return d.ProposerMCMSeed != (legacysolana.PDASeed{})
34+
}
35+
func (d deployedAddresses) hasCancellerMCM() bool {
36+
return d.CancellerMCMSeed != (legacysolana.PDASeed{})
37+
}
38+
func (d deployedAddresses) hasBypasserMCM() bool {
39+
return d.BypasserMCMSeed != (legacysolana.PDASeed{})
40+
}
41+
func (d deployedAddresses) hasTimelock() bool { return d.TimelockSeed != (legacysolana.PDASeed{}) }
42+
43+
// loadDeployedAddresses returns the current deployment state for the given chain
44+
// and qualifier by reading address refs from the datastore. A zero value in any
45+
// field means the corresponding program or account has not been deployed yet.
46+
func loadDeployedAddresses(ds cldfdatastore.DataStore, chainSelector uint64, qualifier string) (deployedAddresses, error) {
47+
if ds == nil {
48+
return deployedAddresses{}, nil
49+
}
50+
51+
var addrs deployedAddresses
52+
53+
// findRef returns the address string for a given contract type at this
54+
// deploy package's version. Qualifier is always matched exactly, including "".
55+
findRef := func(ct cldf.ContractType) (string, bool) {
56+
version := semvers.V1_0_0
57+
refs := ds.Addresses().Filter(
58+
cldfdatastore.AddressRefByChainSelector(chainSelector),
59+
cldfdatastore.AddressRefByType(cldfdatastore.ContractType(ct)),
60+
cldfdatastore.AddressRefByQualifier(qualifier),
61+
cldfdatastore.AddressRefByVersion(&version),
62+
)
63+
if len(refs) != 1 {
64+
return "", false
65+
}
66+
67+
return refs[0].Address, true
68+
}
69+
70+
loadPubkey := func(ct cldf.ContractType, dest *solanago.PublicKey) error {
71+
addr, ok := findRef(ct)
72+
if !ok {
73+
return nil
74+
}
75+
pk, err := solanago.PublicKeyFromBase58(addr)
76+
if err != nil {
77+
return fmt.Errorf("parse %s address %q: %w", ct, addr, err)
78+
}
79+
*dest = pk
80+
81+
return nil
82+
}
83+
84+
// Plain base58 addresses (program IDs and AC accounts)
85+
for _, entry := range []struct {
86+
ct cldf.ContractType
87+
dest *solanago.PublicKey
88+
}{
89+
{mcmscontracts.AccessControllerProgram, &addrs.AccessControllerProgram},
90+
{mcmscontracts.ProposerAccessControllerAccount, &addrs.ProposerAccessControllerAccount},
91+
{mcmscontracts.ExecutorAccessControllerAccount, &addrs.ExecutorAccessControllerAccount},
92+
{mcmscontracts.CancellerAccessControllerAccount, &addrs.CancellerAccessControllerAccount},
93+
{mcmscontracts.BypasserAccessControllerAccount, &addrs.BypasserAccessControllerAccount},
94+
{mcmscontracts.ManyChainMultisigProgram, &addrs.McmProgram},
95+
{mcmscontracts.RBACTimelockProgram, &addrs.TimelockProgram},
96+
} {
97+
if err := loadPubkey(entry.ct, entry.dest); err != nil {
98+
return deployedAddresses{}, err
99+
}
100+
}
101+
102+
// Seed-encoded MCM instance addresses (programID:seed)
103+
for ct, dst := range map[cldf.ContractType]*legacysolana.PDASeed{
104+
mcmscontracts.ProposerManyChainMultisig: &addrs.ProposerMCMSeed,
105+
mcmscontracts.CancellerManyChainMultisig: &addrs.CancellerMCMSeed,
106+
mcmscontracts.BypasserManyChainMultisig: &addrs.BypasserMCMSeed,
107+
} {
108+
addr, ok := findRef(ct)
109+
if !ok {
110+
continue
111+
}
112+
programID, seed, err := legacysolana.DecodeAddressWithSeed(addr)
113+
if err != nil {
114+
return deployedAddresses{}, fmt.Errorf("decode %s address %q: %w", ct, addr, err)
115+
}
116+
// The program ID embedded in instance refs is authoritative when the
117+
// program-level ref is absent.
118+
if addrs.McmProgram.IsZero() {
119+
addrs.McmProgram = programID
120+
}
121+
*dst = seed
122+
}
123+
124+
// Seed-encoded timelock instance (programID:seed)
125+
if addr, ok := findRef(mcmscontracts.RBACTimelock); ok {
126+
programID, seed, err := legacysolana.DecodeAddressWithSeed(addr)
127+
if err != nil {
128+
return deployedAddresses{}, fmt.Errorf("decode %s address %q: %w", mcmscontracts.RBACTimelock, addr, err)
129+
}
130+
if addrs.TimelockProgram.IsZero() {
131+
addrs.TimelockProgram = programID
132+
}
133+
addrs.TimelockSeed = seed
134+
}
135+
136+
return addrs, nil
137+
}

0 commit comments

Comments
 (0)