From 434549cce3517dfd64ead1e12d0c753a8ac66f1e Mon Sep 17 00:00:00 2001 From: FelixFan1992 Date: Mon, 15 Jun 2026 14:18:20 -0400 Subject: [PATCH 1/4] NONEVM-5256: MCMS cap object transfer fix --- .../mcms/mcms_deployer/mcms_deployer.go | 81 ++++++- .../mcms/fast_mcms/sources/mcms_deployer.move | 44 +++- .../fast_mcms/tests/mcms_deployer_test.move | 191 ++++++++++++++++ contracts/mcms/fast_mcms/tests/mcms_test.move | 205 +++++------------- .../mcms/mcms/sources/mcms_deployer.move | 22 +- .../mcms/mcms/tests/mcms_deployer_test.move | 191 ++++++++++++++++ 6 files changed, 573 insertions(+), 161 deletions(-) diff --git a/bindings/generated/mcms/mcms_deployer/mcms_deployer.go b/bindings/generated/mcms/mcms_deployer/mcms_deployer.go index b9156d515..4dc739fa5 100644 --- a/bindings/generated/mcms/mcms_deployer/mcms_deployer.go +++ b/bindings/generated/mcms/mcms_deployer/mcms_deployer.go @@ -18,13 +18,14 @@ var ( _ = big.NewInt ) -const FunctionInfo = `[{"package":"mcms","module":"mcms_deployer","name":"authorize_upgrade","parameters":[{"name":"_","type":"OwnerCap"},{"name":"state","type":"DeployerState"},{"name":"policy","type":"u8"},{"name":"digest","type":"vector"},{"name":"package_address","type":"address"}]},{"package":"mcms","module":"mcms_deployer","name":"commit_upgrade","parameters":[{"name":"state","type":"DeployerState"},{"name":"receipt","type":"UpgradeReceipt"}]},{"package":"mcms","module":"mcms_deployer","name":"has_upgrade_cap","parameters":[{"name":"state","type":"DeployerState"},{"name":"package_address","type":"address"}]},{"package":"mcms","module":"mcms_deployer","name":"register_upgrade_cap","parameters":[{"name":"state","type":"DeployerState"},{"name":"registry","type":"Registry"},{"name":"upgrade_cap","type":"UpgradeCap"}]},{"package":"mcms","module":"mcms_deployer","name":"release_upgrade_cap","parameters":[{"name":"state","type":"DeployerState"},{"name":"registry","type":"Registry"},{"name":"_proof","type":"T"}]}]` +const FunctionInfo = `[{"package":"mcms","module":"mcms_deployer","name":"authorize_upgrade","parameters":[{"name":"_","type":"OwnerCap"},{"name":"state","type":"DeployerState"},{"name":"policy","type":"u8"},{"name":"digest","type":"vector"},{"name":"package_address","type":"address"}]},{"package":"mcms","module":"mcms_deployer","name":"commit_upgrade","parameters":[{"name":"state","type":"DeployerState"},{"name":"receipt","type":"UpgradeReceipt"}]},{"package":"mcms","module":"mcms_deployer","name":"has_upgrade_cap","parameters":[{"name":"state","type":"DeployerState"},{"name":"package_address","type":"address"}]},{"package":"mcms","module":"mcms_deployer","name":"register_upgrade_cap","parameters":[{"name":"state","type":"DeployerState"},{"name":"registry","type":"Registry"},{"name":"upgrade_cap","type":"UpgradeCap"}]},{"package":"mcms","module":"mcms_deployer","name":"release_upgrade_cap","parameters":[{"name":"state","type":"DeployerState"},{"name":"registry","type":"Registry"},{"name":"proof","type":"T"}]},{"package":"mcms","module":"mcms_deployer","name":"release_upgrade_cap_at","parameters":[{"name":"state","type":"DeployerState"},{"name":"registry","type":"Registry"},{"name":"package_address","type":"address"},{"name":"_proof","type":"T"}]}]` type IMcmsDeployer interface { RegisterUpgradeCap(ctx context.Context, opts *bind.CallOpts, state bind.Object, registry bind.Object, upgradeCap bind.Object) (*models.SuiTransactionBlockResponse, error) AuthorizeUpgrade(ctx context.Context, opts *bind.CallOpts, param bind.Object, state bind.Object, policy byte, digest []byte, packageAddress string) (*models.SuiTransactionBlockResponse, error) CommitUpgrade(ctx context.Context, opts *bind.CallOpts, state bind.Object, receipt bind.Object) (*models.SuiTransactionBlockResponse, error) ReleaseUpgradeCap(ctx context.Context, opts *bind.CallOpts, typeArgs []string, state bind.Object, registry bind.Object, proof bind.Object) (*models.SuiTransactionBlockResponse, error) + ReleaseUpgradeCapAt(ctx context.Context, opts *bind.CallOpts, typeArgs []string, state bind.Object, registry bind.Object, packageAddress string, proof bind.Object) (*models.SuiTransactionBlockResponse, error) HasUpgradeCap(ctx context.Context, opts *bind.CallOpts, state bind.Object, packageAddress string) (*models.SuiTransactionBlockResponse, error) DevInspect() IMcmsDeployerDevInspect Encoder() McmsDeployerEncoder @@ -34,6 +35,7 @@ type IMcmsDeployer interface { type IMcmsDeployerDevInspect interface { AuthorizeUpgrade(ctx context.Context, opts *bind.CallOpts, param bind.Object, state bind.Object, policy byte, digest []byte, packageAddress string) (bind.Object, error) ReleaseUpgradeCap(ctx context.Context, opts *bind.CallOpts, typeArgs []string, state bind.Object, registry bind.Object, proof bind.Object) (bind.Object, error) + ReleaseUpgradeCapAt(ctx context.Context, opts *bind.CallOpts, typeArgs []string, state bind.Object, registry bind.Object, packageAddress string, proof bind.Object) (bind.Object, error) HasUpgradeCap(ctx context.Context, opts *bind.CallOpts, state bind.Object, packageAddress string) (bool, error) } @@ -46,6 +48,8 @@ type McmsDeployerEncoder interface { CommitUpgradeWithArgs(args ...any) (*bind.EncodedCall, error) ReleaseUpgradeCap(typeArgs []string, state bind.Object, registry bind.Object, proof bind.Object) (*bind.EncodedCall, error) ReleaseUpgradeCapWithArgs(typeArgs []string, args ...any) (*bind.EncodedCall, error) + ReleaseUpgradeCapAt(typeArgs []string, state bind.Object, registry bind.Object, packageAddress string, proof bind.Object) (*bind.EncodedCall, error) + ReleaseUpgradeCapAtWithArgs(typeArgs []string, args ...any) (*bind.EncodedCall, error) HasUpgradeCap(state bind.Object, packageAddress string) (*bind.EncodedCall, error) HasUpgradeCapWithArgs(args ...any) (*bind.EncodedCall, error) } @@ -158,6 +162,16 @@ func (c *McmsDeployerContract) ReleaseUpgradeCap(ctx context.Context, opts *bind return c.ExecuteTransaction(ctx, opts, encoded) } +// ReleaseUpgradeCapAt executes the release_upgrade_cap_at Move function. +func (c *McmsDeployerContract) ReleaseUpgradeCapAt(ctx context.Context, opts *bind.CallOpts, typeArgs []string, state bind.Object, registry bind.Object, packageAddress string, proof bind.Object) (*models.SuiTransactionBlockResponse, error) { + encoded, err := c.mcmsDeployerEncoder.ReleaseUpgradeCapAt(typeArgs, state, registry, packageAddress, proof) + if err != nil { + return nil, fmt.Errorf("failed to encode function call: %w", err) + } + + return c.ExecuteTransaction(ctx, opts, encoded) +} + // HasUpgradeCap executes the has_upgrade_cap Move function. func (c *McmsDeployerContract) HasUpgradeCap(ctx context.Context, opts *bind.CallOpts, state bind.Object, packageAddress string) (*models.SuiTransactionBlockResponse, error) { encoded, err := c.mcmsDeployerEncoder.HasUpgradeCap(state, packageAddress) @@ -212,6 +226,28 @@ func (d *McmsDeployerDevInspect) ReleaseUpgradeCap(ctx context.Context, opts *bi return result, nil } +// ReleaseUpgradeCapAt executes the release_upgrade_cap_at Move function using DevInspect to get return values. +// +// Returns: UpgradeCap +func (d *McmsDeployerDevInspect) ReleaseUpgradeCapAt(ctx context.Context, opts *bind.CallOpts, typeArgs []string, state bind.Object, registry bind.Object, packageAddress string, proof bind.Object) (bind.Object, error) { + encoded, err := d.contract.mcmsDeployerEncoder.ReleaseUpgradeCapAt(typeArgs, state, registry, packageAddress, proof) + if err != nil { + return bind.Object{}, fmt.Errorf("failed to encode function call: %w", err) + } + results, err := d.contract.Call(ctx, opts, encoded) + if err != nil { + return bind.Object{}, err + } + if len(results) == 0 { + return bind.Object{}, fmt.Errorf("no return value") + } + var result bind.Object + if err := bind.DecodeJSONReturn(results[0], &result); err != nil { + return bind.Object{}, fmt.Errorf("failed to decode return value: %w", err) + } + return result, nil +} + // HasUpgradeCap executes the has_upgrade_cap Move function using DevInspect to get return values. // // Returns: bool @@ -381,6 +417,49 @@ func (c mcmsDeployerEncoder) ReleaseUpgradeCapWithArgs(typeArgs []string, args . }) } +// ReleaseUpgradeCapAt encodes a call to the release_upgrade_cap_at Move function. +func (c mcmsDeployerEncoder) ReleaseUpgradeCapAt(typeArgs []string, state bind.Object, registry bind.Object, packageAddress string, proof bind.Object) (*bind.EncodedCall, error) { + typeArgsList := typeArgs + typeParamsList := []string{ + "T", + } + return c.EncodeCallArgsWithGenerics("release_upgrade_cap_at", typeArgsList, typeParamsList, []string{ + "&mut DeployerState", + "&Registry", + "address", + "T", + }, []any{ + state, + registry, + packageAddress, + proof, + }, []string{ + "UpgradeCap", + }) +} + +// ReleaseUpgradeCapAtWithArgs encodes a call to the release_upgrade_cap_at Move function using arbitrary arguments. +// This method allows passing both regular values and transaction.Argument values for PTB chaining. +func (c mcmsDeployerEncoder) ReleaseUpgradeCapAtWithArgs(typeArgs []string, args ...any) (*bind.EncodedCall, error) { + expectedParams := []string{ + "&mut DeployerState", + "&Registry", + "address", + "T", + } + + if len(args) != len(expectedParams) { + return nil, fmt.Errorf("expected %d arguments, got %d", len(expectedParams), len(args)) + } + typeArgsList := typeArgs + typeParamsList := []string{ + "T", + } + return c.EncodeCallArgsWithGenerics("release_upgrade_cap_at", typeArgsList, typeParamsList, expectedParams, args, []string{ + "UpgradeCap", + }) +} + // HasUpgradeCap encodes a call to the has_upgrade_cap Move function. func (c mcmsDeployerEncoder) HasUpgradeCap(state bind.Object, packageAddress string) (*bind.EncodedCall, error) { typeArgsList := []string{} diff --git a/contracts/mcms/fast_mcms/sources/mcms_deployer.move b/contracts/mcms/fast_mcms/sources/mcms_deployer.move index 4a822839a..0e820ea06 100644 --- a/contracts/mcms/fast_mcms/sources/mcms_deployer.move +++ b/contracts/mcms/fast_mcms/sources/mcms_deployer.move @@ -130,11 +130,28 @@ public fun commit_upgrade( }); } -/// Release the upgrade cap for a registered package -/// This must be called before calling `mcms_registry::release_cap` as it relies on registered proof types in registry +/// Release the upgrade cap for a registered package at the original publish address. +/// Prefer `release_upgrade_cap_at` when the cap was re-keyed after `commit_upgrade`. +/// Must be called before `mcms_registry::release_cap` as registry proof checks still apply. public fun release_upgrade_cap( state: &mut DeployerState, registry: &Registry, + proof: T, +): UpgradeCap { + let proof_type = type_name::with_original_ids(); + let proof_account_address = proof_type.address_string(); + let package_address = address::from_ascii_bytes(&proof_account_address.into_bytes()); + release_upgrade_cap_at(state, registry, package_address, proof) +} + +/// Release the upgrade cap stored at `package_address` in `DeployerState`. +/// Must be called before `mcms_registry::release_cap` as registry proof checks still apply. +/// After a package upgrade, pass the current package address from `commit_upgrade`, not the +/// original publish address. +public fun release_upgrade_cap_at( + state: &mut DeployerState, + registry: &Registry, + package_address: address, _proof: T, ): UpgradeCap { let proof_type = type_name::with_original_ids(); @@ -151,7 +168,6 @@ public fun release_upgrade_cap( ); assert!(proof_type == expected_proof_type, EWrongProofType); - let package_address = address::from_ascii_bytes(&proof_account_address.into_bytes()); assert!(state.upgrade_caps.contains(package_address), EPackageAddressNotRegistered); let upgrade_cap = state.upgrade_caps.remove(package_address); @@ -197,3 +213,25 @@ public fun test_register_upgrade_cap_for_package( public fun test_init(ctx: &mut TxContext) { init(MCMS_DEPLOYER {}, ctx); } + + #[test_only] + /// Register an upgrade cap without requiring MCMS registry check. + /// Needed because in tests @self resolves to 0x0, which Sui >= 1.73 + /// rejects in `authorize_upgrade` (uses 0x0 as a sentinel for in-progress upgrades). + public fun test_register_upgrade_cap( + state: &mut DeployerState, + upgrade_cap: UpgradeCap, + ctx: &mut TxContext, + ) { + let package_address = upgrade_cap.package().to_address(); + let version = upgrade_cap.version(); + let policy = upgrade_cap.policy(); + state.cap_to_package.add(object::id(&upgrade_cap), package_address); + state.upgrade_caps.add(package_address, upgrade_cap); + event::emit(UpgradeCapRegistered { + prev_owner: ctx.sender(), + package_address, + version, + policy, + }); + } diff --git a/contracts/mcms/fast_mcms/tests/mcms_deployer_test.move b/contracts/mcms/fast_mcms/tests/mcms_deployer_test.move index b08725168..7bca2f890 100644 --- a/contracts/mcms/fast_mcms/tests/mcms_deployer_test.move +++ b/contracts/mcms/fast_mcms/tests/mcms_deployer_test.move @@ -21,6 +21,55 @@ fun generate_upgrade_cap(ctx: &mut TxContext): UpgradeCap { package::test_publish(mcms_registry::get_multisig_address().to_id(), ctx) } +fun init_mcms(scenario: &mut Scenario) { + let ctx = ts::ctx(scenario); + mcms_registry::test_init(ctx); + mcms_deployer::test_init(ctx); + mcms_account::test_init(ctx); +} + +fun register_test_package_with_upgrade_cap(scenario: &mut Scenario): address { + ts::next_tx(scenario, @0xB); + let mut deployer_state = ts::take_shared(scenario); + let mut registry = ts::take_shared(scenario); + let ctx = ts::ctx(scenario); + + let upgrade_cap = generate_upgrade_cap(ctx); + let package_address = upgrade_cap.package().to_address(); + + ts::next_tx(scenario, @0xA); + let ctx = ts::ctx(scenario); + let publisher = package::test_claim(MCMS_DEPLOYER_TEST {}, ctx); + let owner_cap = TestOwnerCap { id: object::new(ctx) }; + + let publisher_wrapper = mcms_registry::create_publisher_wrapper( + &publisher, + MCMS_DEPLOYER_TEST {}, + ); + + mcms_registry::register_entrypoint( + &mut registry, + publisher_wrapper, + MCMS_DEPLOYER_TEST {}, + owner_cap, + vector[b"mcms_deployer_test"], + ctx, + ); + + mcms_deployer::register_upgrade_cap( + &mut deployer_state, + ®istry, + upgrade_cap, + ctx, + ); + + transfer::public_transfer(publisher, @0xA); + ts::return_shared(deployer_state); + ts::return_shared(registry); + + package_address +} + #[test] fun test_register_upgrade_cap() { let mut scenario = create_test_scenario(); @@ -74,6 +123,148 @@ fun test_register_upgrade_cap() { ts::end(scenario); } +#[test] +fun test_release_upgrade_cap_at_succeeds() { + let mut scenario = create_test_scenario(); + init_mcms(&mut scenario); + let package_address = register_test_package_with_upgrade_cap(&mut scenario); + + ts::next_tx(&mut scenario, @0xA); + let mut deployer_state = ts::take_shared(&scenario); + let registry = ts::take_shared(&scenario); + + assert!(mcms_deployer::has_upgrade_cap(&deployer_state, package_address)); + + let upgrade_cap = mcms_deployer::release_upgrade_cap_at( + &mut deployer_state, + ®istry, + package_address, + MCMS_DEPLOYER_TEST {}, + ); + + assert!(upgrade_cap.package().to_address() == package_address); + assert!(!mcms_deployer::has_upgrade_cap(&deployer_state, package_address)); + + transfer::public_transfer(upgrade_cap, @0xA); + ts::return_shared(deployer_state); + ts::return_shared(registry); + ts::end(scenario); +} + +#[test] +#[expected_failure(abort_code = mcms::mcms_deployer::EPackageAddressNotRegistered)] +fun test_release_upgrade_cap_at_fails_after_release_cap() { + let mut scenario = create_test_scenario(); + init_mcms(&mut scenario); + let package_address = register_test_package_with_upgrade_cap(&mut scenario); + + ts::next_tx(&mut scenario, @0xA); + let mut deployer_state = ts::take_shared(&scenario); + let mut registry = ts::take_shared(&scenario); + + let _owner_cap = mcms_registry::release_cap( + &mut registry, + MCMS_DEPLOYER_TEST {}, + ); + + mcms_deployer::release_upgrade_cap_at( + &mut deployer_state, + ®istry, + package_address, + MCMS_DEPLOYER_TEST {}, + ); + + ts::return_shared(deployer_state); + ts::return_shared(registry); + ts::end(scenario); +} + +#[test] +fun test_release_upgrade_cap_at_after_commit_upgrade() { + let mut scenario = create_test_scenario(); + init_mcms(&mut scenario); + let old_package_address = register_test_package_with_upgrade_cap(&mut scenario); + + ts::next_tx(&mut scenario, @0xA); + let mut deployer_state = ts::take_shared(&scenario); + let owner_cap = ts::take_from_sender(&scenario); + let ctx = ts::ctx(&mut scenario); + + let ticket = mcms_deployer::authorize_upgrade( + &owner_cap, + &mut deployer_state, + 0, + vector[], + old_package_address, + ctx, + ); + let receipt = package::test_upgrade(ticket); + let new_package_address = receipt.package().to_address(); + + mcms_deployer::commit_upgrade(&mut deployer_state, receipt, ctx); + + assert!(!mcms_deployer::has_upgrade_cap(&deployer_state, old_package_address)); + assert!(mcms_deployer::has_upgrade_cap(&deployer_state, new_package_address)); + + ts::return_to_sender(&scenario, owner_cap); + ts::return_shared(deployer_state); + + ts::next_tx(&mut scenario, @0xA); + let mut deployer_state = ts::take_shared(&scenario); + let registry = ts::take_shared(&scenario); + + let upgrade_cap = mcms_deployer::release_upgrade_cap_at( + &mut deployer_state, + ®istry, + new_package_address, + MCMS_DEPLOYER_TEST {}, + ); + + assert!(upgrade_cap.package().to_address() == new_package_address); + + transfer::public_transfer(upgrade_cap, @0xA); + ts::return_shared(deployer_state); + ts::return_shared(registry); + ts::end(scenario); +} + +#[test] +#[expected_failure(abort_code = mcms::mcms_deployer::EPackageAddressNotRegistered)] +fun test_release_upgrade_cap_fails_after_commit_upgrade() { + let mut scenario = create_test_scenario(); + init_mcms(&mut scenario); + let old_package_address = register_test_package_with_upgrade_cap(&mut scenario); + + ts::next_tx(&mut scenario, @0xA); + let mut deployer_state = ts::take_shared(&scenario); + let owner_cap = ts::take_from_sender(&scenario); + let ctx = ts::ctx(&mut scenario); + + let ticket = mcms_deployer::authorize_upgrade( + &owner_cap, + &mut deployer_state, + 0, + vector[], + old_package_address, + ctx, + ); + let receipt = package::test_upgrade(ticket); + mcms_deployer::commit_upgrade(&mut deployer_state, receipt, ctx); + + ts::return_to_sender(&scenario, owner_cap); + + let registry = ts::take_shared(&scenario); + mcms_deployer::release_upgrade_cap( + &mut deployer_state, + ®istry, + MCMS_DEPLOYER_TEST {}, + ); + + ts::return_shared(deployer_state); + ts::return_shared(registry); + ts::end(scenario); +} + #[test] #[expected_failure(abort_code = mcms::mcms_deployer::EPackageAddressNotRegistered)] fun test_register_upgrade_cap_without_existing_package_fails() { diff --git a/contracts/mcms/fast_mcms/tests/mcms_test.move b/contracts/mcms/fast_mcms/tests/mcms_test.move index fb9e10a41..b84149ef6 100644 --- a/contracts/mcms/fast_mcms/tests/mcms_test.move +++ b/contracts/mcms/fast_mcms/tests/mcms_test.move @@ -1492,81 +1492,62 @@ fun test_cancel_nonexistent_operation() { } #[test] -fun test_bypasser_execute_batch() { +#[expected_failure(abort_code = mcms::EBypasserSelfGovernanceForbidden, location = mcms)] +fun test_bypasser_execute_batch_self_governance_forbidden() { let mut env = setup(); - // Get initial min_delay value let initial_delay = mcms::timelock_min_delay(&env.timelock); - let new_delay = initial_delay + 1000; // Set to a different value + let new_delay = initial_delay + 1000; - // Use bypasser role to execute batch - this should return ExecutingCallbackParams let mut bypasser_update_delay_data = vector[]; bypasser_update_delay_data.append(bcs::to_bytes(&new_delay)); - let mut executing_params = mcms::test_timelock_bypasser_execute_batch( + let executing_params = mcms::test_timelock_bypasser_execute_batch( mcms::bypasser_role(), - vector[mcms_registry::get_multisig_address()], // targets - vector[string::utf8(b"mcms")], // module_names - vector[string::utf8(b"timelock_update_min_delay")], // function_names - vector[bypasser_update_delay_data], // datas + vector[mcms_registry::get_multisig_address()], + vector[string::utf8(b"mcms")], + vector[string::utf8(b"timelock_update_min_delay")], + vector[bypasser_update_delay_data], env.scenario.ctx(), ); - // Verify we got exactly 1 ExecutingCallbackParams - assert!(executing_params.length() == 1); + executing_params.destroy_empty(); + env.destroy(); +} - // Extract the ExecutingCallbackParams and verify its contents - let params = executing_params.borrow(0); - assert!(mcms_registry::target(params) == mcms_registry::get_multisig_address()); - assert!(mcms_registry::module_name(params) == string::utf8(b"mcms")); - assert!(mcms_registry::function_name(params) == string::utf8(b"timelock_update_min_delay")); +#[test] +fun test_timelock_role_bypasser_execute_batch_self_governance() { + let mut env = setup(); - // Now we need to consume the ExecutingCallbackParams by calling the actual function - // Since this is timelock_update_min_delay, we need to dispatch it properly - let mut update_delay_data = vector[]; - update_delay_data.append(bcs::to_bytes(&object::id_address(&env.timelock))); - update_delay_data.append(bcs::to_bytes(&new_delay)); + let initial_delay = mcms::timelock_min_delay(&env.timelock); + let new_delay = initial_delay + 1000; - let callback_params = mcms_registry::test_create_executing_callback_params( - mcms_registry::get_multisig_address(), - string::utf8(b"mcms"), - string::utf8(b"timelock_update_min_delay"), - update_delay_data, - x"0000000000000000000000000000000000000000000000000000000000000001", - 0, - 1, + let mut bypasser_update_delay_data = vector[]; + bypasser_update_delay_data.append(bcs::to_bytes(&object::id_address(&env.timelock))); + bypasser_update_delay_data.append(bcs::to_bytes(&new_delay)); + + let mut executing_params = mcms::test_timelock_bypasser_execute_batch( + mcms::timelock_role(), + vector[mcms_registry::get_multisig_address()], + vector[string::utf8(b"mcms")], + vector[string::utf8(b"timelock_update_min_delay")], + vector[bypasser_update_delay_data], + env.scenario.ctx(), ); - // Dispatch the update_min_delay function to consume the hot potato + assert!(executing_params.length() == 1); + mcms::mcms_timelock_update_min_delay( &mut env.timelock, &mut env.registry, - callback_params, + executing_params.pop_back(), env.scenario.ctx(), ); - // Verify that min_delay was actually updated - this proves the bypasser execution worked let updated_delay = mcms::timelock_min_delay(&env.timelock); assert!(updated_delay == new_delay, 0); - assert!(updated_delay != initial_delay, 1); // Ensure it actually changed - - // Must consume the ExecutingCallbackParams hot potato - // We know there's exactly 1 param, so just consume it directly - let params = executing_params.pop_back(); - let (target, module_name, function_name, data) = mcms_registry::get_callback_params_from_mcms( - &mut env.registry, - params, - ); - - // Verify the ExecutingCallbackParams has the expected structure - assert!(target == mcms_registry::get_multisig_address()); - assert!(module_name == string::utf8(b"mcms")); - assert!(function_name == string::utf8(b"timelock_update_min_delay")); - assert!(!data.is_empty()); // Should contain the serialized new_delay - - // Now the vector should be empty - vector::destroy_empty(executing_params); + executing_params.destroy_empty(); env.destroy(); } @@ -2483,6 +2464,7 @@ fun test_execute_batch_missing_dependency() { } #[test] +#[expected_failure(abort_code = mcms::EBypasserSelfGovernanceForbidden, location = mcms)] fun test_bypasser_execute_blocked_function() { let mut env = setup(); @@ -2490,7 +2472,6 @@ fun test_bypasser_execute_blocked_function() { let module_name = string::utf8(b"mcms"); let function_name = string::utf8(b"timelock_update_min_delay"); - // Block the function first mcms::test_timelock_block_function( &mut env.timelock, mcms::timelock_role(), @@ -2500,67 +2481,19 @@ fun test_bypasser_execute_blocked_function() { env.scenario.ctx(), ); - // Verify function was blocked (check the count increased) - let blocked_count = mcms::timelock_get_blocked_functions_count(&env.timelock); - assert!(blocked_count == 1, 0); - - // Get initial min_delay value - let initial_delay = mcms::timelock_min_delay(&env.timelock); - let new_delay = initial_delay + 1000; - - // Bypasser should be able to directly execute the blocked function - // Prepare data with timelock object ID let mut update_delay_data = vector[]; update_delay_data.append(bcs::to_bytes(&object::id_address(&env.timelock))); - update_delay_data.append(bcs::to_bytes(&new_delay)); + update_delay_data.append(bcs::to_bytes(&(mcms::timelock_min_delay(&env.timelock) + 1000))); - let mut executing_params = mcms::test_timelock_bypasser_execute_batch( + let _executing_params = mcms::test_timelock_bypasser_execute_batch( mcms::bypasser_role(), - vector[target], // targets - vector[module_name], // module_names - vector[function_name], // function_names - vector[update_delay_data], // datas - env.scenario.ctx(), - ); - - // Process the executing callback params - let params = executing_params.pop_back(); - let ( - callback_target, - callback_module, - callback_function, - callback_data, - ) = mcms_registry::get_callback_params_from_mcms(&mut env.registry, params); - - // Verify callback params structure - assert!(callback_target == target); - assert!(callback_module == module_name); - assert!(callback_function == function_name); - assert!(!callback_data.is_empty()); - - // Now dispatch the timelock function to actually update the min delay - let params = mcms_registry::test_create_executing_callback_params( - mcms_registry::get_multisig_address(), - string::utf8(b"mcms"), - string::utf8(b"timelock_update_min_delay"), - callback_data, - x"0000000000000000000000000000000000000000000000000000000000000001", // batch_id - 0, // sequence_number - 1, // total_in_batch - ); - - mcms::mcms_timelock_update_min_delay( - &mut env.timelock, - &mut env.registry, - params, + vector[target], + vector[module_name], + vector[function_name], + vector[update_delay_data], env.scenario.ctx(), ); - // Verify the min delay was updated despite the function being blocked - let updated_delay = mcms::timelock_min_delay(&env.timelock); - assert!(updated_delay == new_delay, 1); - - vector::destroy_empty(executing_params); env.destroy(); } @@ -2699,51 +2632,32 @@ fun test_execute_batch_with_dependencies() { } #[test] +#[expected_failure(abort_code = mcms::EBypasserSelfGovernanceForbidden, location = mcms)] fun test_bypasser_allowed_when_timelock_active() { let mut env = setup(); - let delay = 1800u64; - - // Update min delay to a significant value to ensure timelock is active mcms::test_timelock_update_min_delay( &mut env.timelock, mcms::timelock_role(), - delay, + 1800u64, env.scenario.ctx(), ); - // Get initial delay value - let initial_delay = mcms::timelock_min_delay(&env.timelock); - let new_delay = initial_delay + 1000; + let new_delay = mcms::timelock_min_delay(&env.timelock) + 1000; - // This should succeed because bypassers are allowed to bypass the timelock - // Prepare data with timelock object ID let mut bypasser_active_update_delay_data = vector[]; bypasser_active_update_delay_data.append(bcs::to_bytes(&object::id_address(&env.timelock))); bypasser_active_update_delay_data.append(bcs::to_bytes(&new_delay)); - let mut executing_params = mcms::test_timelock_bypasser_execute_batch( + let _executing_params = mcms::test_timelock_bypasser_execute_batch( mcms::bypasser_role(), - vector[mcms_registry::get_multisig_address()], // targets - vector[string::utf8(b"mcms")], // module_names - vector[string::utf8(b"timelock_update_min_delay")], // function_names - vector[bypasser_active_update_delay_data], // datas - env.scenario.ctx(), - ); - - // Process the executing callback params to complete the operation - mcms::mcms_timelock_update_min_delay( - &mut env.timelock, - &mut env.registry, - executing_params.pop_back(), + vector[mcms_registry::get_multisig_address()], + vector[string::utf8(b"mcms")], + vector[string::utf8(b"timelock_update_min_delay")], + vector[bypasser_active_update_delay_data], env.scenario.ctx(), ); - // Verify the min_delay was updated, confirming the bypass worked - let updated_delay = mcms::timelock_min_delay(&env.timelock); - assert!(updated_delay == new_delay, 0); - - executing_params.destroy_empty(); env.destroy(); } @@ -2976,7 +2890,7 @@ fun test_timelock_dispatch_to_deployer() { data.append(bcs::to_bytes(&mcms_package)); // package address for authorize_upgrade let mut executing_params = mcms::test_timelock_bypasser_execute_batch( - mcms::bypasser_role(), + mcms::timelock_role(), vector[mcms_package], vector[string::utf8(b"mcms_deployer")], vector[string::utf8(b"authorize_upgrade")], @@ -3127,27 +3041,25 @@ fun test_dispatch_timelock_execute_batch() { } #[test] +#[expected_failure(abort_code = mcms::EBypasserSelfGovernanceForbidden, location = mcms)] fun test_dispatch_timelock_bypasser_execute_batch() { let mut env = setup(); - // Create serialized data for timelock_bypasser_execute_batch parameters let targets = vector[mcms_registry::get_multisig_address()]; let module_names = vector[string::utf8(b"mcms")]; let function_names = vector[string::utf8(b"timelock_update_min_delay")]; - // Prepare data with timelock object ID let mut bypasser_batch_data = vector[]; bypasser_batch_data.append(bcs::to_bytes(&object::id_address(&env.timelock))); - bypasser_batch_data.append(bcs::to_bytes(&2000)); + bypasser_batch_data.append(bcs::to_bytes(&2000u64)); - let datas = vector[bypasser_batch_data]; // new min delay with timelock ID + let datas = vector[bypasser_batch_data]; let mut serialized_data = bcs::to_bytes(&targets); serialized_data.append(bcs::to_bytes(&module_names)); serialized_data.append(bcs::to_bytes(&function_names)); serialized_data.append(bcs::to_bytes(&datas)); - // Create TimelockCallbackParams for dispatch_timelock_bypasser_execute_batch let callback_params = mcms::test_create_timelock_callback_params( mcms::bypasser_role(), string::utf8(b"mcms"), @@ -3155,25 +3067,10 @@ fun test_dispatch_timelock_bypasser_execute_batch() { serialized_data, ); - let mut executing_params = mcms::dispatch_timelock_bypasser_execute_batch( + let _executing_params = mcms::dispatch_timelock_bypasser_execute_batch( callback_params, env.scenario.ctx(), ); - assert!(executing_params.length() == 1); - - // Verify the ExecutingCallbackParams has the correct structure - let params = &executing_params[0]; - assert!(mcms_registry::target(params) == mcms_registry::get_multisig_address()); - assert!(mcms_registry::module_name(params) == string::utf8(b"mcms")); - assert!(mcms_registry::function_name(params) == string::utf8(b"timelock_update_min_delay")); - - mcms::mcms_timelock_update_min_delay( - &mut env.timelock, - &mut env.registry, - executing_params.pop_back(), - env.scenario.ctx(), - ); - executing_params.destroy_empty(); env.destroy(); } diff --git a/contracts/mcms/mcms/sources/mcms_deployer.move b/contracts/mcms/mcms/sources/mcms_deployer.move index 2f2aa70c1..d2daf9126 100644 --- a/contracts/mcms/mcms/sources/mcms_deployer.move +++ b/contracts/mcms/mcms/sources/mcms_deployer.move @@ -130,11 +130,28 @@ public fun commit_upgrade( }); } -/// Release the upgrade cap for a registered package -/// This must be called before calling `mcms_registry::release_cap` as it relies on registered proof types in registry +/// Release the upgrade cap for a registered package at the original publish address. +/// Prefer `release_upgrade_cap_at` when the cap was re-keyed after `commit_upgrade`. +/// Must be called before `mcms_registry::release_cap` as registry proof checks still apply. public fun release_upgrade_cap( state: &mut DeployerState, registry: &Registry, + proof: T, +): UpgradeCap { + let proof_type = type_name::with_original_ids(); + let proof_account_address = proof_type.address_string(); + let package_address = address::from_ascii_bytes(&proof_account_address.into_bytes()); + release_upgrade_cap_at(state, registry, package_address, proof) +} + +/// Release the upgrade cap stored at `package_address` in `DeployerState`. +/// Must be called before `mcms_registry::release_cap` as registry proof checks still apply. +/// After a package upgrade, pass the current package address from `commit_upgrade`, not the +/// original publish address. +public fun release_upgrade_cap_at( + state: &mut DeployerState, + registry: &Registry, + package_address: address, _proof: T, ): UpgradeCap { let proof_type = type_name::with_original_ids(); @@ -151,7 +168,6 @@ public fun release_upgrade_cap( ); assert!(proof_type == expected_proof_type, EWrongProofType); - let package_address = address::from_ascii_bytes(&proof_account_address.into_bytes()); assert!(state.upgrade_caps.contains(package_address), EPackageAddressNotRegistered); let upgrade_cap = state.upgrade_caps.remove(package_address); diff --git a/contracts/mcms/mcms/tests/mcms_deployer_test.move b/contracts/mcms/mcms/tests/mcms_deployer_test.move index b08725168..7bca2f890 100644 --- a/contracts/mcms/mcms/tests/mcms_deployer_test.move +++ b/contracts/mcms/mcms/tests/mcms_deployer_test.move @@ -21,6 +21,55 @@ fun generate_upgrade_cap(ctx: &mut TxContext): UpgradeCap { package::test_publish(mcms_registry::get_multisig_address().to_id(), ctx) } +fun init_mcms(scenario: &mut Scenario) { + let ctx = ts::ctx(scenario); + mcms_registry::test_init(ctx); + mcms_deployer::test_init(ctx); + mcms_account::test_init(ctx); +} + +fun register_test_package_with_upgrade_cap(scenario: &mut Scenario): address { + ts::next_tx(scenario, @0xB); + let mut deployer_state = ts::take_shared(scenario); + let mut registry = ts::take_shared(scenario); + let ctx = ts::ctx(scenario); + + let upgrade_cap = generate_upgrade_cap(ctx); + let package_address = upgrade_cap.package().to_address(); + + ts::next_tx(scenario, @0xA); + let ctx = ts::ctx(scenario); + let publisher = package::test_claim(MCMS_DEPLOYER_TEST {}, ctx); + let owner_cap = TestOwnerCap { id: object::new(ctx) }; + + let publisher_wrapper = mcms_registry::create_publisher_wrapper( + &publisher, + MCMS_DEPLOYER_TEST {}, + ); + + mcms_registry::register_entrypoint( + &mut registry, + publisher_wrapper, + MCMS_DEPLOYER_TEST {}, + owner_cap, + vector[b"mcms_deployer_test"], + ctx, + ); + + mcms_deployer::register_upgrade_cap( + &mut deployer_state, + ®istry, + upgrade_cap, + ctx, + ); + + transfer::public_transfer(publisher, @0xA); + ts::return_shared(deployer_state); + ts::return_shared(registry); + + package_address +} + #[test] fun test_register_upgrade_cap() { let mut scenario = create_test_scenario(); @@ -74,6 +123,148 @@ fun test_register_upgrade_cap() { ts::end(scenario); } +#[test] +fun test_release_upgrade_cap_at_succeeds() { + let mut scenario = create_test_scenario(); + init_mcms(&mut scenario); + let package_address = register_test_package_with_upgrade_cap(&mut scenario); + + ts::next_tx(&mut scenario, @0xA); + let mut deployer_state = ts::take_shared(&scenario); + let registry = ts::take_shared(&scenario); + + assert!(mcms_deployer::has_upgrade_cap(&deployer_state, package_address)); + + let upgrade_cap = mcms_deployer::release_upgrade_cap_at( + &mut deployer_state, + ®istry, + package_address, + MCMS_DEPLOYER_TEST {}, + ); + + assert!(upgrade_cap.package().to_address() == package_address); + assert!(!mcms_deployer::has_upgrade_cap(&deployer_state, package_address)); + + transfer::public_transfer(upgrade_cap, @0xA); + ts::return_shared(deployer_state); + ts::return_shared(registry); + ts::end(scenario); +} + +#[test] +#[expected_failure(abort_code = mcms::mcms_deployer::EPackageAddressNotRegistered)] +fun test_release_upgrade_cap_at_fails_after_release_cap() { + let mut scenario = create_test_scenario(); + init_mcms(&mut scenario); + let package_address = register_test_package_with_upgrade_cap(&mut scenario); + + ts::next_tx(&mut scenario, @0xA); + let mut deployer_state = ts::take_shared(&scenario); + let mut registry = ts::take_shared(&scenario); + + let _owner_cap = mcms_registry::release_cap( + &mut registry, + MCMS_DEPLOYER_TEST {}, + ); + + mcms_deployer::release_upgrade_cap_at( + &mut deployer_state, + ®istry, + package_address, + MCMS_DEPLOYER_TEST {}, + ); + + ts::return_shared(deployer_state); + ts::return_shared(registry); + ts::end(scenario); +} + +#[test] +fun test_release_upgrade_cap_at_after_commit_upgrade() { + let mut scenario = create_test_scenario(); + init_mcms(&mut scenario); + let old_package_address = register_test_package_with_upgrade_cap(&mut scenario); + + ts::next_tx(&mut scenario, @0xA); + let mut deployer_state = ts::take_shared(&scenario); + let owner_cap = ts::take_from_sender(&scenario); + let ctx = ts::ctx(&mut scenario); + + let ticket = mcms_deployer::authorize_upgrade( + &owner_cap, + &mut deployer_state, + 0, + vector[], + old_package_address, + ctx, + ); + let receipt = package::test_upgrade(ticket); + let new_package_address = receipt.package().to_address(); + + mcms_deployer::commit_upgrade(&mut deployer_state, receipt, ctx); + + assert!(!mcms_deployer::has_upgrade_cap(&deployer_state, old_package_address)); + assert!(mcms_deployer::has_upgrade_cap(&deployer_state, new_package_address)); + + ts::return_to_sender(&scenario, owner_cap); + ts::return_shared(deployer_state); + + ts::next_tx(&mut scenario, @0xA); + let mut deployer_state = ts::take_shared(&scenario); + let registry = ts::take_shared(&scenario); + + let upgrade_cap = mcms_deployer::release_upgrade_cap_at( + &mut deployer_state, + ®istry, + new_package_address, + MCMS_DEPLOYER_TEST {}, + ); + + assert!(upgrade_cap.package().to_address() == new_package_address); + + transfer::public_transfer(upgrade_cap, @0xA); + ts::return_shared(deployer_state); + ts::return_shared(registry); + ts::end(scenario); +} + +#[test] +#[expected_failure(abort_code = mcms::mcms_deployer::EPackageAddressNotRegistered)] +fun test_release_upgrade_cap_fails_after_commit_upgrade() { + let mut scenario = create_test_scenario(); + init_mcms(&mut scenario); + let old_package_address = register_test_package_with_upgrade_cap(&mut scenario); + + ts::next_tx(&mut scenario, @0xA); + let mut deployer_state = ts::take_shared(&scenario); + let owner_cap = ts::take_from_sender(&scenario); + let ctx = ts::ctx(&mut scenario); + + let ticket = mcms_deployer::authorize_upgrade( + &owner_cap, + &mut deployer_state, + 0, + vector[], + old_package_address, + ctx, + ); + let receipt = package::test_upgrade(ticket); + mcms_deployer::commit_upgrade(&mut deployer_state, receipt, ctx); + + ts::return_to_sender(&scenario, owner_cap); + + let registry = ts::take_shared(&scenario); + mcms_deployer::release_upgrade_cap( + &mut deployer_state, + ®istry, + MCMS_DEPLOYER_TEST {}, + ); + + ts::return_shared(deployer_state); + ts::return_shared(registry); + ts::end(scenario); +} + #[test] #[expected_failure(abort_code = mcms::mcms_deployer::EPackageAddressNotRegistered)] fun test_register_upgrade_cap_without_existing_package_fails() { From 0419c47afbccb42fb752b2dd9424524093561a66 Mon Sep 17 00:00:00 2001 From: FelixFan1992 Date: Mon, 15 Jun 2026 14:22:22 -0400 Subject: [PATCH 2/4] revert --- contracts/mcms/fast_mcms/tests/mcms_test.move | 205 +++++++++++++----- 1 file changed, 154 insertions(+), 51 deletions(-) diff --git a/contracts/mcms/fast_mcms/tests/mcms_test.move b/contracts/mcms/fast_mcms/tests/mcms_test.move index b84149ef6..fb9e10a41 100644 --- a/contracts/mcms/fast_mcms/tests/mcms_test.move +++ b/contracts/mcms/fast_mcms/tests/mcms_test.move @@ -1492,62 +1492,81 @@ fun test_cancel_nonexistent_operation() { } #[test] -#[expected_failure(abort_code = mcms::EBypasserSelfGovernanceForbidden, location = mcms)] -fun test_bypasser_execute_batch_self_governance_forbidden() { +fun test_bypasser_execute_batch() { let mut env = setup(); + // Get initial min_delay value let initial_delay = mcms::timelock_min_delay(&env.timelock); - let new_delay = initial_delay + 1000; + let new_delay = initial_delay + 1000; // Set to a different value + // Use bypasser role to execute batch - this should return ExecutingCallbackParams let mut bypasser_update_delay_data = vector[]; bypasser_update_delay_data.append(bcs::to_bytes(&new_delay)); - let executing_params = mcms::test_timelock_bypasser_execute_batch( + let mut executing_params = mcms::test_timelock_bypasser_execute_batch( mcms::bypasser_role(), - vector[mcms_registry::get_multisig_address()], - vector[string::utf8(b"mcms")], - vector[string::utf8(b"timelock_update_min_delay")], - vector[bypasser_update_delay_data], + vector[mcms_registry::get_multisig_address()], // targets + vector[string::utf8(b"mcms")], // module_names + vector[string::utf8(b"timelock_update_min_delay")], // function_names + vector[bypasser_update_delay_data], // datas env.scenario.ctx(), ); - executing_params.destroy_empty(); - env.destroy(); -} - -#[test] -fun test_timelock_role_bypasser_execute_batch_self_governance() { - let mut env = setup(); + // Verify we got exactly 1 ExecutingCallbackParams + assert!(executing_params.length() == 1); - let initial_delay = mcms::timelock_min_delay(&env.timelock); - let new_delay = initial_delay + 1000; + // Extract the ExecutingCallbackParams and verify its contents + let params = executing_params.borrow(0); + assert!(mcms_registry::target(params) == mcms_registry::get_multisig_address()); + assert!(mcms_registry::module_name(params) == string::utf8(b"mcms")); + assert!(mcms_registry::function_name(params) == string::utf8(b"timelock_update_min_delay")); - let mut bypasser_update_delay_data = vector[]; - bypasser_update_delay_data.append(bcs::to_bytes(&object::id_address(&env.timelock))); - bypasser_update_delay_data.append(bcs::to_bytes(&new_delay)); + // Now we need to consume the ExecutingCallbackParams by calling the actual function + // Since this is timelock_update_min_delay, we need to dispatch it properly + let mut update_delay_data = vector[]; + update_delay_data.append(bcs::to_bytes(&object::id_address(&env.timelock))); + update_delay_data.append(bcs::to_bytes(&new_delay)); - let mut executing_params = mcms::test_timelock_bypasser_execute_batch( - mcms::timelock_role(), - vector[mcms_registry::get_multisig_address()], - vector[string::utf8(b"mcms")], - vector[string::utf8(b"timelock_update_min_delay")], - vector[bypasser_update_delay_data], - env.scenario.ctx(), + let callback_params = mcms_registry::test_create_executing_callback_params( + mcms_registry::get_multisig_address(), + string::utf8(b"mcms"), + string::utf8(b"timelock_update_min_delay"), + update_delay_data, + x"0000000000000000000000000000000000000000000000000000000000000001", + 0, + 1, ); - assert!(executing_params.length() == 1); - + // Dispatch the update_min_delay function to consume the hot potato mcms::mcms_timelock_update_min_delay( &mut env.timelock, &mut env.registry, - executing_params.pop_back(), + callback_params, env.scenario.ctx(), ); + // Verify that min_delay was actually updated - this proves the bypasser execution worked let updated_delay = mcms::timelock_min_delay(&env.timelock); assert!(updated_delay == new_delay, 0); + assert!(updated_delay != initial_delay, 1); // Ensure it actually changed + + // Must consume the ExecutingCallbackParams hot potato + // We know there's exactly 1 param, so just consume it directly + let params = executing_params.pop_back(); + let (target, module_name, function_name, data) = mcms_registry::get_callback_params_from_mcms( + &mut env.registry, + params, + ); + + // Verify the ExecutingCallbackParams has the expected structure + assert!(target == mcms_registry::get_multisig_address()); + assert!(module_name == string::utf8(b"mcms")); + assert!(function_name == string::utf8(b"timelock_update_min_delay")); + assert!(!data.is_empty()); // Should contain the serialized new_delay + + // Now the vector should be empty + vector::destroy_empty(executing_params); - executing_params.destroy_empty(); env.destroy(); } @@ -2464,7 +2483,6 @@ fun test_execute_batch_missing_dependency() { } #[test] -#[expected_failure(abort_code = mcms::EBypasserSelfGovernanceForbidden, location = mcms)] fun test_bypasser_execute_blocked_function() { let mut env = setup(); @@ -2472,6 +2490,7 @@ fun test_bypasser_execute_blocked_function() { let module_name = string::utf8(b"mcms"); let function_name = string::utf8(b"timelock_update_min_delay"); + // Block the function first mcms::test_timelock_block_function( &mut env.timelock, mcms::timelock_role(), @@ -2481,19 +2500,67 @@ fun test_bypasser_execute_blocked_function() { env.scenario.ctx(), ); + // Verify function was blocked (check the count increased) + let blocked_count = mcms::timelock_get_blocked_functions_count(&env.timelock); + assert!(blocked_count == 1, 0); + + // Get initial min_delay value + let initial_delay = mcms::timelock_min_delay(&env.timelock); + let new_delay = initial_delay + 1000; + + // Bypasser should be able to directly execute the blocked function + // Prepare data with timelock object ID let mut update_delay_data = vector[]; update_delay_data.append(bcs::to_bytes(&object::id_address(&env.timelock))); - update_delay_data.append(bcs::to_bytes(&(mcms::timelock_min_delay(&env.timelock) + 1000))); + update_delay_data.append(bcs::to_bytes(&new_delay)); - let _executing_params = mcms::test_timelock_bypasser_execute_batch( + let mut executing_params = mcms::test_timelock_bypasser_execute_batch( mcms::bypasser_role(), - vector[target], - vector[module_name], - vector[function_name], - vector[update_delay_data], + vector[target], // targets + vector[module_name], // module_names + vector[function_name], // function_names + vector[update_delay_data], // datas + env.scenario.ctx(), + ); + + // Process the executing callback params + let params = executing_params.pop_back(); + let ( + callback_target, + callback_module, + callback_function, + callback_data, + ) = mcms_registry::get_callback_params_from_mcms(&mut env.registry, params); + + // Verify callback params structure + assert!(callback_target == target); + assert!(callback_module == module_name); + assert!(callback_function == function_name); + assert!(!callback_data.is_empty()); + + // Now dispatch the timelock function to actually update the min delay + let params = mcms_registry::test_create_executing_callback_params( + mcms_registry::get_multisig_address(), + string::utf8(b"mcms"), + string::utf8(b"timelock_update_min_delay"), + callback_data, + x"0000000000000000000000000000000000000000000000000000000000000001", // batch_id + 0, // sequence_number + 1, // total_in_batch + ); + + mcms::mcms_timelock_update_min_delay( + &mut env.timelock, + &mut env.registry, + params, env.scenario.ctx(), ); + // Verify the min delay was updated despite the function being blocked + let updated_delay = mcms::timelock_min_delay(&env.timelock); + assert!(updated_delay == new_delay, 1); + + vector::destroy_empty(executing_params); env.destroy(); } @@ -2632,32 +2699,51 @@ fun test_execute_batch_with_dependencies() { } #[test] -#[expected_failure(abort_code = mcms::EBypasserSelfGovernanceForbidden, location = mcms)] fun test_bypasser_allowed_when_timelock_active() { let mut env = setup(); + let delay = 1800u64; + + // Update min delay to a significant value to ensure timelock is active mcms::test_timelock_update_min_delay( &mut env.timelock, mcms::timelock_role(), - 1800u64, + delay, env.scenario.ctx(), ); - let new_delay = mcms::timelock_min_delay(&env.timelock) + 1000; + // Get initial delay value + let initial_delay = mcms::timelock_min_delay(&env.timelock); + let new_delay = initial_delay + 1000; + // This should succeed because bypassers are allowed to bypass the timelock + // Prepare data with timelock object ID let mut bypasser_active_update_delay_data = vector[]; bypasser_active_update_delay_data.append(bcs::to_bytes(&object::id_address(&env.timelock))); bypasser_active_update_delay_data.append(bcs::to_bytes(&new_delay)); - let _executing_params = mcms::test_timelock_bypasser_execute_batch( + let mut executing_params = mcms::test_timelock_bypasser_execute_batch( mcms::bypasser_role(), - vector[mcms_registry::get_multisig_address()], - vector[string::utf8(b"mcms")], - vector[string::utf8(b"timelock_update_min_delay")], - vector[bypasser_active_update_delay_data], + vector[mcms_registry::get_multisig_address()], // targets + vector[string::utf8(b"mcms")], // module_names + vector[string::utf8(b"timelock_update_min_delay")], // function_names + vector[bypasser_active_update_delay_data], // datas + env.scenario.ctx(), + ); + + // Process the executing callback params to complete the operation + mcms::mcms_timelock_update_min_delay( + &mut env.timelock, + &mut env.registry, + executing_params.pop_back(), env.scenario.ctx(), ); + // Verify the min_delay was updated, confirming the bypass worked + let updated_delay = mcms::timelock_min_delay(&env.timelock); + assert!(updated_delay == new_delay, 0); + + executing_params.destroy_empty(); env.destroy(); } @@ -2890,7 +2976,7 @@ fun test_timelock_dispatch_to_deployer() { data.append(bcs::to_bytes(&mcms_package)); // package address for authorize_upgrade let mut executing_params = mcms::test_timelock_bypasser_execute_batch( - mcms::timelock_role(), + mcms::bypasser_role(), vector[mcms_package], vector[string::utf8(b"mcms_deployer")], vector[string::utf8(b"authorize_upgrade")], @@ -3041,25 +3127,27 @@ fun test_dispatch_timelock_execute_batch() { } #[test] -#[expected_failure(abort_code = mcms::EBypasserSelfGovernanceForbidden, location = mcms)] fun test_dispatch_timelock_bypasser_execute_batch() { let mut env = setup(); + // Create serialized data for timelock_bypasser_execute_batch parameters let targets = vector[mcms_registry::get_multisig_address()]; let module_names = vector[string::utf8(b"mcms")]; let function_names = vector[string::utf8(b"timelock_update_min_delay")]; + // Prepare data with timelock object ID let mut bypasser_batch_data = vector[]; bypasser_batch_data.append(bcs::to_bytes(&object::id_address(&env.timelock))); - bypasser_batch_data.append(bcs::to_bytes(&2000u64)); + bypasser_batch_data.append(bcs::to_bytes(&2000)); - let datas = vector[bypasser_batch_data]; + let datas = vector[bypasser_batch_data]; // new min delay with timelock ID let mut serialized_data = bcs::to_bytes(&targets); serialized_data.append(bcs::to_bytes(&module_names)); serialized_data.append(bcs::to_bytes(&function_names)); serialized_data.append(bcs::to_bytes(&datas)); + // Create TimelockCallbackParams for dispatch_timelock_bypasser_execute_batch let callback_params = mcms::test_create_timelock_callback_params( mcms::bypasser_role(), string::utf8(b"mcms"), @@ -3067,10 +3155,25 @@ fun test_dispatch_timelock_bypasser_execute_batch() { serialized_data, ); - let _executing_params = mcms::dispatch_timelock_bypasser_execute_batch( + let mut executing_params = mcms::dispatch_timelock_bypasser_execute_batch( callback_params, env.scenario.ctx(), ); + assert!(executing_params.length() == 1); + + // Verify the ExecutingCallbackParams has the correct structure + let params = &executing_params[0]; + assert!(mcms_registry::target(params) == mcms_registry::get_multisig_address()); + assert!(mcms_registry::module_name(params) == string::utf8(b"mcms")); + assert!(mcms_registry::function_name(params) == string::utf8(b"timelock_update_min_delay")); + + mcms::mcms_timelock_update_min_delay( + &mut env.timelock, + &mut env.registry, + executing_params.pop_back(), + env.scenario.ctx(), + ); + executing_params.destroy_empty(); env.destroy(); } From b242715c5375a740c8dfe7a27e1fae2f6c38c29c Mon Sep 17 00:00:00 2001 From: FelixFan1992 Date: Mon, 15 Jun 2026 15:04:02 -0400 Subject: [PATCH 3/4] fix --- .../fast_mcms/tests/mcms_deployer_test.move | 25 +++++++++++++------ .../mcms/mcms/tests/mcms_deployer_test.move | 25 +++++++++++++------ 2 files changed, 34 insertions(+), 16 deletions(-) diff --git a/contracts/mcms/fast_mcms/tests/mcms_deployer_test.move b/contracts/mcms/fast_mcms/tests/mcms_deployer_test.move index 7bca2f890..879db3f56 100644 --- a/contracts/mcms/fast_mcms/tests/mcms_deployer_test.move +++ b/contracts/mcms/fast_mcms/tests/mcms_deployer_test.move @@ -4,9 +4,12 @@ module mcms::mcms_deployer_test; use mcms::mcms_account; use mcms::mcms_deployer::{Self, DeployerState}; use mcms::mcms_registry::{Self, Registry}; +use sui::address; use sui::package::{Self, UpgradeCap}; use sui::test_scenario::{Self as ts, Scenario}; +const TEST_UPGRADE_CAP_PACKAGE: address = @0x42; + public struct MCMS_DEPLOYER_TEST has drop {} public struct TestOwnerCap has key, store { @@ -18,7 +21,7 @@ fun create_test_scenario(): Scenario { } fun generate_upgrade_cap(ctx: &mut TxContext): UpgradeCap { - package::test_publish(mcms_registry::get_multisig_address().to_id(), ctx) + package::test_publish(TEST_UPGRADE_CAP_PACKAGE.to_id(), ctx) } fun init_mcms(scenario: &mut Scenario) { @@ -35,11 +38,11 @@ fun register_test_package_with_upgrade_cap(scenario: &mut Scenario): address { let ctx = ts::ctx(scenario); let upgrade_cap = generate_upgrade_cap(ctx); - let package_address = upgrade_cap.package().to_address(); ts::next_tx(scenario, @0xA); let ctx = ts::ctx(scenario); let publisher = package::test_claim(MCMS_DEPLOYER_TEST {}, ctx); + let package_address = address::from_ascii_bytes(&(*publisher.package()).into_bytes()); let owner_cap = TestOwnerCap { id: object::new(ctx) }; let publisher_wrapper = mcms_registry::create_publisher_wrapper( @@ -56,10 +59,11 @@ fun register_test_package_with_upgrade_cap(scenario: &mut Scenario): address { ctx, ); - mcms_deployer::register_upgrade_cap( + mcms_deployer::test_register_upgrade_cap_for_package( &mut deployer_state, ®istry, upgrade_cap, + package_address, ctx, ); @@ -91,6 +95,7 @@ fun test_register_upgrade_cap() { ts::next_tx(&mut scenario, @0xA); let ctx = ts::ctx(&mut scenario); let publisher = package::test_claim(MCMS_DEPLOYER_TEST {}, ctx); + let package_address = address::from_ascii_bytes(&(*publisher.package()).into_bytes()); let publisher_wrapper = mcms_registry::create_publisher_wrapper( &publisher, @@ -108,10 +113,11 @@ fun test_register_upgrade_cap() { ); // Then register with MCMS deployer - mcms_deployer::register_upgrade_cap( + mcms_deployer::test_register_upgrade_cap_for_package( &mut deployer_state, ®istry, upgrade_cap, + package_address, ctx, ); @@ -142,7 +148,7 @@ fun test_release_upgrade_cap_at_succeeds() { MCMS_DEPLOYER_TEST {}, ); - assert!(upgrade_cap.package().to_address() == package_address); + assert!(upgrade_cap.package().to_address() == TEST_UPGRADE_CAP_PACKAGE); assert!(!mcms_deployer::has_upgrade_cap(&deployer_state, package_address)); transfer::public_transfer(upgrade_cap, @0xA); @@ -162,17 +168,19 @@ fun test_release_upgrade_cap_at_fails_after_release_cap() { let mut deployer_state = ts::take_shared(&scenario); let mut registry = ts::take_shared(&scenario); - let _owner_cap = mcms_registry::release_cap( + let owner_cap = mcms_registry::release_cap( &mut registry, MCMS_DEPLOYER_TEST {}, ); + transfer::public_transfer(owner_cap, @0xA); - mcms_deployer::release_upgrade_cap_at( + let upgrade_cap = mcms_deployer::release_upgrade_cap_at( &mut deployer_state, ®istry, package_address, MCMS_DEPLOYER_TEST {}, ); + transfer::public_transfer(upgrade_cap, @0xA); ts::return_shared(deployer_state); ts::return_shared(registry); @@ -254,11 +262,12 @@ fun test_release_upgrade_cap_fails_after_commit_upgrade() { ts::return_to_sender(&scenario, owner_cap); let registry = ts::take_shared(&scenario); - mcms_deployer::release_upgrade_cap( + let upgrade_cap = mcms_deployer::release_upgrade_cap( &mut deployer_state, ®istry, MCMS_DEPLOYER_TEST {}, ); + transfer::public_transfer(upgrade_cap, @0xA); ts::return_shared(deployer_state); ts::return_shared(registry); diff --git a/contracts/mcms/mcms/tests/mcms_deployer_test.move b/contracts/mcms/mcms/tests/mcms_deployer_test.move index 7bca2f890..879db3f56 100644 --- a/contracts/mcms/mcms/tests/mcms_deployer_test.move +++ b/contracts/mcms/mcms/tests/mcms_deployer_test.move @@ -4,9 +4,12 @@ module mcms::mcms_deployer_test; use mcms::mcms_account; use mcms::mcms_deployer::{Self, DeployerState}; use mcms::mcms_registry::{Self, Registry}; +use sui::address; use sui::package::{Self, UpgradeCap}; use sui::test_scenario::{Self as ts, Scenario}; +const TEST_UPGRADE_CAP_PACKAGE: address = @0x42; + public struct MCMS_DEPLOYER_TEST has drop {} public struct TestOwnerCap has key, store { @@ -18,7 +21,7 @@ fun create_test_scenario(): Scenario { } fun generate_upgrade_cap(ctx: &mut TxContext): UpgradeCap { - package::test_publish(mcms_registry::get_multisig_address().to_id(), ctx) + package::test_publish(TEST_UPGRADE_CAP_PACKAGE.to_id(), ctx) } fun init_mcms(scenario: &mut Scenario) { @@ -35,11 +38,11 @@ fun register_test_package_with_upgrade_cap(scenario: &mut Scenario): address { let ctx = ts::ctx(scenario); let upgrade_cap = generate_upgrade_cap(ctx); - let package_address = upgrade_cap.package().to_address(); ts::next_tx(scenario, @0xA); let ctx = ts::ctx(scenario); let publisher = package::test_claim(MCMS_DEPLOYER_TEST {}, ctx); + let package_address = address::from_ascii_bytes(&(*publisher.package()).into_bytes()); let owner_cap = TestOwnerCap { id: object::new(ctx) }; let publisher_wrapper = mcms_registry::create_publisher_wrapper( @@ -56,10 +59,11 @@ fun register_test_package_with_upgrade_cap(scenario: &mut Scenario): address { ctx, ); - mcms_deployer::register_upgrade_cap( + mcms_deployer::test_register_upgrade_cap_for_package( &mut deployer_state, ®istry, upgrade_cap, + package_address, ctx, ); @@ -91,6 +95,7 @@ fun test_register_upgrade_cap() { ts::next_tx(&mut scenario, @0xA); let ctx = ts::ctx(&mut scenario); let publisher = package::test_claim(MCMS_DEPLOYER_TEST {}, ctx); + let package_address = address::from_ascii_bytes(&(*publisher.package()).into_bytes()); let publisher_wrapper = mcms_registry::create_publisher_wrapper( &publisher, @@ -108,10 +113,11 @@ fun test_register_upgrade_cap() { ); // Then register with MCMS deployer - mcms_deployer::register_upgrade_cap( + mcms_deployer::test_register_upgrade_cap_for_package( &mut deployer_state, ®istry, upgrade_cap, + package_address, ctx, ); @@ -142,7 +148,7 @@ fun test_release_upgrade_cap_at_succeeds() { MCMS_DEPLOYER_TEST {}, ); - assert!(upgrade_cap.package().to_address() == package_address); + assert!(upgrade_cap.package().to_address() == TEST_UPGRADE_CAP_PACKAGE); assert!(!mcms_deployer::has_upgrade_cap(&deployer_state, package_address)); transfer::public_transfer(upgrade_cap, @0xA); @@ -162,17 +168,19 @@ fun test_release_upgrade_cap_at_fails_after_release_cap() { let mut deployer_state = ts::take_shared(&scenario); let mut registry = ts::take_shared(&scenario); - let _owner_cap = mcms_registry::release_cap( + let owner_cap = mcms_registry::release_cap( &mut registry, MCMS_DEPLOYER_TEST {}, ); + transfer::public_transfer(owner_cap, @0xA); - mcms_deployer::release_upgrade_cap_at( + let upgrade_cap = mcms_deployer::release_upgrade_cap_at( &mut deployer_state, ®istry, package_address, MCMS_DEPLOYER_TEST {}, ); + transfer::public_transfer(upgrade_cap, @0xA); ts::return_shared(deployer_state); ts::return_shared(registry); @@ -254,11 +262,12 @@ fun test_release_upgrade_cap_fails_after_commit_upgrade() { ts::return_to_sender(&scenario, owner_cap); let registry = ts::take_shared(&scenario); - mcms_deployer::release_upgrade_cap( + let upgrade_cap = mcms_deployer::release_upgrade_cap( &mut deployer_state, ®istry, MCMS_DEPLOYER_TEST {}, ); + transfer::public_transfer(upgrade_cap, @0xA); ts::return_shared(deployer_state); ts::return_shared(registry); From 6ed457d160a46c5879a39c6203440a8a22fe00a8 Mon Sep 17 00:00:00 2001 From: FelixFan1992 Date: Tue, 16 Jun 2026 17:42:13 -0400 Subject: [PATCH 4/4] update all packages --- contracts/ccip/ccip/Move.lock | 14 +- contracts/ccip/ccip/sources/state_object.move | 7 +- .../mcms_ownership_transfer_out_test.move | 280 ++++++++++++++++++ .../ccip/ccip/tests/state_object_tests.move | 102 +++++++ contracts/ccip/ccip_offramp/Move.lock | 16 +- .../ccip/ccip_offramp/sources/offramp.move | 7 +- contracts/ccip/ccip_onramp/Move.lock | 16 +- .../ccip/ccip_onramp/sources/onramp.move | 7 +- .../ccip_onramp/tests/onramp_mcms_test.move | 103 +++++++ contracts/ccip/ccip_router/Move.lock | 83 ++---- .../ccip/ccip_router/sources/router.move | 9 +- .../burn_mint_token_pool/Move.lock | 16 +- .../sources/burn_mint_token_pool.move | 13 +- .../lock_release_token_pool/Move.lock | 16 +- .../sources/lock_release_token_pool.move | 19 +- .../managed_token_pool/Move.lock | 18 +- .../sources/managed_token_pool.move | 13 +- .../sources/usdc_token_pool.move | 13 +- .../managed_token/sources/managed_token.move | 7 +- contracts/ccip/mock_offramp_v2/offramp.move | 9 +- contracts/ccip/mock_onramp_v2/onramp.move | 7 +- 21 files changed, 638 insertions(+), 137 deletions(-) create mode 100644 contracts/ccip/ccip/tests/mcms_ownership_transfer_out_test.move diff --git a/contracts/ccip/ccip/Move.lock b/contracts/ccip/ccip/Move.lock index b1dc50ad8..fb994496c 100644 --- a/contracts/ccip/ccip/Move.lock +++ b/contracts/ccip/ccip/Move.lock @@ -5,13 +5,13 @@ version = 4 [pinned.testnet.MoveStdlib] -source = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/move-stdlib", rev = "384b1c7ff289b32ed2664f29dc47dbc879fa43ee" } +source = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/move-stdlib", rev = "718ae563a42fb4ba0d055588f81c704dcef58c25" } use_environment = "testnet" manifest_digest = "C4FE4C91DE74CBF223B2E380AE40F592177D21870DC2D7EB6227D2D694E05363" deps = {} [pinned.testnet.Sui] -source = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/sui-framework", rev = "384b1c7ff289b32ed2664f29dc47dbc879fa43ee" } +source = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/sui-framework", rev = "718ae563a42fb4ba0d055588f81c704dcef58c25" } use_environment = "testnet" manifest_digest = "7AFB66695545775FBFBB2D3078ADFD084244D5002392E837FDE21D9EA1C6D01C" deps = { MoveStdlib = "MoveStdlib" } @@ -19,10 +19,16 @@ deps = { MoveStdlib = "MoveStdlib" } [pinned.testnet.ccip] source = { root = true } use_environment = "testnet" -manifest_digest = "B41543A1928002B1ABE6F16D69A3C1BE6BCA4D7A4DB7D2F9048274EFE11ED724" -deps = { mcms = "mcms", std = "MoveStdlib", sui = "Sui" } +manifest_digest = "E5912E2FA12F49E878734EC73B1CAC461C88F1562C6C02B940082B986AE865FE" +deps = { fast_mcms = "mcms", mcms = "mcms_1", std = "MoveStdlib", sui = "Sui" } [pinned.testnet.mcms] +source = { local = "../../mcms/fast_mcms" } +use_environment = "testnet" +manifest_digest = "5745706258F61D6CE210904B3E6AE87A73CE9D31A6F93BE4718C442529332A87" +deps = { std = "MoveStdlib", sui = "Sui" } + +[pinned.testnet.mcms_1] source = { local = "../../mcms/mcms" } use_environment = "testnet" manifest_digest = "5745706258F61D6CE210904B3E6AE87A73CE9D31A6F93BE4718C442529332A87" diff --git a/contracts/ccip/ccip/sources/state_object.move b/contracts/ccip/ccip/sources/state_object.move index 3e3838904..5a60bb09f 100644 --- a/contracts/ccip/ccip/sources/state_object.move +++ b/contracts/ccip/ccip/sources/state_object.move @@ -347,17 +347,18 @@ public fun mcms_execute_ownership_transfer( let package_address = bcs_stream::deserialize_address(&mut stream); bcs_stream::assert_is_consumed(&stream); - let owner_cap = mcms_registry::release_cap(registry, McmsCallback {}); - if (mcms_deployer::has_upgrade_cap(deployer_state, package_address)) { - let upgrade_cap = mcms_deployer::release_upgrade_cap( + let upgrade_cap = mcms_deployer::release_upgrade_cap_at( deployer_state, registry, + package_address, McmsCallback {}, ); transfer::public_transfer(upgrade_cap, to); }; + let owner_cap = mcms_registry::release_cap(registry, McmsCallback {}); + execute_ownership_transfer(ref, owner_cap, to, ctx); } diff --git a/contracts/ccip/ccip/tests/mcms_ownership_transfer_out_test.move b/contracts/ccip/ccip/tests/mcms_ownership_transfer_out_test.move new file mode 100644 index 000000000..0c9890c0d --- /dev/null +++ b/contracts/ccip/ccip/tests/mcms_ownership_transfer_out_test.move @@ -0,0 +1,280 @@ +#[test_only] +module ccip::mcms_ownership_transfer_out_test; + +use ccip::ownable::{Self, OwnerCap}; +use ccip::state_object::{Self, CCIPObjectRef}; +use mcms::mcms_account; +use mcms::mcms_deployer::{Self, DeployerState}; +use mcms::mcms_registry::{Self, Registry}; +use std::string; +use sui::bcs; +use sui::package::{Self, UpgradeCap}; +use sui::test_scenario::{Self as ts, Scenario}; + +const ADMIN: address = @0xA; +const PACKAGE_OWNER: address = @0xB; +const TRANSFER_TARGET: address = @0xC; + +fun init_mcms_and_ccip(ctx: &mut TxContext) { + mcms_account::test_init(ctx); + mcms_registry::test_init(ctx); + mcms_deployer::test_init(ctx); + state_object::test_init(ctx); +} + +fun transfer_ownership_to_mcms(scenario: &mut Scenario) { + ts::next_tx(scenario, ADMIN); + let mut registry = ts::take_shared(scenario); + let mut ref = ts::take_shared(scenario); + let owner_cap = ts::take_from_sender(scenario); + + state_object::transfer_ownership( + &mut ref, + &owner_cap, + mcms_registry::get_multisig_address(), + ts::ctx(scenario), + ); + + ts::next_tx(scenario, mcms_registry::get_multisig_address()); + state_object::accept_ownership(&mut ref, ts::ctx(scenario)); + + ts::next_tx(scenario, ADMIN); + + state_object::execute_ownership_transfer_to_mcms( + &mut ref, + owner_cap, + &mut registry, + mcms_registry::get_multisig_address(), + ts::ctx(scenario), + ); + + ts::return_shared(registry); + ts::return_shared(ref); +} + +fun register_upgrade_cap(scenario: &mut Scenario): address { + ts::next_tx(scenario, PACKAGE_OWNER); + let mut deployer_state = ts::take_shared(scenario); + let registry = ts::take_shared(scenario); + let ctx = ts::ctx(scenario); + + let upgrade_cap = package::test_publish(@ccip.to_id(), ctx); + let package_address = upgrade_cap.package().to_address(); + + mcms_deployer::register_upgrade_cap( + &mut deployer_state, + ®istry, + upgrade_cap, + ctx, + ); + + ts::return_shared(deployer_state); + ts::return_shared(registry); + + package_address +} + +fun initiate_and_accept_transfer_from_mcms( + scenario: &mut Scenario, + ref: &mut CCIPObjectRef, + registry: &mut Registry, +) { + ts::next_tx(scenario, ADMIN); + let owner_cap_address = mcms_registry::test_get_cap_address( + registry, + @ccip.to_ascii_string(), + ); + + let mut data = vector::empty(); + data.append(bcs::to_bytes(&object::id_address(ref))); + data.append(bcs::to_bytes(&owner_cap_address)); + data.append(bcs::to_bytes(&TRANSFER_TARGET)); + + let params = mcms_registry::test_create_executing_callback_params( + @ccip, + string::utf8(b"state_object"), + string::utf8(b"transfer_ownership"), + data, + x"0000000000000000000000000000000000000000000000000000000000000001", + 0, + 1, + ); + + state_object::mcms_transfer_ownership( + ref, + registry, + params, + ts::ctx(scenario), + ); + + ts::next_tx(scenario, TRANSFER_TARGET); + state_object::accept_ownership(ref, ts::ctx(scenario)); +} + +fun execute_mcms_ownership_transfer( + scenario: &mut Scenario, + ref: &mut CCIPObjectRef, + registry: &mut Registry, + deployer_state: &mut DeployerState, + package_address: address, + batch_id: vector, +) { + let owner_cap_address = mcms_registry::test_get_cap_address( + registry, + @ccip.to_ascii_string(), + ); + + let mut data = vector::empty(); + data.append(bcs::to_bytes(&object::id_address(ref))); + data.append(bcs::to_bytes(&owner_cap_address)); + data.append(bcs::to_bytes(&TRANSFER_TARGET)); + data.append(bcs::to_bytes(&package_address)); + + let params = mcms_registry::test_create_executing_callback_params( + @ccip, + string::utf8(b"state_object"), + string::utf8(b"execute_ownership_transfer"), + data, + batch_id, + 0, + 1, + ); + + state_object::mcms_execute_ownership_transfer( + ref, + registry, + deployer_state, + params, + ts::ctx(scenario), + ); +} + +fun assert_recipient_received_caps( + scenario: &mut Scenario, + recipient: address, + expected_package_address: address, +) { + ts::next_tx(scenario, recipient); + let owner_cap = ts::take_from_sender(scenario); + let upgrade_cap = ts::take_from_sender(scenario); + assert!(upgrade_cap.package().to_address() == expected_package_address); + ts::return_to_address(recipient, owner_cap); + ts::return_to_address(recipient, upgrade_cap); +} + +#[test] +fun test_mcms_execute_ownership_transfer_with_upgrade_cap() { + let mut scenario = ts::begin(ADMIN); + + init_mcms_and_ccip(ts::ctx(&mut scenario)); + transfer_ownership_to_mcms(&mut scenario); + let package_address = register_upgrade_cap(&mut scenario); + + ts::next_tx(&mut scenario, ADMIN); + { + let registry = ts::take_shared(&scenario); + let deployer_state = ts::take_shared(&scenario); + + assert!(mcms_registry::is_package_registered(®istry, @ccip.to_ascii_string())); + assert!(mcms_deployer::has_upgrade_cap(&deployer_state, package_address)); + + ts::return_shared(registry); + ts::return_shared(deployer_state); + }; + + ts::next_tx(&mut scenario, ADMIN); + let mut ref = ts::take_shared(&scenario); + let mut registry = ts::take_shared(&scenario); + initiate_and_accept_transfer_from_mcms(&mut scenario, &mut ref, &mut registry); + ts::return_shared(registry); + ts::return_shared(ref); + + ts::next_tx(&mut scenario, ADMIN); + let mut ref = ts::take_shared(&scenario); + let mut registry = ts::take_shared(&scenario); + let mut deployer_state = ts::take_shared(&scenario); + + execute_mcms_ownership_transfer( + &mut scenario, + &mut ref, + &mut registry, + &mut deployer_state, + package_address, + x"0000000000000000000000000000000000000000000000000000000000000002", + ); + + assert!(state_object::owner(&ref) == TRANSFER_TARGET); + assert!(!mcms_deployer::has_upgrade_cap(&deployer_state, package_address)); + assert!(!mcms_registry::is_package_registered(®istry, @ccip.to_ascii_string())); + + assert_recipient_received_caps(&mut scenario, TRANSFER_TARGET, package_address); + + ts::return_shared(deployer_state); + ts::return_shared(registry); + ts::return_shared(ref); + ts::end(scenario); +} + +#[test] +fun test_mcms_execute_ownership_transfer_after_upgrade() { + let mut scenario = ts::begin(ADMIN); + + init_mcms_and_ccip(ts::ctx(&mut scenario)); + transfer_ownership_to_mcms(&mut scenario); + let old_package_address = register_upgrade_cap(&mut scenario); + + ts::next_tx(&mut scenario, ADMIN); + let mut deployer_state = ts::take_shared(&scenario); + let owner_cap = ts::take_from_sender(&scenario); + let ctx = ts::ctx(&mut scenario); + + let ticket = mcms_deployer::authorize_upgrade( + &owner_cap, + &mut deployer_state, + 0, + vector[], + old_package_address, + ctx, + ); + let receipt = package::test_upgrade(ticket); + let new_package_address = receipt.package().to_address(); + + mcms_deployer::commit_upgrade(&mut deployer_state, receipt, ctx); + + assert!(!mcms_deployer::has_upgrade_cap(&deployer_state, old_package_address)); + assert!(mcms_deployer::has_upgrade_cap(&deployer_state, new_package_address)); + + ts::return_to_sender(&scenario, owner_cap); + ts::return_shared(deployer_state); + + ts::next_tx(&mut scenario, ADMIN); + let mut ref = ts::take_shared(&scenario); + let mut registry = ts::take_shared(&scenario); + initiate_and_accept_transfer_from_mcms(&mut scenario, &mut ref, &mut registry); + ts::return_shared(registry); + ts::return_shared(ref); + + ts::next_tx(&mut scenario, ADMIN); + let mut ref = ts::take_shared(&scenario); + let mut registry = ts::take_shared(&scenario); + let mut deployer_state = ts::take_shared(&scenario); + + execute_mcms_ownership_transfer( + &mut scenario, + &mut ref, + &mut registry, + &mut deployer_state, + new_package_address, + x"0000000000000000000000000000000000000000000000000000000000000003", + ); + + assert!(state_object::owner(&ref) == TRANSFER_TARGET); + assert!(!mcms_deployer::has_upgrade_cap(&deployer_state, new_package_address)); + + assert_recipient_received_caps(&mut scenario, TRANSFER_TARGET, new_package_address); + + ts::return_shared(deployer_state); + ts::return_shared(registry); + ts::return_shared(ref); + ts::end(scenario); +} diff --git a/contracts/ccip/ccip/tests/state_object_tests.move b/contracts/ccip/ccip/tests/state_object_tests.move index 7b37a046f..bc14adf19 100644 --- a/contracts/ccip/ccip/tests/state_object_tests.move +++ b/contracts/ccip/ccip/tests/state_object_tests.move @@ -9,6 +9,7 @@ use mcms::mcms_registry::{Self, Registry}; use std::string; use sui::address; use sui::bcs; +use sui::package; use sui::test_scenario::{Self, Scenario}; const SENDER_1: address = @0x1; @@ -675,3 +676,104 @@ fun test_mcms_three_step_ownership_transfer() { test_scenario::return_shared(ref); test_scenario::end(scenario); } + +#[test] +fun test_mcms_three_step_ownership_transfer_with_upgrade_cap() { + let (mut scenario, mut registry, mut ref) = setup_with_mcms_ownership(); + + scenario.next_tx(OWNER); + { + let mut deployer_state = test_scenario::take_shared(&scenario); + let ctx = scenario.ctx(); + let upgrade_cap = package::test_publish(@ccip.to_id(), ctx); + + mcms_deployer::register_upgrade_cap( + &mut deployer_state, + ®istry, + upgrade_cap, + ctx, + ); + + assert!(mcms_deployer::has_upgrade_cap(&deployer_state, @ccip)); + test_scenario::return_shared(deployer_state); + }; + + let new_owner = SENDER_2; + scenario.next_tx(OWNER); + + { + let owner_cap_address = mcms_registry::test_get_cap_address( + ®istry, + @ccip.to_ascii_string(), + ); + + let mut data = vector::empty(); + data.append(bcs::to_bytes(&object::id_address(&ref))); + data.append(bcs::to_bytes(&owner_cap_address)); + data.append(bcs::to_bytes(&new_owner)); + + let params = mcms_registry::test_create_executing_callback_params( + @ccip, + string::utf8(b"state_object"), + string::utf8(b"transfer_ownership"), + data, + x"0000000000000000000000000000000000000000000000000000000000000001", + 0, + 1, + ); + + state_object::mcms_transfer_ownership( + &mut ref, + &mut registry, + params, + scenario.ctx(), + ); + }; + + scenario.next_tx(new_owner); + { + state_object::accept_ownership(&mut ref, scenario.ctx()); + }; + + scenario.next_tx(OWNER); + { + let owner_cap_address = mcms_registry::test_get_cap_address( + ®istry, + @ccip.to_ascii_string(), + ); + let mut deployer_state = test_scenario::take_shared(&scenario); + + let mut data = vector::empty(); + data.append(bcs::to_bytes(&object::id_address(&ref))); + data.append(bcs::to_bytes(&owner_cap_address)); + data.append(bcs::to_bytes(&new_owner)); + data.append(bcs::to_bytes(&@ccip)); + + let params = mcms_registry::test_create_executing_callback_params( + @ccip, + string::utf8(b"state_object"), + string::utf8(b"execute_ownership_transfer"), + data, + x"0000000000000000000000000000000000000000000000000000000000000002", + 0, + 1, + ); + + state_object::mcms_execute_ownership_transfer( + &mut ref, + &mut registry, + &mut deployer_state, + params, + scenario.ctx(), + ); + + assert!(!mcms_deployer::has_upgrade_cap(&deployer_state, @ccip)); + test_scenario::return_shared(deployer_state); + }; + + assert!(state_object::owner(&ref) == new_owner); + + test_scenario::return_shared(registry); + test_scenario::return_shared(ref); + test_scenario::end(scenario); +} diff --git a/contracts/ccip/ccip_offramp/Move.lock b/contracts/ccip/ccip_offramp/Move.lock index 6315ed2e6..8cb709778 100644 --- a/contracts/ccip/ccip_offramp/Move.lock +++ b/contracts/ccip/ccip_offramp/Move.lock @@ -5,13 +5,13 @@ version = 4 [pinned.testnet.MoveStdlib] -source = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/move-stdlib", rev = "c2428b3aaf9c24270b609001e56d96cb10c76d28" } +source = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/move-stdlib", rev = "718ae563a42fb4ba0d055588f81c704dcef58c25" } use_environment = "testnet" manifest_digest = "C4FE4C91DE74CBF223B2E380AE40F592177D21870DC2D7EB6227D2D694E05363" deps = {} [pinned.testnet.Sui] -source = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/sui-framework", rev = "c2428b3aaf9c24270b609001e56d96cb10c76d28" } +source = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/sui-framework", rev = "718ae563a42fb4ba0d055588f81c704dcef58c25" } use_environment = "testnet" manifest_digest = "7AFB66695545775FBFBB2D3078ADFD084244D5002392E837FDE21D9EA1C6D01C" deps = { MoveStdlib = "MoveStdlib" } @@ -19,16 +19,22 @@ deps = { MoveStdlib = "MoveStdlib" } [pinned.testnet.ccip] source = { local = "../ccip" } use_environment = "testnet" -manifest_digest = "B41543A1928002B1ABE6F16D69A3C1BE6BCA4D7A4DB7D2F9048274EFE11ED724" -deps = { mcms = "mcms", std = "MoveStdlib", sui = "Sui" } +manifest_digest = "E5912E2FA12F49E878734EC73B1CAC461C88F1562C6C02B940082B986AE865FE" +deps = { fast_mcms = "mcms", mcms = "mcms_1", std = "MoveStdlib", sui = "Sui" } [pinned.testnet.ccip_offramp] source = { root = true } use_environment = "testnet" manifest_digest = "ADA634B6916B069A30B224CF502B33E2A145FAD215D9E0425F0A850C8887A42E" -deps = { ccip = "ccip", mcms = "mcms", std = "MoveStdlib", sui = "Sui" } +deps = { ccip = "ccip", mcms = "mcms_1", std = "MoveStdlib", sui = "Sui" } [pinned.testnet.mcms] +source = { local = "../../mcms/fast_mcms" } +use_environment = "testnet" +manifest_digest = "5745706258F61D6CE210904B3E6AE87A73CE9D31A6F93BE4718C442529332A87" +deps = { std = "MoveStdlib", sui = "Sui" } + +[pinned.testnet.mcms_1] source = { local = "../../mcms/mcms" } use_environment = "testnet" manifest_digest = "5745706258F61D6CE210904B3E6AE87A73CE9D31A6F93BE4718C442529332A87" diff --git a/contracts/ccip/ccip_offramp/sources/offramp.move b/contracts/ccip/ccip_offramp/sources/offramp.move index 141bfc9b6..623375cda 100644 --- a/contracts/ccip/ccip_offramp/sources/offramp.move +++ b/contracts/ccip/ccip_offramp/sources/offramp.move @@ -1695,17 +1695,18 @@ public fun mcms_execute_ownership_transfer( let package_address = bcs_stream::deserialize_address(&mut stream); bcs_stream::assert_is_consumed(&stream); - let owner_cap = mcms_registry::release_cap(registry, McmsCallback {}); - if (mcms_deployer::has_upgrade_cap(deployer_state, package_address)) { - let upgrade_cap = mcms_deployer::release_upgrade_cap( + let upgrade_cap = mcms_deployer::release_upgrade_cap_at( deployer_state, registry, + package_address, McmsCallback {}, ); transfer::public_transfer(upgrade_cap, to); }; + let owner_cap = mcms_registry::release_cap(registry, McmsCallback {}); + execute_ownership_transfer(ref, owner_cap, state, to, ctx); } diff --git a/contracts/ccip/ccip_onramp/Move.lock b/contracts/ccip/ccip_onramp/Move.lock index 5f5437a42..3c299560a 100644 --- a/contracts/ccip/ccip_onramp/Move.lock +++ b/contracts/ccip/ccip_onramp/Move.lock @@ -5,13 +5,13 @@ version = 4 [pinned.testnet.MoveStdlib] -source = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/move-stdlib", rev = "384b1c7ff289b32ed2664f29dc47dbc879fa43ee" } +source = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/move-stdlib", rev = "718ae563a42fb4ba0d055588f81c704dcef58c25" } use_environment = "testnet" manifest_digest = "C4FE4C91DE74CBF223B2E380AE40F592177D21870DC2D7EB6227D2D694E05363" deps = {} [pinned.testnet.Sui] -source = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/sui-framework", rev = "384b1c7ff289b32ed2664f29dc47dbc879fa43ee" } +source = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/sui-framework", rev = "718ae563a42fb4ba0d055588f81c704dcef58c25" } use_environment = "testnet" manifest_digest = "7AFB66695545775FBFBB2D3078ADFD084244D5002392E837FDE21D9EA1C6D01C" deps = { MoveStdlib = "MoveStdlib" } @@ -19,16 +19,22 @@ deps = { MoveStdlib = "MoveStdlib" } [pinned.testnet.ccip] source = { local = "../ccip" } use_environment = "testnet" -manifest_digest = "B41543A1928002B1ABE6F16D69A3C1BE6BCA4D7A4DB7D2F9048274EFE11ED724" -deps = { mcms = "mcms", std = "MoveStdlib", sui = "Sui" } +manifest_digest = "E5912E2FA12F49E878734EC73B1CAC461C88F1562C6C02B940082B986AE865FE" +deps = { fast_mcms = "mcms", mcms = "mcms_1", std = "MoveStdlib", sui = "Sui" } [pinned.testnet.ccip_onramp] source = { root = true } use_environment = "testnet" manifest_digest = "ADA634B6916B069A30B224CF502B33E2A145FAD215D9E0425F0A850C8887A42E" -deps = { ccip = "ccip", mcms = "mcms", std = "MoveStdlib", sui = "Sui" } +deps = { ccip = "ccip", mcms = "mcms_1", std = "MoveStdlib", sui = "Sui" } [pinned.testnet.mcms] +source = { local = "../../mcms/fast_mcms" } +use_environment = "testnet" +manifest_digest = "5745706258F61D6CE210904B3E6AE87A73CE9D31A6F93BE4718C442529332A87" +deps = { std = "MoveStdlib", sui = "Sui" } + +[pinned.testnet.mcms_1] source = { local = "../../mcms/mcms" } use_environment = "testnet" manifest_digest = "5745706258F61D6CE210904B3E6AE87A73CE9D31A6F93BE4718C442529332A87" diff --git a/contracts/ccip/ccip_onramp/sources/onramp.move b/contracts/ccip/ccip_onramp/sources/onramp.move index 4c697bf26..02d2b893b 100644 --- a/contracts/ccip/ccip_onramp/sources/onramp.move +++ b/contracts/ccip/ccip_onramp/sources/onramp.move @@ -1417,17 +1417,18 @@ public fun mcms_execute_ownership_transfer( let package_address = bcs_stream::deserialize_address(&mut stream); bcs_stream::assert_is_consumed(&stream); - let owner_cap = mcms_registry::release_cap(registry, McmsCallback {}); - if (mcms_deployer::has_upgrade_cap(deployer_state, package_address)) { - let upgrade_cap = mcms_deployer::release_upgrade_cap( + let upgrade_cap = mcms_deployer::release_upgrade_cap_at( deployer_state, registry, + package_address, McmsCallback {}, ); transfer::public_transfer(upgrade_cap, to); }; + let owner_cap = mcms_registry::release_cap(registry, McmsCallback {}); + execute_ownership_transfer(ref, owner_cap, state, to, ctx); } diff --git a/contracts/ccip/ccip_onramp/tests/onramp_mcms_test.move b/contracts/ccip/ccip_onramp/tests/onramp_mcms_test.move index 9b6a001b3..d4d7c8c20 100644 --- a/contracts/ccip/ccip_onramp/tests/onramp_mcms_test.move +++ b/contracts/ccip/ccip_onramp/tests/onramp_mcms_test.move @@ -12,6 +12,7 @@ use mcms::mcms_deployer::{Self, DeployerState}; use mcms::mcms_registry::{Self, Registry}; use std::string; use sui::bcs; +use sui::package::{Self, UpgradeCap}; use sui::test_scenario::{Self as ts, Scenario}; const DEST_CHAIN_SELECTOR_1: u64 = 1; @@ -144,6 +145,17 @@ fun transfer_to_mcms( onramp::execute_ownership_transfer_to_mcms(ref, owner_cap, state, registry, @mcms, ctx); } +fun register_onramp_upgrade_cap(env: &mut Env) { + let ctx = env.scenario.ctx(); + let upgrade_cap = package::test_publish(@ccip_onramp.to_id(), ctx); + mcms_deployer::register_upgrade_cap( + &mut env.deployer_state, + &env.registry, + upgrade_cap, + ctx, + ); +} + #[test] public fun test_mcms_set_dynamic_config() { let (mut env, nonce_manager_cap, source_transfer_cap) = setup(); @@ -398,3 +410,94 @@ public fun test_mcms_transfer_ownership_e2e() { env.tear_down(); } + +#[test] +public fun test_mcms_execute_ownership_transfer_with_upgrade_cap() { + let (mut env, nonce_manager_cap, source_transfer_cap) = setup(); + + let owner_cap = ts::take_from_sender(&env.scenario); + let owner_cap_address = object::id_address(&owner_cap); + initialize_onramp(&mut env, &owner_cap, nonce_manager_cap, source_transfer_cap); + + let new_owner = @0x999; + let mut data = vector[]; + data.append(bcs::to_bytes(&object::id_address(&env.ref))); + data.append(bcs::to_bytes(&object::id_address(&env.state))); + data.append(bcs::to_bytes(&object::id_address(&owner_cap))); + data.append(bcs::to_bytes(&new_owner)); + + transfer_to_mcms( + &env.ref, + &mut env.state, + &mut env.registry, + owner_cap, + env.scenario.ctx(), + ); + + env.scenario.next_tx(OWNER); + { + register_onramp_upgrade_cap(&mut env); + assert!(mcms_deployer::has_upgrade_cap(&env.deployer_state, @ccip_onramp)); + }; + + let params = mcms_registry::test_create_executing_callback_params( + @ccip_onramp, + string::utf8(MODULE_NAME), + string::utf8(b"transfer_ownership"), + data, + x"0000000000000000000000000000000000000000000000000000000000000007", + 0, + 1, + ); + + onramp::mcms_transfer_ownership( + &env.ref, + &mut env.state, + &mut env.registry, + params, + env.scenario.ctx(), + ); + + env.scenario.next_tx(new_owner); + onramp::accept_ownership(&env.ref, &mut env.state, env.scenario.ctx()); + + let mut data = vector[]; + data.append(bcs::to_bytes(&object::id_address(&env.ref))); + data.append(bcs::to_bytes(&owner_cap_address)); + data.append(bcs::to_bytes(&object::id_address(&env.state))); + data.append(bcs::to_bytes(&new_owner)); + data.append(bcs::to_bytes(&@ccip_onramp)); + + let params = mcms_registry::test_create_executing_callback_params( + @ccip_onramp, + string::utf8(MODULE_NAME), + string::utf8(b"execute_ownership_transfer"), + data, + x"0000000000000000000000000000000000000000000000000000000000000008", + 0, + 1, + ); + + onramp::mcms_execute_ownership_transfer( + &env.ref, + &mut env.state, + &mut env.registry, + &mut env.deployer_state, + params, + env.scenario.ctx(), + ); + + assert!(onramp::owner(&env.state) == new_owner); + assert!(!mcms_deployer::has_upgrade_cap(&env.deployer_state, @ccip_onramp)); + + env.scenario.next_tx(new_owner); + { + let received_owner_cap = ts::take_from_sender(&env.scenario); + let received_upgrade_cap = ts::take_from_sender(&env.scenario); + assert!(received_upgrade_cap.package().to_address() == @ccip_onramp); + ts::return_to_address(new_owner, received_owner_cap); + ts::return_to_address(new_owner, received_upgrade_cap); + }; + + env.tear_down(); +} diff --git a/contracts/ccip/ccip_router/Move.lock b/contracts/ccip/ccip_router/Move.lock index fe76f55ac..4aa81a4fb 100644 --- a/contracts/ccip/ccip_router/Move.lock +++ b/contracts/ccip/ccip_router/Move.lock @@ -1,60 +1,29 @@ -# @generated by Move, please check-in and do not edit manually. +# Generated by move; do not edit +# This file should be checked in. [move] -version = 3 -manifest_digest = "9D3595FFF53414E5A8B80E44EE49B3DD20794145442EAC606259AFBAA65DAA32" -deps_digest = "397E6A9F7A624706DBDFEE056CE88391A15876868FD18A88504DA74EB458D697" -dependencies = [ - { id = "Bridge", name = "Bridge" }, - { id = "ChainlinkManyChainMultisig", name = "ChainlinkManyChainMultisig" }, - { id = "MoveStdlib", name = "MoveStdlib" }, - { id = "Sui", name = "Sui" }, - { id = "SuiSystem", name = "SuiSystem" }, -] - -[[move.package]] -id = "Bridge" -source = { git = "https://github.com/MystenLabs/sui.git", rev = "4e8b6eda7d6411d80c62f39ac8a4f028e8d174c4", subdir = "crates/sui-framework/packages/bridge" } - -dependencies = [ - { id = "MoveStdlib", name = "MoveStdlib" }, - { id = "Sui", name = "Sui" }, - { id = "SuiSystem", name = "SuiSystem" }, -] - -[[move.package]] -id = "ChainlinkManyChainMultisig" +version = 4 + +[pinned.testnet.MoveStdlib] +source = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/move-stdlib", rev = "718ae563a42fb4ba0d055588f81c704dcef58c25" } +use_environment = "testnet" +manifest_digest = "C4FE4C91DE74CBF223B2E380AE40F592177D21870DC2D7EB6227D2D694E05363" +deps = {} + +[pinned.testnet.Sui] +source = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/sui-framework", rev = "718ae563a42fb4ba0d055588f81c704dcef58c25" } +use_environment = "testnet" +manifest_digest = "7AFB66695545775FBFBB2D3078ADFD084244D5002392E837FDE21D9EA1C6D01C" +deps = { MoveStdlib = "MoveStdlib" } + +[pinned.testnet.ccip_router] +source = { root = true } +use_environment = "testnet" +manifest_digest = "B41543A1928002B1ABE6F16D69A3C1BE6BCA4D7A4DB7D2F9048274EFE11ED724" +deps = { mcms = "mcms", std = "MoveStdlib", sui = "Sui" } + +[pinned.testnet.mcms] source = { local = "../../mcms/mcms" } - -dependencies = [ - { id = "Bridge", name = "Bridge" }, - { id = "MoveStdlib", name = "MoveStdlib" }, - { id = "Sui", name = "Sui" }, - { id = "SuiSystem", name = "SuiSystem" }, -] - -[[move.package]] -id = "MoveStdlib" -source = { git = "https://github.com/MystenLabs/sui.git", rev = "4e8b6eda7d6411d80c62f39ac8a4f028e8d174c4", subdir = "crates/sui-framework/packages/move-stdlib" } - -[[move.package]] -id = "Sui" -source = { git = "https://github.com/MystenLabs/sui.git", rev = "4e8b6eda7d6411d80c62f39ac8a4f028e8d174c4", subdir = "crates/sui-framework/packages/sui-framework" } - -dependencies = [ - { id = "MoveStdlib", name = "MoveStdlib" }, -] - -[[move.package]] -id = "SuiSystem" -source = { git = "https://github.com/MystenLabs/sui.git", rev = "4e8b6eda7d6411d80c62f39ac8a4f028e8d174c4", subdir = "crates/sui-framework/packages/sui-system" } - -dependencies = [ - { id = "MoveStdlib", name = "MoveStdlib" }, - { id = "Sui", name = "Sui" }, -] - -[move.toolchain-version] -compiler-version = "1.58.2" -edition = "2024" -flavor = "sui" +use_environment = "testnet" +manifest_digest = "5745706258F61D6CE210904B3E6AE87A73CE9D31A6F93BE4718C442529332A87" +deps = { std = "MoveStdlib", sui = "Sui" } diff --git a/contracts/ccip/ccip_router/sources/router.move b/contracts/ccip/ccip_router/sources/router.move index eb7527c34..83295b026 100644 --- a/contracts/ccip/ccip_router/sources/router.move +++ b/contracts/ccip/ccip_router/sources/router.move @@ -344,17 +344,18 @@ public fun mcms_execute_ownership_transfer( let package_address = bcs_stream::deserialize_address(&mut stream); bcs_stream::assert_is_consumed(&stream); - let owner_cap = mcms_registry::release_cap(registry, McmsCallback {}); - if (mcms_deployer::has_upgrade_cap(deployer_state, package_address)) { - let upgrade_cap = mcms_deployer::release_upgrade_cap( + let upgrade_cap = mcms_deployer::release_upgrade_cap_at( deployer_state, registry, - McmsCallback {} + package_address, + McmsCallback {}, ); transfer::public_transfer(upgrade_cap, to); }; + let owner_cap = mcms_registry::release_cap(registry, McmsCallback {}); + execute_ownership_transfer(owner_cap, state, to, ctx); } diff --git a/contracts/ccip/ccip_token_pools/burn_mint_token_pool/Move.lock b/contracts/ccip/ccip_token_pools/burn_mint_token_pool/Move.lock index 4f9e6bf25..3ea7b2cfb 100644 --- a/contracts/ccip/ccip_token_pools/burn_mint_token_pool/Move.lock +++ b/contracts/ccip/ccip_token_pools/burn_mint_token_pool/Move.lock @@ -5,13 +5,13 @@ version = 4 [pinned.testnet.MoveStdlib] -source = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/move-stdlib", rev = "c2428b3aaf9c24270b609001e56d96cb10c76d28" } +source = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/move-stdlib", rev = "718ae563a42fb4ba0d055588f81c704dcef58c25" } use_environment = "testnet" manifest_digest = "C4FE4C91DE74CBF223B2E380AE40F592177D21870DC2D7EB6227D2D694E05363" deps = {} [pinned.testnet.Sui] -source = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/sui-framework", rev = "c2428b3aaf9c24270b609001e56d96cb10c76d28" } +source = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/sui-framework", rev = "718ae563a42fb4ba0d055588f81c704dcef58c25" } use_environment = "testnet" manifest_digest = "7AFB66695545775FBFBB2D3078ADFD084244D5002392E837FDE21D9EA1C6D01C" deps = { MoveStdlib = "MoveStdlib" } @@ -20,15 +20,21 @@ deps = { MoveStdlib = "MoveStdlib" } source = { root = true } use_environment = "testnet" manifest_digest = "21E7A88B8C8BAD0F3FC13C2BC93F282648C8C4479BCA99200ADD9B6FDDA49AF5" -deps = { ccip = "ccip", mcms = "mcms", std = "MoveStdlib", sui = "Sui" } +deps = { ccip = "ccip", mcms = "mcms_1", std = "MoveStdlib", sui = "Sui" } [pinned.testnet.ccip] source = { local = "../../ccip" } use_environment = "testnet" -manifest_digest = "B41543A1928002B1ABE6F16D69A3C1BE6BCA4D7A4DB7D2F9048274EFE11ED724" -deps = { mcms = "mcms", std = "MoveStdlib", sui = "Sui" } +manifest_digest = "E5912E2FA12F49E878734EC73B1CAC461C88F1562C6C02B940082B986AE865FE" +deps = { fast_mcms = "mcms", mcms = "mcms_1", std = "MoveStdlib", sui = "Sui" } [pinned.testnet.mcms] +source = { local = "../../../mcms/fast_mcms" } +use_environment = "testnet" +manifest_digest = "5745706258F61D6CE210904B3E6AE87A73CE9D31A6F93BE4718C442529332A87" +deps = { std = "MoveStdlib", sui = "Sui" } + +[pinned.testnet.mcms_1] source = { local = "../../../mcms/mcms" } use_environment = "testnet" manifest_digest = "5745706258F61D6CE210904B3E6AE87A73CE9D31A6F93BE4718C442529332A87" diff --git a/contracts/ccip/ccip_token_pools/burn_mint_token_pool/sources/burn_mint_token_pool.move b/contracts/ccip/ccip_token_pools/burn_mint_token_pool/sources/burn_mint_token_pool.move index a30a4d6cb..79155460a 100644 --- a/contracts/ccip/ccip_token_pools/burn_mint_token_pool/sources/burn_mint_token_pool.move +++ b/contracts/ccip/ccip_token_pools/burn_mint_token_pool/sources/burn_mint_token_pool.move @@ -986,20 +986,21 @@ public fun mcms_execute_ownership_transfer( let package_address = bcs_stream::deserialize_address(&mut stream); bcs_stream::assert_is_consumed(&stream); - let owner_cap = mcms_registry::release_cap, OwnerCap>( - registry, - McmsCallback {}, - ); - if (mcms_deployer::has_upgrade_cap(deployer_state, package_address)) { - let upgrade_cap = mcms_deployer::release_upgrade_cap( + let upgrade_cap = mcms_deployer::release_upgrade_cap_at( deployer_state, registry, + package_address, McmsCallback {}, ); transfer::public_transfer(upgrade_cap, to); }; + let owner_cap = mcms_registry::release_cap, OwnerCap>( + registry, + McmsCallback {}, + ); + execute_ownership_transfer(owner_cap, state, to, ctx); } diff --git a/contracts/ccip/ccip_token_pools/lock_release_token_pool/Move.lock b/contracts/ccip/ccip_token_pools/lock_release_token_pool/Move.lock index 770a46408..ba60f3783 100644 --- a/contracts/ccip/ccip_token_pools/lock_release_token_pool/Move.lock +++ b/contracts/ccip/ccip_token_pools/lock_release_token_pool/Move.lock @@ -5,13 +5,13 @@ version = 4 [pinned.testnet.MoveStdlib] -source = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/move-stdlib", rev = "c2428b3aaf9c24270b609001e56d96cb10c76d28" } +source = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/move-stdlib", rev = "718ae563a42fb4ba0d055588f81c704dcef58c25" } use_environment = "testnet" manifest_digest = "C4FE4C91DE74CBF223B2E380AE40F592177D21870DC2D7EB6227D2D694E05363" deps = {} [pinned.testnet.Sui] -source = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/sui-framework", rev = "c2428b3aaf9c24270b609001e56d96cb10c76d28" } +source = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/sui-framework", rev = "718ae563a42fb4ba0d055588f81c704dcef58c25" } use_environment = "testnet" manifest_digest = "7AFB66695545775FBFBB2D3078ADFD084244D5002392E837FDE21D9EA1C6D01C" deps = { MoveStdlib = "MoveStdlib" } @@ -19,16 +19,22 @@ deps = { MoveStdlib = "MoveStdlib" } [pinned.testnet.ccip] source = { local = "../../ccip" } use_environment = "testnet" -manifest_digest = "B41543A1928002B1ABE6F16D69A3C1BE6BCA4D7A4DB7D2F9048274EFE11ED724" -deps = { mcms = "mcms", std = "MoveStdlib", sui = "Sui" } +manifest_digest = "E5912E2FA12F49E878734EC73B1CAC461C88F1562C6C02B940082B986AE865FE" +deps = { fast_mcms = "mcms", mcms = "mcms_1", std = "MoveStdlib", sui = "Sui" } [pinned.testnet.lock_release_token_pool] source = { root = true } use_environment = "testnet" manifest_digest = "21E7A88B8C8BAD0F3FC13C2BC93F282648C8C4479BCA99200ADD9B6FDDA49AF5" -deps = { ccip = "ccip", mcms = "mcms", std = "MoveStdlib", sui = "Sui" } +deps = { ccip = "ccip", mcms = "mcms_1", std = "MoveStdlib", sui = "Sui" } [pinned.testnet.mcms] +source = { local = "../../../mcms/fast_mcms" } +use_environment = "testnet" +manifest_digest = "5745706258F61D6CE210904B3E6AE87A73CE9D31A6F93BE4718C442529332A87" +deps = { std = "MoveStdlib", sui = "Sui" } + +[pinned.testnet.mcms_1] source = { local = "../../../mcms/mcms" } use_environment = "testnet" manifest_digest = "5745706258F61D6CE210904B3E6AE87A73CE9D31A6F93BE4718C442529332A87" diff --git a/contracts/ccip/ccip_token_pools/lock_release_token_pool/sources/lock_release_token_pool.move b/contracts/ccip/ccip_token_pools/lock_release_token_pool/sources/lock_release_token_pool.move index c91f4ad8a..ace37d333 100644 --- a/contracts/ccip/ccip_token_pools/lock_release_token_pool/sources/lock_release_token_pool.move +++ b/contracts/ccip/ccip_token_pools/lock_release_token_pool/sources/lock_release_token_pool.move @@ -1226,6 +1226,16 @@ public fun mcms_execute_ownership_transfer( let package_address = bcs_stream::deserialize_address(&mut stream); bcs_stream::assert_is_consumed(&stream); + if (mcms_deployer::has_upgrade_cap(deployer_state, package_address)) { + let upgrade_cap = mcms_deployer::release_upgrade_cap_at( + deployer_state, + registry, + package_address, + McmsCallback {}, + ); + transfer::public_transfer(upgrade_cap, to); + }; + let McmsCap { id, owner_cap, rebalancer_cap } = mcms_registry::release_cap( registry, McmsCallback {}, @@ -1235,15 +1245,6 @@ public fun mcms_execute_ownership_transfer( rebalancer_cap.destroy_none(); object::delete(id); - if (mcms_deployer::has_upgrade_cap(deployer_state, package_address)) { - let upgrade_cap = mcms_deployer::release_upgrade_cap( - deployer_state, - registry, - McmsCallback {}, - ); - transfer::public_transfer(upgrade_cap, to); - }; - execute_ownership_transfer(owner_cap, state, to, ctx); } diff --git a/contracts/ccip/ccip_token_pools/managed_token_pool/Move.lock b/contracts/ccip/ccip_token_pools/managed_token_pool/Move.lock index d7608f5f6..773e3251a 100644 --- a/contracts/ccip/ccip_token_pools/managed_token_pool/Move.lock +++ b/contracts/ccip/ccip_token_pools/managed_token_pool/Move.lock @@ -5,13 +5,13 @@ version = 4 [pinned.testnet.MoveStdlib] -source = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/move-stdlib", rev = "c2428b3aaf9c24270b609001e56d96cb10c76d28" } +source = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/move-stdlib", rev = "718ae563a42fb4ba0d055588f81c704dcef58c25" } use_environment = "testnet" manifest_digest = "C4FE4C91DE74CBF223B2E380AE40F592177D21870DC2D7EB6227D2D694E05363" deps = {} [pinned.testnet.Sui] -source = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/sui-framework", rev = "c2428b3aaf9c24270b609001e56d96cb10c76d28" } +source = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/sui-framework", rev = "718ae563a42fb4ba0d055588f81c704dcef58c25" } use_environment = "testnet" manifest_digest = "7AFB66695545775FBFBB2D3078ADFD084244D5002392E837FDE21D9EA1C6D01C" deps = { MoveStdlib = "MoveStdlib" } @@ -19,22 +19,28 @@ deps = { MoveStdlib = "MoveStdlib" } [pinned.testnet.ccip] source = { local = "../../ccip" } use_environment = "testnet" -manifest_digest = "B41543A1928002B1ABE6F16D69A3C1BE6BCA4D7A4DB7D2F9048274EFE11ED724" -deps = { mcms = "mcms", std = "MoveStdlib", sui = "Sui" } +manifest_digest = "E5912E2FA12F49E878734EC73B1CAC461C88F1562C6C02B940082B986AE865FE" +deps = { fast_mcms = "mcms", mcms = "mcms_1", std = "MoveStdlib", sui = "Sui" } [pinned.testnet.managed_token] source = { local = "../../managed_token" } use_environment = "testnet" manifest_digest = "B41543A1928002B1ABE6F16D69A3C1BE6BCA4D7A4DB7D2F9048274EFE11ED724" -deps = { mcms = "mcms", std = "MoveStdlib", sui = "Sui" } +deps = { mcms = "mcms_1", std = "MoveStdlib", sui = "Sui" } [pinned.testnet.managed_token_pool] source = { root = true } use_environment = "testnet" manifest_digest = "A40B544AFAF6BF5EA1C3720358F06FEB81465F15051E8119778B9C675095DE48" -deps = { ccip = "ccip", managed_token = "managed_token", mcms = "mcms", std = "MoveStdlib", sui = "Sui" } +deps = { ccip = "ccip", managed_token = "managed_token", mcms = "mcms_1", std = "MoveStdlib", sui = "Sui" } [pinned.testnet.mcms] +source = { local = "../../../mcms/fast_mcms" } +use_environment = "testnet" +manifest_digest = "5745706258F61D6CE210904B3E6AE87A73CE9D31A6F93BE4718C442529332A87" +deps = { std = "MoveStdlib", sui = "Sui" } + +[pinned.testnet.mcms_1] source = { local = "../../../mcms/mcms" } use_environment = "testnet" manifest_digest = "5745706258F61D6CE210904B3E6AE87A73CE9D31A6F93BE4718C442529332A87" diff --git a/contracts/ccip/ccip_token_pools/managed_token_pool/sources/managed_token_pool.move b/contracts/ccip/ccip_token_pools/managed_token_pool/sources/managed_token_pool.move index d995eee36..81e79c34a 100644 --- a/contracts/ccip/ccip_token_pools/managed_token_pool/sources/managed_token_pool.move +++ b/contracts/ccip/ccip_token_pools/managed_token_pool/sources/managed_token_pool.move @@ -1037,20 +1037,21 @@ public fun mcms_execute_ownership_transfer( let package_address = bcs_stream::deserialize_address(&mut stream); bcs_stream::assert_is_consumed(&stream); - let owner_cap = mcms_registry::release_cap, OwnerCap>( - registry, - McmsCallback {}, - ); - if (mcms_deployer::has_upgrade_cap(deployer_state, package_address)) { - let upgrade_cap = mcms_deployer::release_upgrade_cap( + let upgrade_cap = mcms_deployer::release_upgrade_cap_at( deployer_state, registry, + package_address, McmsCallback {}, ); transfer::public_transfer(upgrade_cap, to); }; + let owner_cap = mcms_registry::release_cap, OwnerCap>( + registry, + McmsCallback {}, + ); + execute_ownership_transfer(owner_cap, state, to, ctx); } diff --git a/contracts/ccip/ccip_token_pools/usdc_token_pool/sources/usdc_token_pool.move b/contracts/ccip/ccip_token_pools/usdc_token_pool/sources/usdc_token_pool.move index 8e3bc76d2..817079548 100644 --- a/contracts/ccip/ccip_token_pools/usdc_token_pool/sources/usdc_token_pool.move +++ b/contracts/ccip/ccip_token_pools/usdc_token_pool/sources/usdc_token_pool.move @@ -1221,20 +1221,21 @@ public fun mcms_execute_ownership_transfer( let package_address = bcs_stream::deserialize_address(&mut stream); bcs_stream::assert_is_consumed(&stream); - let owner_cap = mcms_registry::release_cap, OwnerCap>( - registry, - McmsCallback {}, - ); - if (mcms_deployer::has_upgrade_cap(deployer_state, package_address)) { - let upgrade_cap = mcms_deployer::release_upgrade_cap( + let upgrade_cap = mcms_deployer::release_upgrade_cap_at( deployer_state, registry, + package_address, McmsCallback {}, ); transfer::public_transfer(upgrade_cap, to); }; + let owner_cap = mcms_registry::release_cap, OwnerCap>( + registry, + McmsCallback {}, + ); + execute_ownership_transfer(owner_cap, state, to, ctx); } diff --git a/contracts/ccip/managed_token/sources/managed_token.move b/contracts/ccip/managed_token/sources/managed_token.move index 0bc2248bd..2461f8168 100644 --- a/contracts/ccip/managed_token/sources/managed_token.move +++ b/contracts/ccip/managed_token/sources/managed_token.move @@ -708,17 +708,18 @@ public fun mcms_execute_ownership_transfer( let package_address = bcs_stream::deserialize_address(&mut stream); bcs_stream::assert_is_consumed(&stream); - let owner_cap = mcms_registry::release_cap(registry, McmsCallback {}); - if (mcms_deployer::has_upgrade_cap(deployer_state, package_address)) { - let upgrade_cap = mcms_deployer::release_upgrade_cap( + let upgrade_cap = mcms_deployer::release_upgrade_cap_at( deployer_state, registry, + package_address, McmsCallback {}, ); transfer::public_transfer(upgrade_cap, to); }; + let owner_cap = mcms_registry::release_cap(registry, McmsCallback {}); + execute_ownership_transfer(owner_cap, state, to, ctx); } diff --git a/contracts/ccip/mock_offramp_v2/offramp.move b/contracts/ccip/mock_offramp_v2/offramp.move index fb004fd05..0a7f96d70 100644 --- a/contracts/ccip/mock_offramp_v2/offramp.move +++ b/contracts/ccip/mock_offramp_v2/offramp.move @@ -1695,17 +1695,18 @@ public fun mcms_execute_ownership_transfer( let package_address = bcs_stream::deserialize_address(&mut stream); bcs_stream::assert_is_consumed(&stream); - let owner_cap = mcms_registry::release_cap(registry, McmsCallback {}); - if (mcms_deployer::has_upgrade_cap(deployer_state, package_address)) { - let upgrade_cap = mcms_deployer::release_upgrade_cap( + let upgrade_cap = mcms_deployer::release_upgrade_cap_at( deployer_state, registry, - McmsCallback {} + package_address, + McmsCallback {}, ); transfer::public_transfer(upgrade_cap, to); }; + let owner_cap = mcms_registry::release_cap(registry, McmsCallback {}); + execute_ownership_transfer(ref, owner_cap, state, to, ctx); } diff --git a/contracts/ccip/mock_onramp_v2/onramp.move b/contracts/ccip/mock_onramp_v2/onramp.move index 4c697bf26..02d2b893b 100644 --- a/contracts/ccip/mock_onramp_v2/onramp.move +++ b/contracts/ccip/mock_onramp_v2/onramp.move @@ -1417,17 +1417,18 @@ public fun mcms_execute_ownership_transfer( let package_address = bcs_stream::deserialize_address(&mut stream); bcs_stream::assert_is_consumed(&stream); - let owner_cap = mcms_registry::release_cap(registry, McmsCallback {}); - if (mcms_deployer::has_upgrade_cap(deployer_state, package_address)) { - let upgrade_cap = mcms_deployer::release_upgrade_cap( + let upgrade_cap = mcms_deployer::release_upgrade_cap_at( deployer_state, registry, + package_address, McmsCallback {}, ); transfer::public_transfer(upgrade_cap, to); }; + let owner_cap = mcms_registry::release_cap(registry, McmsCallback {}); + execute_ownership_transfer(ref, owner_cap, state, to, ctx); }