Skip to content

Commit

Permalink
Identity send & borrow assets (#54)
Browse files Browse the repository at this point in the history
* feat: add linked verifiable presentation example and update dependencies

* identity send & borrow assets

* TransactionOutput to return both parsed output and raw tx response (#58)

* identity send & borrow assets

* identity send & borrow assets

* de-duplicate code in tests

* rename move function

---------

Co-authored-by: Yasir <[email protected]>
  • Loading branch information
UMR1352 and itsyaasir authored Nov 15, 2024
1 parent 9adfff7 commit 9e27627
Show file tree
Hide file tree
Showing 22 changed files with 1,304 additions and 355 deletions.
6 changes: 3 additions & 3 deletions identity_sui_name_tbd/packages/identity_iota/Move.lock
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ latest-published-id = "0xbf2ba9e9383be1dc349b571cbc44006c6fa760f3b900e0be992232d
published-version = "1"

[env.localnet]
chain-id = "6cc84190"
original-published-id = "0xc7dac7b1a15253d593776e59e79647f76be64503f3428b34441b4818f8cb0551"
latest-published-id = "0xc7dac7b1a15253d593776e59e79647f76be64503f3428b34441b4818f8cb0551"
chain-id = "872e97bc"
original-published-id = "0xf583f2e0a68f71b396e57b0606cbfc55e94c1a416d1f8b5f594e6d18572928be"
latest-published-id = "0xf583f2e0a68f71b396e57b0606cbfc55e94c1a416d1f8b5f594e6d18572928be"
published-version = "1"
151 changes: 109 additions & 42 deletions identity_sui_name_tbd/packages/identity_iota/sources/identity.move
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
module identity_iota::identity {
use iota::{transfer::Receiving, vec_map::{Self, VecMap}, vec_set::VecSet};
use iota::{vec_map::{Self, VecMap}, transfer::Receiving};
use identity_iota::{
multicontroller::{Self, Action, ControllerCap, Multicontroller},
multicontroller::{Self, ControllerCap, Multicontroller, Action},
update_value_proposal,
config_proposal,
transfer_proposal::{Self, Send},
borrow_proposal::{Self, Borrow},
did_deactivation_proposal::{Self, DidDeactivation},
};

Expand Down Expand Up @@ -78,13 +79,22 @@ module identity_iota::identity {
cap: &ControllerCap,
expiration: Option<u64>,
ctx: &mut TxContext,
): ID {
self.did_doc.create_proposal(
): Option<ID> {
let proposal_id = self.did_doc.create_proposal(
cap,
did_deactivation_proposal::new(),
expiration,
ctx,
)
);
let is_approved = self
.did_doc
.is_proposal_approved<_, did_deactivation_proposal::DidDeactivation>(proposal_id);
if (is_approved) {
self.execute_deactivation(cap, proposal_id, ctx);
option::none()
} else {
option::some(proposal_id)
}
}

public fun execute_deactivation(
Expand All @@ -107,15 +117,25 @@ module identity_iota::identity {
updated_doc: vector<u8>,
expiration: Option<u64>,
ctx: &mut TxContext,
): ID {
): Option<ID> {
assert!(is_did_output(&updated_doc), ENotADidDocument);
update_value_proposal::propose_update(
let proposal_id = update_value_proposal::propose_update(
&mut self.did_doc,
cap,
updated_doc,
expiration,
ctx,
)
);

let is_approved = self
.did_doc
.is_proposal_approved<_, update_value_proposal::UpdateValue<vector<u8>>>(proposal_id);
if (is_approved) {
self.execute_update(cap, proposal_id, ctx);
option::none()
} else {
option::some(proposal_id)
}
}

public fun execute_update(
Expand All @@ -141,8 +161,8 @@ module identity_iota::identity {
controllers_to_remove: vector<ID>,
controllers_to_update: VecMap<ID, u64>,
ctx: &mut TxContext,
): ID {
config_proposal::propose_modify(
): Option<ID> {
let proposal_id = config_proposal::propose_modify(
&mut self.did_doc,
cap,
expiration,
Expand All @@ -151,7 +171,17 @@ module identity_iota::identity {
controllers_to_remove,
controllers_to_update,
ctx
)
);

let is_approved = self
.did_doc
.is_proposal_approved<_, config_proposal::Modify>(proposal_id);
if (is_approved) {
self.execute_config_change(cap, proposal_id, ctx);
option::none()
} else {
option::some(proposal_id)
}
}

public fun execute_config_change(
Expand All @@ -172,7 +202,7 @@ module identity_iota::identity {
self: &mut Identity,
cap: &ControllerCap,
expiration: Option<u64>,
objects: VecSet<ID>,
objects: vector<ID>,
recipients: vector<address>,
ctx: &mut TxContext,
) {
Expand All @@ -186,12 +216,38 @@ module identity_iota::identity {
);
}

public fun send<T: key + store>(
public fun execute_send<T: key + store>(
self: &mut Identity,
send_action: &mut Action<Send>,
received: Receiving<T>,
receiving: Receiving<T>,
) {
transfer_proposal::send(send_action, &mut self.id, receiving);
}

public fun propose_borrow(
self: &mut Identity,
cap: &ControllerCap,
expiration: Option<u64>,
objects: vector<ID>,
ctx: &mut TxContext,
) {
transfer_proposal::send(send_action, &mut self.id, received);
let identity_address = self.id().to_address();
borrow_proposal::propose_borrow(
&mut self.did_doc,
cap,
expiration,
objects,
identity_address,
ctx,
);
}

public fun execute_borrow<T: key + store>(
self: &mut Identity,
borrow_action: &mut Action<Borrow>,
receiving: Receiving<T>,
): T {
borrow_proposal::borrow(borrow_action, &mut self.id, receiving)
}

public fun propose_new_controller(
Expand All @@ -201,13 +257,22 @@ module identity_iota::identity {
new_controller_addr: address,
voting_power: u64,
ctx: &mut TxContext,
): ID {
): Option<ID> {
let mut new_controllers = vec_map::empty();
new_controllers.insert(new_controller_addr, voting_power);

self.propose_config_change(cap, expiration, option::none(), new_controllers, vector[], vec_map::empty(), ctx)
}

public fun execute_proposal<T: store>(
self: &mut Identity,
cap: &ControllerCap,
proposal_id: ID,
ctx: &mut TxContext,
): Action<T> {
self.did_doc.execute_proposal(cap, proposal_id, ctx)
}

/// Checks if `data` is a state matadata representing a DID.
/// i.e. starts with the bytes b"DID".
public(package) fun is_did_output(data: &vector<u8>): bool {
Expand Down Expand Up @@ -251,12 +316,8 @@ module identity_iota::identity_tests {
// Create a request to add a second controller.
let mut identity = scenario.take_shared<Identity>();
let controller1_cap = scenario.take_from_address<ControllerCap>(controller1);
let proposal_id = identity.propose_new_controller(&controller1_cap, option::none(), controller2, 1, scenario.ctx());

// Request is fullfilled, add a second controller and send the capability to `controller2`.
scenario.next_tx(controller1);

identity.execute_config_change(&controller1_cap, proposal_id, scenario.ctx());
// This is carried out immediately.
identity.propose_new_controller(&controller1_cap, option::none(), controller2, 1, scenario.ctx());

scenario.next_tx(controller2);

Expand Down Expand Up @@ -308,7 +369,7 @@ module identity_iota::identity_tests {
vector[controller3_cap.id().to_inner()],
vec_map::empty(),
scenario.ctx()
);
).destroy_some();

scenario.next_tx(controller2);

Expand Down Expand Up @@ -363,11 +424,8 @@ module identity_iota::identity_tests {
let mut identity = scenario.take_shared<Identity>();
let controller_a_cap = scenario.take_from_address<ControllerCap>(controller_a);

// Create a request to add a new controller.
let proposal_id = identity.propose_new_controller(&controller_a_cap, option::none(), controller_d, 1, scenario.ctx());

scenario.next_tx(controller_a);
identity.execute_config_change(&controller_a_cap, proposal_id, scenario.ctx());
// Create a request to add a new controller. This is carried out immediately as controller_a has enough voting power
identity.propose_new_controller(&controller_a_cap, option::none(), controller_d, 1, scenario.ctx());

scenario.next_tx(controller_d);

Expand Down Expand Up @@ -395,7 +453,7 @@ module identity_iota::identity_tests {
let mut identity = scenario.take_shared<Identity>();
let controller_b_cap = scenario.take_from_address<ControllerCap>(controller_b);

let proposal_id = identity.propose_new_controller(&controller_b_cap, option::none(), controller_d, 1, scenario.ctx());
let proposal_id = identity.propose_new_controller(&controller_b_cap, option::none(), controller_d, 1, scenario.ctx()).destroy_some();

scenario.next_tx(controller_b);
identity.execute_config_change(&controller_b_cap, proposal_id, scenario.ctx());
Expand Down Expand Up @@ -442,7 +500,7 @@ module identity_iota::identity_tests {
let controller_b_cap = scenario.take_from_address<ControllerCap>(controller_b);

// Create a request to add a new controller.
let proposal_id = identity.propose_new_controller(&controller_b_cap, option::none(), controller_d, 10, scenario.ctx());
let proposal_id = identity.propose_new_controller(&controller_b_cap, option::none(), controller_d, 10, scenario.ctx()).destroy_some();

scenario.next_tx(controller_b);
let controller_c_cap = scenario.take_from_address<ControllerCap>(controller_c);
Expand Down Expand Up @@ -496,10 +554,9 @@ module identity_iota::identity_tests {
let mut second_identity = scenario.take_shared<Identity>();

assert!(second_identity.did_doc().controllers().contains(&first_identity_cap.id().to_inner()), 0);

second_identity.propose_new_controller(&first_identity_cap, option::none(), controller_a, 10, scenario.ctx()).destroy_none();

let proposal_id = second_identity.propose_new_controller(&first_identity_cap, option::none(), controller_a, 10, scenario.ctx());

second_identity.execute_config_change(&first_identity_cap, proposal_id, scenario.ctx());
scenario.next_tx(controller_a);
let controller_a_cap = scenario.take_from_address<ControllerCap>(controller_a);

Expand Down Expand Up @@ -537,25 +594,35 @@ module identity_iota::identity_tests {

#[test, expected_failure(abort_code = EExpiredProposal)]
fun expired_proposals_cannot_be_executed() {
let controller = @0x1;
let new_controller = @0x2;
let mut scenario = test_scenario::begin(controller);
let controller_a = @0x1;
let controller_b = @0x2;
let new_controller = @0x3;
let mut scenario = test_scenario::begin(controller_a);
let expiration_epoch = scenario.ctx().epoch();

let mut controllers = vec_map::empty();
controllers.insert(controller_a, 1);
controllers.insert(controller_b, 1);

let identity = new(b"DID", scenario.ctx());
let identity = new_with_controllers(b"DID", controllers, 2, scenario.ctx());
transfer::public_share_object(identity);

scenario.next_tx(controller);
scenario.next_tx(controller_a);

let mut identity = scenario.take_shared<Identity>();
let cap = scenario.take_from_address<ControllerCap>(controller);
let proposal_id = identity.propose_new_controller(&cap, option::some(expiration_epoch), new_controller, 1, scenario.ctx());
let cap = scenario.take_from_address<ControllerCap>(controller_a);
let proposal_id = identity.propose_new_controller(&cap, option::some(expiration_epoch), new_controller, 1, scenario.ctx()).destroy_some();

scenario.later_epoch(100, controller);
scenario.next_tx(controller_b);
let cap_b = scenario.take_from_address<ControllerCap>(controller_b);
identity.approve_proposal<Modify>(&cap_b, proposal_id);

scenario.later_epoch(100, controller_a);
// this should fail!
identity.execute_config_change(&cap, proposal_id, scenario.ctx());

test_scenario::return_to_address(controller, cap);
test_scenario::return_to_address(controller_a, cap);
test_scenario::return_to_address(controller_b, cap_b);
test_scenario::return_shared(identity);

scenario.end();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,6 @@ module identity_iota::multicontroller {
&mut action.inner
}

public struct ActionKey has copy, store, drop {}

public(package) fun assert_is_member<V>(multi: &Multicontroller<V>, cap: &ControllerCap) {
assert!(multi.controllers.contains(&cap.id.to_inner()), EInvalidController);
}
Expand Down Expand Up @@ -224,6 +222,11 @@ module identity_iota::multicontroller {
inner
}

public(package) fun is_proposal_approved<V, A: store>(multi: &Multicontroller<V>, proposal_id: ID): bool {
let proposal = multi.proposals.borrow<ID, Proposal<A>>(proposal_id);
proposal.votes >= multi.threshold
}

public(package) fun add_members<V>(multi: &mut Multicontroller<V>, to_add: VecMap<address, u64>, ctx: &mut TxContext) {
let mut i = 0;
while (i < to_add.size()) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
module identity_iota::borrow_proposal {
use identity_iota::{multicontroller::{Multicontroller, Action, ControllerCap}};
use iota::transfer::Receiving;

const EInvalidObject: u64 = 0;
const EInvalidOwner: u64 = 1;
const EUnreturnedObjects: u64 = 2;

/// Action used to "borrow" assets in a transaction - enforcing their return.
public struct Borrow has store {
objects: vector<ID>,
objects_to_return: vector<ID>,
owner: address,
}

/// Propose the borrowing of a set of assets owned by this multicontroller.
public fun propose_borrow<V>(
multi: &mut Multicontroller<V>,
cap: &ControllerCap,
expiration: Option<u64>,
objects: vector<ID>,
owner: address,
ctx: &mut TxContext,
) {
let action = Borrow { objects, objects_to_return: vector::empty(), owner };

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

/// Borrows an asset from this action. This function will fail if:
/// - the received object is not among `Borrow::objects`;
/// - controllee does not have the same address as `Borrow::owner`;
public fun borrow<T: key + store>(
action: &mut Action<Borrow>,
controllee: &mut UID,
receiving: Receiving<T>,
): T {
let borrow_action = action.borrow_mut();
assert!(borrow_action.owner == controllee.to_address(), EInvalidOwner);
let receiving_object_id = receiving.receiving_object_id();
let (obj_exists, obj_idx) = borrow_action.objects.index_of(&receiving_object_id);
assert!(obj_exists, EInvalidObject);

borrow_action.objects.swap_remove(obj_idx);
borrow_action.objects_to_return.push_back(receiving_object_id);

transfer::public_receive(controllee, receiving)
}

/// Transfer a borrowed object back to its original owner.
public fun put_back<T: key + store>(
action: &mut Action<Borrow>,
obj: T,
) {
let borrow_action = action.borrow_mut();
let object_id = object::id(&obj);
let (contains, obj_idx) = borrow_action.objects_to_return.index_of(&object_id);
assert!(contains, EInvalidObject);

borrow_action.objects_to_return.swap_remove(obj_idx);
transfer::public_transfer(obj, borrow_action.owner);
}

/// Consumes a borrow action.
public fun conclude_borrow(
action: Action<Borrow>
) {
let Borrow { objects: _, objects_to_return, owner: _ } = action.unpack_action();
assert!(objects_to_return.is_empty(), EUnreturnedObjects);
}
}
Loading

0 comments on commit 9e27627

Please sign in to comment.