Skip to content

Commit

Permalink
Add Move tests to verify delegate's permissions (#1464)
Browse files Browse the repository at this point in the history
* chore: add identity and delegation tests

* add permission tests

* review comments

---------

Co-authored-by: Yasir <[email protected]>
  • Loading branch information
UMR1352 and itsyaasir authored Dec 2, 2024
1 parent ffb392a commit 395a950
Show file tree
Hide file tree
Showing 4 changed files with 219 additions and 1 deletion.
177 changes: 177 additions & 0 deletions identity_iota_core/packages/iota_identity/sources/controller.move
Original file line number Diff line number Diff line change
Expand Up @@ -147,4 +147,181 @@ module iota_identity::controller {
} = token;
object::delete(id);
}
}

#[test_only]
module iota_identity::controller_tests {
use iota::test_scenario;
use iota_identity::controller::{Self, ControllerCap, ECannotDelegate, EInvalidPermissions};
use iota_identity::permissions;
use iota_identity::multicontroller::{Self, Multicontroller};

#[test, expected_failure(abort_code = ECannotDelegate)]
fun test_only_delegatable_controllers_can_create_delegation_tokens() {
let owner = @0x1;
let mut scenario = test_scenario::begin(owner);

let non_delegatable = controller::new(false, scenario.ctx());
let delegation_token = non_delegatable.delegate(scenario.ctx());

delegation_token.delete();
non_delegatable.delete();
scenario.end();
}

#[test, expected_failure(abort_code = EInvalidPermissions)]
fun delegate_cannot_create_proposal_when_missing_permission() {
let controller = @0x1;
let mut scenario = test_scenario::begin(controller);

let mut multicontroller: Multicontroller<u64> = multicontroller::new(0, true, scenario.ctx());
scenario.next_tx(controller);

let controller_cap = scenario.take_from_address<ControllerCap>(controller);
let delegation_token = controller_cap.delegate_with_permissions(
permissions::all() & permissions::not(permissions::can_create_proposal()),
scenario.ctx(),
);

scenario.next_tx(controller);

multicontroller.create_proposal<_, u64>(
&delegation_token,
0,
option::none(),
scenario.ctx(),
);

abort(0)
}

#[test, expected_failure(abort_code = EInvalidPermissions)]
fun delegate_cannot_execute_proposal_when_missing_permission() {
let controller = @0x1;
let mut scenario = test_scenario::begin(controller);

let mut multicontroller: Multicontroller<u64> = multicontroller::new(0, true, scenario.ctx());
scenario.next_tx(controller);

let controller_cap = scenario.take_from_address<ControllerCap>(controller);
let delegation_token = controller_cap.delegate_with_permissions(
permissions::all() & permissions::not(permissions::can_execute_proposal()),
scenario.ctx(),
);

scenario.next_tx(controller);

let proposal_id = multicontroller.create_proposal<_, u64>(
&delegation_token,
0,
option::none(),
scenario.ctx(),
);

multicontroller.execute_proposal<_, u64>(
&delegation_token,
proposal_id,
scenario.ctx(),
).unwrap();

abort(0)
}

#[test, expected_failure(abort_code = EInvalidPermissions)]
fun delegate_cannot_approve_proposal_when_missing_permission() {
let controller = @0x1;
let mut scenario = test_scenario::begin(controller);

let mut multicontroller: Multicontroller<u64> = multicontroller::new(0, true, scenario.ctx());
scenario.next_tx(controller);

let controller_cap = scenario.take_from_address<ControllerCap>(controller);
let delegation_token = controller_cap.delegate_with_permissions(
permissions::all() & permissions::not(permissions::can_approve_proposal()),
scenario.ctx(),
);

scenario.next_tx(controller);

let proposal_id = multicontroller.create_proposal<_, u64>(
&delegation_token,
0,
option::none(),
scenario.ctx(),
);

multicontroller.approve_proposal<_, u64>(
&delegation_token,
proposal_id,
);

abort(0)
}

#[test, expected_failure(abort_code = EInvalidPermissions)]
fun delegate_cannot_remove_approval_when_missing_permission() {
let controller = @0x1;
let mut scenario = test_scenario::begin(controller);

let mut multicontroller: Multicontroller<u64> = multicontroller::new(0, true, scenario.ctx());
scenario.next_tx(controller);

let controller_cap = scenario.take_from_address<ControllerCap>(controller);
let delegation_token = controller_cap.delegate_with_permissions(
permissions::all() & permissions::not(permissions::can_remove_approval()),
scenario.ctx(),
);

scenario.next_tx(controller);

let proposal_id = multicontroller.create_proposal<_, u64>(
&delegation_token,
0,
option::none(),
scenario.ctx(),
);

multicontroller.remove_approval<_, u64>(
&delegation_token,
proposal_id,
);

abort(0)
}

#[test, expected_failure(abort_code = EInvalidPermissions)]
fun delegate_cannot_delete_proposal_when_missing_permission() {
let controller = @0x1;
let mut scenario = test_scenario::begin(controller);

let mut multicontroller: Multicontroller<u64> = multicontroller::new(0, true, scenario.ctx());
scenario.next_tx(controller);

let controller_cap = scenario.take_from_address<ControllerCap>(controller);
let delegation_token = controller_cap.delegate_with_permissions(
permissions::all() & permissions::not(permissions::can_remove_approval()),
scenario.ctx(),
);

scenario.next_tx(controller);

let proposal_id = multicontroller.create_proposal<_, u64>(
&delegation_token,
0,
option::none(),
scenario.ctx(),
);

multicontroller.remove_approval<_, u64>(
&delegation_token,
proposal_id,
);

multicontroller.delete_proposal<_, u64>(
&delegation_token,
proposal_id,
);

abort(0)
}
}
11 changes: 10 additions & 1 deletion identity_iota_core/packages/iota_identity/sources/identity.move
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ module iota_identity::identity {
}
}

/// Proposes the deativation of the DID Document contained in this `Identity`.
/// Proposes the deactivation of the DID Document contained in this `Identity`.
public fun propose_deactivation(
self: &mut Identity,
cap: &DelegationToken,
Expand Down Expand Up @@ -481,6 +481,15 @@ module iota_identity::identity {
self.did_doc.execute_proposal(cap, proposal_id, ctx)
}

/// Deletes an `Identity`'s proposal. Only proposals with no votes can be deleted.
public fun delete_proposal<T: store + drop>(
self: &mut Identity,
cap: &DelegationToken,
proposal_id: ID,
) {
self.did_doc.delete_proposal<_, T>(cap, proposal_id);
}

/// revoke the `DelegationToken` with `ID` `deny_id`. Only controllers can perform this operation.
public fun revoke_token(self: &mut Identity, cap: &ControllerCap, deny_id: ID) {
self.did_doc.revoke_token(cap, deny_id);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ module iota_identity::multicontroller {
const EExpiredProposal: u64 = 4;
const ENotVotedYet: u64 = 5;
const EProposalNotFound: u64 = 6;
const ECannotDelete: u64 = 7;

/// Shares control of a value `V` with multiple entities called controllers.
public struct Multicontroller<V> has store {
Expand Down Expand Up @@ -242,6 +243,33 @@ module iota_identity::multicontroller {
proposal.votes = proposal.votes - vp;
}

/// Removes a proposal no one has voted for.
public fun delete_proposal<V, T: store + drop>(
multi: &mut Multicontroller<V>,
cap: &DelegationToken,
proposal_id: ID,
) {
cap.assert_has_permission(permissions::can_delete_proposal());

let proposal = multi.proposals.remove<ID, Proposal<T>>(proposal_id);
assert!(proposal.votes == 0, ECannotDelete);

let Proposal {
id,
votes: _,
voters: _,
expiration_epoch: _,
action: _,
} = proposal;

id.delete();

let (present, i) = multi.active_proposals.index_of(&proposal_id);
assert!(present, EProposalNotFound);

multi.active_proposals.remove(i);
}

/// Returns a reference to `multi`'s value.
public fun value<V: store>(multi: &Multicontroller<V>): &V {
&multi.controlled_value
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,8 @@ module iota_identity::permissions {
public fun can_delete_proposal(): u32 { CAN_DELETE_PROPOSAL }
public fun can_remove_approval(): u32 { CAN_REMOVE_APPROVAL }
public fun all(): u32 { ALL_PERMISSIONS }
/// Negate a permission
public fun not(permission: u32): u32 {
permission ^ all()
}
}

0 comments on commit 395a950

Please sign in to comment.