diff --git a/bindings/generated/ccip/ccip/receiver_registry/receiver_registry.go b/bindings/generated/ccip/ccip/receiver_registry/receiver_registry.go index 0e596881d..3fb599e21 100644 --- a/bindings/generated/ccip/ccip/receiver_registry/receiver_registry.go +++ b/bindings/generated/ccip/ccip/receiver_registry/receiver_registry.go @@ -29,6 +29,7 @@ type IReceiverRegistry interface { GetReceiverConfig(ctx context.Context, opts *bind.CallOpts, ref bind.Object, receiverPackageId string) (*models.SuiTransactionBlockResponse, error) GetReceiverConfigFields(ctx context.Context, opts *bind.CallOpts, rc ReceiverConfig) (*models.SuiTransactionBlockResponse, error) GetReceiverInfo(ctx context.Context, opts *bind.CallOpts, ref bind.Object, receiverPackageId string) (*models.SuiTransactionBlockResponse, error) + McmsUnregisterReceiver(ctx context.Context, opts *bind.CallOpts, ref bind.Object, registry bind.Object, params bind.Object) (*models.SuiTransactionBlockResponse, error) DevInspect() IReceiverRegistryDevInspect Encoder() ReceiverRegistryEncoder Bound() bind.IBoundContract @@ -59,6 +60,8 @@ type ReceiverRegistryEncoder interface { GetReceiverConfigFieldsWithArgs(args ...any) (*bind.EncodedCall, error) GetReceiverInfo(ref bind.Object, receiverPackageId string) (*bind.EncodedCall, error) GetReceiverInfoWithArgs(args ...any) (*bind.EncodedCall, error) + McmsUnregisterReceiver(ref bind.Object, registry bind.Object, params bind.Object) (*bind.EncodedCall, error) + McmsUnregisterReceiverWithArgs(args ...any) (*bind.EncodedCall, error) } type ReceiverRegistryContract struct { @@ -200,6 +203,16 @@ func (c *ReceiverRegistryContract) GetReceiverInfo(ctx context.Context, opts *bi return c.ExecuteTransaction(ctx, opts, encoded) } +// McmsUnregisterReceiver executes the mcms_unregister_receiver Move function. +func (c *ReceiverRegistryContract) McmsUnregisterReceiver(ctx context.Context, opts *bind.CallOpts, ref bind.Object, registry bind.Object, params bind.Object) (*models.SuiTransactionBlockResponse, error) { + encoded, err := c.receiverRegistryEncoder.McmsUnregisterReceiver(ref, registry, params) + if err != nil { + return nil, fmt.Errorf("failed to encode function call: %w", err) + } + + return c.ExecuteTransaction(ctx, opts, encoded) +} + // TypeAndVersion executes the type_and_version Move function using DevInspect to get return values. // // Returns: 0x1::string::String @@ -587,3 +600,35 @@ func (c receiverRegistryEncoder) GetReceiverInfoWithArgs(args ...any) (*bind.Enc "ascii::String", }) } + +// McmsUnregisterReceiver encodes a call to the mcms_unregister_receiver Move function. +func (c receiverRegistryEncoder) McmsUnregisterReceiver(ref bind.Object, registry bind.Object, params bind.Object) (*bind.EncodedCall, error) { + typeArgsList := []string{} + typeParamsList := []string{} + return c.EncodeCallArgsWithGenerics("mcms_unregister_receiver", typeArgsList, typeParamsList, []string{ + "&mut CCIPObjectRef", + "&mut Registry", + "ExecutingCallbackParams", + }, []any{ + ref, + registry, + params, + }, nil) +} + +// McmsUnregisterReceiverWithArgs encodes a call to the mcms_unregister_receiver Move function using arbitrary arguments. +// This method allows passing both regular values and transaction.Argument values for PTB chaining. +func (c receiverRegistryEncoder) McmsUnregisterReceiverWithArgs(args ...any) (*bind.EncodedCall, error) { + expectedParams := []string{ + "&mut CCIPObjectRef", + "&mut Registry", + "ExecutingCallbackParams", + } + + if len(args) != len(expectedParams) { + return nil, fmt.Errorf("expected %d arguments, got %d", len(expectedParams), len(args)) + } + typeArgsList := []string{} + typeParamsList := []string{} + return c.EncodeCallArgsWithGenerics("mcms_unregister_receiver", typeArgsList, typeParamsList, expectedParams, args, nil) +} 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/receiver_registry.move b/contracts/ccip/ccip/sources/receiver_registry.move index dc0337906..731846676 100644 --- a/contracts/ccip/ccip/sources/receiver_registry.move +++ b/contracts/ccip/ccip/sources/receiver_registry.move @@ -4,6 +4,8 @@ use ccip::ownable::OwnerCap; use ccip::publisher_wrapper::{Self, PublisherWrapper}; use ccip::state_object::{Self, CCIPObjectRef}; use ccip::upgrade_registry::verify_function_allowed; +use mcms::bcs_stream; +use mcms::mcms_registry::{Self, Registry, ExecutingCallbackParams}; use std::ascii; use std::string::{Self, String}; use std::type_name; @@ -35,6 +37,7 @@ const EAlreadyRegistered: u64 = 1; const EAlreadyInitialized: u64 = 2; const EUnknownReceiver: u64 = 3; const EInvalidOwnerCap: u64 = 4; +const EInvalidFunction: u64 = 5; const VERSION: u8 = 1; @@ -156,3 +159,34 @@ public fun get_receiver_info( (string::utf8(b""), ascii::string(b"")) } + +// ================================================================ +// | MCMS Functions | +// ================================================================ + +public fun mcms_unregister_receiver( + ref: &mut CCIPObjectRef, + registry: &mut Registry, + params: ExecutingCallbackParams, + ctx: &mut TxContext, +) { + let (owner_cap, function, data) = mcms_registry::get_callback_params_with_caps< + state_object::McmsCallback, + OwnerCap, + >( + registry, + state_object::mcms_callback(), + params, + ); + assert!(function == string::utf8(b"unregister_receiver"), EInvalidFunction); + + let mut stream = bcs_stream::new(data); + bcs_stream::validate_obj_addrs( + vector[object::id_address(ref), object::id_address(owner_cap)], + &mut stream, + ); + let receiver_package_id = bcs_stream::deserialize_address(&mut stream); + bcs_stream::assert_is_consumed(&stream); + + unregister_receiver(ref, owner_cap, receiver_package_id, ctx); +} diff --git a/contracts/ccip/ccip/sources/state_object.move b/contracts/ccip/ccip/sources/state_object.move index 3e3838904..4200ace52 100644 --- a/contracts/ccip/ccip/sources/state_object.move +++ b/contracts/ccip/ccip/sources/state_object.move @@ -172,6 +172,7 @@ public fun execute_ownership_transfer_to_mcms( vector[ b"fee_quoter", b"rmn_remote", + b"receiver_registry", b"state_object", b"token_admin_registry", b"upgrade_registry", diff --git a/contracts/ccip/ccip/tests/receiver_registry_mcms_test.move b/contracts/ccip/ccip/tests/receiver_registry_mcms_test.move new file mode 100644 index 000000000..11572247a --- /dev/null +++ b/contracts/ccip/ccip/tests/receiver_registry_mcms_test.move @@ -0,0 +1,187 @@ +#[test_only] +#[allow(implicit_const_copy)] +module ccip::receiver_registry_mcms_test; + +use ccip::ownable::OwnerCap; +use ccip::publisher_wrapper; +use ccip::receiver_registry; +use ccip::state_object::{Self, CCIPObjectRef}; +use ccip::upgrade_registry; +use mcms::mcms_account; +use mcms::mcms_deployer; +use mcms::mcms_registry::{Self, Registry}; +use std::string; +use std::type_name; +use sui::address; +use sui::bcs; +use sui::package; +use sui::test_scenario::{Self as ts, Scenario}; + +const OWNER: address = @0x123; +const RECEIVER_REGISTRY_MODULE_NAME: vector = b"receiver_registry"; + +public struct RECEIVER_REGISTRY_MCMS_TEST has drop {} +public struct TestReceiverProof has drop, copy {} + +public struct Env { + scenario: Scenario, + ref: CCIPObjectRef, + registry: Registry, +} + +fun get_package_id_from_proof(): address { + let proof_tn = type_name::with_defining_ids(); + let address_str = type_name::address_string(&proof_tn); + address::from_ascii_bytes(&std::ascii::into_bytes(address_str)) +} + +fun setup(): Env { + let mut scenario = ts::begin(OWNER); + + { + let ctx = scenario.ctx(); + mcms_account::test_init(ctx); + mcms_registry::test_init(ctx); + mcms_deployer::test_init(ctx); + state_object::test_init(ctx); + }; + + scenario.next_tx(OWNER); + + let registry = ts::take_shared(&scenario); + let mut ref = ts::take_shared(&scenario); + let owner_cap = ts::take_from_sender(&scenario); + + upgrade_registry::initialize(&mut ref, &owner_cap, scenario.ctx()); + receiver_registry::initialize(&mut ref, &owner_cap, scenario.ctx()); + ts::return_to_address(OWNER, owner_cap); + + scenario.next_tx(OWNER); + + Env { + scenario, + ref, + registry, + } +} + +fun tear_down(env: Env) { + let Env { scenario, ref, registry } = env; + ts::return_shared(ref); + ts::return_shared(registry); + ts::end(scenario); +} + +fun register_test_receiver(ref: &mut CCIPObjectRef, ctx: &mut TxContext) { + let proof = TestReceiverProof {}; + let publisher = package::test_claim(RECEIVER_REGISTRY_MCMS_TEST {}, ctx); + let publisher_wrapper = publisher_wrapper::create(&publisher, proof); + receiver_registry::register_receiver(ref, publisher_wrapper, proof); + package::burn_publisher(publisher); +} + +fun transfer_ownership_to_mcms(env: &mut Env, owner_cap: OwnerCap) { + state_object::transfer_ownership( + &mut env.ref, + &owner_cap, + mcms_registry::get_multisig_address(), + env.scenario.ctx(), + ); + + env.scenario.next_tx(mcms_registry::get_multisig_address()); + state_object::accept_ownership(&mut env.ref, env.scenario.ctx()); + + state_object::execute_ownership_transfer_to_mcms( + &mut env.ref, + owner_cap, + &mut env.registry, + @mcms, + env.scenario.ctx(), + ); + + env.scenario.next_tx(OWNER); +} + +#[test] +public fun test_mcms_unregister_receiver() { + let mut env = setup(); + let package_id = get_package_id_from_proof(); + + register_test_receiver(&mut env.ref, env.scenario.ctx()); + assert!(receiver_registry::is_registered_receiver(&env.ref, package_id)); + + let owner_cap = ts::take_from_sender(&env.scenario); + transfer_ownership_to_mcms(&mut env, owner_cap); + + let owner_cap_address = mcms_registry::test_get_cap_address( + &env.registry, + @ccip.to_ascii_string(), + ); + + 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(&package_id)); + + let params = mcms_registry::test_create_executing_callback_params( + @ccip, + string::utf8(RECEIVER_REGISTRY_MODULE_NAME), + string::utf8(b"unregister_receiver"), + data, + x"0000000000000000000000000000000000000000000000000000000000000001", + 0, + 1, + ); + + receiver_registry::mcms_unregister_receiver( + &mut env.ref, + &mut env.registry, + params, + env.scenario.ctx(), + ); + + assert!(!receiver_registry::is_registered_receiver(&env.ref, package_id)); + + env.tear_down(); +} + +#[test] +#[expected_failure(abort_code = receiver_registry::EInvalidFunction)] +public fun test_mcms_unregister_receiver_wrong_function_name() { + let mut env = setup(); + let package_id = get_package_id_from_proof(); + + register_test_receiver(&mut env.ref, env.scenario.ctx()); + + let owner_cap = ts::take_from_sender(&env.scenario); + transfer_ownership_to_mcms(&mut env, owner_cap); + + let owner_cap_address = mcms_registry::test_get_cap_address( + &env.registry, + @ccip.to_ascii_string(), + ); + + 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(&package_id)); + + let params = mcms_registry::test_create_executing_callback_params( + @ccip, + string::utf8(RECEIVER_REGISTRY_MODULE_NAME), + string::utf8(b"register_receiver"), + data, + x"0000000000000000000000000000000000000000000000000000000000000001", + 0, + 1, + ); + + receiver_registry::mcms_unregister_receiver( + &mut env.ref, + &mut env.registry, + params, + env.scenario.ctx(), + ); + + env.tear_down(); +} diff --git a/contracts/ccip/ccip/tests/state_object_tests.move b/contracts/ccip/ccip/tests/state_object_tests.move index 7b37a046f..cfc35c54a 100644 --- a/contracts/ccip/ccip/tests/state_object_tests.move +++ b/contracts/ccip/ccip/tests/state_object_tests.move @@ -272,16 +272,18 @@ fun setup_with_mcms_ownership(): (Scenario, Registry, CCIPObjectRef) { fun test_mcms_add_allowed_modules_success() { let (mut scenario, mut registry, ref) = setup_with_mcms_ownership(); - // Verify initial allowed modules (should have fee_quoter, rmn_remote, state_object, token_admin_registry) + // Verify initial allowed modules let initial_modules = mcms_registry::get_allowed_modules( ®istry, address::to_ascii_string(@ccip), ); assert!(initial_modules.contains(&b"fee_quoter"), 0); assert!(initial_modules.contains(&b"rmn_remote"), 1); - assert!(initial_modules.contains(&b"state_object"), 2); - assert!(initial_modules.contains(&b"token_admin_registry"), 3); - assert!(!initial_modules.contains(&b"nonce_manager"), 4); // Should not exist yet + assert!(initial_modules.contains(&b"receiver_registry"), 2); + assert!(initial_modules.contains(&b"state_object"), 3); + assert!(initial_modules.contains(&b"token_admin_registry"), 4); + assert!(initial_modules.contains(&b"upgrade_registry"), 5); + assert!(!initial_modules.contains(&b"nonce_manager"), 6); // Should not exist yet // Prepare data for mcms_add_allowed_modules // Data format: [registry_address][vector> of module names]