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