diff --git a/aptos-move/framework/aptos-framework/doc/overview.md b/aptos-move/framework/aptos-framework/doc/overview.md index 314baa3612ba96..7ad32f3ea8ec0d 100644 --- a/aptos-move/framework/aptos-framework/doc/overview.md +++ b/aptos-move/framework/aptos-framework/doc/overview.md @@ -46,6 +46,7 @@ This is the reference documentation of the Aptos framework. - [`0x1::object`](object.md#0x1_object) - [`0x1::object_code_deployment`](object_code_deployment.md#0x1_object_code_deployment) - [`0x1::optional_aggregator`](optional_aggregator.md#0x1_optional_aggregator) +- [`0x1::permissioned_signer`](permissioned_signer.md#0x1_permissioned_signer) - [`0x1::primary_fungible_store`](primary_fungible_store.md#0x1_primary_fungible_store) - [`0x1::randomness`](randomness.md#0x1_randomness) - [`0x1::randomness_api_v0_config`](randomness_api_v0_config.md#0x1_randomness_api_v0_config) diff --git a/aptos-move/framework/aptos-framework/doc/permissioned_signer.md b/aptos-move/framework/aptos-framework/doc/permissioned_signer.md new file mode 100644 index 00000000000000..d8196b081cf2eb --- /dev/null +++ b/aptos-move/framework/aptos-framework/doc/permissioned_signer.md @@ -0,0 +1,1491 @@ + + + +# Module `0x1::permissioned_signer` + +A _permissioned signer_ consists of a pair of the original signer and a generated +signer which is used store information about associated permissions. + +A permissioned signer behaves compatible with the original signer as it comes to move_to, address_of, and +existing basic signer functionality. However, the permissions can be queried to assert additional +restrictions on the use of the signer. + +A client which is interested in restricting access granted via a signer can create a permissioned signer +and pass on to other existing code without changes to existing APIs. Core functions in the framework, for +example account functions, can then assert availability of permissions, effectively restricting +existing code in a compatible way. + +After introducing the core functionality, examples are provided for withdraw limit on accounts, and +for blind signing. + + +- [Resource `GrantedPermissionHandles`](#0x1_permissioned_signer_GrantedPermissionHandles) +- [Struct `PermissionedHandle`](#0x1_permissioned_signer_PermissionedHandle) +- [Struct `StorablePermissionedHandle`](#0x1_permissioned_signer_StorablePermissionedHandle) +- [Resource `PermStorage`](#0x1_permissioned_signer_PermStorage) +- [Struct `Permission`](#0x1_permissioned_signer_Permission) +- [Constants](#@Constants_0) +- [Function `create_permissioned_handle`](#0x1_permissioned_signer_create_permissioned_handle) +- [Function `create_storable_permissioned_handle`](#0x1_permissioned_signer_create_storable_permissioned_handle) +- [Function `destroy_permissioned_handle`](#0x1_permissioned_signer_destroy_permissioned_handle) +- [Function `destroy_storable_permissioned_handle`](#0x1_permissioned_signer_destroy_storable_permissioned_handle) +- [Function `destroy_permission_address`](#0x1_permissioned_signer_destroy_permission_address) +- [Function `remove_permission_address`](#0x1_permissioned_signer_remove_permission_address) +- [Function `signer_from_permissioned`](#0x1_permissioned_signer_signer_from_permissioned) +- [Function `signer_from_storable_permissioned`](#0x1_permissioned_signer_signer_from_storable_permissioned) +- [Function `revoke_permission_handle`](#0x1_permissioned_signer_revoke_permission_handle) +- [Function `revoke_all_handles`](#0x1_permissioned_signer_revoke_all_handles) +- [Function `permission_address`](#0x1_permissioned_signer_permission_address) +- [Function `assert_master_signer`](#0x1_permissioned_signer_assert_master_signer) +- [Function `authorize`](#0x1_permissioned_signer_authorize) +- [Function `check_permission_exists`](#0x1_permissioned_signer_check_permission_exists) +- [Function `check_permission_capacity_above`](#0x1_permissioned_signer_check_permission_capacity_above) +- [Function `check_permission_consume`](#0x1_permissioned_signer_check_permission_consume) +- [Function `capacity`](#0x1_permissioned_signer_capacity) +- [Function `revoke_permission`](#0x1_permissioned_signer_revoke_permission) +- [Function `extract_permission`](#0x1_permissioned_signer_extract_permission) +- [Function `get_key`](#0x1_permissioned_signer_get_key) +- [Function `address_of`](#0x1_permissioned_signer_address_of) +- [Function `consume_permission`](#0x1_permissioned_signer_consume_permission) +- [Function `store_permission`](#0x1_permissioned_signer_store_permission) +- [Function `is_permissioned_signer`](#0x1_permissioned_signer_is_permissioned_signer) +- [Function `permission_signer`](#0x1_permissioned_signer_permission_signer) +- [Function `signer_from_permissioned_impl`](#0x1_permissioned_signer_signer_from_permissioned_impl) +- [Specification](#@Specification_1) + - [Function `create_permissioned_handle`](#@Specification_1_create_permissioned_handle) + - [Function `create_storable_permissioned_handle`](#@Specification_1_create_storable_permissioned_handle) + - [Function `destroy_permissioned_handle`](#@Specification_1_destroy_permissioned_handle) + - [Function `destroy_storable_permissioned_handle`](#@Specification_1_destroy_storable_permissioned_handle) + - [Function `revoke_permission_handle`](#@Specification_1_revoke_permission_handle) + - [Function `authorize`](#@Specification_1_authorize) + - [Function `check_permission_exists`](#@Specification_1_check_permission_exists) + - [Function `check_permission_capacity_above`](#@Specification_1_check_permission_capacity_above) + - [Function `check_permission_consume`](#@Specification_1_check_permission_consume) + - [Function `capacity`](#@Specification_1_capacity) + - [Function `consume_permission`](#@Specification_1_consume_permission) + - [Function `is_permissioned_signer`](#@Specification_1_is_permissioned_signer) + - [Function `permission_signer`](#@Specification_1_permission_signer) + - [Function `signer_from_permissioned_impl`](#@Specification_1_signer_from_permissioned_impl) + + +
use 0x1::copyable_any;
+use 0x1::create_signer;
+use 0x1::error;
+use 0x1::option;
+use 0x1::signer;
+use 0x1::smart_table;
+use 0x1::timestamp;
+use 0x1::transaction_context;
+use 0x1::vector;
+
+ + + + + +## Resource `GrantedPermissionHandles` + + + +
struct GrantedPermissionHandles has key
+
+ + + +
+Fields + + +
+
+active_handles: vector<address> +
+
+ +
+
+ + +
+ + + +## Struct `PermissionedHandle` + + + +
struct PermissionedHandle
+
+ + + +
+Fields + + +
+
+master_addr: address +
+
+ +
+
+permission_addr: address +
+
+ +
+
+ + +
+ + + +## Struct `StorablePermissionedHandle` + + + +
struct StorablePermissionedHandle has store
+
+ + + +
+Fields + + +
+
+master_addr: address +
+
+ +
+
+permission_addr: address +
+
+ +
+
+expiration_time: u64 +
+
+ +
+
+ + +
+ + + +## Resource `PermStorage` + + + +
struct PermStorage has key
+
+ + + +
+Fields + + +
+
+perms: smart_table::SmartTable<copyable_any::Any, u256> +
+
+ +
+
+ + +
+ + + +## Struct `Permission` + + + +
struct Permission<K>
+
+ + + +
+Fields + + +
+
+owner_address: address +
+
+ +
+
+key: K +
+
+ +
+
+capacity: u256 +
+
+ +
+
+ + +
+ + + +## Constants + + + + +Cannot authorize a permission. + + +
const ECANNOT_AUTHORIZE: u64 = 2;
+
+ + + + + +signer doesn't have enough capacity to extract permission. + + +
const ECANNOT_EXTRACT_PERMISSION: u64 = 4;
+
+ + + + + +Trying to grant permission using master signer. + + +
const ENOT_MASTER_SIGNER: u64 = 1;
+
+ + + + + +Access permission information from a master signer. + + +
const ENOT_PERMISSIONED_SIGNER: u64 = 3;
+
+ + + + + +permission handle has expired. + + +
const E_PERMISSION_EXPIRED: u64 = 5;
+
+ + + + + +storing extracted permission into a different signer. + + +
const E_PERMISSION_MISMATCH: u64 = 6;
+
+ + + + + +permission handle has been revoked by the original signer. + + +
const E_PERMISSION_REVOKED: u64 = 7;
+
+ + + + + +## Function `create_permissioned_handle` + + + +
public fun create_permissioned_handle(master: &signer): permissioned_signer::PermissionedHandle
+
+ + + +
+Implementation + + +
public fun create_permissioned_handle(master: &signer): PermissionedHandle {
+    assert_master_signer(master);
+    let permission_addr = generate_auid_address();
+    let master_addr = signer::address_of(master);
+
+    move_to(&create_signer(permission_addr), PermStorage { perms: smart_table::new()});
+
+    PermissionedHandle {
+        master_addr,
+        permission_addr,
+    }
+}
+
+ + + +
+ + + +## Function `create_storable_permissioned_handle` + + + +
public(friend) fun create_storable_permissioned_handle(master: &signer, expiration_time: u64): permissioned_signer::StorablePermissionedHandle
+
+ + + +
+Implementation + + +
public(friend) fun create_storable_permissioned_handle(master: &signer, expiration_time: u64): StorablePermissionedHandle acquires GrantedPermissionHandles {
+    assert_master_signer(master);
+    let permission_addr = generate_auid_address();
+    let master_addr = signer::address_of(master);
+
+    if(!exists<GrantedPermissionHandles>(master_addr)) {
+        move_to<GrantedPermissionHandles>(master, GrantedPermissionHandles {
+            active_handles: vector::empty(),
+        });
+    };
+
+    vector::push_back(
+        &mut borrow_global_mut<GrantedPermissionHandles>(master_addr).active_handles,
+        permission_addr
+    );
+
+    move_to(&create_signer(permission_addr), PermStorage { perms: smart_table::new()});
+
+    // Do we need to move sth similar to ObjectCore to register this address as permission address?
+    StorablePermissionedHandle {
+        master_addr,
+        permission_addr,
+        expiration_time,
+    }
+}
+
+ + + +
+ + + +## Function `destroy_permissioned_handle` + + + +
public fun destroy_permissioned_handle(p: permissioned_signer::PermissionedHandle)
+
+ + + +
+Implementation + + +
public fun destroy_permissioned_handle(p: PermissionedHandle) acquires PermStorage {
+    let PermissionedHandle { master_addr: _, permission_addr } = p;
+    destroy_permission_address(permission_addr);
+}
+
+ + + +
+ + + +## Function `destroy_storable_permissioned_handle` + + + +
public fun destroy_storable_permissioned_handle(p: permissioned_signer::StorablePermissionedHandle)
+
+ + + +
+Implementation + + +
public fun destroy_storable_permissioned_handle(p: StorablePermissionedHandle) acquires PermStorage, GrantedPermissionHandles {
+    let StorablePermissionedHandle { master_addr, permission_addr, expiration_time: _ } = p;
+    destroy_permission_address(permission_addr);
+    remove_permission_address(master_addr, permission_addr);
+}
+
+ + + +
+ + + +## Function `destroy_permission_address` + + + +
fun destroy_permission_address(permission_addr: address)
+
+ + + +
+Implementation + + +
inline fun destroy_permission_address(permission_addr: address) acquires PermStorage {
+    if(exists<PermStorage>(permission_addr)) {
+        let PermStorage { perms } = move_from<PermStorage>(permission_addr);
+        smart_table::destroy(perms);
+    }
+}
+
+ + + +
+ + + +## Function `remove_permission_address` + + + +
fun remove_permission_address(master_addr: address, permission_addr: address)
+
+ + + +
+Implementation + + +
inline fun remove_permission_address(master_addr: address, permission_addr: address) acquires GrantedPermissionHandles {
+    if(exists<GrantedPermissionHandles>(master_addr)) {
+       let granted_permissions = borrow_global_mut<GrantedPermissionHandles>(master_addr);
+        let (found, idx) = vector::index_of(&granted_permissions.active_handles, &permission_addr);
+        if(found) {
+            vector::swap_remove(&mut granted_permissions.active_handles, idx);
+        };
+    };
+}
+
+ + + +
+ + + +## Function `signer_from_permissioned` + + + +
public fun signer_from_permissioned(p: &permissioned_signer::PermissionedHandle): signer
+
+ + + +
+Implementation + + +
public fun signer_from_permissioned(p: &PermissionedHandle): signer {
+    signer_from_permissioned_impl(p.master_addr, p.permission_addr)
+}
+
+ + + +
+ + + +## Function `signer_from_storable_permissioned` + + + +
public fun signer_from_storable_permissioned(p: &permissioned_signer::StorablePermissionedHandle): signer
+
+ + + +
+Implementation + + +
public fun signer_from_storable_permissioned(p: &StorablePermissionedHandle): signer {
+    assert!(
+        timestamp::now_seconds() < p.expiration_time,
+        error::permission_denied(E_PERMISSION_EXPIRED)
+    );
+    assert!(
+        exists<PermStorage>(p.permission_addr),
+        error::permission_denied(E_PERMISSION_REVOKED)
+    );
+    signer_from_permissioned_impl(p.master_addr, p.permission_addr)
+}
+
+ + + +
+ + + +## Function `revoke_permission_handle` + + + +
public fun revoke_permission_handle(s: &signer, permission_addr: address)
+
+ + + +
+Implementation + + +
public fun revoke_permission_handle(s: &signer, permission_addr: address) acquires GrantedPermissionHandles, PermStorage {
+    assert!(!is_permissioned_signer(s), error::permission_denied(ENOT_MASTER_SIGNER));
+    let master_addr = signer::address_of(s);
+    destroy_permission_address(permission_addr);
+    remove_permission_address(master_addr, permission_addr);
+}
+
+ + + +
+ + + +## Function `revoke_all_handles` + + + +
public fun revoke_all_handles(s: &signer)
+
+ + + +
+Implementation + + +
public fun revoke_all_handles(s: &signer) acquires GrantedPermissionHandles, PermStorage {
+    assert!(!is_permissioned_signer(s), error::permission_denied(ENOT_MASTER_SIGNER));
+    let master_addr = signer::address_of(s);
+    if(!exists<GrantedPermissionHandles>(master_addr)) {
+        return
+    };
+
+    let granted_permissions = borrow_global_mut<GrantedPermissionHandles>(master_addr);
+    let delete_list = vector::trim_reverse(&mut granted_permissions.active_handles, 0);
+    vector::destroy(delete_list, |address| {
+        destroy_permission_address(address);
+    })
+}
+
+ + + +
+ + + +## Function `permission_address` + + + +
public fun permission_address(p: &permissioned_signer::StorablePermissionedHandle): address
+
+ + + +
+Implementation + + +
public fun permission_address(p: &StorablePermissionedHandle): address {
+    p.permission_addr
+}
+
+ + + +
+ + + +## Function `assert_master_signer` + + + +
public fun assert_master_signer(s: &signer)
+
+ + + +
+Implementation + + +
public fun assert_master_signer(s: &signer) {
+    assert!(!is_permissioned_signer(s), error::permission_denied(ENOT_MASTER_SIGNER));
+}
+
+ + + +
+ + + +## Function `authorize` + +===================================================================================================== +Permission Management + +Authorizes permissioned with the given permission. This requires to have access to the master +signer. + + +
public fun authorize<PermKey: copy, drop, store>(master: &signer, permissioned: &signer, capacity: u256, perm: PermKey)
+
+ + + +
+Implementation + + +
public fun authorize<PermKey: copy + drop + store>(
+    master: &signer,
+    permissioned: &signer,
+    capacity: u256,
+    perm: PermKey
+) acquires PermStorage {
+    assert!(
+        is_permissioned_signer(permissioned) &&
+        !is_permissioned_signer(master) &&
+        signer::address_of(master) == signer::address_of(permissioned),
+        error::permission_denied(ECANNOT_AUTHORIZE)
+    );
+    let permission_signer = permission_signer(permissioned);
+    let permission_signer_addr = signer::address_of(&permission_signer);
+    let perms = &mut borrow_global_mut<PermStorage>(permission_signer_addr).perms;
+    let key = copyable_any::pack(perm);
+    if(smart_table::contains(perms, key)) {
+        let entry = smart_table::borrow_mut(perms, key);
+        *entry = *entry + capacity;
+    } else {
+        smart_table::add(perms, key, capacity);
+    }
+}
+
+ + + +
+ + + +## Function `check_permission_exists` + + + +
public fun check_permission_exists<PermKey: copy, drop, store>(s: &signer, perm: PermKey): bool
+
+ + + +
+Implementation + + +
public fun check_permission_exists<PermKey: copy + drop + store>(
+    s: &signer,
+    perm: PermKey
+): bool acquires PermStorage {
+    if (!is_permissioned_signer(s)) {
+        // master signer has all permissions
+        return true
+    };
+    let addr = signer::address_of(&permission_signer(s));
+    if(!exists<PermStorage>(addr)) {
+        return false
+    };
+    smart_table::contains(&borrow_global<PermStorage>(addr).perms, copyable_any::pack(perm))
+}
+
+ + + +
+ + + +## Function `check_permission_capacity_above` + + + +
public fun check_permission_capacity_above<PermKey: copy, drop, store>(s: &signer, threshold: u256, perm: PermKey): bool
+
+ + + +
+Implementation + + +
public fun check_permission_capacity_above<PermKey: copy + drop + store>(
+    s: &signer,
+    threshold: u256,
+    perm: PermKey
+): bool acquires PermStorage {
+    if (!is_permissioned_signer(s)) {
+        // master signer has all permissions
+        return true
+    };
+    let addr = signer::address_of(&permission_signer(s));
+    if(!exists<PermStorage>(addr)) {
+        return false
+    };
+    let key = copyable_any::pack(perm);
+    let storage = &borrow_global<PermStorage>(addr).perms;
+    if(!smart_table::contains(storage, key)) {
+        return false
+    };
+    let perm = smart_table::borrow(storage, key);
+    if(*perm > threshold) {
+        true
+    } else {
+        false
+    }
+}
+
+ + + +
+ + + +## Function `check_permission_consume` + + + +
public fun check_permission_consume<PermKey: copy, drop, store>(s: &signer, threshold: u256, perm: PermKey): bool
+
+ + + +
+Implementation + + +
public fun check_permission_consume<PermKey: copy + drop + store>(
+    s: &signer,
+    threshold: u256,
+    perm: PermKey
+): bool acquires PermStorage {
+    if (!is_permissioned_signer(s)) {
+        // master signer has all permissions
+        return true
+    };
+    let addr = signer::address_of(&permission_signer(s));
+    if(!exists<PermStorage>(addr)) {
+        return false
+    };
+    let key = copyable_any::pack(perm);
+    let storage = &mut borrow_global_mut<PermStorage>(addr).perms;
+    if(!smart_table::contains(storage, key)) {
+        return false
+    };
+    let perm = smart_table::borrow_mut(storage, key);
+    if(*perm >= threshold) {
+        *perm = *perm - threshold;
+        true
+    } else {
+        false
+    }
+}
+
+ + + +
+ + + +## Function `capacity` + + + +
public fun capacity<PermKey: copy, drop, store>(s: &signer, perm: PermKey): option::Option<u256>
+
+ + + +
+Implementation + + +
public fun capacity<PermKey: copy + drop + store>(s: &signer, perm: PermKey): Option<u256> acquires PermStorage {
+    assert!(is_permissioned_signer(s), error::permission_denied(ENOT_PERMISSIONED_SIGNER));
+    let addr = signer::address_of(&permission_signer(s));
+    if(!exists<PermStorage>(addr)) {
+        return option::none()
+    };
+    let perm_storage = &borrow_global<PermStorage>(addr).perms;
+    let key = copyable_any::pack(perm);
+    if(smart_table::contains(perm_storage, key)) {
+        option::some(*smart_table::borrow(&borrow_global<PermStorage>(addr).perms, key))
+    } else {
+        option::none()
+    }
+}
+
+ + + +
+ + + +## Function `revoke_permission` + + + +
public fun revoke_permission<PermKey: copy, drop, store>(permissioned: &signer, perm: PermKey)
+
+ + + +
+Implementation + + +
public fun revoke_permission<PermKey: copy + drop + store>(permissioned: &signer, perm: PermKey) acquires PermStorage {
+    if(!is_permissioned_signer(permissioned)) {
+        // Master signer has no permissions associated with it.
+        return
+    };
+    let addr = signer::address_of(&permission_signer(permissioned));
+    if(!exists<PermStorage>(addr)) {
+        return
+    };
+    smart_table::remove(&mut borrow_global_mut<PermStorage>(addr).perms, copyable_any::pack(perm));
+}
+
+ + + +
+ + + +## Function `extract_permission` + +Another flavor of api to extract and store permissions + + +
public fun extract_permission<PermKey: copy, drop, store>(s: &signer, weight: u256, perm: PermKey): permissioned_signer::Permission<PermKey>
+
+ + + +
+Implementation + + +
public fun extract_permission<PermKey: copy + drop + store>(
+    s: &signer,
+    weight: u256,
+    perm: PermKey
+): Permission<PermKey> acquires PermStorage {
+    assert!(check_permission_consume(s, weight, perm), error::permission_denied(ECANNOT_EXTRACT_PERMISSION));
+    Permission {
+        owner_address: signer::address_of(s),
+        key: perm,
+        capacity: weight,
+    }
+}
+
+ + + +
+ + + +## Function `get_key` + + + +
public fun get_key<PermKey>(perm: &permissioned_signer::Permission<PermKey>): &PermKey
+
+ + + +
+Implementation + + +
public fun get_key<PermKey>(perm: &Permission<PermKey>): &PermKey {
+    &perm.key
+}
+
+ + + +
+ + + +## Function `address_of` + + + +
public fun address_of<PermKey>(perm: &permissioned_signer::Permission<PermKey>): address
+
+ + + +
+Implementation + + +
public fun address_of<PermKey>(perm: &Permission<PermKey>): address {
+    perm.owner_address
+}
+
+ + + +
+ + + +## Function `consume_permission` + + + +
public fun consume_permission<PermKey: copy, drop, store>(perm: &mut permissioned_signer::Permission<PermKey>, weight: u256, perm_key: PermKey): bool
+
+ + + +
+Implementation + + +
public fun consume_permission<PermKey: copy + drop + store>(
+    perm: &mut Permission<PermKey>,
+    weight: u256,
+    perm_key: PermKey
+): bool {
+    if(perm.key != perm_key) {
+        return false
+    };
+    if(perm.capacity >= weight) {
+        perm.capacity = perm.capacity - weight;
+        return true
+    } else {
+        return false
+    }
+}
+
+ + + +
+ + + +## Function `store_permission` + + + +
public fun store_permission<PermKey: copy, drop, store>(s: &signer, perm: permissioned_signer::Permission<PermKey>)
+
+ + + +
+Implementation + + +
public fun store_permission<PermKey: copy + drop + store>(
+    s: &signer,
+    perm: Permission<PermKey>
+) acquires PermStorage {
+    assert!(is_permissioned_signer(s), error::permission_denied(ENOT_PERMISSIONED_SIGNER));
+    let Permission { key, capacity, owner_address } = perm;
+
+    assert!(signer::address_of(s) == owner_address, error::permission_denied(E_PERMISSION_MISMATCH));
+
+    let permission_signer = permission_signer(s);
+    let permission_signer_addr = signer::address_of(&permission_signer);
+    if(!exists<PermStorage>(permission_signer_addr)) {
+        move_to(&permission_signer, PermStorage { perms: smart_table::new()});
+    };
+    let perms = &mut borrow_global_mut<PermStorage>(permission_signer_addr).perms;
+    let key = copyable_any::pack(key);
+    if(smart_table::contains(perms, key)) {
+        let entry = smart_table::borrow_mut(perms, key);
+        *entry = *entry + capacity;
+    } else {
+        smart_table::add(perms, key, capacity)
+    }
+}
+
+ + + +
+ + + +## Function `is_permissioned_signer` + +Creates a permissioned signer from an existing universal signer. The function aborts if the +given signer is already a permissioned signer. + +The implementation of this function requires to extend the value representation for signers in the VM. + +Check whether this is a permissioned signer. + + +
public fun is_permissioned_signer(s: &signer): bool
+
+ + + +
+Implementation + + +
public native fun is_permissioned_signer(s: &signer): bool;
+
+ + + +
+ + + +## Function `permission_signer` + +Return the signer used for storing permissions. Aborts if not a permissioned signer. + + +
fun permission_signer(permissioned: &signer): signer
+
+ + + +
+Implementation + + +
native fun permission_signer(permissioned: &signer): signer;
+
+ + + +
+ + + +## Function `signer_from_permissioned_impl` + + +invariants: +signer::address_of(master) == signer::address_of(signer_from_permissioned(create_permissioned_handle(master))), + + +
fun signer_from_permissioned_impl(master_addr: address, permission_addr: address): signer
+
+ + + +
+Implementation + + +
native fun signer_from_permissioned_impl(master_addr: address, permission_addr: address): signer;
+
+ + + +
+ + + +## Specification + + + +
axiom forall a: GrantedPermissionHandles:
+    (forall i in 0..len(a.active_handles):
+        forall j in 0..len(a.active_handles):
+            i != j ==> a.active_handles[i] != a.active_handles[j]
+    );
+
+ + + + + + + +
fun spec_is_permissioned_signer(s: signer): bool;
+
+ + + + + +### Function `create_permissioned_handle` + + +
public fun create_permissioned_handle(master: &signer): permissioned_signer::PermissionedHandle
+
+ + + + +
pragma opaque;
+aborts_if [abstract] spec_is_permissioned_signer(master);
+let permission_addr = transaction_context::spec_generate_unique_address();
+modifies global<PermStorage>(permission_addr);
+let master_addr = signer::address_of(master);
+ensures result.master_addr == master_addr;
+ensures result.permission_addr == permission_addr;
+
+ + + + + +### Function `create_storable_permissioned_handle` + + +
public(friend) fun create_storable_permissioned_handle(master: &signer, expiration_time: u64): permissioned_signer::StorablePermissionedHandle
+
+ + + + +
pragma opaque;
+aborts_if [abstract] spec_is_permissioned_signer(master);
+let permission_addr = transaction_context::spec_generate_unique_address();
+modifies global<PermStorage>(permission_addr);
+let master_addr = signer::address_of(master);
+modifies global<GrantedPermissionHandles>(master_addr);
+ensures result.master_addr == master_addr;
+ensures result.permission_addr == permission_addr;
+ensures result.expiration_time == expiration_time;
+ensures vector::spec_contains(global<GrantedPermissionHandles>(master_addr).active_handles, permission_addr);
+ensures exists<GrantedPermissionHandles>(master_addr);
+
+ + + + + +### Function `destroy_permissioned_handle` + + +
public fun destroy_permissioned_handle(p: permissioned_signer::PermissionedHandle)
+
+ + + + +
ensures !exists<PermStorage>(p.permission_addr);
+
+ + + + + +### Function `destroy_storable_permissioned_handle` + + +
public fun destroy_storable_permissioned_handle(p: permissioned_signer::StorablePermissionedHandle)
+
+ + + + +
ensures !exists<PermStorage>(p.permission_addr);
+let post granted_permissions = global<GrantedPermissionHandles>(p.master_addr);
+
+ + + + + +### Function `revoke_permission_handle` + + +
public fun revoke_permission_handle(s: &signer, permission_addr: address)
+
+ + + + +
aborts_if spec_is_permissioned_signer(s);
+
+ + + + + +### Function `authorize` + + +
public fun authorize<PermKey: copy, drop, store>(master: &signer, permissioned: &signer, capacity: u256, perm: PermKey)
+
+ + + + +
pragma aborts_if_is_partial;
+aborts_if !spec_is_permissioned_signer(permissioned);
+aborts_if spec_is_permissioned_signer(master);
+aborts_if signer::address_of(permissioned) != signer::address_of(master);
+ensures exists<PermStorage>(signer::address_of(spec_permission_signer(permissioned)));
+
+ + + + + +### Function `check_permission_exists` + + +
public fun check_permission_exists<PermKey: copy, drop, store>(s: &signer, perm: PermKey): bool
+
+ + + + +
pragma opaque;
+aborts_if false;
+ensures result == spec_check_permission_exists(s, perm);
+
+ + + + + + + +
fun spec_check_permission_exists<PermKey: copy + drop + store>(
+   s: signer,
+   perm: PermKey
+): bool {
+   use aptos_std::type_info;
+   use std::bcs;
+   let addr = signer::address_of(spec_permission_signer(s));
+   let key = Any {
+       type_name: type_info::type_name<PermKey>(),
+       data: bcs::serialize(perm)
+   };
+   if (!spec_is_permissioned_signer(s)) {
+       true
+   } else if(!exists<PermStorage>(addr)) {
+       false
+   } else {
+       smart_table::spec_contains(global<PermStorage>(addr).perms, key)
+   }
+}
+
+ + + + + +### Function `check_permission_capacity_above` + + +
public fun check_permission_capacity_above<PermKey: copy, drop, store>(s: &signer, threshold: u256, perm: PermKey): bool
+
+ + + + +
let permissioned_signer_addr = signer::address_of(spec_permission_signer(s));
+ensures !spec_is_permissioned_signer(s) ==> result == true;
+ensures (spec_is_permissioned_signer(s) && !exists<PermStorage>(permissioned_signer_addr)) ==> result == false;
+let key = Any {
+    type_name: type_info::type_name<SmartTable<Any, u256>>(),
+    data: bcs::serialize(perm)
+};
+
+ + + + + +### Function `check_permission_consume` + + +
public fun check_permission_consume<PermKey: copy, drop, store>(s: &signer, threshold: u256, perm: PermKey): bool
+
+ + + + +
let permissioned_signer_addr = signer::address_of(spec_permission_signer(s));
+ensures !spec_is_permissioned_signer(s) ==> result == true;
+ensures (spec_is_permissioned_signer(s) && !exists<PermStorage>(permissioned_signer_addr)) ==> result == false;
+
+ + + + + +### Function `capacity` + + +
public fun capacity<PermKey: copy, drop, store>(s: &signer, perm: PermKey): option::Option<u256>
+
+ + + + +
aborts_if !spec_is_permissioned_signer(s);
+let permissioned_signer_addr = signer::address_of(spec_permission_signer(s));
+ensures !exists<PermStorage>(permissioned_signer_addr) ==> option::is_none(result);
+
+ + + + + +### Function `consume_permission` + + +
public fun consume_permission<PermKey: copy, drop, store>(perm: &mut permissioned_signer::Permission<PermKey>, weight: u256, perm_key: PermKey): bool
+
+ + + + +
ensures perm.key != perm_key ==> result == false;
+ensures perm.key == perm_key && old(perm.capacity) < weight ==> result == false;
+ensures perm.key == perm_key && perm.capacity >= weight ==>
+    (perm.capacity == old(perm.capacity) - weight && result == true);
+
+ + + + + +### Function `is_permissioned_signer` + + +
public fun is_permissioned_signer(s: &signer): bool
+
+ + + + +
pragma opaque;
+aborts_if [abstract] false;
+ensures [abstract] result == spec_is_permissioned_signer(s);
+
+ + + + + + + +
fun spec_permission_signer(s: signer): signer;
+
+ + + + + +### Function `permission_signer` + + +
fun permission_signer(permissioned: &signer): signer
+
+ + + + +
pragma opaque;
+aborts_if [abstract] !spec_is_permissioned_signer(permissioned);
+ensures [abstract] result == spec_permission_signer(permissioned);
+
+ + + + + + + +
fun spec_signer_from_permissioned_impl(master_addr: address, permission_addr: address): signer;
+
+ + + + + +### Function `signer_from_permissioned_impl` + + +
fun signer_from_permissioned_impl(master_addr: address, permission_addr: address): signer
+
+ + + + +
pragma opaque;
+ensures [abstract] result == spec_signer_from_permissioned_impl(master_addr, permission_addr);
+
+ + +[move-book]: https://aptos.dev/move/book/SUMMARY diff --git a/aptos-move/framework/aptos-framework/sources/create_signer.move b/aptos-move/framework/aptos-framework/sources/create_signer.move index 3da0c50c904f02..a92dd888455688 100644 --- a/aptos-move/framework/aptos-framework/sources/create_signer.move +++ b/aptos-move/framework/aptos-framework/sources/create_signer.move @@ -16,6 +16,7 @@ module aptos_framework::create_signer { friend aptos_framework::genesis; friend aptos_framework::multisig_account; friend aptos_framework::object; + friend aptos_framework::permissioned_signer; public(friend) native fun create_signer(addr: address): signer; } diff --git a/aptos-move/framework/aptos-framework/sources/permissioned_signer.move b/aptos-move/framework/aptos-framework/sources/permissioned_signer.move new file mode 100644 index 00000000000000..edc379b760fcd1 --- /dev/null +++ b/aptos-move/framework/aptos-framework/sources/permissioned_signer.move @@ -0,0 +1,413 @@ +/// A _permissioned signer_ consists of a pair of the original signer and a generated +/// signer which is used store information about associated permissions. +/// +/// A permissioned signer behaves compatible with the original signer as it comes to `move_to`, `address_of`, and +/// existing basic signer functionality. However, the permissions can be queried to assert additional +/// restrictions on the use of the signer. +/// +/// A client which is interested in restricting access granted via a signer can create a permissioned signer +/// and pass on to other existing code without changes to existing APIs. Core functions in the framework, for +/// example account functions, can then assert availability of permissions, effectively restricting +/// existing code in a compatible way. +/// +/// After introducing the core functionality, examples are provided for withdraw limit on accounts, and +/// for blind signing. +module aptos_framework::permissioned_signer { + use std::signer; + use std::error; + use std::vector; + use std::option::{Option, Self}; + use aptos_std::copyable_any::{Self, Any}; + use aptos_std::smart_table::{Self, SmartTable}; + use aptos_framework::create_signer::create_signer; + use aptos_framework::transaction_context::generate_auid_address; + use aptos_framework::timestamp; + + #[test_only] + friend aptos_framework::permissioned_signer_tests; + + /// Trying to grant permission using master signer. + const ENOT_MASTER_SIGNER: u64 = 1; + + /// Cannot authorize a permission. + const ECANNOT_AUTHORIZE: u64 = 2; + + /// Access permission information from a master signer. + const ENOT_PERMISSIONED_SIGNER: u64 = 3; + + /// signer doesn't have enough capacity to extract permission. + const ECANNOT_EXTRACT_PERMISSION: u64 = 4; + + /// permission handle has expired. + const E_PERMISSION_EXPIRED: u64 = 5; + + /// storing extracted permission into a different signer. + const E_PERMISSION_MISMATCH: u64 = 6; + + /// permission handle has been revoked by the original signer. + const E_PERMISSION_REVOKED: u64 = 7; + + struct GrantedPermissionHandles has key { + active_handles: vector
, + } + + struct PermissionedHandle { + master_addr: address, + permission_addr: address, + } + + struct StorablePermissionedHandle has store { + master_addr: address, + permission_addr: address, + expiration_time: u64, + } + + struct PermStorage has key { + perms: SmartTable, + } + + struct Permission { + owner_address: address, + key: K, + capacity: u256, + } + + public fun create_permissioned_handle(master: &signer): PermissionedHandle { + assert_master_signer(master); + let permission_addr = generate_auid_address(); + let master_addr = signer::address_of(master); + + move_to(&create_signer(permission_addr), PermStorage { perms: smart_table::new()}); + + PermissionedHandle { + master_addr, + permission_addr, + } + } + + public(friend) fun create_storable_permissioned_handle(master: &signer, expiration_time: u64): StorablePermissionedHandle acquires GrantedPermissionHandles { + assert_master_signer(master); + let permission_addr = generate_auid_address(); + let master_addr = signer::address_of(master); + + if(!exists(master_addr)) { + move_to(master, GrantedPermissionHandles { + active_handles: vector::empty(), + }); + }; + + vector::push_back( + &mut borrow_global_mut(master_addr).active_handles, + permission_addr + ); + + move_to(&create_signer(permission_addr), PermStorage { perms: smart_table::new()}); + + // Do we need to move sth similar to ObjectCore to register this address as permission address? + StorablePermissionedHandle { + master_addr, + permission_addr, + expiration_time, + } + } + + public fun destroy_permissioned_handle(p: PermissionedHandle) acquires PermStorage { + let PermissionedHandle { master_addr: _, permission_addr } = p; + destroy_permission_address(permission_addr); + } + + public fun destroy_storable_permissioned_handle(p: StorablePermissionedHandle) acquires PermStorage, GrantedPermissionHandles { + let StorablePermissionedHandle { master_addr, permission_addr, expiration_time: _ } = p; + destroy_permission_address(permission_addr); + remove_permission_address(master_addr, permission_addr); + } + + inline fun destroy_permission_address(permission_addr: address) acquires PermStorage { + if(exists(permission_addr)) { + let PermStorage { perms } = move_from(permission_addr); + smart_table::destroy(perms); + } + } + + inline fun remove_permission_address(master_addr: address, permission_addr: address) acquires GrantedPermissionHandles { + if(exists(master_addr)) { + let granted_permissions = borrow_global_mut(master_addr); + let (found, idx) = vector::index_of(&granted_permissions.active_handles, &permission_addr); + if(found) { + vector::swap_remove(&mut granted_permissions.active_handles, idx); + }; + }; + } + + public fun signer_from_permissioned(p: &PermissionedHandle): signer { + signer_from_permissioned_impl(p.master_addr, p.permission_addr) + } + + public fun signer_from_storable_permissioned(p: &StorablePermissionedHandle): signer { + assert!( + timestamp::now_seconds() < p.expiration_time, + error::permission_denied(E_PERMISSION_EXPIRED) + ); + assert!( + exists(p.permission_addr), + error::permission_denied(E_PERMISSION_REVOKED) + ); + signer_from_permissioned_impl(p.master_addr, p.permission_addr) + } + + public fun revoke_permission_handle(s: &signer, permission_addr: address) acquires GrantedPermissionHandles, PermStorage { + assert!(!is_permissioned_signer(s), error::permission_denied(ENOT_MASTER_SIGNER)); + let master_addr = signer::address_of(s); + destroy_permission_address(permission_addr); + remove_permission_address(master_addr, permission_addr); + } + + public fun revoke_all_handles(s: &signer) acquires GrantedPermissionHandles, PermStorage { + assert!(!is_permissioned_signer(s), error::permission_denied(ENOT_MASTER_SIGNER)); + let master_addr = signer::address_of(s); + if(!exists(master_addr)) { + return + }; + + let granted_permissions = borrow_global_mut(master_addr); + let delete_list = vector::trim_reverse(&mut granted_permissions.active_handles, 0); + vector::destroy(delete_list, |address| { + destroy_permission_address(address); + }) + } + + public fun permission_address(p: &StorablePermissionedHandle): address { + p.permission_addr + } + + public fun assert_master_signer(s: &signer) { + assert!(!is_permissioned_signer(s), error::permission_denied(ENOT_MASTER_SIGNER)); + } + + /// ===================================================================================================== + /// Permission Management + /// + + /// Authorizes `permissioned` with the given permission. This requires to have access to the `master` + /// signer. + public fun authorize( + master: &signer, + permissioned: &signer, + capacity: u256, + perm: PermKey + ) acquires PermStorage { + assert!( + is_permissioned_signer(permissioned) && + !is_permissioned_signer(master) && + signer::address_of(master) == signer::address_of(permissioned), + error::permission_denied(ECANNOT_AUTHORIZE) + ); + let permission_signer = permission_signer(permissioned); + let permission_signer_addr = signer::address_of(&permission_signer); + let perms = &mut borrow_global_mut(permission_signer_addr).perms; + let key = copyable_any::pack(perm); + if(smart_table::contains(perms, key)) { + let entry = smart_table::borrow_mut(perms, key); + *entry = *entry + capacity; + } else { + smart_table::add(perms, key, capacity); + } + } + + public fun check_permission_exists( + s: &signer, + perm: PermKey + ): bool acquires PermStorage { + if (!is_permissioned_signer(s)) { + // master signer has all permissions + return true + }; + let addr = signer::address_of(&permission_signer(s)); + if(!exists(addr)) { + return false + }; + smart_table::contains(&borrow_global(addr).perms, copyable_any::pack(perm)) + } + + public fun check_permission_capacity_above( + s: &signer, + threshold: u256, + perm: PermKey + ): bool acquires PermStorage { + if (!is_permissioned_signer(s)) { + // master signer has all permissions + return true + }; + let addr = signer::address_of(&permission_signer(s)); + if(!exists(addr)) { + return false + }; + let key = copyable_any::pack(perm); + let storage = &borrow_global(addr).perms; + if(!smart_table::contains(storage, key)) { + return false + }; + let perm = smart_table::borrow(storage, key); + if(*perm > threshold) { + true + } else { + false + } + } + + public fun check_permission_consume( + s: &signer, + threshold: u256, + perm: PermKey + ): bool acquires PermStorage { + if (!is_permissioned_signer(s)) { + // master signer has all permissions + return true + }; + let addr = signer::address_of(&permission_signer(s)); + if(!exists(addr)) { + return false + }; + let key = copyable_any::pack(perm); + let storage = &mut borrow_global_mut(addr).perms; + if(!smart_table::contains(storage, key)) { + return false + }; + let perm = smart_table::borrow_mut(storage, key); + if(*perm >= threshold) { + *perm = *perm - threshold; + true + } else { + false + } + } + + public fun capacity(s: &signer, perm: PermKey): Option acquires PermStorage { + assert!(is_permissioned_signer(s), error::permission_denied(ENOT_PERMISSIONED_SIGNER)); + let addr = signer::address_of(&permission_signer(s)); + if(!exists(addr)) { + return option::none() + }; + let perm_storage = &borrow_global(addr).perms; + let key = copyable_any::pack(perm); + if(smart_table::contains(perm_storage, key)) { + option::some(*smart_table::borrow(&borrow_global(addr).perms, key)) + } else { + option::none() + } + } + + public fun revoke_permission(permissioned: &signer, perm: PermKey) acquires PermStorage { + if(!is_permissioned_signer(permissioned)) { + // Master signer has no permissions associated with it. + return + }; + let addr = signer::address_of(&permission_signer(permissioned)); + if(!exists(addr)) { + return + }; + smart_table::remove(&mut borrow_global_mut(addr).perms, copyable_any::pack(perm)); + } + + /// Another flavor of api to extract and store permissions + public fun extract_permission( + s: &signer, + weight: u256, + perm: PermKey + ): Permission acquires PermStorage { + assert!(check_permission_consume(s, weight, perm), error::permission_denied(ECANNOT_EXTRACT_PERMISSION)); + Permission { + owner_address: signer::address_of(s), + key: perm, + capacity: weight, + } + } + + public fun get_key(perm: &Permission): &PermKey { + &perm.key + } + + public fun address_of(perm: &Permission): address { + perm.owner_address + } + + public fun consume_permission( + perm: &mut Permission, + weight: u256, + perm_key: PermKey + ): bool { + if(perm.key != perm_key) { + return false + }; + if(perm.capacity >= weight) { + perm.capacity = perm.capacity - weight; + return true + } else { + return false + } + } + + public fun store_permission( + s: &signer, + perm: Permission + ) acquires PermStorage { + assert!(is_permissioned_signer(s), error::permission_denied(ENOT_PERMISSIONED_SIGNER)); + let Permission { key, capacity, owner_address } = perm; + + assert!(signer::address_of(s) == owner_address, error::permission_denied(E_PERMISSION_MISMATCH)); + + let permission_signer = permission_signer(s); + let permission_signer_addr = signer::address_of(&permission_signer); + if(!exists(permission_signer_addr)) { + move_to(&permission_signer, PermStorage { perms: smart_table::new()}); + }; + let perms = &mut borrow_global_mut(permission_signer_addr).perms; + let key = copyable_any::pack(key); + if(smart_table::contains(perms, key)) { + let entry = smart_table::borrow_mut(perms, key); + *entry = *entry + capacity; + } else { + smart_table::add(perms, key, capacity) + } + } + + // ===================================================================================================== + // Native Functions + + /// Creates a permissioned signer from an existing universal signer. The function aborts if the + /// given signer is already a permissioned signer. + /// + /// The implementation of this function requires to extend the value representation for signers in the VM. + /// + /// Check whether this is a permissioned signer. + public native fun is_permissioned_signer(s: &signer): bool; + /// Return the signer used for storing permissions. Aborts if not a permissioned signer. + native fun permission_signer(permissioned: &signer): signer; + /// + /// invariants: + /// signer::address_of(master) == signer::address_of(signer_from_permissioned(create_permissioned_handle(master))), + /// + native fun signer_from_permissioned_impl(master_addr: address, permission_addr: address): signer; + + #[test(creator = @0xcafe)] + fun signer_address_roundtrip(creator: &signer) acquires PermStorage, GrantedPermissionHandles { + let aptos_framework = create_signer(@0x1); + timestamp::set_time_has_started_for_testing(&aptos_framework); + + let handle = create_permissioned_handle(creator); + let perm_signer = signer_from_permissioned(&handle); + assert!(signer::address_of(&perm_signer) == signer::address_of(creator), 1); + assert!(signer::address_of(&permission_signer(&perm_signer)) == handle.permission_addr, 1); + assert!(exists(handle.permission_addr), 1); + + destroy_permissioned_handle(handle); + + let handle = create_storable_permissioned_handle(creator, 60); + let perm_signer = signer_from_storable_permissioned(&handle); + assert!(signer::address_of(&perm_signer) == signer::address_of(creator), 1); + assert!(signer::address_of(&permission_signer(&perm_signer)) == handle.permission_addr, 1); + assert!(exists(handle.permission_addr), 1); + + destroy_storable_permissioned_handle(handle); + } +} diff --git a/aptos-move/framework/aptos-framework/tests/permissioned_signer_tests.move b/aptos-move/framework/aptos-framework/tests/permissioned_signer_tests.move new file mode 100644 index 00000000000000..e378084cd2f3f9 --- /dev/null +++ b/aptos-move/framework/aptos-framework/tests/permissioned_signer_tests.move @@ -0,0 +1,201 @@ +#[test_only] +module aptos_framework::permissioned_signer_tests { + use aptos_framework::account::create_signer_for_test; + use aptos_framework::permissioned_signer; + use aptos_framework::timestamp; + use std::option; + use std::signer; + + struct OnePermission has copy, drop, store {} + + struct AddressPermission has copy, drop, store { + addr: address + } + + + #[test(creator = @0xcafe)] + fun test_permission_e2e( + creator: &signer, + ) { + let aptos_framework = create_signer_for_test(@0x1); + timestamp::set_time_has_started_for_testing(&aptos_framework); + + let perm_handle = permissioned_signer::create_permissioned_handle(creator); + let perm_signer = permissioned_signer::signer_from_permissioned(&perm_handle); + + assert!(permissioned_signer::is_permissioned_signer(&perm_signer), 1); + assert!(!permissioned_signer::is_permissioned_signer(creator), 1); + assert!(signer::address_of(&perm_signer) == signer::address_of(creator), 1); + + permissioned_signer::authorize(creator, &perm_signer, 100, OnePermission {}); + assert!(permissioned_signer::capacity(&perm_signer, OnePermission {}) == option::some(100), 1); + + assert!(permissioned_signer::check_permission_consume(&perm_signer, 10, OnePermission {}), 1); + assert!(permissioned_signer::capacity(&perm_signer, OnePermission {}) == option::some(90), 1); + + permissioned_signer::authorize(creator, &perm_signer, 5, AddressPermission { addr: @0x1 }); + + assert!(permissioned_signer::capacity(&perm_signer, AddressPermission { addr: @0x1 }) == option::some(5), 1); + assert!(permissioned_signer::capacity(&perm_signer, AddressPermission { addr: @0x2 }) == option::none(), 1); + + // Not enough capacity, check permission should return false + assert!(!permissioned_signer::check_permission_consume(&perm_signer, 10, AddressPermission { addr: @0x1 }), 1); + + permissioned_signer::revoke_permission(&perm_signer, OnePermission {}); + assert!(permissioned_signer::capacity(&perm_signer, OnePermission {}) == option::none(), 1); + + permissioned_signer::destroy_permissioned_handle(perm_handle); + } + + #[test(creator = @0xcafe)] + fun test_storable_permission_e2e( + creator: &signer, + ) { + let aptos_framework = create_signer_for_test(@0x1); + timestamp::set_time_has_started_for_testing(&aptos_framework); + + let perm_handle = permissioned_signer::create_storable_permissioned_handle(creator, 60); + let perm_signer = permissioned_signer::signer_from_storable_permissioned(&perm_handle); + + assert!(permissioned_signer::is_permissioned_signer(&perm_signer), 1); + assert!(!permissioned_signer::is_permissioned_signer(creator), 1); + assert!(signer::address_of(&perm_signer) == signer::address_of(creator), 1); + + permissioned_signer::authorize(creator, &perm_signer, 100, OnePermission {}); + assert!(permissioned_signer::capacity(&perm_signer, OnePermission {}) == option::some(100), 1); + + assert!(permissioned_signer::check_permission_consume(&perm_signer, 10, OnePermission {}), 1); + assert!(permissioned_signer::capacity(&perm_signer, OnePermission {}) == option::some(90), 1); + + permissioned_signer::authorize(creator, &perm_signer, 5, AddressPermission { addr: @0x1 }); + + assert!(permissioned_signer::capacity(&perm_signer, AddressPermission { addr: @0x1 }) == option::some(5), 1); + assert!(permissioned_signer::capacity(&perm_signer, AddressPermission { addr: @0x2 }) == option::none(), 1); + + // Not enough capacity, check permission should return false + assert!(!permissioned_signer::check_permission_consume(&perm_signer, 10, AddressPermission { addr: @0x1 }), 1); + + permissioned_signer::revoke_permission(&perm_signer, OnePermission {}); + assert!(permissioned_signer::capacity(&perm_signer, OnePermission {}) == option::none(), 1); + + permissioned_signer::destroy_storable_permissioned_handle(perm_handle); + } + + #[test(creator = @0xcafe)] + #[expected_failure(abort_code = 0x50005, location = aptos_framework::permissioned_signer)] + fun test_permission_expiration( + creator: &signer, + ) { + let aptos_framework = create_signer_for_test(@0x1); + timestamp::set_time_has_started_for_testing(&aptos_framework); + + let perm_handle = permissioned_signer::create_storable_permissioned_handle(creator, 60); + let _perm_signer = permissioned_signer::signer_from_storable_permissioned(&perm_handle); + + timestamp::fast_forward_seconds(60); + let _perm_signer = permissioned_signer::signer_from_storable_permissioned(&perm_handle); + + permissioned_signer::destroy_storable_permissioned_handle(perm_handle); + } + + // invalid authorization + // 1. master signer is a permissioned signer + // 2. permissioned signer is a master signer + // 3. permissioned and main signer address mismatch + #[test(creator = @0xcafe)] + #[expected_failure(abort_code = 0x50002, location = aptos_framework::permissioned_signer)] + fun test_auth_1( + creator: &signer, + ) { + let aptos_framework = create_signer_for_test(@0x1); + timestamp::set_time_has_started_for_testing(&aptos_framework); + + let perm_handle = permissioned_signer::create_permissioned_handle(creator); + let perm_signer = permissioned_signer::signer_from_permissioned(&perm_handle); + + permissioned_signer::authorize(&perm_signer, &perm_signer, 100, OnePermission {}); + permissioned_signer::destroy_permissioned_handle(perm_handle); + } + + #[test(creator = @0xcafe)] + #[expected_failure(abort_code = 0x50002, location = aptos_framework::permissioned_signer)] + fun test_auth_2( + creator: &signer, + ) { + permissioned_signer::authorize(creator, creator, 100, OnePermission {}); + } + + #[test(creator = @0xcafe, creator2 = @0xbeef)] + #[expected_failure(abort_code = 0x50002, location = aptos_framework::permissioned_signer)] + fun test_auth_3( + creator: &signer, + creator2: &signer, + ) { + let aptos_framework = create_signer_for_test(@0x1); + timestamp::set_time_has_started_for_testing(&aptos_framework); + + let perm_handle = permissioned_signer::create_permissioned_handle(creator); + let perm_signer = permissioned_signer::signer_from_permissioned(&perm_handle); + + permissioned_signer::authorize(creator2, &perm_signer, 100, OnePermission {}); + permissioned_signer::destroy_permissioned_handle(perm_handle); + } + + // Accessing capacity on a master signer + #[test(creator = @0xcafe)] + #[expected_failure(abort_code = 0x50003, location = aptos_framework::permissioned_signer)] + fun test_invalid_capacity( + creator: &signer, + ) { + permissioned_signer::capacity(creator, OnePermission {}); + } + + // creating permission using a permissioned signer + #[test(creator = @0xcafe)] + #[expected_failure(abort_code = 0x50001, location = aptos_framework::permissioned_signer)] + fun test_invalid_creation( + creator: &signer, + ) { + let aptos_framework = create_signer_for_test(@0x1); + timestamp::set_time_has_started_for_testing(&aptos_framework); + + let perm_handle = permissioned_signer::create_permissioned_handle(creator); + let perm_signer = permissioned_signer::signer_from_permissioned(&perm_handle); + + let perm_handle_2 = permissioned_signer::create_permissioned_handle(&perm_signer); + permissioned_signer::destroy_permissioned_handle(perm_handle); + permissioned_signer::destroy_permissioned_handle(perm_handle_2); + } + + #[test(creator = @0xcafe)] + fun test_permission_revokation_success( + creator: &signer, + ) { + let aptos_framework = create_signer_for_test(@0x1); + timestamp::set_time_has_started_for_testing(&aptos_framework); + + let perm_handle = permissioned_signer::create_storable_permissioned_handle(creator, 60); + let _perm_signer = permissioned_signer::signer_from_storable_permissioned(&perm_handle); + + permissioned_signer::revoke_permission_handle(creator, permissioned_signer::permission_address(&perm_handle)); + + permissioned_signer::destroy_storable_permissioned_handle(perm_handle); + } + + #[test(creator = @0xcafe)] + #[expected_failure(abort_code = 0x50007, location = aptos_framework::permissioned_signer)] + fun test_permission_revokation_and_access( + creator: &signer, + ) { + let aptos_framework = create_signer_for_test(@0x1); + timestamp::set_time_has_started_for_testing(&aptos_framework); + + let perm_handle = permissioned_signer::create_storable_permissioned_handle(creator, 60); + let _perm_signer = permissioned_signer::signer_from_storable_permissioned(&perm_handle); + + permissioned_signer::revoke_permission_handle(creator, permissioned_signer::permission_address(&perm_handle)); + let _perm_signer = permissioned_signer::signer_from_storable_permissioned(&perm_handle); + + permissioned_signer::destroy_storable_permissioned_handle(perm_handle); + } +}