From 253304d4fd3c0c931ebecd9c5574281f9a7ede3c Mon Sep 17 00:00:00 2001 From: RodrigoAD <15104916+RodrigoAD@users.noreply.github.com> Date: Wed, 8 Apr 2026 17:50:52 +0100 Subject: [PATCH 1/7] add simulation test --- integration-tests/mcms/ccip_test.go | 203 +++++++++++++++++++++++--- integration-tests/mcms/runner_test.go | 18 +-- 2 files changed, 191 insertions(+), 30 deletions(-) diff --git a/integration-tests/mcms/ccip_test.go b/integration-tests/mcms/ccip_test.go index 8a01e62e..19fb453d 100644 --- a/integration-tests/mcms/ccip_test.go +++ b/integration-tests/mcms/ccip_test.go @@ -66,32 +66,96 @@ func (s *CCIPMCMSTestSuite) Test_CCIP_MCMS() { s.RunUpgradeCCIPProposal("FeeQuoter 1.8.0") }) - // CCIP OFFRAMP UPGRADE - s.T().Run("Upgrade CCIPOfframp through MCMS", func(t *testing.T) { - s.RunUpgradeOfframpProposal("OffRamp 1.7.0") + s.T().Run("Test CCIP works after upgrade", func(t *testing.T) { + s.RunUpgradeBreakingCCIPProposal() + s.RegisterCCIPUpgradeCap() + s.RunLatestUpgradedCCIPProposal() }) - s.T().Run("Re-Upgrade CCIPOfframp through MCMS", func(t *testing.T) { - s.RunUpgradeOfframpProposal("OffRamp 1.8.0") - }) + // // CCIP OFFRAMP UPGRADE + // s.T().Run("Upgrade CCIPOfframp through MCMS", func(t *testing.T) { + // s.RunUpgradeOfframpProposal("OffRamp 1.7.0") + // }) - // ONRAMP UPGRADE - s.T().Run("Upgrade CCIPOnramp through MCMS", func(t *testing.T) { - s.RunUpgradeOnrampProposal("OnRamp 1.7.0") - }) + // s.T().Run("Re-Upgrade CCIPOfframp through MCMS", func(t *testing.T) { + // s.RunUpgradeOfframpProposal("OffRamp 1.8.0") + // }) - s.T().Run("Re-Upgrade CCIPOnramp through MCMS", func(t *testing.T) { - s.RunUpgradeOnrampProposal("OnRamp 1.8.0") - }) + // // ONRAMP UPGRADE + // s.T().Run("Upgrade CCIPOnramp through MCMS", func(t *testing.T) { + // s.RunUpgradeOnrampProposal("OnRamp 1.7.0") + // }) + + // s.T().Run("Re-Upgrade CCIPOnramp through MCMS", func(t *testing.T) { + // s.RunUpgradeOnrampProposal("OnRamp 1.8.0") + // }) + + // // ROUTER UPGRADE + // s.T().Run("Upgrade CCIPRouter through MCMS", func(t *testing.T) { + // s.RunUpgradeRouterProposal("Router 1.7.0") + // }) + + // s.T().Run("Re-Upgrade CCIPRouter through MCMS", func(t *testing.T) { + // s.RunUpgradeRouterProposal("Router 1.8.0") + // }) +} - // ROUTER UPGRADE - s.T().Run("Upgrade CCIPRouter through MCMS", func(t *testing.T) { - s.RunUpgradeRouterProposal("Router 1.7.0") +func (s *CCIPMCMSTestSuite) RunLatestUpgradedCCIPProposal() { + // 1. Record the current package_ids before the proposal to compare after execution. + stateObjectBefore, err := s.client.SuiGetObject(s.T().Context(), models.SuiGetObjectRequest{ + ObjectId: s.ccipObjects.CCIPObjectRefObjectId, + Options: models.SuiObjectDataOptions{ShowContent: true}, }) + s.Require().NoError(err) + pkgIdsBefore := stateObjectBefore.Data.Content.Fields["package_ids"].([]any) - s.T().Run("Re-Upgrade CCIPRouter through MCMS", func(t *testing.T) { - s.RunUpgradeRouterProposal("Router 1.8.0") + // 2. Build an MCMS proposal to call add_package_id routing through the upgraded (breaking) + // package. Because the breaking package's add_package_id is a no-op (push_back was removed), + // latestCcipPackageId should NOT appear in the list after execution — which proves MCMS + // targets the latest package code, not the old one. + input := mcmsops.ProposalGenerateInput{ + Defs: []cld_ops.Definition{ + ccipops.AddPackageIdStateObjectOp.Def(), + }, + Inputs: []any{ + ccipops.AddPackageIdStateObjectInput{ + PackageId: s.latestCcipPackageId, + CCIPPackageId: s.ccipPackageId, + OwnerCapObjectId: s.ccipObjects.OwnerCapObjectId, + CCIPObjectRefObjectId: s.ccipObjects.CCIPObjectRefObjectId, + }, + }, + MmcsPackageID: s.mcmsPackageID, + McmsStateObjID: s.mcmsObj, + TimelockObjID: s.timelockObj, + AccountObjID: s.accountObj, + RegistryObjID: s.registryObj, + DeployerStateObjID: s.deployerStateObj, + ChainSelector: uint64(s.chainSelector), + TimelockConfig: utils.TimelockConfig{ + MCMSAction: types.TimelockActionBypass, + MinDelay: 0, + OverrideRoot: false, + }, + } + report, err := cld_ops.ExecuteSequence(s.bundle, mcmsops.MCMSDynamicProposalGenerateSeq, s.deps, input) + s.Require().NoError(err, "building add_package_id proposal via upgraded CCIP package") + + // 3. Execute proposal via bypasser — no timelock delay needed. + s.ExecuteProposalE2e(&report.Output, s.bypasserConfig, 0) + + // 4. Verify the package_ids list did NOT grow: the upgraded add_package_id is a no-op. + stateObjectAfter, err := s.client.SuiGetObject(s.T().Context(), models.SuiGetObjectRequest{ + ObjectId: s.ccipObjects.CCIPObjectRefObjectId, + Options: models.SuiObjectDataOptions{ShowContent: true}, }) + s.Require().NoError(err) + pkgIdsAfter := stateObjectAfter.Data.Content.Fields["package_ids"].([]any) + + s.Require().Equal(len(pkgIdsBefore), len(pkgIdsAfter), + "package_ids list should not grow: upgraded add_package_id is a no-op") + s.Require().NotContains(pkgIdsAfter, s.latestCcipPackageId, + "latestCcipPackageId should not be in package_ids since upgraded add_package_id is a no-op") } // TODO: For prod env, the initial deployment sequence should start the ownership transfer flow of every deployed contract @@ -138,6 +202,7 @@ func RunTestCCIPFeeQuoterProposal(s *CCIPMCMSTestSuite) { ccipops.FeeQuoterApplyTokenTransferFeeConfigUpdatesOp.Def(), ccipops.FeeQuoterApplyDestChainConfigUpdatesOp.Def(), ccipops.FeeQuoterApplyPremiumMultiplierWeiPerEthUpdatesOp.Def(), + ccipops.AddPackageIdStateObjectOp.Def(), }, Inputs: []any{ ccipops.FeeQuoterApplyFeeTokenUpdatesInput{ @@ -193,6 +258,12 @@ func RunTestCCIPFeeQuoterProposal(s *CCIPMCMSTestSuite) { Tokens: []string{s.linkObjects.CoinMetadataObjectId}, PremiumMultiplierWeiPerEth: []uint64{expectedPremiumMultiplier}, }, + ccipops.AddPackageIdStateObjectInput{ + PackageId: s.latestCcipPackageId, + CCIPPackageId: s.ccipPackageId, + OwnerCapObjectId: s.ccipObjects.OwnerCapObjectId, + CCIPObjectRefObjectId: s.ccipObjects.CCIPObjectRefObjectId, + }, }, // MCMS related MmcsPackageID: s.mcmsPackageID, @@ -204,7 +275,7 @@ func RunTestCCIPFeeQuoterProposal(s *CCIPMCMSTestSuite) { ChainSelector: uint64(s.chainSelector), // Proposal TimelockConfig: utils.TimelockConfig{ - MCMSAction: types.TimelockActionBypass, + MCMSAction: types.TimelockActionSchedule, MinDelay: 0, OverrideRoot: false, }, @@ -215,7 +286,7 @@ func RunTestCCIPFeeQuoterProposal(s *CCIPMCMSTestSuite) { timelockProposal := feeQuoterReport.Output // 3. Execute proposal - s.ExecuteProposalE2e(&timelockProposal, s.bypasserConfig, 0) + s.ExecuteProposalE2e(&timelockProposal, s.proposerConfig, 1*time.Second) // 4. Verify the changes in CCIP state object fqContract, err := module_fee_quoter.NewFeeQuoter(s.ccipPackageId, s.client) @@ -243,6 +314,14 @@ func RunTestCCIPFeeQuoterProposal(s *CCIPMCMSTestSuite) { actualPremiumMultiplier, err := fqContract.DevInspect().GetPremiumMultiplierWeiPerEth(s.T().Context(), s.deps.GetCallOpts(), ccipObjRef, linkTokenID) require.NoError(s.T(), err) require.Equal(s.T(), expectedPremiumMultiplier, actualPremiumMultiplier) + + // Add check to verify latest ccip package was added to state object fetching the object directly + ccipStateObject, err := s.client.SuiGetObject(s.T().Context(), models.SuiGetObjectRequest{ + ObjectId: s.ccipObjects.CCIPObjectRefObjectId, + Options: models.SuiObjectDataOptions{ShowContent: true}, + }) + require.NoError(s.T(), err) + require.Contains(s.T(), ccipStateObject.Data.Content.Fields["package_ids"].([]any), s.latestCcipPackageId) } func RunCCIPRampsProposal(s *CCIPMCMSTestSuite) { @@ -469,7 +548,7 @@ func RunTestRouterProposal(s *CCIPMCMSTestSuite) { func (s *CCIPMCMSTestSuite) RegisterCCIPUpgradeCap() { // Register CCIP package's UpgradeCap with MCMS deployer - ccipContract, err := module_state_object.NewStateObject(s.ccipPackageId, s.client) + ccipContract, err := module_state_object.NewStateObject(s.latestCcipPackageId, s.client) require.NoError(s.T(), err, "creating CCIP state object contract") _, err = ccipContract.McmsRegisterUpgradeCap( @@ -826,3 +905,85 @@ func (s *CCIPMCMSTestSuite) RunUpgradeRouterProposal(newVersion string) { s.Require().Equal(newVersion, version, "router version should be upgraded to "+newVersion) s.latestCcipRouterPackageId = newAddress } + +func (s *CCIPMCMSTestSuite) RunUpgradeBreakingCCIPProposal() { + bind.SetTestModifier(func(packageRoot string) error { + // Bump fee_quoter version so the upgraded package bytecode is recognizably different. + fqPath := filepath.Join(packageRoot, "sources", "fee_quoter.move") + fqContent, _ := os.ReadFile(fqPath) + reVersion := regexp.MustCompile(`FeeQuoter \d+\.\d+\.\d+`) + if err := os.WriteFile(fqPath, []byte(reVersion.ReplaceAllString(string(fqContent), "FeeQuoter 1.9.0")), 0o644); err != nil { + return err + } + // Remove ref.package_ids.push_back(package_id) from add_package_id to simulate a + // breaking change: the upgraded contract's add_package_id becomes a no-op, so any MCMS + // proposal targeting this package will not actually store the package ID. + soPath := filepath.Join(packageRoot, "sources", "state_object.move") + soContent, _ := os.ReadFile(soPath) + rePushBack := regexp.MustCompile(`(assert!\(object::id\(owner_cap\)[^\n]+EInvalidOwnerCap\);\n)\s+ref\.package_ids\.push_back\(package_id\);`) + return os.WriteFile(soPath, []byte(rePushBack.ReplaceAllString(string(soContent), "$1")), 0o644) + }) + defer bind.ClearTestModifier() + + signerAddress, err := s.signer.GetAddress() + s.Require().NoError(err, "getting signer address") + + // 1. Build upgrade input for CCIP package + input := mcmsops.UpgradeCCIPInput{ + // Package related + PackageName: contracts.CCIP, + TargetPackageId: s.latestCcipPackageId, + NamedAddresses: map[string]string{ + "signer": signerAddress, + "mcms": s.mcmsPackageID, + "link": s.linkPackageId, + "original_ccip_pkg": s.ccipPackageId, + }, + + ChainSelector: uint64(s.chainSelector), + // MCMS related + MmcsPackageID: s.mcmsPackageID, + McmsStateObjID: s.mcmsObj, + RegistryObjID: s.registryObj, + TimelockObjID: s.timelockObj, + AccountObjID: s.accountObj, + DeployerStateObjID: s.deployerStateObj, + OwnerCapObjID: s.ownerCapObj, // MCMS OwnerCap + + // Timelock related + TimelockConfig: utils.TimelockConfig{ + MCMSAction: types.TimelockActionSchedule, + MinDelay: 5 * time.Second, + OverrideRoot: false, + }, + } + + // 2. Execute operation to generate upgrade proposal + upgradeReport, err := cld_ops.ExecuteOperation(s.NewOpBundle(), mcmsops.UpgradeCCIPOp, s.deps, input) + s.Require().NoError(err, "executing CCIP upgrade operation") + + timelockProposal := upgradeReport.Output + + s.T().Logf("✅ Generated CCIP upgrade proposal: %s", timelockProposal.Description) + + // 3. Execute the upgrade proposal through MCMS using Schedule path + responses := s.ExecuteProposalE2e(&timelockProposal, s.proposerConfig, 6*time.Second) + + tx, ok := responses[len(responses)-1].RawData.(*models.SuiTransactionBlockResponse) + s.Require().True(ok) + + newAddress, err := s.GetUpgradedAddress(tx, s.mcmsPackageID) + s.Require().NoError(err) + s.Require().NotEmpty(newAddress) + + s.T().Logf("✅ Successfully upgraded CCIP package from %s to %s", s.ccipPackageId, newAddress) + + // 4. Verify the new package version + feequoter, err := module_fee_quoter.NewFeeQuoter(newAddress, s.client) + s.Require().NoError(err) + + version, err := feequoter.DevInspect().TypeAndVersion(s.T().Context(), s.deps.GetCallOpts()) + s.Require().NoError(err) + s.Require().Equal("FeeQuoter 1.9.0", version, "fee quoter version should be upgraded to FeeQuoter 1.9.0") + s.latestCcipPackageId = newAddress +} diff --git a/integration-tests/mcms/runner_test.go b/integration-tests/mcms/runner_test.go index fd70c052..308d8df7 100644 --- a/integration-tests/mcms/runner_test.go +++ b/integration-tests/mcms/runner_test.go @@ -13,15 +13,15 @@ func TestMCMSandCCIPSuite(t *testing.T) { suite.Run(t, new(CCIPMCMSTestSuite)) }) - t.Run("TokenPoolTestSuite", func(t *testing.T) { - suite.Run(t, new(TokenPoolTestSuite)) - }) + // t.Run("TokenPoolTestSuite", func(t *testing.T) { + // suite.Run(t, new(TokenPoolTestSuite)) + // }) - t.Run("MCMSUserTestSuite", func(t *testing.T) { - suite.Run(t, new(UpgradeTestSuite)) - }) + // t.Run("MCMSUserTestSuite", func(t *testing.T) { + // suite.Run(t, new(UpgradeTestSuite)) + // }) - t.Run("CCIPCurseMCMSSuite", func(t *testing.T) { - suite.Run(t, new(CCIPCurseMCMSTestSuite)) - }) + // t.Run("CCIPCurseMCMSSuite", func(t *testing.T) { + // suite.Run(t, new(CCIPCurseMCMSTestSuite)) + // }) } From 242f4a9f3cd4a45402d9b870f04c446dc84125aa Mon Sep 17 00:00:00 2001 From: RodrigoAD <15104916+RodrigoAD@users.noreply.github.com> Date: Thu, 9 Apr 2026 12:44:55 +0100 Subject: [PATCH 2/7] integrate with latest mcms --- .../changesets/cs_add_ccip_package_id.go | 8 +- deployment/go.mod | 2 +- deployment/go.sum | 4 +- deployment/ops/ccip/op_state_object.go | 29 +- deployment/ops/ccip/op_state_object_test.go | 8 +- deployment/ops/ccip_offramp/op_deploy.go | 48 ++- deployment/ops/mcms/op_proposal_generate.go | 7 + .../ops/mcms/op_proposal_generate_test.go | 12 +- deployment/ops/types.go | 13 +- integration-tests/go.mod | 4 +- integration-tests/go.sum | 4 +- integration-tests/mcms/ccip_test.go | 324 +++++++----------- scripts/go.mod | 4 +- scripts/go.sum | 4 +- 14 files changed, 232 insertions(+), 239 deletions(-) diff --git a/deployment/changesets/cs_add_ccip_package_id.go b/deployment/changesets/cs_add_ccip_package_id.go index d14fab76..e90a41a2 100644 --- a/deployment/changesets/cs_add_ccip_package_id.go +++ b/deployment/changesets/cs_add_ccip_package_id.go @@ -106,10 +106,10 @@ func executeAddPackageId(e cldf.Environment, config AddCCIPPackageIdConfig, chai switch target { case AddPackageIdTargetCCIP: r, err := operations.ExecuteOperation(e.OperationsBundle, ccipops.AddPackageIdStateObjectOp, deps, ccipops.AddPackageIdStateObjectInput{ - CCIPPackageId: config.CCIPPackageId, + PackageId: config.CCIPPackageId, CCIPObjectRefObjectId: chainState.CCIPObjectRef, OwnerCapObjectId: chainState.CCIPOwnerCapObjectId, - PackageId: config.PackageId, + NewPackageId: config.PackageId, }) if err != nil { return operations.Report[any, any]{}, fmt.Errorf("failed to add package ID to CCIP state object for Sui chain %d: %w", config.SuiChainSelector, err) @@ -130,10 +130,10 @@ func executeAddPackageId(e cldf.Environment, config AddCCIPPackageIdConfig, chai case AddPackageIdTargetOffRamp: r, err := operations.ExecuteOperation(e.OperationsBundle, offrampops.AddPackageIdOffRampOp, deps, offrampops.AddPackageIdOffRampInput{ - OffRampPackageId: config.OffRampPackageId, + PackageId: config.OffRampPackageId, StateObjectId: chainState.OffRampStateObjectId, OwnerCapObjectId: chainState.OffRampOwnerCapId, - PackageId: config.PackageId, + NewPackageId: config.PackageId, }) if err != nil { return operations.Report[any, any]{}, fmt.Errorf("failed to add package ID to OffRamp for Sui chain %d: %w", config.SuiChainSelector, err) diff --git a/deployment/go.mod b/deployment/go.mod index 44d8b7fc..94891840 100644 --- a/deployment/go.mod +++ b/deployment/go.mod @@ -17,7 +17,7 @@ require ( github.com/smartcontractkit/chainlink-common v0.11.2-0.20260406055916-9aa6b6c0ae81 github.com/smartcontractkit/chainlink-deployments-framework v0.75.0 github.com/smartcontractkit/chainlink-sui v0.0.0-20260205175622-33e65031f9a9 - github.com/smartcontractkit/mcms v0.40.1 + github.com/smartcontractkit/mcms v0.40.5-0.20260409113859-92ddf0d61451 // Replace with official release before merging github.com/stretchr/testify v1.11.1 golang.org/x/sync v0.19.0 ) diff --git a/deployment/go.sum b/deployment/go.sum index 0f735058..44a3484a 100644 --- a/deployment/go.sum +++ b/deployment/go.sum @@ -652,8 +652,8 @@ github.com/smartcontractkit/grpc-proxy v0.0.0-20240830132753-a7e17fec5ab7 h1:12i github.com/smartcontractkit/grpc-proxy v0.0.0-20240830132753-a7e17fec5ab7/go.mod h1:FX7/bVdoep147QQhsOPkYsPEXhGZjeYx6lBSaSXtZOA= github.com/smartcontractkit/libocr v0.0.0-20250912173940-f3ab0246e23d h1:LokA9PoCNb8mm8mDT52c3RECPMRsGz1eCQORq+J3n74= github.com/smartcontractkit/libocr v0.0.0-20250912173940-f3ab0246e23d/go.mod h1:Acy3BTBxou83ooMESLO90s8PKSu7RvLCzwSTbxxfOK0= -github.com/smartcontractkit/mcms v0.40.1 h1:r9bU/2GfIf6mHHM4PklSBkfi2Lq4+EGILC81e4IqKz0= -github.com/smartcontractkit/mcms v0.40.1/go.mod h1:7YqJPR8w9GiO1L/JjjTrwlSwAZ7i3J7cgOcu88PqtvU= +github.com/smartcontractkit/mcms v0.40.5-0.20260409113859-92ddf0d61451 h1:2l1sSlzCxwKsiE58x3Yq5gZMLnn0OMkrJrOs0tTNMgI= +github.com/smartcontractkit/mcms v0.40.5-0.20260409113859-92ddf0d61451/go.mod h1:VTg6wrglc+efxfc7YIk4AcOZlrw1i+vkj5UqzG2p7K8= github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= diff --git a/deployment/ops/ccip/op_state_object.go b/deployment/ops/ccip/op_state_object.go index db87d70b..49200653 100644 --- a/deployment/ops/ccip/op_state_object.go +++ b/deployment/ops/ccip/op_state_object.go @@ -15,10 +15,11 @@ import ( // =================== Add Package ID Operations =================== // type AddPackageIdStateObjectInput struct { - CCIPPackageId string + PackageId string // original package ID (MCMS registry identity; used as binary when LatestPackageId is "") + LatestPackageId string // optional: upgraded package ID (PTB execution target when set) CCIPObjectRefObjectId string OwnerCapObjectId string - PackageId string + NewPackageId string // the package ID to register in the CCIP StateObject } type AddPackageIdStateObjectObjects struct { @@ -26,12 +27,17 @@ type AddPackageIdStateObjectObjects struct { } var addPackageIdStateObjectHandler = func(b cld_ops.Bundle, deps sui_ops.OpTxDeps, input AddPackageIdStateObjectInput) (output sui_ops.OpTxResult[AddPackageIdStateObjectObjects], err error) { - contract, err := module_state_object.NewStateObject(input.CCIPPackageId, deps.Client) + // When the package has been upgraded, PTB must target the latest bytecode for execution. + binaryPkgId := input.PackageId + if input.LatestPackageId != "" { + binaryPkgId = input.LatestPackageId + } + contract, err := module_state_object.NewStateObject(binaryPkgId, deps.Client) if err != nil { return sui_ops.OpTxResult[AddPackageIdStateObjectObjects]{}, fmt.Errorf("failed to create StateObject contract: %w", err) } - encodedCall, err := contract.Encoder().AddPackageId(bind.Object{Id: input.CCIPObjectRefObjectId}, bind.Object{Id: input.OwnerCapObjectId}, input.PackageId) + encodedCall, err := contract.Encoder().AddPackageId(bind.Object{Id: input.CCIPObjectRefObjectId}, bind.Object{Id: input.OwnerCapObjectId}, input.NewPackageId) if err != nil { return sui_ops.OpTxResult[AddPackageIdStateObjectObjects]{}, fmt.Errorf("failed to encode AddPackageId call: %w", err) } @@ -39,11 +45,18 @@ var addPackageIdStateObjectHandler = func(b cld_ops.Bundle, deps sui_ops.OpTxDep if err != nil { return sui_ops.OpTxResult[AddPackageIdStateObjectObjects]{}, fmt.Errorf("failed to convert encoded call to TransactionCall: %w", err) } + // When the package has been upgraded, the on-chain MCMS registry still holds the original package's + // proof type, so tx.To must be the original package ID. The PTB MoveCall must target the latest package + // so upgraded bytecode runs. Use LatestPackageId so the proposal generator can separate the two. + if input.LatestPackageId != "" { + call.LatestPackageID = call.PackageID // current PackageID is the latest (from binaryPkgId) + call.PackageID = input.PackageId // replace with original for on-chain identity + } if deps.Signer == nil { - b.Logger.Infow("Skipping execution of AddPackageId on StateObject as per no Signer provided", "packageId", input.PackageId) + b.Logger.Infow("Skipping execution of AddPackageId on StateObject as per no Signer provided", "newPackageId", input.NewPackageId) return sui_ops.OpTxResult[AddPackageIdStateObjectObjects]{ Digest: "", - PackageId: input.CCIPPackageId, + PackageId: input.PackageId, Objects: AddPackageIdStateObjectObjects{}, Call: call, }, nil @@ -60,11 +73,11 @@ var addPackageIdStateObjectHandler = func(b cld_ops.Bundle, deps sui_ops.OpTxDep return sui_ops.OpTxResult[AddPackageIdStateObjectObjects]{}, fmt.Errorf("failed to execute AddPackageId on StateObject: %w", err) } - b.Logger.Infow("Package ID added to CCIP StateObject", "packageId", input.PackageId) + b.Logger.Infow("Package ID added to CCIP StateObject", "newPackageId", input.NewPackageId) return sui_ops.OpTxResult[AddPackageIdStateObjectObjects]{ Digest: tx.Digest, - PackageId: input.CCIPPackageId, + PackageId: input.PackageId, Objects: AddPackageIdStateObjectObjects{}, Call: call, }, nil diff --git a/deployment/ops/ccip/op_state_object_test.go b/deployment/ops/ccip/op_state_object_test.go index 9fbb2603..ed051b50 100644 --- a/deployment/ops/ccip/op_state_object_test.go +++ b/deployment/ops/ccip/op_state_object_test.go @@ -88,10 +88,10 @@ func TestStateObjectOperations(t *testing.T) { t.Run("Test Add Package ID", func(t *testing.T) { newPackageId := "0x123456789abcdef" // Example package ID addReport, err := cld_ops.ExecuteOperation(bundle, AddPackageIdStateObjectOp, deps, AddPackageIdStateObjectInput{ - CCIPPackageId: ccipReport.Output.PackageId, + PackageId: ccipReport.Output.PackageId, CCIPObjectRefObjectId: ccipReport.Output.Objects.CCIPObjectRefObjectId, OwnerCapObjectId: ccipReport.Output.Objects.OwnerCapObjectId, - PackageId: newPackageId, + NewPackageId: newPackageId, }) require.NoError(t, err, "failed to add package ID") require.NotEmpty(t, addReport.Output.Digest, "add package ID transaction should have a digest") @@ -101,10 +101,10 @@ func TestStateObjectOperations(t *testing.T) { // First add a package ID to remove newPackageId := "0xabcdef1234567890abcdef1234567890abcdef12" _, err := cld_ops.ExecuteOperation(bundle, AddPackageIdStateObjectOp, deps, AddPackageIdStateObjectInput{ - CCIPPackageId: ccipReport.Output.PackageId, + PackageId: ccipReport.Output.PackageId, CCIPObjectRefObjectId: ccipReport.Output.Objects.CCIPObjectRefObjectId, OwnerCapObjectId: ccipReport.Output.Objects.OwnerCapObjectId, - PackageId: newPackageId, + NewPackageId: newPackageId, }) // Now remove the package ID removeReport, err := cld_ops.ExecuteOperation(bundle, RemovePackageIdStateObjectOp, deps, RemovePackageIdStateObjectInput{ diff --git a/deployment/ops/ccip_offramp/op_deploy.go b/deployment/ops/ccip_offramp/op_deploy.go index 7fbadac1..d4f95818 100644 --- a/deployment/ops/ccip_offramp/op_deploy.go +++ b/deployment/ops/ccip_offramp/op_deploy.go @@ -241,10 +241,11 @@ var applySourceChainConfigUpdateHandler = func(b cld_ops.Bundle, deps sui_ops.Op } type AddPackageIdOffRampInput struct { - OffRampPackageId string + PackageId string // original package ID (MCMS registry identity; used as binary when LatestPackageId is "") + LatestPackageId string // optional: upgraded package ID (PTB execution target when set) StateObjectId string OwnerCapObjectId string - PackageId string + NewPackageId string // the package ID to register in the OffRamp state } type AddPackageIdOffRampObjects struct { @@ -252,30 +253,59 @@ type AddPackageIdOffRampObjects struct { } var addPackageIdOffRampHandler = func(b cld_ops.Bundle, deps sui_ops.OpTxDeps, input AddPackageIdOffRampInput) (output sui_ops.OpTxResult[AddPackageIdOffRampObjects], err error) { - offRampPackage, err := module_offramp.NewOfframp(input.OffRampPackageId, deps.Client) + // When the package has been upgraded, PTB must target the latest bytecode for execution. + binaryPkgId := input.PackageId + if input.LatestPackageId != "" { + binaryPkgId = input.LatestPackageId + } + offRampPackage, err := module_offramp.NewOfframp(binaryPkgId, deps.Client) if err != nil { return sui_ops.OpTxResult[AddPackageIdOffRampObjects]{}, err } + encodedCall, err := offRampPackage.Encoder().AddPackageId(bind.Object{Id: input.StateObjectId}, bind.Object{Id: input.OwnerCapObjectId}, input.NewPackageId) + if err != nil { + return sui_ops.OpTxResult[AddPackageIdOffRampObjects]{}, fmt.Errorf("failed to encode AddPackageId call: %w", err) + } + call, err := sui_ops.ToTransactionCall(encodedCall, input.StateObjectId) + if err != nil { + return sui_ops.OpTxResult[AddPackageIdOffRampObjects]{}, fmt.Errorf("failed to convert encoded call to TransactionCall: %w", err) + } + // When the package has been upgraded, the on-chain MCMS registry still holds the original package's + // proof type, so tx.To must be the original package ID. Use LatestPackageId so the proposal + // generator routes the PTB MoveCall to the upgraded bytecode. + if input.LatestPackageId != "" { + call.LatestPackageID = call.PackageID // current PackageID is the latest (from binaryPkgId) + call.PackageID = input.PackageId // replace with original for on-chain identity + } + if deps.Signer == nil { + b.Logger.Infow("Skipping execution of AddPackageId on OffRamp as per no Signer provided", "newPackageId", input.NewPackageId) + return sui_ops.OpTxResult[AddPackageIdOffRampObjects]{ + Digest: "", + PackageId: input.PackageId, + Objects: AddPackageIdOffRampObjects{}, + Call: call, + }, nil + } + opts := deps.GetCallOpts() opts.Signer = deps.Signer - tx, err := offRampPackage.AddPackageId( + tx, err := offRampPackage.Bound().ExecuteTransaction( b.GetContext(), opts, - bind.Object{Id: input.StateObjectId}, - bind.Object{Id: input.OwnerCapObjectId}, - input.PackageId, + encodedCall, ) if err != nil { return sui_ops.OpTxResult[AddPackageIdOffRampObjects]{}, fmt.Errorf("failed to execute AddPackageId on offRamp: %w", err) } - b.Logger.Infow("Package ID added to OffRamp", "packageId", input.PackageId) + b.Logger.Infow("Package ID added to OffRamp", "newPackageId", input.NewPackageId) return sui_ops.OpTxResult[AddPackageIdOffRampObjects]{ Digest: tx.Digest, - PackageId: input.OffRampPackageId, + PackageId: input.PackageId, Objects: AddPackageIdOffRampObjects{}, + Call: call, }, nil } diff --git a/deployment/ops/mcms/op_proposal_generate.go b/deployment/ops/mcms/op_proposal_generate.go index 55cede7c..c4c8eff3 100644 --- a/deployment/ops/mcms/op_proposal_generate.go +++ b/deployment/ops/mcms/op_proposal_generate.go @@ -78,6 +78,13 @@ var generateProposalHandler = func(b cld_ops.Bundle, deps sui_ops.OpTxDeps, inpu if err != nil { return mcms.TimelockProposal{}, fmt.Errorf("failed to create transaction for operation %s: %w", def.ID, err) } + // If the op signalled an upgraded package, record it so the timelock converter + // propagates it as InternalLatestPackageIDs in the outer batch operation. + if call.LatestPackageID != "" { + if setErr := suisdk.SetLatestPackageID(&tx, call.LatestPackageID); setErr != nil { + return mcms.TimelockProposal{}, fmt.Errorf("failed to set latest package ID for operation %s: %w", def.ID, setErr) + } + } mcmsTxs[i] = tx } diff --git a/deployment/ops/mcms/op_proposal_generate_test.go b/deployment/ops/mcms/op_proposal_generate_test.go index 19ae50d5..f9eaa080 100644 --- a/deployment/ops/mcms/op_proposal_generate_test.go +++ b/deployment/ops/mcms/op_proposal_generate_test.go @@ -98,10 +98,10 @@ func TestMCMSDynamicProposalGenerateSeq(t *testing.T) { inputs := []any{ ccipops.AddPackageIdStateObjectInput{ - CCIPPackageId: testCCIPPackageId, + PackageId: testCCIPPackageId, CCIPObjectRefObjectId: testObjectRefId, OwnerCapObjectId: testOwnerCapId, - PackageId: testPackageId, + NewPackageId: testPackageId, }, ccipops.TransferOwnershipStateObjectInput{ CCIPPackageId: testCCIPPackageId, @@ -228,10 +228,10 @@ func TestMCMSDynamicProposalGenerateSeq(t *testing.T) { inputs := []any{ ccipops.AddPackageIdStateObjectInput{ - CCIPPackageId: testCCIPPackageId, + PackageId: testCCIPPackageId, CCIPObjectRefObjectId: testObjectRefId, OwnerCapObjectId: testOwnerCapId, - PackageId: testPackageId, + NewPackageId: testPackageId, }, } @@ -270,10 +270,10 @@ func TestMCMSDynamicProposalGenerateSeq(t *testing.T) { inputs := []any{ ccipops.AddPackageIdStateObjectInput{ - CCIPPackageId: testCCIPPackageId, + PackageId: testCCIPPackageId, CCIPObjectRefObjectId: testObjectRefId, OwnerCapObjectId: testOwnerCapId, - PackageId: testPackageId, + NewPackageId: testPackageId, }, // Missing second input diff --git a/deployment/ops/types.go b/deployment/ops/types.go index 19344340..b46f39a0 100644 --- a/deployment/ops/types.go +++ b/deployment/ops/types.go @@ -22,12 +22,13 @@ type OpTxResult[O any] struct { } type TransactionCall struct { - PackageID string - Module string - Function string - Data []byte - StateObjID string - TypeArgs []string + PackageID string + LatestPackageID string // optional: when set, the PTB MoveCall targets this (upgraded) package while PackageID remains the on-chain MCMS identity + Module string + Function string + Data []byte + StateObjID string + TypeArgs []string } type OpTxDeps struct { diff --git a/integration-tests/go.mod b/integration-tests/go.mod index a15e01ee..bf7d2020 100644 --- a/integration-tests/go.mod +++ b/integration-tests/go.mod @@ -10,9 +10,9 @@ require ( github.com/smartcontractkit/chainlink-ccip/deployment v0.0.0-20260311190822-5cbfc939dd16 github.com/smartcontractkit/chainlink-common v0.11.2-0.20260406055916-9aa6b6c0ae81 github.com/smartcontractkit/chainlink-deployments-framework v0.75.0 - github.com/smartcontractkit/chainlink-sui v0.0.0-20260205175622-33e65031f9a9 + github.com/smartcontractkit/chainlink-sui v0.0.0-20260408222042-6c7b4c27a8b2 github.com/smartcontractkit/chainlink-sui/deployment v0.0.0-20250903045200-c3d973201e55 - github.com/smartcontractkit/mcms v0.40.1 + github.com/smartcontractkit/mcms v0.40.5-0.20260409113859-92ddf0d61451 // Replace with official release before merging github.com/stretchr/testify v1.11.1 ) diff --git a/integration-tests/go.sum b/integration-tests/go.sum index 7f5517ab..ca0b00c5 100644 --- a/integration-tests/go.sum +++ b/integration-tests/go.sum @@ -656,8 +656,8 @@ github.com/smartcontractkit/grpc-proxy v0.0.0-20240830132753-a7e17fec5ab7 h1:12i github.com/smartcontractkit/grpc-proxy v0.0.0-20240830132753-a7e17fec5ab7/go.mod h1:FX7/bVdoep147QQhsOPkYsPEXhGZjeYx6lBSaSXtZOA= github.com/smartcontractkit/libocr v0.0.0-20250912173940-f3ab0246e23d h1:LokA9PoCNb8mm8mDT52c3RECPMRsGz1eCQORq+J3n74= github.com/smartcontractkit/libocr v0.0.0-20250912173940-f3ab0246e23d/go.mod h1:Acy3BTBxou83ooMESLO90s8PKSu7RvLCzwSTbxxfOK0= -github.com/smartcontractkit/mcms v0.40.1 h1:r9bU/2GfIf6mHHM4PklSBkfi2Lq4+EGILC81e4IqKz0= -github.com/smartcontractkit/mcms v0.40.1/go.mod h1:7YqJPR8w9GiO1L/JjjTrwlSwAZ7i3J7cgOcu88PqtvU= +github.com/smartcontractkit/mcms v0.40.5-0.20260409113859-92ddf0d61451 h1:2l1sSlzCxwKsiE58x3Yq5gZMLnn0OMkrJrOs0tTNMgI= +github.com/smartcontractkit/mcms v0.40.5-0.20260409113859-92ddf0d61451/go.mod h1:VTg6wrglc+efxfc7YIk4AcOZlrw1i+vkj5UqzG2p7K8= github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= diff --git a/integration-tests/mcms/ccip_test.go b/integration-tests/mcms/ccip_test.go index 19fb453d..faf62834 100644 --- a/integration-tests/mcms/ccip_test.go +++ b/integration-tests/mcms/ccip_test.go @@ -57,73 +57,78 @@ func (s *CCIPMCMSTestSuite) Test_CCIP_MCMS() { s.RegisterRouterUpgradeCap() }) - // CCIP UPGRADE - s.T().Run("Upgrade CCIP through MCMS", func(t *testing.T) { - s.RunUpgradeCCIPProposal("FeeQuoter 1.7.0") - }) - - s.T().Run("Re-Upgrade CCIP through MCMS", func(t *testing.T) { - s.RunUpgradeCCIPProposal("FeeQuoter 1.8.0") - }) - - s.T().Run("Test CCIP works after upgrade", func(t *testing.T) { + s.T().Run("CCIP/Offramp breaking upgrades with post calls", func(t *testing.T) { s.RunUpgradeBreakingCCIPProposal() - s.RegisterCCIPUpgradeCap() + s.RunUpgradeBreakingOfframpProposal() s.RunLatestUpgradedCCIPProposal() }) - // // CCIP OFFRAMP UPGRADE - // s.T().Run("Upgrade CCIPOfframp through MCMS", func(t *testing.T) { - // s.RunUpgradeOfframpProposal("OffRamp 1.7.0") - // }) - - // s.T().Run("Re-Upgrade CCIPOfframp through MCMS", func(t *testing.T) { - // s.RunUpgradeOfframpProposal("OffRamp 1.8.0") - // }) - - // // ONRAMP UPGRADE - // s.T().Run("Upgrade CCIPOnramp through MCMS", func(t *testing.T) { - // s.RunUpgradeOnrampProposal("OnRamp 1.7.0") - // }) + // ONRAMP SIMPLE UPGRADE + s.T().Run("Upgrade CCIPOnramp through MCMS", func(t *testing.T) { + s.RunUpgradeOnrampProposal("OnRamp 1.7.0") + }) - // s.T().Run("Re-Upgrade CCIPOnramp through MCMS", func(t *testing.T) { - // s.RunUpgradeOnrampProposal("OnRamp 1.8.0") - // }) + s.T().Run("Re-Upgrade CCIPOnramp through MCMS", func(t *testing.T) { + s.RunUpgradeOnrampProposal("OnRamp 1.8.0") + }) - // // ROUTER UPGRADE - // s.T().Run("Upgrade CCIPRouter through MCMS", func(t *testing.T) { - // s.RunUpgradeRouterProposal("Router 1.7.0") - // }) + // ROUTER SIMPLE UPGRADE + s.T().Run("Upgrade CCIPRouter through MCMS", func(t *testing.T) { + s.RunUpgradeRouterProposal("Router 1.7.0") + }) - // s.T().Run("Re-Upgrade CCIPRouter through MCMS", func(t *testing.T) { - // s.RunUpgradeRouterProposal("Router 1.8.0") - // }) + s.T().Run("Re-Upgrade CCIPRouter through MCMS", func(t *testing.T) { + s.RunUpgradeRouterProposal("Router 1.8.0") + }) } +// RunLatestUpgradedCCIPProposal verifies the InternalLatestPackageIDs feature: a single MCMS bypass +// proposal batches two operations targeting two independently-upgraded packages (CCIP and Offramp). +// Both breaking upgrades made their respective add_package_id a no-op, so executing via MCMS with +// OriginalPackageId→LatestPackageId routing must run upgraded bytecode (no-op) while passing +// on-chain registry validation against the original package's proof type. func (s *CCIPMCMSTestSuite) RunLatestUpgradedCCIPProposal() { - // 1. Record the current package_ids before the proposal to compare after execution. - stateObjectBefore, err := s.client.SuiGetObject(s.T().Context(), models.SuiGetObjectRequest{ + // 1. Record the current package_ids lists before the proposal. + ccipStateObjectBefore, err := s.client.SuiGetObject(s.T().Context(), models.SuiGetObjectRequest{ ObjectId: s.ccipObjects.CCIPObjectRefObjectId, Options: models.SuiObjectDataOptions{ShowContent: true}, }) s.Require().NoError(err) - pkgIdsBefore := stateObjectBefore.Data.Content.Fields["package_ids"].([]any) + ccipPkgIdsBefore := ccipStateObjectBefore.Data.Content.Fields["package_ids"].([]any) - // 2. Build an MCMS proposal to call add_package_id routing through the upgraded (breaking) - // package. Because the breaking package's add_package_id is a no-op (push_back was removed), - // latestCcipPackageId should NOT appear in the list after execution — which proves MCMS - // targets the latest package code, not the old one. + offrampStateObjectBefore, err := s.client.SuiGetObject(s.T().Context(), models.SuiGetObjectRequest{ + ObjectId: s.ccipOfframpObjects.StateObjectId, + Options: models.SuiObjectDataOptions{ShowContent: true}, + }) + s.Require().NoError(err) + offrampPkgIdsBefore := offrampStateObjectBefore.Data.Content.Fields["package_ids"].([]any) + + // 2. Build a single bypass proposal batching: + // a) CCIP state_object::add_package_id — upgraded package has push_back removed (no-op) + // b) Offramp::add_package_id — upgraded package has push_back removed (no-op) + // The InternalLatestPackageIDs mechanism routes each PTB MoveCall to the corresponding latest + // (upgraded) package while keeping the original package IDs as on-chain MCMS targets so the + // registry validation passes. input := mcmsops.ProposalGenerateInput{ Defs: []cld_ops.Definition{ ccipops.AddPackageIdStateObjectOp.Def(), + offrampops.AddPackageIdOffRampOp.Def(), }, Inputs: []any{ ccipops.AddPackageIdStateObjectInput{ - PackageId: s.latestCcipPackageId, - CCIPPackageId: s.ccipPackageId, + PackageId: s.ccipPackageId, // original (MCMS registry identity) + LatestPackageId: s.latestCcipPackageId, // upgraded binary (no-op add_package_id) + NewPackageId: s.latestCcipPackageId, // ID to register in CCIP state OwnerCapObjectId: s.ccipObjects.OwnerCapObjectId, CCIPObjectRefObjectId: s.ccipObjects.CCIPObjectRefObjectId, }, + offrampops.AddPackageIdOffRampInput{ + PackageId: s.ccipOfframpPackageId, // original (MCMS registry identity) + LatestPackageId: s.latestCcipOfframpPackageId, // upgraded binary (no-op add_package_id) + NewPackageId: s.latestCcipOfframpPackageId, // ID to register in OffRamp state + OwnerCapObjectId: s.ccipOfframpObjects.OwnerCapId, + StateObjectId: s.ccipOfframpObjects.StateObjectId, + }, }, MmcsPackageID: s.mcmsPackageID, McmsStateObjID: s.mcmsObj, @@ -139,23 +144,36 @@ func (s *CCIPMCMSTestSuite) RunLatestUpgradedCCIPProposal() { }, } report, err := cld_ops.ExecuteSequence(s.bundle, mcmsops.MCMSDynamicProposalGenerateSeq, s.deps, input) - s.Require().NoError(err, "building add_package_id proposal via upgraded CCIP package") + s.Require().NoError(err, "building batch proposal via upgraded CCIP and Offramp packages") // 3. Execute proposal via bypasser — no timelock delay needed. s.ExecuteProposalE2e(&report.Output, s.bypasserConfig, 0) - // 4. Verify the package_ids list did NOT grow: the upgraded add_package_id is a no-op. - stateObjectAfter, err := s.client.SuiGetObject(s.T().Context(), models.SuiGetObjectRequest{ + // 4a. Verify CCIP package_ids did NOT grow: the upgraded add_package_id is a no-op. + ccipStateObjectAfter, err := s.client.SuiGetObject(s.T().Context(), models.SuiGetObjectRequest{ ObjectId: s.ccipObjects.CCIPObjectRefObjectId, Options: models.SuiObjectDataOptions{ShowContent: true}, }) s.Require().NoError(err) - pkgIdsAfter := stateObjectAfter.Data.Content.Fields["package_ids"].([]any) + ccipPkgIdsAfter := ccipStateObjectAfter.Data.Content.Fields["package_ids"].([]any) + + s.Require().Equal(len(ccipPkgIdsBefore), len(ccipPkgIdsAfter), + "CCIP package_ids list should not grow: upgraded add_package_id is a no-op") + s.Require().NotContains(ccipPkgIdsAfter, s.latestCcipPackageId, + "latestCcipPackageId should not be in CCIP package_ids since upgraded add_package_id is a no-op") + + // 4b. Verify Offramp package_ids did NOT grow: the upgraded add_package_id is also a no-op. + offrampStateObjectAfter, err := s.client.SuiGetObject(s.T().Context(), models.SuiGetObjectRequest{ + ObjectId: s.ccipOfframpObjects.StateObjectId, + Options: models.SuiObjectDataOptions{ShowContent: true}, + }) + s.Require().NoError(err) + offrampPkgIdsAfter := offrampStateObjectAfter.Data.Content.Fields["package_ids"].([]any) - s.Require().Equal(len(pkgIdsBefore), len(pkgIdsAfter), - "package_ids list should not grow: upgraded add_package_id is a no-op") - s.Require().NotContains(pkgIdsAfter, s.latestCcipPackageId, - "latestCcipPackageId should not be in package_ids since upgraded add_package_id is a no-op") + s.Require().Equal(len(offrampPkgIdsBefore), len(offrampPkgIdsAfter), + "Offramp package_ids list should not grow: upgraded add_package_id is a no-op") + s.Require().NotContains(offrampPkgIdsAfter, s.latestCcipOfframpPackageId, + "latestCcipOfframpPackageId should not be in Offramp package_ids since upgraded add_package_id is a no-op") } // TODO: For prod env, the initial deployment sequence should start the ownership transfer flow of every deployed contract @@ -259,8 +277,8 @@ func RunTestCCIPFeeQuoterProposal(s *CCIPMCMSTestSuite) { PremiumMultiplierWeiPerEth: []uint64{expectedPremiumMultiplier}, }, ccipops.AddPackageIdStateObjectInput{ - PackageId: s.latestCcipPackageId, - CCIPPackageId: s.ccipPackageId, + PackageId: s.ccipPackageId, // original binary (no upgrade) + NewPackageId: s.latestCcipPackageId, // ID to register in CCIP state OwnerCapObjectId: s.ccipObjects.OwnerCapObjectId, CCIPObjectRefObjectId: s.ccipObjects.CCIPObjectRefObjectId, }, @@ -612,85 +630,12 @@ func (s *CCIPMCMSTestSuite) RegisterRouterUpgradeCap() { s.T().Logf("✅ Registered CCIPRouter UpgradeCap with MCMS deployer") } -func (s *CCIPMCMSTestSuite) RunUpgradeCCIPProposal(newVersion string) { - bind.SetTestModifier(func(packageRoot string) error { - sourcePath := filepath.Join(packageRoot, "sources", "fee_quoter.move") - content, _ := os.ReadFile(sourcePath) - re := regexp.MustCompile(`FeeQuoter \d+\.\d+\.\d+`) - modified := re.ReplaceAllString(string(content), newVersion) - return os.WriteFile(sourcePath, []byte(modified), 0o644) - }) - defer bind.ClearTestModifier() - - signerAddress, err := s.signer.GetAddress() - s.Require().NoError(err, "getting signer address") - - // 1. Build upgrade input for CCIP package - input := mcmsops.UpgradeCCIPInput{ - // Package related - PackageName: contracts.CCIP, - TargetPackageId: s.latestCcipPackageId, - NamedAddresses: map[string]string{ - "signer": signerAddress, - "mcms": s.mcmsPackageID, - "link": s.linkPackageId, - "original_ccip_pkg": s.ccipPackageId, - }, - - ChainSelector: uint64(s.chainSelector), - // MCMS related - MmcsPackageID: s.mcmsPackageID, - McmsStateObjID: s.mcmsObj, - RegistryObjID: s.registryObj, - TimelockObjID: s.timelockObj, - AccountObjID: s.accountObj, - DeployerStateObjID: s.deployerStateObj, - OwnerCapObjID: s.ownerCapObj, // MCMS OwnerCap - - // Timelock related - TimelockConfig: utils.TimelockConfig{ - MCMSAction: types.TimelockActionSchedule, - MinDelay: 5 * time.Second, - OverrideRoot: false, - }, - } - - // 2. Execute operation to generate upgrade proposal - upgradeReport, err := cld_ops.ExecuteOperation(s.NewOpBundle(), mcmsops.UpgradeCCIPOp, s.deps, input) - s.Require().NoError(err, "executing CCIP upgrade operation") - - timelockProposal := upgradeReport.Output - - s.T().Logf("✅ Generated CCIP upgrade proposal: %s", timelockProposal.Description) - - // 3. Execute the upgrade proposal through MCMS using Schedule path - responses := s.ExecuteProposalE2e(&timelockProposal, s.proposerConfig, 6*time.Second) - - tx, ok := responses[len(responses)-1].RawData.(*models.SuiTransactionBlockResponse) - s.Require().True(ok) - - newAddress, err := s.GetUpgradedAddress(tx, s.mcmsPackageID) - s.Require().NoError(err) - s.Require().NotEmpty(newAddress) - - s.T().Logf("✅ Successfully upgraded CCIP package from %s to %s", s.ccipPackageId, newAddress) - - // 4. Verify the new package version - feequoter, err := module_fee_quoter.NewFeeQuoter(newAddress, s.client) - s.Require().NoError(err) - - version, err := feequoter.DevInspect().TypeAndVersion(s.T().Context(), s.deps.GetCallOpts()) - s.Require().NoError(err) - s.Require().Equal(newVersion, version, "fee quoter version should be upgraded to %s", newVersion) - s.latestCcipPackageId = newAddress -} - -func (s *CCIPMCMSTestSuite) RunUpgradeOfframpProposal(newVersion string) { - // Set test modifier to upgrade Offramp version +func (s *CCIPMCMSTestSuite) RunUpgradeOnrampProposal(newVersion string) { + // Set test modifier to upgrade Onramp version bind.SetTestModifier(func(packageRoot string) error { - sourcePath := filepath.Join(packageRoot, "sources", "offramp.move") + sourcePath := filepath.Join(packageRoot, "sources", "onramp.move") content, _ := os.ReadFile(sourcePath) - re := regexp.MustCompile(`OffRamp \d+\.\d+\.\d+`) + re := regexp.MustCompile(`OnRamp \d+\.\d+\.\d+`) modified := re.ReplaceAllString(string(content), newVersion) return os.WriteFile(sourcePath, []byte(modified), 0o644) }) @@ -699,16 +644,16 @@ func (s *CCIPMCMSTestSuite) RunUpgradeOfframpProposal(newVersion string) { signerAddress, err := s.signer.GetAddress() s.Require().NoError(err, "getting signer address") - // 1. Build upgrade input for CCIPOfframp package + // 1. Build upgrade input for CCIPOnramp package input := mcmsops.UpgradeCCIPInput{ // Package related - PackageName: contracts.CCIPOfframp, - TargetPackageId: s.latestCcipOfframpPackageId, + PackageName: contracts.CCIPOnramp, + TargetPackageId: s.latestCcipOnrampPackageId, NamedAddresses: map[string]string{ - "signer": signerAddress, - "mcms": s.mcmsPackageID, - "ccip": s.ccipPackageId, - "original_ccip_offramp_pkg": s.ccipOfframpPackageId, + "signer": signerAddress, + "mcms": s.mcmsPackageID, + "ccip": s.ccipPackageId, + "original_ccip_onramp_pkg": s.ccipOnrampPackageId, }, ChainSelector: uint64(s.chainSelector), @@ -731,11 +676,11 @@ func (s *CCIPMCMSTestSuite) RunUpgradeOfframpProposal(newVersion string) { // 2. Execute operation to generate upgrade proposal upgradeReport, err := cld_ops.ExecuteOperation(s.NewOpBundle(), mcmsops.UpgradeCCIPOp, s.deps, input) - s.Require().NoError(err, "executing CCIPOfframp upgrade operation") + s.Require().NoError(err, "executing CCIPOnramp upgrade operation") timelockProposal := upgradeReport.Output - s.T().Logf("✅ Generated CCIPOfframp upgrade proposal: %s", timelockProposal.Description) + s.T().Logf("✅ Generated CCIPOnramp upgrade proposal: %s", timelockProposal.Description) // 3. Execute the upgrade proposal through MCMS using Schedule path responses := s.ExecuteProposalE2e(&timelockProposal, s.proposerConfig, 6*time.Second) @@ -747,24 +692,24 @@ func (s *CCIPMCMSTestSuite) RunUpgradeOfframpProposal(newVersion string) { s.Require().NoError(err) s.Require().NotEmpty(newAddress) - s.T().Logf("✅ Successfully upgraded CCIPOfframp package from %s to %s", s.ccipOfframpPackageId, newAddress) + s.T().Logf("✅ Successfully upgraded CCIPOnramp package from %s to %s", s.ccipOnrampPackageId, newAddress) // 4. Verify the new package version - offramp, err := module_offramp.NewOfframp(newAddress, s.client) + onramp, err := module_onramp.NewOnramp(newAddress, s.client) s.Require().NoError(err) - version, err := offramp.DevInspect().TypeAndVersion(s.T().Context(), s.deps.GetCallOpts()) + version, err := onramp.DevInspect().TypeAndVersion(s.T().Context(), s.deps.GetCallOpts()) s.Require().NoError(err) - s.Require().Equal(newVersion, version, "offramp version should be upgraded to "+newVersion) - s.latestCcipOfframpPackageId = newAddress + s.Require().Equal(newVersion, version, "onramp version should be upgraded to "+newVersion) + s.latestCcipOnrampPackageId = newAddress } -func (s *CCIPMCMSTestSuite) RunUpgradeOnrampProposal(newVersion string) { - // Set test modifier to upgrade Onramp version +func (s *CCIPMCMSTestSuite) RunUpgradeRouterProposal(newVersion string) { + // Set test modifier to upgrade Router version bind.SetTestModifier(func(packageRoot string) error { - sourcePath := filepath.Join(packageRoot, "sources", "onramp.move") + sourcePath := filepath.Join(packageRoot, "sources", "router.move") content, _ := os.ReadFile(sourcePath) - re := regexp.MustCompile(`OnRamp \d+\.\d+\.\d+`) + re := regexp.MustCompile(`Router \d+\.\d+\.\d+`) modified := re.ReplaceAllString(string(content), newVersion) return os.WriteFile(sourcePath, []byte(modified), 0o644) }) @@ -773,16 +718,15 @@ func (s *CCIPMCMSTestSuite) RunUpgradeOnrampProposal(newVersion string) { signerAddress, err := s.signer.GetAddress() s.Require().NoError(err, "getting signer address") - // 1. Build upgrade input for CCIPOnramp package + // 1. Build upgrade input for CCIPRouter package input := mcmsops.UpgradeCCIPInput{ // Package related - PackageName: contracts.CCIPOnramp, - TargetPackageId: s.latestCcipOnrampPackageId, + PackageName: contracts.CCIPRouter, + TargetPackageId: s.latestCcipRouterPackageId, NamedAddresses: map[string]string{ "signer": signerAddress, "mcms": s.mcmsPackageID, - "ccip": s.ccipPackageId, - "original_ccip_onramp_pkg": s.ccipOnrampPackageId, + "original_ccip_router_pkg": s.ccipRouterPackageId, }, ChainSelector: uint64(s.chainSelector), @@ -805,11 +749,11 @@ func (s *CCIPMCMSTestSuite) RunUpgradeOnrampProposal(newVersion string) { // 2. Execute operation to generate upgrade proposal upgradeReport, err := cld_ops.ExecuteOperation(s.NewOpBundle(), mcmsops.UpgradeCCIPOp, s.deps, input) - s.Require().NoError(err, "executing CCIPOnramp upgrade operation") + s.Require().NoError(err, "executing CCIPRouter upgrade operation") timelockProposal := upgradeReport.Output - s.T().Logf("✅ Generated CCIPOnramp upgrade proposal: %s", timelockProposal.Description) + s.T().Logf("✅ Generated CCIPRouter upgrade proposal: %s", timelockProposal.Description) // 3. Execute the upgrade proposal through MCMS using Schedule path responses := s.ExecuteProposalE2e(&timelockProposal, s.proposerConfig, 6*time.Second) @@ -821,45 +765,49 @@ func (s *CCIPMCMSTestSuite) RunUpgradeOnrampProposal(newVersion string) { s.Require().NoError(err) s.Require().NotEmpty(newAddress) - s.T().Logf("✅ Successfully upgraded CCIPOnramp package from %s to %s", s.ccipOnrampPackageId, newAddress) + s.T().Logf("✅ Successfully upgraded CCIPRouter package from %s to %s", s.ccipRouterPackageId, newAddress) // 4. Verify the new package version - onramp, err := module_onramp.NewOnramp(newAddress, s.client) + router, err := module_router.NewRouter(newAddress, s.client) s.Require().NoError(err) - version, err := onramp.DevInspect().TypeAndVersion(s.T().Context(), s.deps.GetCallOpts()) + version, err := router.DevInspect().TypeAndVersion(s.T().Context(), s.deps.GetCallOpts()) s.Require().NoError(err) - s.Require().Equal(newVersion, version, "onramp version should be upgraded to "+newVersion) - s.latestCcipOnrampPackageId = newAddress + s.Require().Equal(newVersion, version, "router version should be upgraded to "+newVersion) + s.latestCcipRouterPackageId = newAddress } -func (s *CCIPMCMSTestSuite) RunUpgradeRouterProposal(newVersion string) { - // Set test modifier to upgrade Router version +func (s *CCIPMCMSTestSuite) RunUpgradeBreakingOfframpProposal() { bind.SetTestModifier(func(packageRoot string) error { - sourcePath := filepath.Join(packageRoot, "sources", "router.move") - content, _ := os.ReadFile(sourcePath) - re := regexp.MustCompile(`Router \d+\.\d+\.\d+`) - modified := re.ReplaceAllString(string(content), newVersion) - return os.WriteFile(sourcePath, []byte(modified), 0o644) + // Bump OffRamp version so the upgraded package bytecode is recognizably different. + offrampPath := filepath.Join(packageRoot, "sources", "offramp.move") + offrampContent, _ := os.ReadFile(offrampPath) + reVersion := regexp.MustCompile(`OffRamp \d+\.\d+\.\d+`) + if err := os.WriteFile(offrampPath, []byte(reVersion.ReplaceAllString(string(offrampContent), "OffRamp 1.9.0")), 0o644); err != nil { + return err + } + // Remove state.package_ids.push_back(package_id) from add_package_id to simulate a + // breaking change: the upgraded contract's add_package_id becomes a no-op, so any MCMS + // proposal targeting this package will not actually store the package ID. + rePushBack := regexp.MustCompile(`(assert!\(object::id\(owner_cap\) == ownable::owner_cap_id\(&state\.ownable_state\)[^\n]+EInvalidOwnerCap\);\n)\s+state\.package_ids\.push_back\(package_id\);`) + return os.WriteFile(offrampPath, []byte(rePushBack.ReplaceAllString(string(reVersion.ReplaceAllString(string(offrampContent), "OffRamp 1.9.0")), "$1")), 0o644) }) defer bind.ClearTestModifier() signerAddress, err := s.signer.GetAddress() s.Require().NoError(err, "getting signer address") - // 1. Build upgrade input for CCIPRouter package + // Build upgrade input for CCIPOfframp package with the breaking change input := mcmsops.UpgradeCCIPInput{ - // Package related - PackageName: contracts.CCIPRouter, - TargetPackageId: s.latestCcipRouterPackageId, + PackageName: contracts.CCIPOfframp, + TargetPackageId: s.latestCcipOfframpPackageId, NamedAddresses: map[string]string{ - "signer": signerAddress, - "mcms": s.mcmsPackageID, - "original_ccip_router_pkg": s.ccipRouterPackageId, + "signer": signerAddress, + "mcms": s.mcmsPackageID, + "ccip": s.ccipPackageId, + "original_ccip_offramp_pkg": s.ccipOfframpPackageId, }, - - ChainSelector: uint64(s.chainSelector), - // MCMS related + ChainSelector: uint64(s.chainSelector), MmcsPackageID: s.mcmsPackageID, McmsStateObjID: s.mcmsObj, RegistryObjID: s.registryObj, @@ -867,8 +815,6 @@ func (s *CCIPMCMSTestSuite) RunUpgradeRouterProposal(newVersion string) { AccountObjID: s.accountObj, DeployerStateObjID: s.deployerStateObj, OwnerCapObjID: s.ownerCapObj, - - // Timelock related TimelockConfig: utils.TimelockConfig{ MCMSAction: types.TimelockActionSchedule, MinDelay: 5 * time.Second, @@ -876,15 +822,12 @@ func (s *CCIPMCMSTestSuite) RunUpgradeRouterProposal(newVersion string) { }, } - // 2. Execute operation to generate upgrade proposal upgradeReport, err := cld_ops.ExecuteOperation(s.NewOpBundle(), mcmsops.UpgradeCCIPOp, s.deps, input) - s.Require().NoError(err, "executing CCIPRouter upgrade operation") + s.Require().NoError(err, "executing breaking Offramp upgrade operation") timelockProposal := upgradeReport.Output + s.T().Logf("✅ Generated breaking Offramp upgrade proposal: %s", timelockProposal.Description) - s.T().Logf("✅ Generated CCIPRouter upgrade proposal: %s", timelockProposal.Description) - - // 3. Execute the upgrade proposal through MCMS using Schedule path responses := s.ExecuteProposalE2e(&timelockProposal, s.proposerConfig, 6*time.Second) tx, ok := responses[len(responses)-1].RawData.(*models.SuiTransactionBlockResponse) @@ -894,16 +837,15 @@ func (s *CCIPMCMSTestSuite) RunUpgradeRouterProposal(newVersion string) { s.Require().NoError(err) s.Require().NotEmpty(newAddress) - s.T().Logf("✅ Successfully upgraded CCIPRouter package from %s to %s", s.ccipRouterPackageId, newAddress) + s.T().Logf("✅ Successfully upgraded Offramp package (breaking) from %s to %s", s.ccipOfframpPackageId, newAddress) - // 4. Verify the new package version - router, err := module_router.NewRouter(newAddress, s.client) + // Verify the new version + offramp, err := module_offramp.NewOfframp(newAddress, s.client) s.Require().NoError(err) - - version, err := router.DevInspect().TypeAndVersion(s.T().Context(), s.deps.GetCallOpts()) + version, err := offramp.DevInspect().TypeAndVersion(s.T().Context(), s.deps.GetCallOpts()) s.Require().NoError(err) - s.Require().Equal(newVersion, version, "router version should be upgraded to "+newVersion) - s.latestCcipRouterPackageId = newAddress + s.Require().Equal("OffRamp 1.9.0", version, "offramp version should be upgraded to OffRamp 1.9.0") + s.latestCcipOfframpPackageId = newAddress } func (s *CCIPMCMSTestSuite) RunUpgradeBreakingCCIPProposal() { diff --git a/scripts/go.mod b/scripts/go.mod index c10af3ff..a94aef40 100644 --- a/scripts/go.mod +++ b/scripts/go.mod @@ -130,13 +130,13 @@ require ( github.com/smartcontractkit/chainlink-protos/job-distributor v0.17.0 // indirect github.com/smartcontractkit/chainlink-protos/linking-service/go v0.0.0-20251002192024-d2ad9222409b // indirect github.com/smartcontractkit/chainlink-protos/node-platform v0.0.0-20260205130626-db2a2aab956b // indirect - github.com/smartcontractkit/chainlink-sui v0.0.0-20260205175622-33e65031f9a9 // indirect + github.com/smartcontractkit/chainlink-sui v0.0.0-20260408222042-6c7b4c27a8b2 // indirect github.com/smartcontractkit/chainlink-ton v0.0.0-20260219201907-054376f21418 // indirect github.com/smartcontractkit/chainlink-tron/relayer v0.0.11-0.20250908203554-5bd9d2fe9513 // indirect github.com/smartcontractkit/freeport v0.1.3-0.20250828155247-add56fa28aad // indirect github.com/smartcontractkit/grpc-proxy v0.0.0-20240830132753-a7e17fec5ab7 // indirect github.com/smartcontractkit/libocr v0.0.0-20250912173940-f3ab0246e23d // indirect - github.com/smartcontractkit/mcms v0.40.1 // indirect + github.com/smartcontractkit/mcms v0.40.5-0.20260409113859-92ddf0d61451 // indirect github.com/spf13/cast v1.10.0 // indirect github.com/stephenlacy/go-ethereum-hdwallet v0.0.0-20230913225845-a4fa94429863 // indirect github.com/streamingfast/logging v0.0.0-20230608130331-f22c91403091 // indirect diff --git a/scripts/go.sum b/scripts/go.sum index 0f735058..44a3484a 100644 --- a/scripts/go.sum +++ b/scripts/go.sum @@ -652,8 +652,8 @@ github.com/smartcontractkit/grpc-proxy v0.0.0-20240830132753-a7e17fec5ab7 h1:12i github.com/smartcontractkit/grpc-proxy v0.0.0-20240830132753-a7e17fec5ab7/go.mod h1:FX7/bVdoep147QQhsOPkYsPEXhGZjeYx6lBSaSXtZOA= github.com/smartcontractkit/libocr v0.0.0-20250912173940-f3ab0246e23d h1:LokA9PoCNb8mm8mDT52c3RECPMRsGz1eCQORq+J3n74= github.com/smartcontractkit/libocr v0.0.0-20250912173940-f3ab0246e23d/go.mod h1:Acy3BTBxou83ooMESLO90s8PKSu7RvLCzwSTbxxfOK0= -github.com/smartcontractkit/mcms v0.40.1 h1:r9bU/2GfIf6mHHM4PklSBkfi2Lq4+EGILC81e4IqKz0= -github.com/smartcontractkit/mcms v0.40.1/go.mod h1:7YqJPR8w9GiO1L/JjjTrwlSwAZ7i3J7cgOcu88PqtvU= +github.com/smartcontractkit/mcms v0.40.5-0.20260409113859-92ddf0d61451 h1:2l1sSlzCxwKsiE58x3Yq5gZMLnn0OMkrJrOs0tTNMgI= +github.com/smartcontractkit/mcms v0.40.5-0.20260409113859-92ddf0d61451/go.mod h1:VTg6wrglc+efxfc7YIk4AcOZlrw1i+vkj5UqzG2p7K8= github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= From ffbae5a44f2385acc2bf41a0f96066ccd027da1c Mon Sep 17 00:00:00 2001 From: RodrigoAD <15104916+RodrigoAD@users.noreply.github.com> Date: Thu, 9 Apr 2026 12:47:08 +0100 Subject: [PATCH 3/7] tidy modules --- deployment/go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deployment/go.mod b/deployment/go.mod index 94891840..a3db915b 100644 --- a/deployment/go.mod +++ b/deployment/go.mod @@ -16,7 +16,7 @@ require ( github.com/smartcontractkit/chainlink-ccip/deployment v0.0.0-20260311190822-5cbfc939dd16 github.com/smartcontractkit/chainlink-common v0.11.2-0.20260406055916-9aa6b6c0ae81 github.com/smartcontractkit/chainlink-deployments-framework v0.75.0 - github.com/smartcontractkit/chainlink-sui v0.0.0-20260205175622-33e65031f9a9 + github.com/smartcontractkit/chainlink-sui v0.0.0-20260408222042-6c7b4c27a8b2 github.com/smartcontractkit/mcms v0.40.5-0.20260409113859-92ddf0d61451 // Replace with official release before merging github.com/stretchr/testify v1.11.1 golang.org/x/sync v0.19.0 From acf999facf6aca0216019e6d6e97a2defd23f088 Mon Sep 17 00:00:00 2001 From: RodrigoAD <15104916+RodrigoAD@users.noreply.github.com> Date: Thu, 9 Apr 2026 13:16:56 +0100 Subject: [PATCH 4/7] reenable tests --- integration-tests/mcms/runner_test.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/integration-tests/mcms/runner_test.go b/integration-tests/mcms/runner_test.go index 308d8df7..fd70c052 100644 --- a/integration-tests/mcms/runner_test.go +++ b/integration-tests/mcms/runner_test.go @@ -13,15 +13,15 @@ func TestMCMSandCCIPSuite(t *testing.T) { suite.Run(t, new(CCIPMCMSTestSuite)) }) - // t.Run("TokenPoolTestSuite", func(t *testing.T) { - // suite.Run(t, new(TokenPoolTestSuite)) - // }) + t.Run("TokenPoolTestSuite", func(t *testing.T) { + suite.Run(t, new(TokenPoolTestSuite)) + }) - // t.Run("MCMSUserTestSuite", func(t *testing.T) { - // suite.Run(t, new(UpgradeTestSuite)) - // }) + t.Run("MCMSUserTestSuite", func(t *testing.T) { + suite.Run(t, new(UpgradeTestSuite)) + }) - // t.Run("CCIPCurseMCMSSuite", func(t *testing.T) { - // suite.Run(t, new(CCIPCurseMCMSTestSuite)) - // }) + t.Run("CCIPCurseMCMSSuite", func(t *testing.T) { + suite.Run(t, new(CCIPCurseMCMSTestSuite)) + }) } From a7d587dc08f311055cca1cc16541e84469d473be Mon Sep 17 00:00:00 2001 From: RodrigoAD <15104916+RodrigoAD@users.noreply.github.com> Date: Thu, 9 Apr 2026 14:48:03 +0100 Subject: [PATCH 5/7] add block and unblock to latest mcms --- .../changesets/cs_add_ccip_package_id.go | 24 +++--- deployment/changesets/cs_block_function.go | 10 ++- deployment/changesets/cs_block_version.go | 10 ++- deployment/ops/ccip/op_upgrade_registry.go | 76 ++++++++++++++----- .../ops/ccip/op_upgrade_registry_test.go | 6 +- deployment/ops/ccip_onramp/op_deploy.go | 25 ++++-- 6 files changed, 103 insertions(+), 48 deletions(-) diff --git a/deployment/changesets/cs_add_ccip_package_id.go b/deployment/changesets/cs_add_ccip_package_id.go index e90a41a2..ccabf1ce 100644 --- a/deployment/changesets/cs_add_ccip_package_id.go +++ b/deployment/changesets/cs_add_ccip_package_id.go @@ -26,13 +26,16 @@ const ( ) type AddCCIPPackageIdConfig struct { - SuiChainSelector uint64 `yaml:"suiChainSelector"` - Target AddPackageIdTarget `yaml:"target"` - CCIPPackageId string `yaml:"ccipPackageId"` - OnRampPackageId string `yaml:"onRampPackageId,omitempty"` - OffRampPackageId string `yaml:"offRampPackageId,omitempty"` - PackageId string `yaml:"packageId"` - TimelockConfig *utils.TimelockConfig `yaml:"timelockConfig,omitempty"` + SuiChainSelector uint64 `yaml:"suiChainSelector"` + Target AddPackageIdTarget `yaml:"target"` + CCIPPackageId string `yaml:"ccipPackageId"` // original CCIP package ID + LatestCCIPPackageId string `yaml:"latestCcipPackageId,omitempty"` // optional: upgraded CCIP binary + OnRampPackageId string `yaml:"onRampPackageId,omitempty"` + LatestOnRampPackageId string `yaml:"latestOnRampPackageId,omitempty"` // optional: upgraded OnRamp binary + OffRampPackageId string `yaml:"offRampPackageId,omitempty"` + LatestOffRampPackageId string `yaml:"latestOffRampPackageId,omitempty"` // optional: upgraded OffRamp binary + PackageId string `yaml:"packageId"` + TimelockConfig *utils.TimelockConfig `yaml:"timelockConfig,omitempty"` } var _ cldf.ChangeSetV2[AddCCIPPackageIdConfig] = AddCCIPPackageId{} @@ -107,6 +110,7 @@ func executeAddPackageId(e cldf.Environment, config AddCCIPPackageIdConfig, chai case AddPackageIdTargetCCIP: r, err := operations.ExecuteOperation(e.OperationsBundle, ccipops.AddPackageIdStateObjectOp, deps, ccipops.AddPackageIdStateObjectInput{ PackageId: config.CCIPPackageId, + LatestPackageId: config.LatestCCIPPackageId, CCIPObjectRefObjectId: chainState.CCIPObjectRef, OwnerCapObjectId: chainState.CCIPOwnerCapObjectId, NewPackageId: config.PackageId, @@ -118,10 +122,11 @@ func executeAddPackageId(e cldf.Environment, config AddCCIPPackageIdConfig, chai case AddPackageIdTargetOnRamp: r, err := operations.ExecuteOperation(e.OperationsBundle, onrampops.AddPackageIdOp, deps, onrampops.AddPackageIdInput{ - OnRampPackageId: config.OnRampPackageId, + PackageId: config.OnRampPackageId, + LatestPackageId: config.LatestOnRampPackageId, StateObjectId: chainState.OnRampStateObjectId, OwnerCapObjectId: chainState.OnRampOwnerCapObjectId, - PackageId: config.PackageId, + NewPackageId: config.PackageId, }) if err != nil { return operations.Report[any, any]{}, fmt.Errorf("failed to add package ID to OnRamp for Sui chain %d: %w", config.SuiChainSelector, err) @@ -131,6 +136,7 @@ func executeAddPackageId(e cldf.Environment, config AddCCIPPackageIdConfig, chai case AddPackageIdTargetOffRamp: r, err := operations.ExecuteOperation(e.OperationsBundle, offrampops.AddPackageIdOffRampOp, deps, offrampops.AddPackageIdOffRampInput{ PackageId: config.OffRampPackageId, + LatestPackageId: config.LatestOffRampPackageId, StateObjectId: chainState.OffRampStateObjectId, OwnerCapObjectId: chainState.OffRampOwnerCapId, NewPackageId: config.PackageId, diff --git a/deployment/changesets/cs_block_function.go b/deployment/changesets/cs_block_function.go index d887ea8e..fc5c4dc1 100644 --- a/deployment/changesets/cs_block_function.go +++ b/deployment/changesets/cs_block_function.go @@ -18,7 +18,8 @@ import ( type BlockFunctionConfig struct { SuiChainSelector uint64 `yaml:"suiChainSelector"` - CCIPPackageId string `yaml:"ccipPackageId"` + PackageId string `yaml:"packageId"` + LatestPackageId string `yaml:"latestPackageId,omitempty"` ModuleName string `yaml:"moduleName"` FunctionName string `yaml:"functionName"` Version uint8 `yaml:"version"` @@ -56,7 +57,8 @@ func (d BlockFunction) Apply(e cldf.Environment, config BlockFunctionConfig) (cl } report, err := operations.ExecuteOperation(e.OperationsBundle, ccipops.BlockFunctionOp, deps, ccipops.BlockFunctionInput{ - CCIPPackageId: config.CCIPPackageId, + PackageId: config.PackageId, + LatestPackageId: config.LatestPackageId, StateObjectId: chainState.CCIPObjectRef, OwnerCapObjectId: chainState.CCIPOwnerCapObjectId, ModuleName: config.ModuleName, @@ -95,8 +97,8 @@ func (d BlockFunction) Apply(e cldf.Environment, config BlockFunctionConfig) (cl } func (d BlockFunction) VerifyPreconditions(e cldf.Environment, config BlockFunctionConfig) error { - if strings.TrimSpace(config.CCIPPackageId) == "" { - return fmt.Errorf("ccipPackageId is required") + if strings.TrimSpace(config.PackageId) == "" { + return fmt.Errorf("packageId is required") } return nil } diff --git a/deployment/changesets/cs_block_version.go b/deployment/changesets/cs_block_version.go index cf35b368..d889d59e 100644 --- a/deployment/changesets/cs_block_version.go +++ b/deployment/changesets/cs_block_version.go @@ -18,7 +18,8 @@ import ( type BlockVersionConfig struct { SuiChainSelector uint64 `yaml:"suiChainSelector"` - CCIPPackageId string `yaml:"ccipPackageId"` + PackageId string `yaml:"packageId"` + LatestPackageId string `yaml:"latestPackageId,omitempty"` ModuleName string `yaml:"moduleName"` Version uint8 `yaml:"version"` TimelockConfig *utils.TimelockConfig `yaml:"timelockConfig,omitempty"` @@ -55,7 +56,8 @@ func (d BlockVersion) Apply(e cldf.Environment, config BlockVersionConfig) (cldf } report, err := operations.ExecuteOperation(e.OperationsBundle, ccipops.BlockVersionOp, deps, ccipops.BlockVersionInput{ - CCIPPackageId: config.CCIPPackageId, + PackageId: config.PackageId, + LatestPackageId: config.LatestPackageId, StateObjectId: chainState.CCIPObjectRef, OwnerCapObjectId: chainState.CCIPOwnerCapObjectId, ModuleName: config.ModuleName, @@ -93,8 +95,8 @@ func (d BlockVersion) Apply(e cldf.Environment, config BlockVersionConfig) (cldf } func (d BlockVersion) VerifyPreconditions(e cldf.Environment, config BlockVersionConfig) error { - if strings.TrimSpace(config.CCIPPackageId) == "" { - return fmt.Errorf("ccipPackageId is required") + if strings.TrimSpace(config.PackageId) == "" { + return fmt.Errorf("packageId is required") } return nil } diff --git a/deployment/ops/ccip/op_upgrade_registry.go b/deployment/ops/ccip/op_upgrade_registry.go index 98effac2..2e59411c 100644 --- a/deployment/ops/ccip/op_upgrade_registry.go +++ b/deployment/ops/ccip/op_upgrade_registry.go @@ -79,7 +79,8 @@ var UpgradeRegistryInitializeOp = cld_ops.NewOperation( // =================== Version Blocking Operations =================== // type BlockVersionInput struct { - CCIPPackageId string + PackageId string // original package ID (MCMS registry identity; used as binary when LatestPackageId is "") + LatestPackageId string // optional: upgraded package ID (PTB execution target when set) StateObjectId string OwnerCapObjectId string ModuleName string @@ -91,10 +92,14 @@ type BlockVersionObjects struct { } var blockVersionHandler = func(b cld_ops.Bundle, deps sui_ops.OpTxDeps, input BlockVersionInput) (output sui_ops.OpTxResult[BlockVersionObjects], err error) { - if err := requireCCIPPackageID(input.CCIPPackageId); err != nil { + if err := requireCCIPPackageID(input.PackageId); err != nil { return sui_ops.OpTxResult[BlockVersionObjects]{}, err } - contract, err := module_upgrade_registry.NewUpgradeRegistry(input.CCIPPackageId, deps.Client) + binaryPkgId := input.PackageId + if input.LatestPackageId != "" { + binaryPkgId = input.LatestPackageId + } + contract, err := module_upgrade_registry.NewUpgradeRegistry(binaryPkgId, deps.Client) if err != nil { return sui_ops.OpTxResult[BlockVersionObjects]{}, fmt.Errorf("failed to create UpgradeRegistry contract: %w", err) } @@ -109,12 +114,16 @@ var blockVersionHandler = func(b cld_ops.Bundle, deps sui_ops.OpTxDeps, input Bl if err != nil { return sui_ops.OpTxResult[BlockVersionObjects]{}, fmt.Errorf("failed to convert encoded call to TransactionCall: %w", err) } + if input.LatestPackageId != "" { + call.LatestPackageID = call.PackageID // current PackageID is the latest (from binaryPkgId) + call.PackageID = input.PackageId // replace with original for on-chain identity + } if deps.Signer == nil { b.Logger.Infow("Skipping execution of BlockVersion on UpgradeRegistry as per no Signer provided", "moduleName", input.ModuleName, "version", input.Version) return sui_ops.OpTxResult[BlockVersionObjects]{ Digest: "", - PackageId: input.CCIPPackageId, + PackageId: input.PackageId, Objects: BlockVersionObjects{}, Call: call, }, nil @@ -134,7 +143,7 @@ var blockVersionHandler = func(b cld_ops.Bundle, deps sui_ops.OpTxDeps, input Bl return sui_ops.OpTxResult[BlockVersionObjects]{ Digest: tx.Digest, - PackageId: input.CCIPPackageId, + PackageId: input.PackageId, Objects: BlockVersionObjects{}, Call: call, }, nil @@ -150,7 +159,8 @@ var BlockVersionOp = cld_ops.NewOperation( // =================== Unblock Version Operations =================== // type UnblockVersionInput struct { - CCIPPackageId string + PackageId string // original package ID (MCMS registry identity; used as binary when LatestPackageId is "") + LatestPackageId string // optional: upgraded package ID (PTB execution target when set) StateObjectId string OwnerCapObjectId string ModuleName string @@ -162,10 +172,14 @@ type UnblockVersionObjects struct { } var unblockVersionHandler = func(b cld_ops.Bundle, deps sui_ops.OpTxDeps, input UnblockVersionInput) (output sui_ops.OpTxResult[UnblockVersionObjects], err error) { - if err := requireCCIPPackageID(input.CCIPPackageId); err != nil { + if err := requireCCIPPackageID(input.PackageId); err != nil { return sui_ops.OpTxResult[UnblockVersionObjects]{}, err } - contract, err := module_upgrade_registry.NewUpgradeRegistry(input.CCIPPackageId, deps.Client) + binaryPkgId := input.PackageId + if input.LatestPackageId != "" { + binaryPkgId = input.LatestPackageId + } + contract, err := module_upgrade_registry.NewUpgradeRegistry(binaryPkgId, deps.Client) if err != nil { return sui_ops.OpTxResult[UnblockVersionObjects]{}, fmt.Errorf("failed to create UpgradeRegistry contract: %w", err) } @@ -180,12 +194,16 @@ var unblockVersionHandler = func(b cld_ops.Bundle, deps sui_ops.OpTxDeps, input if err != nil { return sui_ops.OpTxResult[UnblockVersionObjects]{}, fmt.Errorf("failed to convert encoded call to TransactionCall: %w", err) } + if input.LatestPackageId != "" { + call.LatestPackageID = call.PackageID // current PackageID is the latest (from binaryPkgId) + call.PackageID = input.PackageId // replace with original for on-chain identity + } if deps.Signer == nil { b.Logger.Infow("Skipping execution of UnblockVersion on UpgradeRegistry as per no Signer provided", "moduleName", input.ModuleName, "version", input.Version) return sui_ops.OpTxResult[UnblockVersionObjects]{ Digest: "", - PackageId: input.CCIPPackageId, + PackageId: input.PackageId, Objects: UnblockVersionObjects{}, Call: call, }, nil @@ -205,7 +223,7 @@ var unblockVersionHandler = func(b cld_ops.Bundle, deps sui_ops.OpTxDeps, input return sui_ops.OpTxResult[UnblockVersionObjects]{ Digest: tx.Digest, - PackageId: input.CCIPPackageId, + PackageId: input.PackageId, Objects: UnblockVersionObjects{}, Call: call, }, nil @@ -221,7 +239,8 @@ var UnblockVersionOp = cld_ops.NewOperation( // =================== Function Blocking Operations =================== // type BlockFunctionInput struct { - CCIPPackageId string + PackageId string // original package ID (MCMS registry identity; used as binary when LatestPackageId is "") + LatestPackageId string // optional: upgraded package ID (PTB execution target when set) StateObjectId string OwnerCapObjectId string ModuleName string @@ -234,10 +253,14 @@ type BlockFunctionObjects struct { } var blockFunctionHandler = func(b cld_ops.Bundle, deps sui_ops.OpTxDeps, input BlockFunctionInput) (output sui_ops.OpTxResult[BlockFunctionObjects], err error) { - if err := requireCCIPPackageID(input.CCIPPackageId); err != nil { + if err := requireCCIPPackageID(input.PackageId); err != nil { return sui_ops.OpTxResult[BlockFunctionObjects]{}, err } - contract, err := module_upgrade_registry.NewUpgradeRegistry(input.CCIPPackageId, deps.Client) + binaryPkgId := input.PackageId + if input.LatestPackageId != "" { + binaryPkgId = input.LatestPackageId + } + contract, err := module_upgrade_registry.NewUpgradeRegistry(binaryPkgId, deps.Client) if err != nil { return sui_ops.OpTxResult[BlockFunctionObjects]{}, fmt.Errorf("failed to create UpgradeRegistry contract: %w", err) } @@ -252,12 +275,16 @@ var blockFunctionHandler = func(b cld_ops.Bundle, deps sui_ops.OpTxDeps, input B if err != nil { return sui_ops.OpTxResult[BlockFunctionObjects]{}, fmt.Errorf("failed to convert encoded call to TransactionCall: %w", err) } + if input.LatestPackageId != "" { + call.LatestPackageID = call.PackageID // current PackageID is the latest (from binaryPkgId) + call.PackageID = input.PackageId // replace with original for on-chain identity + } if deps.Signer == nil { b.Logger.Infow("Skipping execution of BlockFunction on UpgradeRegistry as per no Signer provided", "moduleName", input.ModuleName, "functionName", input.FunctionName, "version", input.Version) return sui_ops.OpTxResult[BlockFunctionObjects]{ Digest: "", - PackageId: input.CCIPPackageId, + PackageId: input.PackageId, Objects: BlockFunctionObjects{}, Call: call, }, nil @@ -278,7 +305,7 @@ var blockFunctionHandler = func(b cld_ops.Bundle, deps sui_ops.OpTxDeps, input B return sui_ops.OpTxResult[BlockFunctionObjects]{ Digest: tx.Digest, - PackageId: input.CCIPPackageId, + PackageId: input.PackageId, Objects: BlockFunctionObjects{}, Call: call, }, nil @@ -294,7 +321,8 @@ var BlockFunctionOp = cld_ops.NewOperation( // =================== Unblock Function Operations =================== // type UnblockFunctionInput struct { - CCIPPackageId string + PackageId string // original package ID (MCMS registry identity; used as binary when LatestPackageId is "") + LatestPackageId string // optional: upgraded package ID (PTB execution target when set) StateObjectId string OwnerCapObjectId string ModuleName string @@ -307,10 +335,14 @@ type UnblockFunctionObjects struct { } var unblockFunctionHandler = func(b cld_ops.Bundle, deps sui_ops.OpTxDeps, input UnblockFunctionInput) (output sui_ops.OpTxResult[UnblockFunctionObjects], err error) { - if err := requireCCIPPackageID(input.CCIPPackageId); err != nil { + if err := requireCCIPPackageID(input.PackageId); err != nil { return sui_ops.OpTxResult[UnblockFunctionObjects]{}, err } - contract, err := module_upgrade_registry.NewUpgradeRegistry(input.CCIPPackageId, deps.Client) + binaryPkgId := input.PackageId + if input.LatestPackageId != "" { + binaryPkgId = input.LatestPackageId + } + contract, err := module_upgrade_registry.NewUpgradeRegistry(binaryPkgId, deps.Client) if err != nil { return sui_ops.OpTxResult[UnblockFunctionObjects]{}, fmt.Errorf("failed to create UpgradeRegistry contract: %w", err) } @@ -325,12 +357,16 @@ var unblockFunctionHandler = func(b cld_ops.Bundle, deps sui_ops.OpTxDeps, input if err != nil { return sui_ops.OpTxResult[UnblockFunctionObjects]{}, fmt.Errorf("failed to convert encoded call to TransactionCall: %w", err) } + if input.LatestPackageId != "" { + call.LatestPackageID = call.PackageID // current PackageID is the latest (from binaryPkgId) + call.PackageID = input.PackageId // replace with original for on-chain identity + } if deps.Signer == nil { b.Logger.Infow("Skipping execution of UnblockFunction on UpgradeRegistry as per no Signer provided", "moduleName", input.ModuleName, "functionName", input.FunctionName, "version", input.Version) return sui_ops.OpTxResult[UnblockFunctionObjects]{ Digest: "", - PackageId: input.CCIPPackageId, + PackageId: input.PackageId, Objects: UnblockFunctionObjects{}, Call: call, }, nil @@ -351,7 +387,7 @@ var unblockFunctionHandler = func(b cld_ops.Bundle, deps sui_ops.OpTxDeps, input return sui_ops.OpTxResult[UnblockFunctionObjects]{ Digest: tx.Digest, - PackageId: input.CCIPPackageId, + PackageId: input.PackageId, Objects: UnblockFunctionObjects{}, Call: call, }, nil diff --git a/deployment/ops/ccip/op_upgrade_registry_test.go b/deployment/ops/ccip/op_upgrade_registry_test.go index 155f585a..8cca8c98 100644 --- a/deployment/ops/ccip/op_upgrade_registry_test.go +++ b/deployment/ops/ccip/op_upgrade_registry_test.go @@ -129,7 +129,7 @@ func TestUpgradeRegistryOperations(t *testing.T) { t.Run("Test Version Blocking", func(t *testing.T) { // Test blocking a version _, err := cld_ops.ExecuteOperation(bundle, BlockVersionOp, deps, BlockVersionInput{ - CCIPPackageId: report.Output.CCIPPackageId, + PackageId: report.Output.CCIPPackageId, StateObjectId: report.Output.Objects.CCIPObjectRefObjectId, OwnerCapObjectId: report.Output.Objects.OwnerCapObjectId, ModuleName: "test_module", @@ -172,7 +172,7 @@ func TestUpgradeRegistryOperations(t *testing.T) { t.Run("Test Function Blocking", func(t *testing.T) { // Test blocking a specific function _, err := cld_ops.ExecuteOperation(bundle, BlockFunctionOp, deps, BlockFunctionInput{ - CCIPPackageId: report.Output.CCIPPackageId, + PackageId: report.Output.CCIPPackageId, StateObjectId: report.Output.Objects.CCIPObjectRefObjectId, OwnerCapObjectId: report.Output.Objects.OwnerCapObjectId, ModuleName: "test_module_2", @@ -218,7 +218,7 @@ func TestUpgradeRegistryOperations(t *testing.T) { // Test verifying a blocked function (should fail) // First block the function _, err = cld_ops.ExecuteOperation(bundle, BlockFunctionOp, deps, BlockFunctionInput{ - CCIPPackageId: report.Output.CCIPPackageId, + PackageId: report.Output.CCIPPackageId, StateObjectId: report.Output.Objects.CCIPObjectRefObjectId, OwnerCapObjectId: report.Output.Objects.OwnerCapObjectId, ModuleName: "test_module_3", diff --git a/deployment/ops/ccip_onramp/op_deploy.go b/deployment/ops/ccip_onramp/op_deploy.go index e8f673e1..3eb1400a 100644 --- a/deployment/ops/ccip_onramp/op_deploy.go +++ b/deployment/ops/ccip_onramp/op_deploy.go @@ -515,10 +515,11 @@ var GetDestChainConfigOp = cld_ops.NewOperation( ) type AddPackageIdInput struct { - OnRampPackageId string + PackageId string // original package ID (MCMS registry identity; used as binary when LatestPackageId is "") + LatestPackageId string // optional: upgraded package ID (PTB execution target when set) StateObjectId string OwnerCapObjectId string - PackageId string + NewPackageId string // the package ID to register in the OnRamp state } type AddPackageIdObjects struct { @@ -526,7 +527,11 @@ type AddPackageIdObjects struct { } var addPackageIdHandler = func(b cld_ops.Bundle, deps sui_ops.OpTxDeps, input AddPackageIdInput) (output sui_ops.OpTxResult[AddPackageIdObjects], err error) { - onRampPackage, err := module_onramp.NewOnramp(input.OnRampPackageId, deps.Client) + binaryPkgId := input.PackageId + if input.LatestPackageId != "" { + binaryPkgId = input.LatestPackageId + } + onRampPackage, err := module_onramp.NewOnramp(binaryPkgId, deps.Client) if err != nil { return sui_ops.OpTxResult[AddPackageIdObjects]{}, err } @@ -534,7 +539,7 @@ var addPackageIdHandler = func(b cld_ops.Bundle, deps sui_ops.OpTxDeps, input Ad encodedCall, err := onRampPackage.Encoder().AddPackageId( bind.Object{Id: input.StateObjectId}, bind.Object{Id: input.OwnerCapObjectId}, - input.PackageId, + input.NewPackageId, ) if err != nil { return sui_ops.OpTxResult[AddPackageIdObjects]{}, fmt.Errorf("failed to encode AddPackageId call: %w", err) @@ -543,11 +548,15 @@ var addPackageIdHandler = func(b cld_ops.Bundle, deps sui_ops.OpTxDeps, input Ad if err != nil { return sui_ops.OpTxResult[AddPackageIdObjects]{}, fmt.Errorf("failed to convert encoded call to TransactionCall: %w", err) } + if input.LatestPackageId != "" { + call.LatestPackageID = call.PackageID // current PackageID is the latest (from binaryPkgId) + call.PackageID = input.PackageId // replace with original for on-chain identity + } if deps.Signer == nil { - b.Logger.Infow("Skipping execution of AddPackageId on OnRamp as per no Signer provided", "packageId", input.PackageId) + b.Logger.Infow("Skipping execution of AddPackageId on OnRamp as per no Signer provided", "newPackageId", input.NewPackageId) return sui_ops.OpTxResult[AddPackageIdObjects]{ Digest: "", - PackageId: input.OnRampPackageId, + PackageId: input.PackageId, Objects: AddPackageIdObjects{}, Call: call, }, nil @@ -564,11 +573,11 @@ var addPackageIdHandler = func(b cld_ops.Bundle, deps sui_ops.OpTxDeps, input Ad return sui_ops.OpTxResult[AddPackageIdObjects]{}, fmt.Errorf("failed to execute AddPackageId on OnRamp: %w", err) } - b.Logger.Infow("Package ID added to OnRamp", "packageId", input.PackageId) + b.Logger.Infow("Package ID added to OnRamp", "newPackageId", input.NewPackageId) return sui_ops.OpTxResult[AddPackageIdObjects]{ Digest: tx.Digest, - PackageId: input.OnRampPackageId, + PackageId: input.PackageId, Objects: AddPackageIdObjects{}, Call: call, }, nil From 95b9481feedbf892e4aef7184f0da08c517b299e Mon Sep 17 00:00:00 2001 From: RodrigoAD <15104916+RodrigoAD@users.noreply.github.com> Date: Thu, 9 Apr 2026 17:03:15 +0100 Subject: [PATCH 6/7] add support for add allowed modules. bump mcms --- deployment/go.mod | 2 +- deployment/go.sum | 4 + deployment/ops/ccip/op_registry.go | 1 + deployment/ops/ccip/op_state_object.go | 108 +++++++++++++++++++++++++ integration-tests/go.mod | 2 +- integration-tests/go.sum | 2 + integration-tests/mcms/ccip_test.go | 7 ++ 7 files changed, 124 insertions(+), 2 deletions(-) diff --git a/deployment/go.mod b/deployment/go.mod index a3db915b..2ddf6577 100644 --- a/deployment/go.mod +++ b/deployment/go.mod @@ -17,7 +17,7 @@ require ( github.com/smartcontractkit/chainlink-common v0.11.2-0.20260406055916-9aa6b6c0ae81 github.com/smartcontractkit/chainlink-deployments-framework v0.75.0 github.com/smartcontractkit/chainlink-sui v0.0.0-20260408222042-6c7b4c27a8b2 - github.com/smartcontractkit/mcms v0.40.5-0.20260409113859-92ddf0d61451 // Replace with official release before merging + github.com/smartcontractkit/mcms v0.41.0 // Replace with official release before merging github.com/stretchr/testify v1.11.1 golang.org/x/sync v0.19.0 ) diff --git a/deployment/go.sum b/deployment/go.sum index 44a3484a..3acb6a86 100644 --- a/deployment/go.sum +++ b/deployment/go.sum @@ -652,8 +652,12 @@ github.com/smartcontractkit/grpc-proxy v0.0.0-20240830132753-a7e17fec5ab7 h1:12i github.com/smartcontractkit/grpc-proxy v0.0.0-20240830132753-a7e17fec5ab7/go.mod h1:FX7/bVdoep147QQhsOPkYsPEXhGZjeYx6lBSaSXtZOA= github.com/smartcontractkit/libocr v0.0.0-20250912173940-f3ab0246e23d h1:LokA9PoCNb8mm8mDT52c3RECPMRsGz1eCQORq+J3n74= github.com/smartcontractkit/libocr v0.0.0-20250912173940-f3ab0246e23d/go.mod h1:Acy3BTBxou83ooMESLO90s8PKSu7RvLCzwSTbxxfOK0= +github.com/smartcontractkit/mcms v0.40.4 h1:bsINpoeiLREkLd4hY/87Wc4vhechi+cEhzda6VMecgg= +github.com/smartcontractkit/mcms v0.40.4/go.mod h1:VTg6wrglc+efxfc7YIk4AcOZlrw1i+vkj5UqzG2p7K8= github.com/smartcontractkit/mcms v0.40.5-0.20260409113859-92ddf0d61451 h1:2l1sSlzCxwKsiE58x3Yq5gZMLnn0OMkrJrOs0tTNMgI= github.com/smartcontractkit/mcms v0.40.5-0.20260409113859-92ddf0d61451/go.mod h1:VTg6wrglc+efxfc7YIk4AcOZlrw1i+vkj5UqzG2p7K8= +github.com/smartcontractkit/mcms v0.41.0 h1:SKRR/znedyDmj11isYgUW00j9+/n3FwGPHi1L/QWFfk= +github.com/smartcontractkit/mcms v0.41.0/go.mod h1:VTg6wrglc+efxfc7YIk4AcOZlrw1i+vkj5UqzG2p7K8= github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= diff --git a/deployment/ops/ccip/op_registry.go b/deployment/ops/ccip/op_registry.go index b8040bf9..54edf750 100644 --- a/deployment/ops/ccip/op_registry.go +++ b/deployment/ops/ccip/op_registry.go @@ -21,6 +21,7 @@ var AllOperationsCCIP = []any{ *TransferOwnershipStateObjectOp, *AcceptOwnershipStateObjectOp, *ExecuteOwnershipTransferToMcmsStateObjectOp, + *AddAllowedModulesStateObjectOp, // Token Admin Registry Operations *TokenAdminRegistryInitializeOp, *TokenAdminRegistryUnregisterPoolOp, diff --git a/deployment/ops/ccip/op_state_object.go b/deployment/ops/ccip/op_state_object.go index 49200653..f51af4b0 100644 --- a/deployment/ops/ccip/op_state_object.go +++ b/deployment/ops/ccip/op_state_object.go @@ -1,7 +1,9 @@ package ccipops import ( + "encoding/hex" "fmt" + "strings" "github.com/Masterminds/semver/v3" @@ -549,3 +551,109 @@ var ExecuteOwnershipTransferToMcmsStateObjectOp = cld_ops.NewOperation( "Executes ownership transfer to MCMS for the CCIP StateObject", executeOwnershipTransferToMcmsStateObjectHandler, ) + +// AddAllowedModulesStateObjectInput configures mcms_registry::add_allowed_modules +type AddAllowedModulesStateObjectInput struct { + PackageId string // original CCIP package ID (MCMS registry identity; used as binary when LatestPackageId is "") + LatestPackageId string // optional: upgraded CCIP package ID (PTB execution target when set) + MCMSRegistryObjId string // mcms_registry Registry shared object ID + AllowedModules []string +} + +type AddAllowedModulesStateObjectObjects struct{} + +// bcsAppendULEB128 appends n encoded as an unsigned LEB128 integer to data (BCS length encoding). +func bcsAppendULEB128(data []byte, n uint64) []byte { + for { + b := byte(n & 0x7F) + n >>= 7 + if n == 0 { + return append(data, b) + } + data = append(data, b|0x80) + } +} + +var addAllowedModulesStateObjectHandler = func(b cld_ops.Bundle, deps sui_ops.OpTxDeps, input AddAllowedModulesStateObjectInput) (output sui_ops.OpTxResult[AddAllowedModulesStateObjectObjects], err error) { + if strings.TrimSpace(input.PackageId) == "" { + return sui_ops.OpTxResult[AddAllowedModulesStateObjectObjects]{}, fmt.Errorf("PackageId is required") + } + if len(input.AllowedModules) == 0 { + return sui_ops.OpTxResult[AddAllowedModulesStateObjectObjects]{}, fmt.Errorf("AllowedModules must not be empty") + } + + // When the package has been upgraded, PTB must target the latest bytecode for execution. + binaryPkgId := input.PackageId + if input.LatestPackageId != "" { + binaryPkgId = input.LatestPackageId + } + + // state_object::mcms_add_allowed_modules routes to mcms_registry::add_allowed_modules internally. + // There is no direct (non-MCMS) add_allowed_modules function; this op is for proposal generation only. + // The function BCS-decodes data as: + // 1. validate_obj_addr(registry) → 32 raw address bytes + // 2. deserialize_vector>(modules) → ULEB128(N) + each: ULEB128(len)+bytes + registryHex := strings.TrimPrefix(strings.TrimSpace(input.MCMSRegistryObjId), "0x") + if len(registryHex)%2 != 0 { + registryHex = "0" + registryHex + } + registryBytes, err := hex.DecodeString(registryHex) + if err != nil { + return sui_ops.OpTxResult[AddAllowedModulesStateObjectObjects]{}, fmt.Errorf("invalid MCMSRegistryObjId: %w", err) + } + if len(registryBytes) < 32 { + padded := make([]byte, 32) + copy(padded[32-len(registryBytes):], registryBytes) + registryBytes = padded + } + if len(registryBytes) != 32 { + return sui_ops.OpTxResult[AddAllowedModulesStateObjectObjects]{}, fmt.Errorf("MCMSRegistryObjId must be a 32-byte Sui address, got %d bytes", len(registryBytes)) + } + + data := make([]byte, 0, 32+1+len(input.AllowedModules)*16) + data = append(data, registryBytes...) + data = bcsAppendULEB128(data, uint64(len(input.AllowedModules))) + for _, m := range input.AllowedModules { + modBytes := []byte(m) + data = bcsAppendULEB128(data, uint64(len(modBytes))) + data = append(data, modBytes...) + } + + call := sui_ops.TransactionCall{ + PackageID: binaryPkgId, + Module: "state_object", + Function: "add_allowed_modules", + Data: data, + StateObjID: input.MCMSRegistryObjId, + TypeArgs: []string{}, + } + // When the package has been upgraded, the on-chain MCMS registry still holds the original package's + // proof type, so tx.To must be the original package ID. The PTB MoveCall must target the latest package + // so upgraded bytecode runs. Use LatestPackageID so the proposal generator can separate the two. + if input.LatestPackageId != "" { + call.LatestPackageID = call.PackageID // current PackageID is the latest (from binaryPkgId) + call.PackageID = input.PackageId // replace with original for on-chain MCMS identity + } + + if deps.Signer == nil { + b.Logger.Infow("Skipping execution of add_allowed_modules on StateObject as per no Signer provided", + "registry", input.MCMSRegistryObjId, "modules", input.AllowedModules) + return sui_ops.OpTxResult[AddAllowedModulesStateObjectObjects]{ + Digest: "", + PackageId: input.PackageId, + Objects: AddAllowedModulesStateObjectObjects{}, + Call: call, + }, nil + } + + return sui_ops.OpTxResult[AddAllowedModulesStateObjectObjects]{}, fmt.Errorf( + "direct execution of state_object::add_allowed_modules is not supported; the function can only be invoked through the MCMS executor", + ) +} + +var AddAllowedModulesStateObjectOp = cld_ops.NewOperation( + sui_ops.NewSuiOperationName("ccip", "state_object", "add_allowed_modules"), + semver.MustParse("0.1.0"), + "Adds module names to the MCMS registry allowlist via the CCIP state_object MCMS entrypoint (state_object::add_allowed_modules)", + addAllowedModulesStateObjectHandler, +) diff --git a/integration-tests/go.mod b/integration-tests/go.mod index bf7d2020..3a5813e0 100644 --- a/integration-tests/go.mod +++ b/integration-tests/go.mod @@ -12,7 +12,7 @@ require ( github.com/smartcontractkit/chainlink-deployments-framework v0.75.0 github.com/smartcontractkit/chainlink-sui v0.0.0-20260408222042-6c7b4c27a8b2 github.com/smartcontractkit/chainlink-sui/deployment v0.0.0-20250903045200-c3d973201e55 - github.com/smartcontractkit/mcms v0.40.5-0.20260409113859-92ddf0d61451 // Replace with official release before merging + github.com/smartcontractkit/mcms v0.41.0 // Replace with official release before merging github.com/stretchr/testify v1.11.1 ) diff --git a/integration-tests/go.sum b/integration-tests/go.sum index ca0b00c5..edc3fde1 100644 --- a/integration-tests/go.sum +++ b/integration-tests/go.sum @@ -658,6 +658,8 @@ github.com/smartcontractkit/libocr v0.0.0-20250912173940-f3ab0246e23d h1:LokA9Po github.com/smartcontractkit/libocr v0.0.0-20250912173940-f3ab0246e23d/go.mod h1:Acy3BTBxou83ooMESLO90s8PKSu7RvLCzwSTbxxfOK0= github.com/smartcontractkit/mcms v0.40.5-0.20260409113859-92ddf0d61451 h1:2l1sSlzCxwKsiE58x3Yq5gZMLnn0OMkrJrOs0tTNMgI= github.com/smartcontractkit/mcms v0.40.5-0.20260409113859-92ddf0d61451/go.mod h1:VTg6wrglc+efxfc7YIk4AcOZlrw1i+vkj5UqzG2p7K8= +github.com/smartcontractkit/mcms v0.41.0 h1:SKRR/znedyDmj11isYgUW00j9+/n3FwGPHi1L/QWFfk= +github.com/smartcontractkit/mcms v0.41.0/go.mod h1:VTg6wrglc+efxfc7YIk4AcOZlrw1i+vkj5UqzG2p7K8= github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= diff --git a/integration-tests/mcms/ccip_test.go b/integration-tests/mcms/ccip_test.go index faf62834..50bb0d79 100644 --- a/integration-tests/mcms/ccip_test.go +++ b/integration-tests/mcms/ccip_test.go @@ -112,6 +112,7 @@ func (s *CCIPMCMSTestSuite) RunLatestUpgradedCCIPProposal() { input := mcmsops.ProposalGenerateInput{ Defs: []cld_ops.Definition{ ccipops.AddPackageIdStateObjectOp.Def(), + ccipops.AddAllowedModulesStateObjectOp.Def(), offrampops.AddPackageIdOffRampOp.Def(), }, Inputs: []any{ @@ -122,6 +123,12 @@ func (s *CCIPMCMSTestSuite) RunLatestUpgradedCCIPProposal() { OwnerCapObjectId: s.ccipObjects.OwnerCapObjectId, CCIPObjectRefObjectId: s.ccipObjects.CCIPObjectRefObjectId, }, + ccipops.AddAllowedModulesStateObjectInput{ + PackageId: s.ccipPackageId, // original (MCMS registry identity) + LatestPackageId: s.latestCcipPackageId, // upgraded binary (no-op add_allowed_modules) + MCMSRegistryObjId: s.registryObj, + AllowedModules: []string{"dummy"}, + }, offrampops.AddPackageIdOffRampInput{ PackageId: s.ccipOfframpPackageId, // original (MCMS registry identity) LatestPackageId: s.latestCcipOfframpPackageId, // upgraded binary (no-op add_package_id) From 5aef9bc48ed823461782e1b32c377b0921de4978 Mon Sep 17 00:00:00 2001 From: RodrigoAD <15104916+RodrigoAD@users.noreply.github.com> Date: Thu, 9 Apr 2026 17:06:37 +0100 Subject: [PATCH 7/7] tidy modules --- deployment/go.sum | 4 ---- integration-tests/go.sum | 2 -- scripts/go.mod | 2 +- scripts/go.sum | 4 ++-- 4 files changed, 3 insertions(+), 9 deletions(-) diff --git a/deployment/go.sum b/deployment/go.sum index 3acb6a86..197e12f4 100644 --- a/deployment/go.sum +++ b/deployment/go.sum @@ -652,10 +652,6 @@ github.com/smartcontractkit/grpc-proxy v0.0.0-20240830132753-a7e17fec5ab7 h1:12i github.com/smartcontractkit/grpc-proxy v0.0.0-20240830132753-a7e17fec5ab7/go.mod h1:FX7/bVdoep147QQhsOPkYsPEXhGZjeYx6lBSaSXtZOA= github.com/smartcontractkit/libocr v0.0.0-20250912173940-f3ab0246e23d h1:LokA9PoCNb8mm8mDT52c3RECPMRsGz1eCQORq+J3n74= github.com/smartcontractkit/libocr v0.0.0-20250912173940-f3ab0246e23d/go.mod h1:Acy3BTBxou83ooMESLO90s8PKSu7RvLCzwSTbxxfOK0= -github.com/smartcontractkit/mcms v0.40.4 h1:bsINpoeiLREkLd4hY/87Wc4vhechi+cEhzda6VMecgg= -github.com/smartcontractkit/mcms v0.40.4/go.mod h1:VTg6wrglc+efxfc7YIk4AcOZlrw1i+vkj5UqzG2p7K8= -github.com/smartcontractkit/mcms v0.40.5-0.20260409113859-92ddf0d61451 h1:2l1sSlzCxwKsiE58x3Yq5gZMLnn0OMkrJrOs0tTNMgI= -github.com/smartcontractkit/mcms v0.40.5-0.20260409113859-92ddf0d61451/go.mod h1:VTg6wrglc+efxfc7YIk4AcOZlrw1i+vkj5UqzG2p7K8= github.com/smartcontractkit/mcms v0.41.0 h1:SKRR/znedyDmj11isYgUW00j9+/n3FwGPHi1L/QWFfk= github.com/smartcontractkit/mcms v0.41.0/go.mod h1:VTg6wrglc+efxfc7YIk4AcOZlrw1i+vkj5UqzG2p7K8= github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= diff --git a/integration-tests/go.sum b/integration-tests/go.sum index edc3fde1..c2b8557e 100644 --- a/integration-tests/go.sum +++ b/integration-tests/go.sum @@ -656,8 +656,6 @@ github.com/smartcontractkit/grpc-proxy v0.0.0-20240830132753-a7e17fec5ab7 h1:12i github.com/smartcontractkit/grpc-proxy v0.0.0-20240830132753-a7e17fec5ab7/go.mod h1:FX7/bVdoep147QQhsOPkYsPEXhGZjeYx6lBSaSXtZOA= github.com/smartcontractkit/libocr v0.0.0-20250912173940-f3ab0246e23d h1:LokA9PoCNb8mm8mDT52c3RECPMRsGz1eCQORq+J3n74= github.com/smartcontractkit/libocr v0.0.0-20250912173940-f3ab0246e23d/go.mod h1:Acy3BTBxou83ooMESLO90s8PKSu7RvLCzwSTbxxfOK0= -github.com/smartcontractkit/mcms v0.40.5-0.20260409113859-92ddf0d61451 h1:2l1sSlzCxwKsiE58x3Yq5gZMLnn0OMkrJrOs0tTNMgI= -github.com/smartcontractkit/mcms v0.40.5-0.20260409113859-92ddf0d61451/go.mod h1:VTg6wrglc+efxfc7YIk4AcOZlrw1i+vkj5UqzG2p7K8= github.com/smartcontractkit/mcms v0.41.0 h1:SKRR/znedyDmj11isYgUW00j9+/n3FwGPHi1L/QWFfk= github.com/smartcontractkit/mcms v0.41.0/go.mod h1:VTg6wrglc+efxfc7YIk4AcOZlrw1i+vkj5UqzG2p7K8= github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= diff --git a/scripts/go.mod b/scripts/go.mod index a94aef40..008aaf2c 100644 --- a/scripts/go.mod +++ b/scripts/go.mod @@ -136,7 +136,7 @@ require ( github.com/smartcontractkit/freeport v0.1.3-0.20250828155247-add56fa28aad // indirect github.com/smartcontractkit/grpc-proxy v0.0.0-20240830132753-a7e17fec5ab7 // indirect github.com/smartcontractkit/libocr v0.0.0-20250912173940-f3ab0246e23d // indirect - github.com/smartcontractkit/mcms v0.40.5-0.20260409113859-92ddf0d61451 // indirect + github.com/smartcontractkit/mcms v0.41.0 // indirect github.com/spf13/cast v1.10.0 // indirect github.com/stephenlacy/go-ethereum-hdwallet v0.0.0-20230913225845-a4fa94429863 // indirect github.com/streamingfast/logging v0.0.0-20230608130331-f22c91403091 // indirect diff --git a/scripts/go.sum b/scripts/go.sum index 44a3484a..197e12f4 100644 --- a/scripts/go.sum +++ b/scripts/go.sum @@ -652,8 +652,8 @@ github.com/smartcontractkit/grpc-proxy v0.0.0-20240830132753-a7e17fec5ab7 h1:12i github.com/smartcontractkit/grpc-proxy v0.0.0-20240830132753-a7e17fec5ab7/go.mod h1:FX7/bVdoep147QQhsOPkYsPEXhGZjeYx6lBSaSXtZOA= github.com/smartcontractkit/libocr v0.0.0-20250912173940-f3ab0246e23d h1:LokA9PoCNb8mm8mDT52c3RECPMRsGz1eCQORq+J3n74= github.com/smartcontractkit/libocr v0.0.0-20250912173940-f3ab0246e23d/go.mod h1:Acy3BTBxou83ooMESLO90s8PKSu7RvLCzwSTbxxfOK0= -github.com/smartcontractkit/mcms v0.40.5-0.20260409113859-92ddf0d61451 h1:2l1sSlzCxwKsiE58x3Yq5gZMLnn0OMkrJrOs0tTNMgI= -github.com/smartcontractkit/mcms v0.40.5-0.20260409113859-92ddf0d61451/go.mod h1:VTg6wrglc+efxfc7YIk4AcOZlrw1i+vkj5UqzG2p7K8= +github.com/smartcontractkit/mcms v0.41.0 h1:SKRR/znedyDmj11isYgUW00j9+/n3FwGPHi1L/QWFfk= +github.com/smartcontractkit/mcms v0.41.0/go.mod h1:VTg6wrglc+efxfc7YIk4AcOZlrw1i+vkj5UqzG2p7K8= github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=