diff --git a/aptos-move/framework/aptos-framework/doc/aptos_account.md b/aptos-move/framework/aptos-framework/doc/aptos_account.md index 4777da7049787..0684910f2d8c4 100644 --- a/aptos-move/framework/aptos-framework/doc/aptos_account.md +++ b/aptos-move/framework/aptos-framework/doc/aptos_account.md @@ -707,6 +707,7 @@ to transfer APT) - if we want to allow APT PFS without account itself // 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) + fungible_asset::withdraw_permission_check_by_address(source, @aptos_fungible_asset, amount); fungible_asset::deposit_internal(recipient_store, fungible_asset::withdraw_internal(sender_store, amount)); } diff --git a/aptos-move/framework/aptos-framework/doc/aptos_coin.md b/aptos-move/framework/aptos-framework/doc/aptos_coin.md index 30f3eae067ed8..3e8e237398f2f 100644 --- a/aptos-move/framework/aptos-framework/doc/aptos_coin.md +++ b/aptos-move/framework/aptos-framework/doc/aptos_coin.md @@ -529,7 +529,8 @@ Claim the delegated mint capability and destroy the delegated token. -
let addr = signer::address_of(aptos_framework);
+aborts_if permissioned_signer::spec_is_permissioned_signer(aptos_framework);
+let addr = signer::address_of(aptos_framework);
aborts_if addr != @aptos_framework;
aborts_if !string::spec_internal_check_utf8(b"Aptos Coin");
aborts_if !string::spec_internal_check_utf8(b"APT");
diff --git a/aptos-move/framework/aptos-framework/doc/coin.md b/aptos-move/framework/aptos-framework/doc/coin.md
index 273f2a2f3e1f0..c8bccab4e87d7 100644
--- a/aptos-move/framework/aptos-framework/doc/coin.md
+++ b/aptos-move/framework/aptos-framework/doc/coin.md
@@ -145,6 +145,7 @@ This module provides the foundation for typesafe Coins.
use 0x1::object;
use 0x1::option;
use 0x1::optional_aggregator;
+use 0x1::permissioned_signer;
use 0x1::primary_fungible_store;
use 0x1::signer;
use 0x1::string;
@@ -2097,6 +2098,7 @@ Voluntarily migrate to fungible store for CoinType
if not yet.
public entry fun migrate_to_fungible_store<CoinType>(
account: &signer
) acquires CoinStore, CoinConversionMap, CoinInfo {
+ permissioned_signer::assert_master_signer(account);
maybe_convert_to_fungible_store<CoinType>(signer::address_of(account));
}
@@ -2953,6 +2955,7 @@ Same as initialize
but supply can be initialized to parallelizable
monitor_supply: bool,
parallelizable: bool,
): (BurnCapability<CoinType>, FreezeCapability<CoinType>, MintCapability<CoinType>) {
+ permissioned_signer::assert_master_signer(account);
let account_addr = signer::address_of(account);
assert!(
@@ -3070,6 +3073,7 @@ Returns minted Coin
.
public fun register<CoinType>(account: &signer) acquires CoinConversionMap {
+ permissioned_signer::assert_master_signer(account);
let account_addr = signer::address_of(account);
// Short-circuit and do nothing if account is already registered for CoinType.
if (is_account_registered<CoinType>(account_addr)) {
@@ -3173,6 +3177,17 @@ Withdraw specified amount
of coin CoinType
from the si
amount
);
let withdrawn_coin = if (coin_amount_to_withdraw > 0) {
+ let metadata = paired_metadata<CoinType>();
+ if(option::is_some(&metadata)) {
+ fungible_asset::withdraw_permission_check_by_address(
+ account,
+ object::object_address(&option::destroy_some(metadata)),
+ coin_amount_to_withdraw
+ );
+ } else {
+ permissioned_signer::assert_master_signer(account);
+ };
+
let coin_store = borrow_global_mut<CoinStore<CoinType>>(account_addr);
assert!(
!coin_store.frozen,
@@ -4162,7 +4177,8 @@ The creator of CoinType
must be @aptos_framework
.
-let account_addr = signer::address_of(account);
+aborts_if permissioned_signer::spec_is_permissioned_signer(account);
+let account_addr = signer::address_of(account);
// This enforces high-level requirement 1:
aborts_if type_info::type_of<CoinType>().account_address != account_addr;
// This enforces high-level requirement 2:
@@ -4184,7 +4200,8 @@ The creator of CoinType
must be @aptos_framework
.
-let addr = signer::address_of(account);
+aborts_if permissioned_signer::spec_is_permissioned_signer(account);
+let addr = signer::address_of(account);
aborts_if addr != @aptos_framework;
aborts_if monitor_supply && !exists<aggregator_factory::AggregatorFactory>(@aptos_framework);
include InitializeInternalSchema<CoinType> {
@@ -4228,7 +4245,8 @@ Only the creator of CoinType
can initialize.
-include InitializeInternalSchema<CoinType> {
+aborts_if permissioned_signer::spec_is_permissioned_signer(account);
+include InitializeInternalSchema<CoinType> {
name: name.bytes,
symbol: symbol.bytes
};
diff --git a/aptos-move/framework/aptos-framework/doc/dispatchable_fungible_asset.md b/aptos-move/framework/aptos-framework/doc/dispatchable_fungible_asset.md
index b3b76f108128c..da71484cc0c00 100644
--- a/aptos-move/framework/aptos-framework/doc/dispatchable_fungible_asset.md
+++ b/aptos-move/framework/aptos-framework/doc/dispatchable_fungible_asset.md
@@ -220,6 +220,7 @@ The semantics of deposit will be governed by the function specified in DispatchF
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!(
diff --git a/aptos-move/framework/aptos-framework/doc/fungible_asset.md b/aptos-move/framework/aptos-framework/doc/fungible_asset.md
index 17ee3056667da..9444aaf1af41b 100644
--- a/aptos-move/framework/aptos-framework/doc/fungible_asset.md
+++ b/aptos-move/framework/aptos-framework/doc/fungible_asset.md
@@ -20,6 +20,7 @@ metadata object can be any object that equipped with use 0x1::function_info;
use 0x1::object;
use 0x1::option;
+use 0x1::permissioned_signer;
use 0x1::signer;
use 0x1::string;
@@ -557,6 +567,33 @@ MutateMetadataRef can be used to directly modify the fungible asset's Metadata.
+
+
+
+
+## Struct `WithdrawPermission`
+
+
+
+struct WithdrawPermission has copy, drop, store
+
+
+
+
+
+Fields
+
+
+
+-
+
metadata_address: address
+
+-
+
+
+
+
+
@@ -1135,6 +1172,16 @@ Provided withdraw function type doesn't meet the signature requirement.
+
+
+signer don't have the permission to perform withdraw operation
+
+
+const EWITHDRAW_PERMISSION_DENIED: u64 = 34;
+
+
+
+
@@ -2675,12 +2722,110 @@ Withdraw amount
of the fungible asset from store
by th
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)
}
+
+
+
+
+## Function `withdraw_with_permission`
+
+
+
+public fun withdraw_with_permission<T: key>(perm: &mut permissioned_signer::Permission<fungible_asset::WithdrawPermission>, store: object::Object<T>, amount: u64): fungible_asset::FungibleAsset
+
+
+
+
+
+Implementation
+
+
+public fun withdraw_with_permission<T: key>(
+ perm: &mut Permission<WithdrawPermission>,
+ store: Object<T>,
+ amount: u64,
+): FungibleAsset acquires FungibleStore, DispatchFunctionStore, ConcurrentFungibleBalance {
+ withdraw_sanity_check_impl(permissioned_signer::address_of(perm), store, true);
+ assert!(
+ permissioned_signer::consume_permission(perm, amount as u256, WithdrawPermission {
+ metadata_address: object::object_address(&borrow_store_resource(&store).metadata)
+ }),
+ error::permission_denied(EWITHDRAW_PERMISSION_DENIED)
+ );
+ withdraw_internal(object::object_address(&store), amount)
+}
+
+
+
+
+
+
+
+
+## Function `withdraw_permission_check`
+
+Check the permission for withdraw operation.
+
+
+public(friend) fun withdraw_permission_check<T: key>(owner: &signer, store: object::Object<T>, amount: u64)
+
+
+
+
+
+Implementation
+
+
+public(friend) fun withdraw_permission_check<T: key>(
+ owner: &signer,
+ store: Object<T>,
+ amount: u64,
+) acquires FungibleStore {
+ assert!(permissioned_signer::check_permission_consume(owner, amount as u256, WithdrawPermission {
+ metadata_address: object::object_address(&borrow_store_resource(&store).metadata)
+ }), error::permission_denied(EWITHDRAW_PERMISSION_DENIED));
+}
+
+
+
+
+
+
+
+
+## Function `withdraw_permission_check_by_address`
+
+Check the permission for withdraw operation.
+
+
+public(friend) fun withdraw_permission_check_by_address(owner: &signer, metadata_address: address, amount: u64)
+
+
+
+
+
+Implementation
+
+
+public(friend) fun withdraw_permission_check_by_address(
+ owner: &signer,
+ metadata_address: address,
+ amount: u64,
+) {
+ assert!(permissioned_signer::check_permission_consume(owner, amount as u256, WithdrawPermission {
+ metadata_address,
+ }), error::permission_denied(EWITHDRAW_PERMISSION_DENIED));
+}
+
+
+
+
@@ -2704,7 +2849,39 @@ Check the permission for withdraw operation.
store: Object<T>,
abort_on_dispatch: bool,
) acquires FungibleStore, DispatchFunctionStore {
- assert!(object::owns(store, signer::address_of(owner)), error::permission_denied(ENOT_STORE_OWNER));
+ withdraw_sanity_check_impl(
+ signer::address_of(owner),
+ store,
+ abort_on_dispatch,
+ )
+}
+
+
+
+
+
+
+
+
+## Function `withdraw_sanity_check_impl`
+
+
+
+fun withdraw_sanity_check_impl<T: key>(owner_address: address, store: object::Object<T>, abort_on_dispatch: bool)
+
+
+
+
+
+Implementation
+
+
+inline fun withdraw_sanity_check_impl<T: key>(
+ owner_address: address,
+ store: Object<T>,
+ abort_on_dispatch: bool,
+) acquires FungibleStore, DispatchFunctionStore {
+ assert!(object::owns(store, owner_address), error::permission_denied(ENOT_STORE_OWNER));
let fa_store = borrow_store_resource(&store);
assert!(
!abort_on_dispatch || !has_withdraw_dispatch_function(fa_store.metadata),
@@ -3682,6 +3859,138 @@ Ensure a known
+
+## Function `grant_permission`
+
+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::Object<fungible_asset::Metadata>, amount: u64)
+
+
+
+
+
+Implementation
+
+
+public fun grant_permission(
+ master: &signer,
+ permissioned: &signer,
+ token_type: Object<Metadata>,
+ amount: u64
+) {
+ permissioned_signer::authorize(
+ master,
+ permissioned,
+ amount as u256,
+ WithdrawPermission {
+ metadata_address: object::object_address(&token_type),
+ }
+ )
+}
+
+
+
+
+
+
+
+
+## Function `grant_apt_permission`
+
+
+
+public fun grant_apt_permission(master: &signer, permissioned: &signer, amount: u64)
+
+
+
+
+
+Implementation
+
+
+public fun grant_apt_permission(
+ master: &signer,
+ permissioned: &signer,
+ amount: u64
+) {
+ permissioned_signer::authorize(
+ master,
+ permissioned,
+ amount as u256,
+ WithdrawPermission { metadata_address: @aptos_fungible_asset }
+ )
+}
+
+
+
+
+
+
+
+
+## Function `refill_permission_with_fa`
+
+
+
+public(friend) fun refill_permission_with_fa(permissioned: &signer, fa: &fungible_asset::FungibleAsset)
+
+
+
+
+
+Implementation
+
+
+public(friend) fun refill_permission_with_fa(
+ permissioned: &signer,
+ fa: &FungibleAsset
+) {
+ permissioned_signer::increase_limit(
+ permissioned,
+ amount(fa) as u256,
+ WithdrawPermission {
+ metadata_address: object::object_address(&metadata_from_asset(fa)),
+ }
+ )
+}
+
+
+
+
+
+
+
+
+## Function `revoke_permission`
+
+Removing permissions from permissioned signer.
+
+
+public fun revoke_permission(permissioned: &signer, token_type: object::Object<fungible_asset::Metadata>)
+
+
+
+
+
+Implementation
+
+
+public fun revoke_permission(permissioned: &signer, token_type: Object<Metadata>) {
+ permissioned_signer::revoke_permission(permissioned, WithdrawPermission {
+ metadata_address: object::object_address(&token_type),
+ })
+}
+
+
+
+
diff --git a/aptos-move/framework/aptos-framework/doc/primary_fungible_store.md b/aptos-move/framework/aptos-framework/doc/primary_fungible_store.md
index 28b1374e4f850..ad5f5312aa431 100644
--- a/aptos-move/framework/aptos-framework/doc/primary_fungible_store.md
+++ b/aptos-move/framework/aptos-framework/doc/primary_fungible_store.md
@@ -33,6 +33,7 @@ fungible asset to it. This emits an deposit event.
- [Function `is_frozen`](#0x1_primary_fungible_store_is_frozen)
- [Function `withdraw`](#0x1_primary_fungible_store_withdraw)
- [Function `deposit`](#0x1_primary_fungible_store_deposit)
+- [Function `deposit_with_signer`](#0x1_primary_fungible_store_deposit_with_signer)
- [Function `force_deposit`](#0x1_primary_fungible_store_force_deposit)
- [Function `transfer`](#0x1_primary_fungible_store_transfer)
- [Function `transfer_assert_minimum_deposit`](#0x1_primary_fungible_store_transfer_assert_minimum_deposit)
@@ -507,6 +508,37 @@ Deposit fungible asset fa
to the given account's primary store.
+
+
+
+
+## Function `deposit_with_signer`
+
+Deposit fungible asset fa
to the given account's primary store using signer.
+
+If owner
is a permissioned signer, the signer will be granted with permission to withdraw
+the same amount of fund in the future.
+
+
+public fun deposit_with_signer(owner: &signer, fa: fungible_asset::FungibleAsset)
+
+
+
+
+
+Implementation
+
+
+public fun deposit_with_signer(owner: &signer, fa: FungibleAsset) acquires DeriveRefPod {
+ fungible_asset::refill_permission_with_fa(owner, &fa);
+ let metadata = fungible_asset::asset_metadata(&fa);
+ let store = ensure_primary_store_exists(signer::address_of(owner), metadata);
+ dispatchable_fungible_asset::deposit(store, fa);
+}
+
+
+
+
diff --git a/aptos-move/framework/aptos-framework/sources/aptos_account.move b/aptos-move/framework/aptos-framework/sources/aptos_account.move
index d18abf2125186..14f4a2708c66a 100644
--- a/aptos-move/framework/aptos-framework/sources/aptos_account.move
+++ b/aptos-move/framework/aptos-framework/sources/aptos_account.move
@@ -247,6 +247,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)
+ fungible_asset::withdraw_permission_check_by_address(source, @aptos_fungible_asset, amount);
fungible_asset::deposit_internal(recipient_store, fungible_asset::withdraw_internal(sender_store, amount));
}
@@ -315,6 +316,28 @@ module aptos_framework::aptos_account {
coin::destroy_mint_cap(mint_cap);
}
+ #[test(alice = @0xa11ce, core = @0x1)]
+ public fun test_transfer_permission(alice: &signer, core: &signer) {
+ use aptos_framework::permissioned_signer;
+
+ let bob = from_bcs::to_address(x"0000000000000000000000000000000000000000000000000000000000000b0b");
+ let carol = from_bcs::to_address(x"00000000000000000000000000000000000000000000000000000000000ca501");
+
+ let (burn_cap, mint_cap) = aptos_framework::aptos_coin::initialize_for_test(core);
+ create_account(signer::address_of(alice));
+ coin::deposit(signer::address_of(alice), coin::mint(10000, &mint_cap));
+
+ let perm_handle = permissioned_signer::create_permissioned_handle(alice);
+ let alice_perm_signer = permissioned_signer::signer_from_permissioned(&perm_handle);
+ fungible_asset::grant_apt_permission(alice, &alice_perm_signer, 500);
+
+ transfer(&alice_perm_signer, bob, 500);
+
+ coin::destroy_burn_cap(burn_cap);
+ coin::destroy_mint_cap(mint_cap);
+ permissioned_signer::destroy_permissioned_handle(perm_handle);
+ }
+
#[test(alice = @0xa11ce, core = @0x1)]
public fun test_transfer_to_resource_account(alice: &signer, core: &signer) {
let (resource_account, _) = account::create_resource_account(alice, vector[]);
diff --git a/aptos-move/framework/aptos-framework/sources/aptos_coin.spec.move b/aptos-move/framework/aptos-framework/sources/aptos_coin.spec.move
index 9983eee23265f..c542427ee211d 100644
--- a/aptos-move/framework/aptos-framework/sources/aptos_coin.spec.move
+++ b/aptos-move/framework/aptos-framework/sources/aptos_coin.spec.move
@@ -36,7 +36,8 @@ spec aptos_framework::aptos_coin {
spec initialize(aptos_framework: &signer): (BurnCapability, MintCapability) {
use aptos_framework::aggregator_factory;
-
+ use aptos_framework::permissioned_signer;
+ aborts_if permissioned_signer::spec_is_permissioned_signer(aptos_framework);
let addr = signer::address_of(aptos_framework);
aborts_if addr != @aptos_framework;
aborts_if !string::spec_internal_check_utf8(b"Aptos Coin");
diff --git a/aptos-move/framework/aptos-framework/sources/coin.move b/aptos-move/framework/aptos-framework/sources/coin.move
index f1d9a81962785..b846c77f1f519 100644
--- a/aptos-move/framework/aptos-framework/sources/coin.move
+++ b/aptos-move/framework/aptos-framework/sources/coin.move
@@ -13,6 +13,7 @@ module aptos_framework::coin {
use aptos_framework::event::{Self, EventHandle};
use aptos_framework::guid;
use aptos_framework::optional_aggregator::{Self, OptionalAggregator};
+ use aptos_framework::permissioned_signer;
use aptos_framework::system_addresses;
use aptos_framework::fungible_asset::{Self, FungibleAsset, Metadata, MintRef, TransferRef, BurnRef};
@@ -611,6 +612,7 @@ module aptos_framework::coin {
public entry fun migrate_to_fungible_store(
account: &signer
) acquires CoinStore, CoinConversionMap, CoinInfo {
+ permissioned_signer::assert_master_signer(account);
maybe_convert_to_fungible_store(signer::address_of(account));
}
@@ -954,6 +956,7 @@ module aptos_framework::coin {
monitor_supply: bool,
parallelizable: bool,
): (BurnCapability, FreezeCapability, MintCapability) {
+ permissioned_signer::assert_master_signer(account);
let account_addr = signer::address_of(account);
assert!(
@@ -1011,6 +1014,7 @@ module aptos_framework::coin {
}
public fun register(account: &signer) acquires CoinConversionMap {
+ permissioned_signer::assert_master_signer(account);
let account_addr = signer::address_of(account);
// Short-circuit and do nothing if account is already registered for CoinType.
if (is_account_registered(account_addr)) {
@@ -1054,6 +1058,17 @@ module aptos_framework::coin {
amount
);
let withdrawn_coin = if (coin_amount_to_withdraw > 0) {
+ let metadata = paired_metadata();
+ if(option::is_some(&metadata)) {
+ fungible_asset::withdraw_permission_check_by_address(
+ account,
+ object::object_address(&option::destroy_some(metadata)),
+ coin_amount_to_withdraw
+ );
+ } else {
+ permissioned_signer::assert_master_signer(account);
+ };
+
let coin_store = borrow_global_mut>(account_addr);
assert!(
!coin_store.frozen,
diff --git a/aptos-move/framework/aptos-framework/sources/dispatchable_fungible_asset.move b/aptos-move/framework/aptos-framework/sources/dispatchable_fungible_asset.move
index 5a70aff95d2c1..37c16214fd879 100644
--- a/aptos-move/framework/aptos-framework/sources/dispatchable_fungible_asset.move
+++ b/aptos-move/framework/aptos-framework/sources/dispatchable_fungible_asset.move
@@ -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!(
diff --git a/aptos-move/framework/aptos-framework/sources/fungible_asset.move b/aptos-move/framework/aptos-framework/sources/fungible_asset.move
index 50a77348c630c..91ed0202482b2 100644
--- a/aptos-move/framework/aptos-framework/sources/fungible_asset.move
+++ b/aptos-move/framework/aptos-framework/sources/fungible_asset.move
@@ -6,6 +6,7 @@ module aptos_framework::fungible_asset {
use aptos_framework::event;
use aptos_framework::function_info::{Self, FunctionInfo};
use aptos_framework::object::{Self, Object, ConstructorRef, DeleteRef, ExtendRef};
+ use aptos_framework::permissioned_signer::{Self, Permission};
use std::string;
use std::features;
@@ -87,7 +88,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
//
@@ -194,6 +196,10 @@ module aptos_framework::fungible_asset {
metadata: Object
}
+ struct WithdrawPermission has copy, drop, store {
+ metadata_address: address,
+ }
+
#[event]
/// Emitted when fungible assets are deposited into a store.
struct Deposit has drop, store {
@@ -785,16 +791,66 @@ 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)
+ }
+
+ public fun withdraw_with_permission(
+ perm: &mut Permission,
+ store: Object,
+ amount: u64,
+ ): FungibleAsset acquires FungibleStore, DispatchFunctionStore, ConcurrentFungibleBalance {
+ withdraw_sanity_check_impl(permissioned_signer::address_of(perm), store, true);
+ assert!(
+ permissioned_signer::consume_permission(perm, amount as u256, WithdrawPermission {
+ metadata_address: object::object_address(&borrow_store_resource(&store).metadata)
+ }),
+ error::permission_denied(EWITHDRAW_PERMISSION_DENIED)
+ );
withdraw_internal(object::object_address(&store), amount)
}
+ /// Check the permission for withdraw operation.
+ public(friend) fun withdraw_permission_check(
+ owner: &signer,
+ store: Object,
+ amount: u64,
+ ) acquires FungibleStore {
+ assert!(permissioned_signer::check_permission_consume(owner, amount as u256, 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(
+ owner: &signer,
+ metadata_address: address,
+ amount: u64,
+ ) {
+ assert!(permissioned_signer::check_permission_consume(owner, amount as u256, WithdrawPermission {
+ metadata_address,
+ }), error::permission_denied(EWITHDRAW_PERMISSION_DENIED));
+ }
+
/// Check the permission for withdraw operation.
public(friend) fun withdraw_sanity_check(
owner: &signer,
store: Object,
abort_on_dispatch: bool,
) acquires FungibleStore, DispatchFunctionStore {
- assert!(object::owns(store, signer::address_of(owner)), error::permission_denied(ENOT_STORE_OWNER));
+ withdraw_sanity_check_impl(
+ signer::address_of(owner),
+ store,
+ abort_on_dispatch,
+ )
+ }
+
+ inline fun withdraw_sanity_check_impl(
+ owner_address: address,
+ store: Object,
+ abort_on_dispatch: bool,
+ ) acquires FungibleStore, DispatchFunctionStore {
+ assert!(object::owns(store, owner_address), error::permission_denied(ENOT_STORE_OWNER));
let fa_store = borrow_store_resource(&store);
assert!(
!abort_on_dispatch || !has_withdraw_dispatch_function(fa_store.metadata),
@@ -1189,6 +1245,58 @@ 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,
+ amount: u64
+ ) {
+ permissioned_signer::authorize(
+ master,
+ permissioned,
+ amount as u256,
+ WithdrawPermission {
+ metadata_address: object::object_address(&token_type),
+ }
+ )
+ }
+
+ public fun grant_apt_permission(
+ master: &signer,
+ permissioned: &signer,
+ amount: u64
+ ) {
+ permissioned_signer::authorize(
+ master,
+ permissioned,
+ amount as u256,
+ WithdrawPermission { metadata_address: @aptos_fungible_asset }
+ )
+ }
+
+ public(friend) fun refill_permission_with_fa(
+ permissioned: &signer,
+ fa: &FungibleAsset
+ ) {
+ permissioned_signer::increase_limit(
+ permissioned,
+ amount(fa) as u256,
+ WithdrawPermission {
+ metadata_address: object::object_address(&metadata_from_asset(fa)),
+ }
+ )
+ }
+
+ /// Removing permissions from permissioned signer.
+ public fun revoke_permission(permissioned: &signer, token_type: Object) {
+ permissioned_signer::revoke_permission(permissioned, WithdrawPermission {
+ metadata_address: object::object_address(&token_type),
+ })
+ }
+
#[test_only]
use aptos_framework::account;
@@ -1244,6 +1352,9 @@ module aptos_framework::fungible_asset {
create_store(&object::create_object_from_account(owner), metadata)
}
+ #[test_only]
+ use aptos_framework::timestamp;
+
#[test(creator = @0xcafe)]
fun test_metadata_basic_flow(creator: &signer) acquires Metadata, Supply, ConcurrentSupply {
let (creator_ref, metadata) = create_test_token(creator);
@@ -1648,6 +1759,50 @@ module aptos_framework::fungible_asset {
assert!(aggregator_v2::read(&borrow_global(object::object_address(&creator_store)).balance) == 30, 12);
}
+ #[test(creator = @0xcafe, aaron = @0xface)]
+ fun test_e2e_withdraw_limit(
+ creator: &signer,
+ aaron: &signer,
+ ) acquires FungibleStore, Supply, ConcurrentSupply, DispatchFunctionStore, ConcurrentFungibleBalance {
+ let aptos_framework = account::create_signer_for_test(@0x1);
+ timestamp::set_time_has_started_for_testing(&aptos_framework);
+
+ let (mint_ref, _, _, _, test_token) = create_fungible_asset(creator);
+ let metadata = mint_ref.metadata;
+ let creator_store = create_test_store(creator, metadata);
+ let aaron_store = create_test_store(aaron, metadata);
+
+ assert!(supply(test_token) == option::some(0), 1);
+ // Mint
+ let fa = mint(&mint_ref, 100);
+ assert!(supply(test_token) == option::some(100), 2);
+ // Deposit
+ deposit(creator_store, fa);
+ // Withdraw
+ let fa = withdraw(creator, creator_store, 80);
+ assert!(supply(test_token) == option::some(100), 3);
+ deposit(aaron_store, fa);
+
+ // Create a permissioned signer
+ let aaron_permission_handle = permissioned_signer::create_permissioned_handle(aaron);
+ let aaron_permission_signer = permissioned_signer::signer_from_permissioned_handle(&aaron_permission_handle);
+
+ // Grant aaron_permission_signer permission to withdraw 10 apt
+ grant_permission(aaron, &aaron_permission_signer, metadata, 10);
+
+ let fa = withdraw(&aaron_permission_signer, aaron_store, 5);
+ deposit(aaron_store, fa);
+
+ let fa = withdraw(&aaron_permission_signer, aaron_store, 5);
+ deposit(aaron_store, fa);
+
+ // aaron signer don't abide to the same limit
+ let fa = withdraw(aaron, aaron_store, 5);
+ deposit(aaron_store, fa);
+
+ permissioned_signer::destroy_permissioned_handle(aaron_permission_handle);
+ }
+
#[deprecated]
#[resource_group_member(group = aptos_framework::object::ObjectGroup)]
struct FungibleAssetEvents has key {
diff --git a/aptos-move/framework/aptos-framework/sources/primary_fungible_store.move b/aptos-move/framework/aptos-framework/sources/primary_fungible_store.move
index 9e39b97fa2854..19362bf1cd02b 100644
--- a/aptos-move/framework/aptos-framework/sources/primary_fungible_store.move
+++ b/aptos-move/framework/aptos-framework/sources/primary_fungible_store.move
@@ -168,6 +168,17 @@ module aptos_framework::primary_fungible_store {
dispatchable_fungible_asset::deposit(store, fa);
}
+ /// Deposit fungible asset `fa` to the given account's primary store using signer.
+ ///
+ /// If `owner` is a permissioned signer, the signer will be granted with permission to withdraw
+ /// the same amount of fund in the future.
+ public fun deposit_with_signer(owner: &signer, fa: FungibleAsset) acquires DeriveRefPod {
+ fungible_asset::refill_permission_with_fa(owner, &fa);
+ let metadata = fungible_asset::asset_metadata(&fa);
+ let store = ensure_primary_store_exists(signer::address_of(owner), metadata);
+ dispatchable_fungible_asset::deposit(store, fa);
+ }
+
/// Deposit fungible asset `fa` to the given account's primary store.
public(friend) fun force_deposit(owner: address, fa: FungibleAsset) acquires DeriveRefPod {
let metadata = fungible_asset::asset_metadata(&fa);