Skip to content

Commit b928c8c

Browse files
authored
deployment: use timelock qualifier in SetConfigMCMSV2 changeset (#21716)
* deployment: use timelock qualifier in SetConfigMCMSV2 changeset The SetConfigMCMSV2 changeset was loading MCMS with timelock state using MaybeLoadMCMSWithTimelockState, which does not support timelock qualifiers. This meant that when a ProposalConfig specified a TimelockQualifierPerChain, the qualifier was silently ignored during both validation and execution, causing the changeset to load the wrong timelock state. Update both Validate() and SetConfigMCMSV2() to extract the qualifier from ProposalConfig.TimelockQualifierPerChain and pass it to the new MaybeLoadMCMSWithTimelockStateWithQualifier function. When ProposalConfig is nil, the qualifier defaults to an empty string, preserving backward compatibility. * deployment: add unit tests for timelock qualifier in SetConfigMCMSV2 Add TestSetConfigMCMSV2WithTimelockQualifier to verify that the TimelockQualifierPerChain field in ProposalConfig is correctly extracted and passed to MaybeLoadMCMSWithTimelockStateWithQualifier during validation. The test deploys two MCMS instances on the same chain with different qualifiers (CLLCCIP and RMNMCMS), mirroring the production setup, and validates four scenarios: - CLLCCIP qualifier resolves to the correct MCMS deployment - RMNMCMS qualifier resolves to the correct MCMS deployment - Empty qualifier fails with "found more than one instance" when multiple MCMS deployments exist, documenting the problem this changeset solves - Non-existent qualifier fails with "no addresses found"
1 parent ef4e240 commit b928c8c

2 files changed

Lines changed: 103 additions & 2 deletions

File tree

deployment/common/changeset/set_config_mcms.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,11 @@ func (cfg MCMSConfigV2) Validate(e cldf.Environment, selectors []uint64) error {
6666

6767
switch family {
6868
case chain_selectors.FamilyEVM:
69-
state, err := commonState.MaybeLoadMCMSWithTimelockState(e, []uint64{chainSelector})
69+
qualifier := ""
70+
if cfg.ProposalConfig != nil {
71+
qualifier = cfg.ProposalConfig.TimelockQualifierPerChain[chainSelector]
72+
}
73+
state, err := commonState.MaybeLoadMCMSWithTimelockStateWithQualifier(e, []uint64{chainSelector}, qualifier)
7074
if err != nil {
7175
return err
7276
}
@@ -213,7 +217,11 @@ func SetConfigMCMSV2(e cldf.Environment, cfg MCMSConfigV2) (cldf.ChangesetOutput
213217
switch family {
214218
case chain_selectors.FamilyEVM:
215219
chain := e.BlockChains.EVMChains()[chainSelector]
216-
mcmsStatePerChain, err := commonState.MaybeLoadMCMSWithTimelockState(e, []uint64{chainSelector})
220+
qualifier := ""
221+
if cfg.ProposalConfig != nil {
222+
qualifier = cfg.ProposalConfig.TimelockQualifierPerChain[chainSelector]
223+
}
224+
mcmsStatePerChain, err := commonState.MaybeLoadMCMSWithTimelockStateWithQualifier(e, []uint64{chainSelector}, qualifier)
217225
if err != nil {
218226
return cldf.ChangesetOutput{}, err
219227
}

deployment/common/changeset/set_config_mcms_test.go

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -514,6 +514,99 @@ func createSolSigner(t *testing.T) (*ecdsa.PrivateKey, common.Address) {
514514
return key, crypto.PubkeyToAddress(*publicKey)
515515
}
516516

517+
func TestSetConfigMCMSV2WithTimelockQualifier(t *testing.T) {
518+
t.Parallel()
519+
520+
selector := chain_selectors.TEST_90000001.Selector
521+
cllccipQualifier := "CLLCCIP"
522+
rmnmcmsQualifier := "RMNMCMS"
523+
524+
rt, err := runtime.New(t.Context(), runtime.WithEnvOpts(
525+
environment.WithEVMSimulated(t, []uint64{selector}),
526+
environment.WithLogger(logger.Test(t)),
527+
))
528+
require.NoError(t, err)
529+
530+
// Deploy two MCMS instances on the same chain with different qualifiers,
531+
// mirroring the production setup where each chain has CLLCCIP and RMNMCMS deployments
532+
cllccipConfig := proposalutils.SingleGroupTimelockConfigV2(t)
533+
cllccipConfig.Qualifier = &cllccipQualifier
534+
535+
rmnmcmsConfig := proposalutils.SingleGroupTimelockConfigV2(t)
536+
rmnmcmsConfig.Qualifier = &rmnmcmsQualifier
537+
538+
err = rt.Exec(
539+
runtime.ChangesetTask(cldf.CreateLegacyChangeSet(commonchangeset.DeployMCMSWithTimelockV2), map[uint64]commontypes.MCMSWithTimelockConfigV2{
540+
selector: cllccipConfig,
541+
}),
542+
)
543+
require.NoError(t, err)
544+
545+
err = rt.Exec(
546+
runtime.ChangesetTask(cldf.CreateLegacyChangeSet(commonchangeset.DeployMCMSWithTimelockV2), map[uint64]commontypes.MCMSWithTimelockConfigV2{
547+
selector: rmnmcmsConfig,
548+
}),
549+
)
550+
require.NoError(t, err)
551+
552+
// Load state via the CLLCCIP qualifier to build a valid proposer config
553+
cllccipState, err := state.MaybeLoadMCMSWithTimelockStateWithQualifier(rt.Environment(), []uint64{selector}, cllccipQualifier)
554+
require.NoError(t, err)
555+
require.NotNil(t, cllccipState[selector])
556+
557+
cfgProposer := proposalutils.SingleGroupMCMSV2(t)
558+
cfgProposer.Signers = append(cfgProposer.Signers, cllccipState[selector].Timelock.Address())
559+
cfgProposer.Quorum = 2
560+
561+
for _, tt := range []struct {
562+
name string
563+
qualifier string
564+
wantErr string
565+
}{
566+
{
567+
name: "CLLCCIP qualifier matches qualified deployment",
568+
qualifier: "CLLCCIP",
569+
},
570+
{
571+
name: "RMNMCMS qualifier matches qualified deployment",
572+
qualifier: "RMNMCMS",
573+
},
574+
{
575+
name: "no qualifier fails with duplicate MCMS instances",
576+
qualifier: "",
577+
wantErr: "found more than one instance",
578+
},
579+
{
580+
name: "non-existent qualifier fails",
581+
qualifier: "does-not-exist",
582+
wantErr: "no addresses found",
583+
},
584+
} {
585+
t.Run(tt.name, func(t *testing.T) {
586+
mcmsCfg := commonchangeset.MCMSConfigV2{
587+
ProposalConfig: &proposalutils.TimelockConfig{
588+
MinDelay: 0,
589+
TimelockQualifierPerChain: map[uint64]string{
590+
selector: tt.qualifier,
591+
},
592+
},
593+
ConfigsPerChain: map[uint64]commonchangeset.ConfigPerRoleV2{
594+
selector: {
595+
Proposer: &cfgProposer,
596+
},
597+
},
598+
}
599+
err := mcmsCfg.Validate(rt.Environment(), []uint64{selector})
600+
if tt.wantErr != "" {
601+
require.Error(t, err)
602+
require.ErrorContains(t, err, tt.wantErr)
603+
} else {
604+
require.NoError(t, err)
605+
}
606+
})
607+
}
608+
}
609+
517610
func TestSetConfigMCMSV2Partial(t *testing.T) {
518611
t.Parallel()
519612

0 commit comments

Comments
 (0)