Skip to content

Commit

Permalink
create permissioned signer example
Browse files Browse the repository at this point in the history
  • Loading branch information
runtian-zhou committed Sep 3, 2024
1 parent 5eb0f1d commit 887d7a2
Show file tree
Hide file tree
Showing 4 changed files with 183 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@ module aptos_framework::aptos_account {
// as APT cannot be frozen or have dispatch, and PFS cannot be transfered
// (PFS could potentially be burned. regular transfer would permanently unburn the store.
// Ignoring the check here has the equivalent of unburning, transfers, and then burning again)
assert!(fungible_asset::withdraw_permission_check_by_address(source, @aptos_fungible_asset, amount), error::permission_denied(ENO_WITHDRAW_PERMISSION));
fungible_asset::deposit_internal(recipient_store, fungible_asset::withdraw_internal(sender_store, amount));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ module aptos_framework::dispatchable_fungible_asset {
amount: u64,
): FungibleAsset acquires TransferRefStore {
fungible_asset::withdraw_sanity_check(owner, store, false);
fungible_asset::withdraw_permission_check(owner, store, amount);
let func_opt = fungible_asset::withdraw_dispatch_function(store);
if (option::is_some(&func_opt)) {
assert!(
Expand Down
51 changes: 50 additions & 1 deletion aptos-move/framework/aptos-framework/sources/fungible_asset.move
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,8 @@ module aptos_framework::fungible_asset {
const ECONCURRENT_BALANCE_NOT_ENABLED: u64 = 32;
/// Provided derived_supply function type doesn't meet the signature requirement.
const EDERIVED_SUPPLY_FUNCTION_SIGNATURE_MISMATCH: u64 = 33;

/// signer don't have the permission to perform withdraw operation
const EWITHDRAW_PERMISSION_DENIED: u64 = 34;
//
// Constants
//
Expand Down Expand Up @@ -194,6 +195,10 @@ module aptos_framework::fungible_asset {
metadata: Object<Metadata>
}

struct WithdrawPermission has copy, drop, store {
metadata_address: address,
}

#[event]
/// Emitted when fungible assets are deposited into a store.
struct Deposit has drop, store {
Expand Down Expand Up @@ -785,9 +790,32 @@ module aptos_framework::fungible_asset {
amount: u64,
): FungibleAsset acquires FungibleStore, DispatchFunctionStore, ConcurrentFungibleBalance {
withdraw_sanity_check(owner, store, true);
withdraw_permission_check(owner, store, amount);
withdraw_internal(object::object_address(&store), amount)
}

/// Check the permission for withdraw operation.
public(friend) fun withdraw_permission_check<T: key>(
owner: &signer,
store: Object<T>,
amount: u64,
) {
assert!(permissioned_signer::check_permission(owner, amount, WithdrawPermission {
metadata_address: object::object_address(&borrow_store_resource(store).metadata)
}), error::permission_denied(EWITHDRAW_PERMISSION_DENIED));
}

/// Check the permission for withdraw operation.
public(friend) fun withdraw_permission_check_by_address<T: key>(
owner: &signer,
metadata_address: address,
amount: u64,
) {
assert!(permissioned_signer::check_permission(owner, amount, WithdrawPermission {
metadata_address,
}), error::permission_denied(EWITHDRAW_PERMISSION_DENIED));
}

/// Check the permission for withdraw operation.
public(friend) fun withdraw_sanity_check<T: key>(
owner: &signer,
Expand Down Expand Up @@ -1179,6 +1207,27 @@ module aptos_framework::fungible_asset {
move_to(&object_signer, ConcurrentFungibleBalance { balance });
}

/// Permission management
///
/// Master signer grant permissioned signer ability to withdraw a given amount of fungible asset.
public fun grant_permission(
master: &signer,
permissioned: &signer,
token_type: Object<Metadata>,
amount: u64
) {
permissioned_signer::authorize(permissioned, amount, master, WithdrawPermission {
metadata_address: object::object_of(token_type),
})
}

/// Removing permissions from permissioned signer.
public fun revoke_permission(permissioned: &signer, token_type: Object<Metadata>) {
permissioned_signer::revoke_permission(permissioned, WithdrawPermission {
metadata_address: object::object_of(token_type),
})
}

#[test_only]
use aptos_framework::account;

Expand Down
131 changes: 131 additions & 0 deletions aptos-move/framework/aptos-framework/sources/permissioned_signer.move
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
/// 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::address_of;
use std::error;
use std::string::String;
use aptos_std::copyable_any::{Self, Any};
use aptos_std::simple_map::{Self, SimpleMap};
use aptos_std::smart_table::{Self, SmartTable};
use aptos_std::type_info::type_name;
use aptos_framework::transaction_context::generate_auid_address;


/// Trying to grant permission using master signer.
const ENOT_MASTER_SIGNER: u64 = 1;

/// Cannot authorize a permission.
const ECANNOT_AUTHORIZE: u64 = 2;

struct PermissionedHandle has store {
master_addr: address,
permission_addr: address,
}


// Do we define a resource group similar to ObjectGroup so that all permission objects can be stored in the same slot?

public fun create_permissioned_signer(master: &signer): PermissionedHandle {
assert!(!is_permissioned_signer(master), error::permission_denied(ENOT_MASTER_SIGNER));
// Do we need to move sth similar to ObjectCore to register this address as permission address?
PermissionedHandle {
master_addr: address_of(master),
permission_addr: generate_auid_address(),
}
}

public fun destroy_permissioned_signer(p: PermissionedHandle) acquires PermStorage {
let PermissionedHandle { master_addr: _, permission_addr } = p;
let PermStorage { perms } = move_from<PermStorage>(permission_addr);
smart_table::destroy(perms);
}

/// =====================================================================================================
/// Permission Management
///
struct PermStorage has key {
perms: SmartTable<Any, u256>,
}

/// Authorizes `permissioned` with the given permission. This requires to have access to the `master`
/// signer.
public fun authorize<PermKey: copy + drop + store>(permissioned: &signer, capacity: u256, master: &signer, perm: PermKey) acquires PermStorage {
assert!(
is_permissioned_signer(permissioned) &&
!is_permissioned_signer(master) &&
address_of(master) == address_of(permissioned),
error::permission_denied(ECANNOT_AUTHORIZE)
);
let permission_signer = permission_signer(permissioned);
let permission_signer_addr = 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;
smart_table::add(perms, copyable_any::pack(perm), capacity);
}
/// Asserts that the given signer has permission `PermKey`, and the capacity
/// to handle `weight`, which will be subtracted from capacity.
public fun check_permission<PermKey: copy + drop + store>(s: &signer, weight: u256, perm: PermKey): bool acquires PermStorage {
if (!is_permissioned_signer(s)) {
// master signer has all permissions
return true
};
let addr = address_of(permission_signer(s));
if(!exists<PermStorage>(addr)) {
return false
};
let perm = smart_table::borrow_mut(&mut borrow_global_mut<PermStorage>(addr).perms, copyable_any::pack(perm));
if(*perm < weight) {
return false
};
*perm = *perm - weight;
return true
}

public fun capacity<PermKey: copy + drop + store>(s: &signer, perm: PermKey): u256 acquires PermStorage {
*smart_table::borrow(&borrow_global<PermStorage>(address_of(permission_signer(s))).perms, copyable_any::pack(perm))
}

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 = address_of(permission_signer(permissioned));
if(!exists<PermStorage>(addr)) {
return;
};
smart_table::remove(&mut borrow_global_mut<PermStorage>(addr).perms, copyable_any::pack(perm));
}

// =====================================================================================================
// 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.
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:
/// address_of(master) == address_of(signer_from_permissioned(create_permissioned_signer(master))),
///
public native fun signer_from_permissioned(p: &PermissionedHandle): signer;
}

0 comments on commit 887d7a2

Please sign in to comment.