From c44ef6d6f69e6e84de704150e2c05c949a38eabe Mon Sep 17 00:00:00 2001 From: umr1352 Date: Wed, 27 Nov 2024 10:40:35 +0100 Subject: [PATCH] e2e test for controller execution --- .../iota_identity/sources/identity.move | 2 +- .../sources/multicontroller.move | 2 +- .../src/rebased/client/read_only.rs | 4 +- .../src/rebased/migration/identity.rs | 12 ++- identity_iota_core/tests/e2e/identity.rs | 82 +++++++++++++++++++ 5 files changed, 95 insertions(+), 7 deletions(-) diff --git a/identity_iota_core/packages/iota_identity/sources/identity.move b/identity_iota_core/packages/iota_identity/sources/identity.move index 69a38f72a..f25e9db6e 100644 --- a/identity_iota_core/packages/iota_identity/sources/identity.move +++ b/identity_iota_core/packages/iota_identity/sources/identity.move @@ -397,7 +397,7 @@ module iota_identity::identity { /// Destroys a `ControllerCap`. Can only be used after a controller has been removed from /// the controller committee. - public fun destroy_controller_cap(self: &Identity, cap: ControllerCap) { + public(package) fun destroy_controller_cap(self: &Identity, cap: ControllerCap) { self.did_doc.destroy_controller_cap(cap); } diff --git a/identity_iota_core/packages/iota_identity/sources/multicontroller.move b/identity_iota_core/packages/iota_identity/sources/multicontroller.move index 3a49b5687..da88fd270 100644 --- a/identity_iota_core/packages/iota_identity/sources/multicontroller.move +++ b/identity_iota_core/packages/iota_identity/sources/multicontroller.move @@ -293,7 +293,7 @@ module iota_identity::multicontroller { /// Destroys a `ControllerCap`. Can only be used after a controller has been removed from /// the controller committee. public fun destroy_controller_cap(self: &Multicontroller, cap: ControllerCap) { - assert!(self.controllers.contains(&cap.id().to_inner()), EInvalidController); + assert!(!self.controllers.contains(&cap.id().to_inner()), EInvalidController); cap.delete(); } diff --git a/identity_iota_core/src/rebased/client/read_only.rs b/identity_iota_core/src/rebased/client/read_only.rs index 96cb6e800..cf155d7dd 100644 --- a/identity_iota_core/src/rebased/client/read_only.rs +++ b/identity_iota_core/src/rebased/client/read_only.rs @@ -118,8 +118,8 @@ impl IdentityClientReadOnly { .map_err(|e| Error::ObjectLookup(e.to_string())) } - #[allow(dead_code)] - pub(crate) async fn get_object_ref_by_id(&self, obj: ObjectID) -> Result, Error> { + /// Returns an object's [`OwnedObjectRef`], if any. + pub async fn get_object_ref_by_id(&self, obj: ObjectID) -> Result, Error> { self .read_api() .get_object_with_options(obj, IotaObjectDataOptions::default().with_owner()) diff --git a/identity_iota_core/src/rebased/migration/identity.rs b/identity_iota_core/src/rebased/migration/identity.rs index 938c03ded..7e494a653 100644 --- a/identity_iota_core/src/rebased/migration/identity.rs +++ b/identity_iota_core/src/rebased/migration/identity.rs @@ -6,6 +6,7 @@ use std::collections::HashSet; use std::ops::Deref; use std::str::FromStr; +use crate::rebased::proposals::ControllerExecution; use crate::rebased::sui::types::Number; use crate::IotaDID; use crate::IotaDocument; @@ -175,13 +176,18 @@ impl OnChainIdentity { } /// Borrows assets owned by this [`OnChainIdentity`] to use them in a custom transaction. - /// # Notes - /// Make sure to call [`super::Proposal::with_intent`] before executing the proposal. - /// Failing to do so will make [`crate::proposals::ProposalT::execute`] return an error. pub fn borrow_assets(&mut self) -> ProposalBuilder<'_, BorrowAction> { ProposalBuilder::new(self, BorrowAction::default()) } + /// Borrows a `ControllerCap` with ID `controller_cap` owned by this identity in a transaction. + /// This proposal is used to perform operation on a sub-identity controlled + /// by this one. + pub fn controller_execution(&mut self, controller_cap: ObjectID) -> ProposalBuilder<'_, ControllerExecution> { + let action = ControllerExecution::new(controller_cap, self); + ProposalBuilder::new(self, action) + } + /// Returns historical data for this [`OnChainIdentity`]. pub async fn get_history( &self, diff --git a/identity_iota_core/tests/e2e/identity.rs b/identity_iota_core/tests/e2e/identity.rs index 2bd6236ad..ab0e66ebb 100644 --- a/identity_iota_core/tests/e2e/identity.rs +++ b/identity_iota_core/tests/e2e/identity.rs @@ -20,7 +20,10 @@ use identity_iota_core::IotaDocument; use identity_verification::MethodScope; use identity_verification::VerificationMethod; use iota_sdk::rpc_types::IotaObjectData; +use iota_sdk::types::base_types::ObjectID; use iota_sdk::types::base_types::SequenceNumber; +use iota_sdk::types::object::Owner; +use iota_sdk::types::transaction::ObjectArg; use iota_sdk::types::TypeTag; use iota_sdk::types::IOTA_FRAMEWORK_PACKAGE_ID; use move_core_types::ident_str; @@ -355,3 +358,82 @@ async fn borrow_proposal_works() -> anyhow::Result<()> { Ok(()) } + +#[tokio::test] +async fn controller_execution_works() -> anyhow::Result<()> { + let test_client = get_test_client().await?; + let identity_client = test_client.new_user_client().await?; + + let mut identity = identity_client + .create_identity(TEST_DOC) + .finish() + .execute(&identity_client) + .await? + .output; + let identity_address = identity.id().into(); + + // Create a second identity owned by the first. + let identity2 = identity_client + .create_identity(TEST_DOC) + .controller(identity_address, 1) + .threshold(1) + .finish() + .execute(&identity_client) + .await? + .output; + + // Let's find identity's controller cap for identity2. + let controller_cap = identity_client + .find_owned_ref_for_address( + identity_address, + format!("{}::controller::ControllerCap", identity_client.package_id()).parse()?, + |_| true, + ) + .await? + .expect("identity is a controller of identity2"); + + // Perform an action on `identity2` as a controller of `identity`. + let ProposalResult::Pending(controller_execution) = identity + .controller_execution(controller_cap.0) + .finish(&identity_client) + .await? + .execute(&identity_client) + .await? + .output + else { + panic!("controller execution proposals cannot be executed without being driven by the user") + }; + let identity2_ref = identity_client.get_object_ref_by_id(identity2.id()).await?.unwrap(); + let Owner::Shared { initial_shared_version } = identity2_ref.owner else { + panic!("identity2 is shared") + }; + let tx_output = controller_execution + .into_tx(&mut identity, &identity_client) + .await? + // specify the operation to perform with the borrowed identity's controller_cap + .with_intent(|ptb, controller_cap| { + let identity2 = ptb + .obj(ObjectArg::SharedObject { + id: identity2_ref.object_id(), + initial_shared_version, + mutable: true, + }) + .unwrap(); + + let token_to_revoke = ptb.pure(ObjectID::ZERO).unwrap(); + + ptb.programmable_move_call( + identity_client.package_id(), + ident_str!("identity").into(), + ident_str!("revoke_token").into(), + vec![], + vec![identity2, *controller_cap, token_to_revoke], + ); + }) + .execute(&identity_client) + .await?; + + assert!(tx_output.response.status_ok().unwrap()); + + Ok(()) +}