diff --git a/deployment/changesets/cs_block_function.go b/deployment/changesets/cs_block_function.go index d887ea8e..eb00c4e2 100644 --- a/deployment/changesets/cs_block_function.go +++ b/deployment/changesets/cs_block_function.go @@ -22,6 +22,7 @@ type BlockFunctionConfig struct { ModuleName string `yaml:"moduleName"` FunctionName string `yaml:"functionName"` Version uint8 `yaml:"version"` + LatestPackageId string `yaml:"latestPackageId,omitempty"` TimelockConfig *utils.TimelockConfig `yaml:"timelockConfig,omitempty"` } @@ -62,6 +63,7 @@ func (d BlockFunction) Apply(e cldf.Environment, config BlockFunctionConfig) (cl ModuleName: config.ModuleName, FunctionName: config.FunctionName, Version: config.Version, + LatestPackageId: config.LatestPackageId, }) if err != nil { return cldf.ChangesetOutput{}, fmt.Errorf("failed to block function for Sui chain %d: %w", config.SuiChainSelector, err) diff --git a/deployment/changesets/cs_block_version.go b/deployment/changesets/cs_block_version.go index cf35b368..31002879 100644 --- a/deployment/changesets/cs_block_version.go +++ b/deployment/changesets/cs_block_version.go @@ -21,6 +21,7 @@ type BlockVersionConfig struct { CCIPPackageId string `yaml:"ccipPackageId"` ModuleName string `yaml:"moduleName"` Version uint8 `yaml:"version"` + LatestPackageId string `yaml:"latestPackageId,omitempty"` TimelockConfig *utils.TimelockConfig `yaml:"timelockConfig,omitempty"` } @@ -60,6 +61,7 @@ func (d BlockVersion) Apply(e cldf.Environment, config BlockVersionConfig) (cldf OwnerCapObjectId: chainState.CCIPOwnerCapObjectId, ModuleName: config.ModuleName, Version: config.Version, + LatestPackageId: config.LatestPackageId, }) if err != nil { return cldf.ChangesetOutput{}, fmt.Errorf("failed to block version for Sui chain %d: %w", config.SuiChainSelector, err) diff --git a/deployment/ops/ccip/op_state_object.go b/deployment/ops/ccip/op_state_object.go index db87d70b..153245ad 100644 --- a/deployment/ops/ccip/op_state_object.go +++ b/deployment/ops/ccip/op_state_object.go @@ -26,7 +26,7 @@ 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) + contract, err := module_state_object.NewStateObject(input.PackageId, deps.Client) if err != nil { return sui_ops.OpTxResult[AddPackageIdStateObjectObjects]{}, fmt.Errorf("failed to create StateObject contract: %w", err) } @@ -91,7 +91,7 @@ type RemovePackageIdStateObjectObjects struct { } var removePackageIdStateObjectHandler = func(b cld_ops.Bundle, deps sui_ops.OpTxDeps, input RemovePackageIdStateObjectInput) (output sui_ops.OpTxResult[RemovePackageIdStateObjectObjects], err error) { - contract, err := module_state_object.NewStateObject(input.CCIPPackageId, deps.Client) + contract, err := module_state_object.NewStateObject(input.PackageId, deps.Client) if err != nil { return sui_ops.OpTxResult[RemovePackageIdStateObjectObjects]{}, fmt.Errorf("failed to create StateObject contract: %w", err) } diff --git a/deployment/ops/ccip/op_state_object_test.go b/deployment/ops/ccip/op_state_object_test.go index 9fbb2603..e760416b 100644 --- a/deployment/ops/ccip/op_state_object_test.go +++ b/deployment/ops/ccip/op_state_object_test.go @@ -86,32 +86,31 @@ 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, CCIPObjectRefObjectId: ccipReport.Output.Objects.CCIPObjectRefObjectId, OwnerCapObjectId: ccipReport.Output.Objects.OwnerCapObjectId, - PackageId: newPackageId, + PackageId: ccipReport.Output.PackageId, }) require.NoError(t, err, "failed to add package ID") require.NotEmpty(t, addReport.Output.Digest, "add package ID transaction should have a digest") }) t.Run("Test Remove Package ID", func(t *testing.T) { - // First add a package ID to remove - newPackageId := "0xabcdef1234567890abcdef1234567890abcdef12" + // First add the package ID so we can remove it _, err := cld_ops.ExecuteOperation(bundle, AddPackageIdStateObjectOp, deps, AddPackageIdStateObjectInput{ CCIPPackageId: ccipReport.Output.PackageId, CCIPObjectRefObjectId: ccipReport.Output.Objects.CCIPObjectRefObjectId, OwnerCapObjectId: ccipReport.Output.Objects.OwnerCapObjectId, - PackageId: newPackageId, + PackageId: ccipReport.Output.PackageId, }) + require.NoError(t, err, "failed to add package ID for removal test") // Now remove the package ID removeReport, err := cld_ops.ExecuteOperation(bundle, RemovePackageIdStateObjectOp, deps, RemovePackageIdStateObjectInput{ CCIPPackageId: ccipReport.Output.PackageId, CCIPObjectRefObjectId: ccipReport.Output.Objects.CCIPObjectRefObjectId, OwnerCapObjectId: ccipReport.Output.Objects.OwnerCapObjectId, - PackageId: newPackageId, + PackageId: ccipReport.Output.PackageId, }) require.NoError(t, err, "failed to remove package ID") require.NotEmpty(t, removeReport.Output.Digest, "remove package ID transaction should have a digest") diff --git a/deployment/ops/ccip/op_upgrade_registry.go b/deployment/ops/ccip/op_upgrade_registry.go index 98effac2..ec11f15b 100644 --- a/deployment/ops/ccip/op_upgrade_registry.go +++ b/deployment/ops/ccip/op_upgrade_registry.go @@ -84,6 +84,8 @@ type BlockVersionInput struct { OwnerCapObjectId string ModuleName string Version uint8 + // LatestPackageId is emitted on Call for MCMS proposals as additionalFields.latest_package_id. + LatestPackageId string `json:"latestPackageId,omitempty"` } type BlockVersionObjects struct { @@ -109,6 +111,7 @@ 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) } + call.LatestPackageId = input.LatestPackageId if deps.Signer == nil { b.Logger.Infow("Skipping execution of BlockVersion on UpgradeRegistry as per no Signer provided", "moduleName", input.ModuleName, "version", input.Version) @@ -155,6 +158,8 @@ type UnblockVersionInput struct { OwnerCapObjectId string ModuleName string Version uint8 + // LatestPackageId is emitted on Call for MCMS proposals as additionalFields.latest_package_id. + LatestPackageId string `json:"latestPackageId,omitempty"` } type UnblockVersionObjects struct { @@ -180,6 +185,7 @@ 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) } + call.LatestPackageId = input.LatestPackageId if deps.Signer == nil { b.Logger.Infow("Skipping execution of UnblockVersion on UpgradeRegistry as per no Signer provided", "moduleName", input.ModuleName, "version", input.Version) @@ -227,6 +233,8 @@ type BlockFunctionInput struct { ModuleName string FunctionName string Version uint8 + // LatestPackageId is emitted on Call for MCMS proposals as additionalFields.latest_package_id. + LatestPackageId string `json:"latestPackageId,omitempty"` } type BlockFunctionObjects struct { @@ -252,6 +260,7 @@ 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) } + call.LatestPackageId = input.LatestPackageId 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) @@ -300,6 +309,8 @@ type UnblockFunctionInput struct { ModuleName string FunctionName string Version uint8 + // LatestPackageId is emitted on Call for MCMS proposals as additionalFields.latest_package_id. + LatestPackageId string `json:"latestPackageId,omitempty"` } type UnblockFunctionObjects struct { @@ -325,6 +336,7 @@ 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) } + call.LatestPackageId = input.LatestPackageId 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) diff --git a/deployment/ops/mcms/op_proposal_generate.go b/deployment/ops/mcms/op_proposal_generate.go index 55cede7c..ef2764b2 100644 --- a/deployment/ops/mcms/op_proposal_generate.go +++ b/deployment/ops/mcms/op_proposal_generate.go @@ -78,6 +78,10 @@ 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) } + tx, err = utils.MergeLatestPackageIDIntoMCMSTransaction(tx, call.LatestPackageId) + if err != nil { + return mcms.TimelockProposal{}, fmt.Errorf("set latest_package_id for operation %s: %w", def.ID, err) + } mcmsTxs[i] = tx } diff --git a/deployment/ops/types.go b/deployment/ops/types.go index 19344340..327f3cb7 100644 --- a/deployment/ops/types.go +++ b/deployment/ops/types.go @@ -28,6 +28,9 @@ type TransactionCall struct { Data []byte StateObjID string TypeArgs []string + // LatestPackageId is copied into MCMS transaction additionalFields as latest_package_id when + // building timelock proposals; PackageID remains the on-chain target (original package). + LatestPackageId string `json:"latestPackageId,omitempty"` } type OpTxDeps struct { diff --git a/deployment/utils/mcms.go b/deployment/utils/mcms.go index 44b75291..83eb6b1a 100644 --- a/deployment/utils/mcms.go +++ b/deployment/utils/mcms.go @@ -115,6 +115,30 @@ func ExtractTransactionCall(output interface{}, operationID string) (sui_ops.Tra return call, nil } +// MergeLatestPackageIDIntoMCMSTransaction sets sdk/sui.AdditionalFields.latest_package_id on a built +// MCMS transaction. Pass empty latestPackageID for a no-op. Preserves existing JSON object keys +// (e.g. compiled_modules) by merging at RawMessage granularity. +func MergeLatestPackageIDIntoMCMSTransaction(tx types.Transaction, latestPackageID string) (types.Transaction, error) { + if latestPackageID == "" { + return tx, nil + } + var fields map[string]json.RawMessage + if err := json.Unmarshal(tx.AdditionalFields, &fields); err != nil { + return types.Transaction{}, fmt.Errorf("unmarshal additionalFields: %w", err) + } + quoted, err := json.Marshal(latestPackageID) + if err != nil { + return types.Transaction{}, err + } + fields["latest_package_id"] = quoted + merged, err := json.Marshal(fields) + if err != nil { + return types.Transaction{}, err + } + tx.AdditionalFields = merged + return tx, nil +} + func getRoleFromAction(action types.TimelockAction) (suisdk.TimelockRole, error) { switch action { case types.TimelockActionSchedule: