diff --git a/aptos-move/framework/aptos-framework/doc/aptos_account.md b/aptos-move/framework/aptos-framework/doc/aptos_account.md
index 33cd0ff87e5a13..6b4c97226eece0 100644
--- a/aptos-move/framework/aptos-framework/doc/aptos_account.md
+++ b/aptos-move/framework/aptos-framework/doc/aptos_account.md
@@ -605,6 +605,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/coin.md b/aptos-move/framework/aptos-framework/doc/coin.md
index 07e97171f99b19..93c6b94cdc8f5f 100644
--- a/aptos-move/framework/aptos-framework/doc/coin.md
+++ b/aptos-move/framework/aptos-framework/doc/coin.md
@@ -158,6 +158,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;
@@ -3423,6 +3424,13 @@ Withdraw specified amount
of coin CoinType
from the si
): Coin<CoinType> acquires CoinStore, CoinConversionMap, CoinInfo, PairedCoinType {
let account_addr = signer::address_of(account);
+ 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)), amount);
+ } else {
+ permissioned_signer::assert_master_signer(account);
+ };
+
let (coin_amount_to_withdraw, fa_amount_to_withdraw) = calculate_amount_to_withdraw<CoinType>(
account_addr,
amount
@@ -3814,64 +3822,6 @@ initialize, initialize_internal, initialize_with_parallelizable_supply;
-
-
-
-
-
fun spec_is_account_registered<CoinType>(account_addr: address): bool {
- let paired_metadata_opt = spec_paired_metadata<CoinType>();
- exists<CoinStore<CoinType>>(account_addr) || (option::spec_is_some(
- paired_metadata_opt
- ) && primary_fungible_store::spec_primary_store_exists(account_addr, option::spec_borrow(paired_metadata_opt)))
-}
-
-
-
-
-
-
-
-
-schema CoinSubAbortsIf<CoinType> {
- amount: u64;
- let addr = type_info::type_of<CoinType>().account_address;
- let maybe_supply = global<CoinInfo<CoinType>>(addr).supply;
- include (option::is_some(
- maybe_supply
- )) ==> optional_aggregator::SubAbortsIf { optional_aggregator: option::borrow(maybe_supply), value: amount };
-}
-
-
-
-
-
-
-
-
-schema CoinAddAbortsIf<CoinType> {
- amount: u64;
- let addr = type_info::type_of<CoinType>().account_address;
- let maybe_supply = global<CoinInfo<CoinType>>(addr).supply;
- include (option::is_some(
- maybe_supply
- )) ==> optional_aggregator::AddAbortsIf { optional_aggregator: option::borrow(maybe_supply), value: amount };
-}
-
-
-
-
-
-
-
-
-schema AbortsIfNotExistCoinInfo<CoinType> {
- let addr = type_info::type_of<CoinType>().account_address;
- aborts_if !exists<CoinInfo<CoinType>>(addr);
-}
-
-
-
-
### Struct `AggregatableCoin`
@@ -4215,6 +4165,64 @@ Get address by reflection.
+
+
+
+
+fun spec_is_account_registered<CoinType>(account_addr: address): bool {
+ let paired_metadata_opt = spec_paired_metadata<CoinType>();
+ exists<CoinStore<CoinType>>(account_addr) || (option::spec_is_some(
+ paired_metadata_opt
+ ) && primary_fungible_store::spec_primary_store_exists(account_addr, option::spec_borrow(paired_metadata_opt)))
+}
+
+
+
+
+
+
+
+
+schema CoinSubAbortsIf<CoinType> {
+ amount: u64;
+ let addr = type_info::type_of<CoinType>().account_address;
+ let maybe_supply = global<CoinInfo<CoinType>>(addr).supply;
+ include (option::is_some(
+ maybe_supply
+ )) ==> optional_aggregator::SubAbortsIf { optional_aggregator: option::borrow(maybe_supply), value: amount };
+}
+
+
+
+
+
+
+
+
+schema CoinAddAbortsIf<CoinType> {
+ amount: u64;
+ let addr = type_info::type_of<CoinType>().account_address;
+ let maybe_supply = global<CoinInfo<CoinType>>(addr).supply;
+ include (option::is_some(
+ maybe_supply
+ )) ==> optional_aggregator::AddAbortsIf { optional_aggregator: option::borrow(maybe_supply), value: amount };
+}
+
+
+
+
+
+
+
+
+schema AbortsIfNotExistCoinInfo<CoinType> {
+ let addr = type_info::type_of<CoinType>().account_address;
+ aborts_if !exists<CoinInfo<CoinType>>(addr);
+}
+
+
+
+
### Function `name`
@@ -4595,27 +4603,6 @@ The creator of CoinType
must be @aptos_framework
.
-Make sure name
and symbol
are legal length.
-Only the creator of CoinType
can initialize.
-
-
-
-
-
-schema InitializeInternalSchema<CoinType> {
- account: signer;
- name: vector<u8>;
- symbol: vector<u8>;
- let account_addr = signer::address_of(account);
- let coin_address = type_info::type_of<CoinType>().account_address;
- aborts_if coin_address != account_addr;
- aborts_if exists<CoinInfo<CoinType>>(account_addr);
- aborts_if len(name) > MAX_COIN_NAME_LENGTH;
- aborts_if len(symbol) > MAX_COIN_SYMBOL_LENGTH;
-}
-
-
-
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 b3b76f108128ca..da71484cc0c008 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 f12ee14942b4f4..b3a42cd2297ba0 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 +563,33 @@ MutateMetadataRef can be used to directly modify the fungible asset's Metadata.
+
+
+
+
+## Struct `WithdrawPermission`
+
+
+
+struct WithdrawPermission has copy, drop, store
+
+
+
+
+metadata_address: address
+const EWITHDRAW_PERMISSION_DENIED: u64 = 34;
+
+
+
+
@@ -2675,12 +2718,75 @@ 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_permission_check`
+
+Check the permission for withdraw operation.
+
+
+public(friend) fun withdraw_permission_check<T: key>(owner: &signer, store: object::Object<T>, amount: u64)
+
+
+
+
+public(friend) fun withdraw_permission_check<T: key>(
+ owner: &signer,
+ store: Object<T>,
+ amount: u64,
+) acquires FungibleStore {
+ assert!(permissioned_signer::check_permission(owner, amount as u256, WithdrawPermission {
+ metadata_address: object::object_address(&borrow_store_resource(&store).metadata)
+ }), error::permission_denied(EWITHDRAW_PERMISSION_DENIED));
+}
+
+
+
+
+public(friend) fun withdraw_permission_check_by_address(owner: &signer, metadata_address: address, amount: u64)
+
+
+
+
+public(friend) fun withdraw_permission_check_by_address(
+ owner: &signer,
+ metadata_address: address,
+ amount: u64,
+) {
+ assert!(permissioned_signer::check_permission(owner, amount as u256, WithdrawPermission {
+ metadata_address,
+ }), error::permission_denied(EWITHDRAW_PERMISSION_DENIED));
+}
+
+
+
+
+
+## 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 `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/sources/aptos_account.move b/aptos-move/framework/aptos-framework/sources/aptos_account.move
index 34addca77cf286..e13a84be4772f7 100644
--- a/aptos-move/framework/aptos-framework/sources/aptos_account.move
+++ b/aptos-move/framework/aptos-framework/sources/aptos_account.move
@@ -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)
+ 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/sources/coin.move b/aptos-move/framework/aptos-framework/sources/coin.move
index 91a54edb7fdddb..b134d36d8c6667 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};
@@ -1168,11 +1169,29 @@ module aptos_framework::coin {
): Coin acquires CoinStore, CoinConversionMap, CoinInfo, PairedCoinType {
let account_addr = signer::address_of(account);
+ let metadata = paired_metadata();
+ if(option::is_some(&metadata)) {
+ fungible_asset::withdraw_permission_check_by_address(account, object::object_address(&option::destroy_some(metadata)), amount);
+ } else {
+ permissioned_signer::assert_master_signer(account);
+ };
+
let (coin_amount_to_withdraw, fa_amount_to_withdraw) = calculate_amount_to_withdraw(
account_addr,
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 5a70aff95d2c11..37c16214fd8795 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 946d7b05eb415c..8a72c88ba1a2f0 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;
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,9 +791,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(
+ owner: &signer,
+ store: Object,
+ amount: u64,
+ ) acquires FungibleStore {
+ assert!(permissioned_signer::check_permission(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(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,
@@ -1179,6 +1208,32 @@ 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),
+ }
+ )
+ }
+
+ /// 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;
@@ -1234,6 +1289,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);
@@ -1541,6 +1599,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(&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 {