Skip to content

Commit

Permalink
Borrow an Identity's ControllerCap in order to perform operations…
Browse files Browse the repository at this point in the history
… on sub-Identities (#1454)

* rebase commit

* borrow controller cap proposal

* e2e test for controller execution

* fix merge issues

* fix merge
  • Loading branch information
UMR1352 authored Nov 27, 2024
1 parent a970f25 commit f5d229a
Show file tree
Hide file tree
Showing 13 changed files with 546 additions and 43 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ module iota_identity::controller {

public use fun delete_controller_cap as ControllerCap.delete;
public use fun delete_delegation_token as DelegationToken.delete;
public use fun delegation_token_id as DelegationToken.id;

/// This `ControllerCap` cannot delegate access.
const ECannotDelegate: u64 = 0;
Expand Down Expand Up @@ -61,6 +62,11 @@ module iota_identity::controller {
controller: ID,
}

/// Returns the ID of this `DelegationToken`.
public fun delegation_token_id(self: &DelegationToken): ID {
self.id.to_inner()
}

/// Returns the controller's ID of this `DelegationToken`.
public fun controller(self: &DelegationToken): ID {
self.controller
Expand Down
63 changes: 48 additions & 15 deletions identity_iota_core/packages/iota_identity/sources/identity.move
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ module iota_identity::identity {
transfer_proposal::{Self, Send},
borrow_proposal::{Self, Borrow},
did_deactivation_proposal::{Self, DidDeactivation},
controller_proposal::{Self, ControllerExecution},
upgrade_proposal::{Self, Upgrade},
};

Expand Down Expand Up @@ -190,7 +191,7 @@ module iota_identity::identity {
self.execute_deactivation(cap, proposal_id, clock, ctx);
option::none()
} else {
emit_proposal_event(self.id().to_inner(), cap.id().to_inner(), proposal_id, false);
emit_proposal_event(self.id().to_inner(), cap.id(), proposal_id, false);
option::some(proposal_id)
}
}
Expand All @@ -211,13 +212,43 @@ module iota_identity::identity {
self.did_doc.set_controlled_value(vector[]);
self.updated = clock.timestamp_ms();

emit_proposal_event(self.id().to_inner(), cap.id().to_inner(), proposal_id, true);
emit_proposal_event(self.id().to_inner(), cap.id(), proposal_id, true);
}

/// Creates a new `ControllerExecution` proposal.
public fun propose_controller_execution(
self: &mut Identity,
cap: &DelegationToken,
controller_cap_id: ID,
expiration: Option<u64>,
ctx: &mut TxContext,
): ID {
let identity_address = self.id().to_address();
let proposal_id = self.did_doc.create_proposal(
cap,
controller_proposal::new(controller_cap_id, identity_address),
expiration,
ctx,
);

emit_proposal_event(self.id().to_inner(), cap.id(), proposal_id, false);
proposal_id
}

/// Borrow the identity-owned controller cap specified in `ControllerExecution`.
/// The borrowed cap must be put back by calling `controller_proposal::put_back`.
public fun borrow_controller_cap(
self: &mut Identity,
action: &mut Action<ControllerExecution>,
receiving: Receiving<ControllerCap>,
): ControllerCap {
controller_proposal::receive(action, &mut self.id, receiving)
}

/// Proposes to upgrade this `Identity` to this package's version.
public fun propose_upgrade(
self: &mut Identity,
cap: &ControllerCap,
cap: &DelegationToken,
expiration: Option<u64>,
ctx: &mut TxContext,
): Option<ID> {
Expand All @@ -235,7 +266,7 @@ module iota_identity::identity {
self.execute_upgrade(cap, proposal_id, ctx);
option::none()
} else {
emit_proposal_event(self.id().to_inner(), cap.id().to_inner(), proposal_id, false);
emit_proposal_event(self.id().to_inner(), cap.id(), proposal_id, false);
option::some(proposal_id)
}
}
Expand All @@ -244,13 +275,13 @@ module iota_identity::identity {
/// package's version.
public fun execute_upgrade(
self: &mut Identity,
cap: &ControllerCap,
cap: &DelegationToken,
proposal_id: ID,
ctx: &mut TxContext,
) {
self.execute_proposal<Upgrade>(cap, proposal_id, ctx).unwrap();
self.migrate();
emit_proposal_event(self.id().to_inner(), cap.id().to_inner(), proposal_id, true);
emit_proposal_event(self.id().to_inner(), cap.id(), proposal_id, true);
}

/// Migrates this `Identity` to this package's version.
Expand Down Expand Up @@ -286,7 +317,7 @@ module iota_identity::identity {
self.execute_update(cap, proposal_id, clock, ctx);
option::none()
} else {
emit_proposal_event(self.id().to_inner(), cap.id().to_inner(), proposal_id, false);
emit_proposal_event(self.id().to_inner(), cap.id(), proposal_id, false);
option::some(proposal_id)
}
}
Expand All @@ -307,7 +338,7 @@ module iota_identity::identity {
);

self.updated = clock.timestamp_ms();
emit_proposal_event(self.id().to_inner(), cap.id().to_inner(), proposal_id, true);
emit_proposal_event(self.id().to_inner(), cap.id(), proposal_id, true);
}

/// Proposes to update this `Identity`'s AC.
Expand Down Expand Up @@ -341,7 +372,7 @@ module iota_identity::identity {
self.execute_config_change(cap, proposal_id, ctx);
option::none()
} else {
emit_proposal_event(self.id().to_inner(), cap.id().to_inner(), proposal_id, false);
emit_proposal_event(self.id().to_inner(), cap.id(), proposal_id, false);
option::some(proposal_id)
}
}
Expand All @@ -359,7 +390,7 @@ module iota_identity::identity {
proposal_id,
ctx,
);
emit_proposal_event(self.id().to_inner(), cap.id().to_inner(), proposal_id, true);
emit_proposal_event(self.id().to_inner(), cap.id(), proposal_id, true);
}

/// Proposes the transfer of a set of objects owned by this `Identity`.
Expand All @@ -370,7 +401,7 @@ module iota_identity::identity {
objects: vector<ID>,
recipients: vector<address>,
ctx: &mut TxContext,
) {
): ID {
let proposal_id = transfer_proposal::propose_send(
&mut self.did_doc,
cap,
Expand All @@ -379,7 +410,8 @@ module iota_identity::identity {
recipients,
ctx
);
emit_proposal_event(self.id().to_inner(), cap.id().to_inner(), proposal_id, false);
emit_proposal_event(self.id().to_inner(), cap.id(), proposal_id, false);
proposal_id
}

/// Sends one object among the one specified in a `Send` proposal.
Expand All @@ -399,7 +431,7 @@ module iota_identity::identity {
expiration: Option<u64>,
objects: vector<ID>,
ctx: &mut TxContext,
) {
): ID {
let identity_address = self.id().to_address();
let proposal_id = borrow_proposal::propose_borrow(
&mut self.did_doc,
Expand All @@ -409,7 +441,8 @@ module iota_identity::identity {
identity_address,
ctx,
);
emit_proposal_event(self.id().to_inner(), cap.id().to_inner(), proposal_id, false);
emit_proposal_event(self.id().to_inner(), cap.id(), proposal_id, false);
proposal_id
}

/// Takes one of the borrowed assets.
Expand Down Expand Up @@ -444,7 +477,7 @@ module iota_identity::identity {
proposal_id: ID,
ctx: &mut TxContext,
): Action<T> {
emit_proposal_event(self.id().to_inner(), cap.id().to_inner(), proposal_id, true);
emit_proposal_event(self.id().to_inner(), cap.id(), proposal_id, true);
self.did_doc.execute_proposal(cap, proposal_id, ctx)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<V>(self: &Multicontroller<V>, cap: ControllerCap) {
assert!(self.controllers.contains(&cap.id().to_inner()), EInvalidController);
assert!(!self.controllers.contains(&cap.id().to_inner()), EInvalidController);

cap.delete();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ module iota_identity::borrow_proposal {
): ID {
let action = Borrow { objects, objects_to_return: vector::empty(), owner };

multi.create_proposal(cap, action,expiration, ctx)
multi.create_proposal(cap, action, expiration, ctx)
}

/// Borrows an asset from this action. This function will fail if:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
module iota_identity::controller_proposal {
use iota::transfer::Receiving;
use iota_identity::controller::{Self, ControllerCap};
use iota_identity::multicontroller::Action;

/// The received `ControllerCap` does not match the one
/// specified in the `ControllerExecution` action.
const EControllerCapMismatch: u64 = 0;
/// The provided `UID` is not the `UID` of the `Identity`
/// specified in the action.
const EInvalidIdentityUID: u64 = 1;

/// Borrow a given `ControllerCap` from an `Identity` for
/// a single transaction.
public struct ControllerExecution has store {
/// ID of the `ControllerCap` to borrow.
controller_cap: ID,
/// The address of the `Identity` that owns
/// the `ControllerCap` we are borrowing.
identity: address,
}

/// Returns a new `ControllerExecution` that - in a Proposal - allows whoever
/// executes it to receive `identity`'s `ControllerCap` (the one that has ID `controller_cap`)
/// for the duration of a single transaction.
public fun new(controller_cap: ID, identity: address): ControllerExecution {
ControllerExecution {
controller_cap,
identity,
}
}

/// Returns the `ControllerCap` specified in this action.
public fun receive(
self: &mut Action<ControllerExecution>,
identity: &mut UID,
cap: Receiving<ControllerCap>
): ControllerCap {
assert!(identity.to_address() == self.borrow().identity, EInvalidIdentityUID);
assert!(cap.receiving_object_id() == self.borrow().controller_cap, EControllerCapMismatch);

controller::receive(identity, cap)
}

/// Consumes a `ControllerExecution` action by returning the borrowed `ControllerCap`
/// to the corresponding `Identity`.
public fun put_back(
action: Action<ControllerExecution>,
cap: ControllerCap,
) {
let ControllerExecution { identity, controller_cap } = action.unwrap();
assert!(object::id(&cap) == controller_cap, EControllerCapMismatch);

cap.transfer(identity);
}
}
4 changes: 2 additions & 2 deletions identity_iota_core/src/rebased/client/read_only.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Option<OwnedObjectRef>, Error> {
/// Returns an object's [`OwnedObjectRef`], if any.
pub async fn get_object_ref_by_id(&self, obj: ObjectID) -> Result<Option<OwnedObjectRef>, Error> {
self
.read_api()
.get_object_with_options(obj, IotaObjectDataOptions::default().with_owner())
Expand Down
14 changes: 10 additions & 4 deletions identity_iota_core/src/rebased/migration/identity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ use crate::rebased::client::IdentityClientReadOnly;
use crate::rebased::client::IotaKeySignature;
use crate::rebased::proposals::BorrowAction;
use crate::rebased::proposals::ConfigChange;
use crate::rebased::proposals::ControllerExecution;
use crate::rebased::proposals::DeactivateDid;
use crate::rebased::proposals::ProposalBuilder;
use crate::rebased::proposals::SendAction;
Expand Down Expand Up @@ -182,13 +183,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,
Expand Down Expand Up @@ -409,7 +415,7 @@ pub(crate) fn unpack_identity_data(
did_doc: multi_controller,
created,
updated,
version
version,
} = serde_json::from_value::<TempOnChainIdentity>(value.fields.to_json_value())
.map_err(|err| Error::ObjectLookup(format!("could not parse identity document with DID {did}; {err}")))?;

Expand Down
Loading

0 comments on commit f5d229a

Please sign in to comment.