@@ -11,6 +11,8 @@ import (
1111 cldf "github.com/smartcontractkit/chainlink-deployments-framework/deployment"
1212 "github.com/smartcontractkit/chainlink-deployments-framework/operations"
1313 mcmstypes "github.com/smartcontractkit/mcms/types"
14+
15+ "github.com/smartcontractkit/cld-changesets/internal/maputil"
1416)
1517
1618var _ cldf.ChangeSetV2 [Input ] = Changeset {}
@@ -40,7 +42,7 @@ func (Changeset) Apply(e cldf.Environment, input Input) (cldf.ChangesetOutput, e
4042 var agg ChainOutput
4143 var reports []operations.Report [any , any ]
4244
43- for _ , chainSelector := range sortedChainSelectors (input .Cfg .ChainConfigs ) {
45+ for _ , chainSelector := range maputil . SortedMapKeys (input .Cfg .ChainConfigs ) {
4446 seq , seqErr := SequenceForChainSelector (chainSelector )
4547 if seqErr != nil {
4648 return buildOutput (e , input .MCMS , agg , reports , fmt .Errorf ("chain selector %d: %w" , chainSelector , seqErr ))
@@ -140,7 +142,7 @@ func validateConfig(e cldf.Environment, cfg Config, mcms *cldf.MCMSTimelockPropo
140142 }
141143
142144 byFamily := make (map [string ][]ChainInput )
143- for _ , chainSelector := range sortedChainSelectors (cfg .ChainConfigs ) {
145+ for _ , chainSelector := range maputil . SortedMapKeys (cfg .ChainConfigs ) {
144146 chainCfg := cfg .ChainConfigs [chainSelector ]
145147 family , err := chain_selectors .GetSelectorFamily (chainSelector )
146148 if err != nil {
@@ -173,29 +175,39 @@ func validateConfig(e cldf.Environment, cfg Config, mcms *cldf.MCMSTimelockPropo
173175 return nil
174176}
175177
176- // validateChainConfig checks a single chain's topology: unique MCM refs, every
177- // referenced MCMRef is declared, every RoleHolder is well-formed, and ownership
178- // transfers require an MCMS input.
178+ // validateChainConfig checks a single chain's topology: unique refs within MCMs
179+ // and timelocks (with no overlap between the two), every referenced MCMRef is
180+ // declared, every RoleHolder is well-formed, and ownership transfers require an
181+ // MCMS input.
179182func validateChainConfig (chainSelector uint64 , cfg ChainTopologyConfig , mcms * cldf.MCMSTimelockProposalInput ) error {
180183 if len (cfg .MCMs ) == 0 && len (cfg .Timelocks ) == 0 {
181184 return fmt .Errorf ("chain %d: no MCMs or timelocks to deploy" , chainSelector )
182185 }
183186
184- declared := make (map [string ]struct {}, len (cfg .MCMs ))
187+ mcmRefs := make (map [string ]struct {}, len (cfg .MCMs ))
185188 for i , m := range cfg .MCMs {
186189 if m .Ref == "" {
187190 return fmt .Errorf ("chain %d: mcms[%d]: ref is required" , chainSelector , i )
188191 }
189- if _ , dup := declared [m .Ref ]; dup {
192+ if _ , dup := mcmRefs [m .Ref ]; dup {
190193 return fmt .Errorf ("chain %d: duplicate MCM ref %q" , chainSelector , m .Ref )
191194 }
192- declared [m .Ref ] = struct {}{}
195+ mcmRefs [m .Ref ] = struct {}{}
193196 }
194197
198+ timelockRefs := make (map [string ]struct {}, len (cfg .Timelocks ))
195199 for i , t := range cfg .Timelocks {
196200 if t .Ref == "" {
197201 return fmt .Errorf ("chain %d: timelocks[%d]: ref is required" , chainSelector , i )
198202 }
203+ if _ , dup := timelockRefs [t .Ref ]; dup {
204+ return fmt .Errorf ("chain %d: duplicate timelock ref %q" , chainSelector , t .Ref )
205+ }
206+ if _ , collides := mcmRefs [t .Ref ]; collides {
207+ return fmt .Errorf ("chain %d: timelock ref %q conflicts with an MCM ref" , chainSelector , t .Ref )
208+ }
209+ timelockRefs [t .Ref ] = struct {}{}
210+
199211 if t .Qualifier == "" {
200212 return fmt .Errorf ("chain %d: timelock %q: qualifier is required" , chainSelector , t .Ref )
201213 }
@@ -207,11 +219,11 @@ func validateChainConfig(chainSelector uint64, cfg ChainTopologyConfig, mcms *cl
207219 "admins" : t .Roles .Admins ,
208220 }
209221 for name , holders := range roleSets {
210- if err := validateRoleHolders (chainSelector , t .Ref , name , holders , declared ); err != nil {
222+ if err := validateRoleHolders (chainSelector , t .Ref , name , holders , mcmRefs ); err != nil {
211223 return err
212224 }
213225 }
214- if err := validateRoleHolders (chainSelector , t .Ref , "transferOwnership" , t .TransferOwnership , declared ); err != nil {
226+ if err := validateRoleHolders (chainSelector , t .Ref , "transferOwnership" , t .TransferOwnership , mcmRefs ); err != nil {
215227 return err
216228 }
217229 if len (t .TransferOwnership ) > 0 && mcms == nil {
@@ -239,16 +251,6 @@ func validateRoleHolders(chainSelector uint64, timelockRef, field string, holder
239251 return nil
240252}
241253
242- func sortedChainSelectors (byChain map [uint64 ]ChainTopologyConfig ) []uint64 {
243- selectors := make ([]uint64 , 0 , len (byChain ))
244- for chainSelector := range byChain {
245- selectors = append (selectors , chainSelector )
246- }
247- slices .Sort (selectors )
248-
249- return selectors
250- }
251-
252254// mergeChainOutputs concatenates metadata and proposal groups from one chain's
253255// sequence output into the running aggregate.
254256func mergeChainOutputs (agg , out ChainOutput ) ChainOutput {
0 commit comments