Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Borrow an Identity's ControllerCap in order to perform operations on sub-Identities #1454

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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