diff --git a/aptos-move/framework/aptos-framework/doc/aptos_account.md b/aptos-move/framework/aptos-framework/doc/aptos_account.md index ca0c0c6d96600..d3be0dbd22b85 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, sender_store, amount); fungible_asset::unchecked_deposit(recipient_store, fungible_asset::unchecked_withdraw(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..af9bed9d9ba9f 100644 --- a/aptos-move/framework/aptos-framework/doc/aptos_coin.md +++ b/aptos-move/framework/aptos-framework/doc/aptos_coin.md @@ -513,7 +513,7 @@ Claim the delegated mint capability and destroy the delegated token.
pragma verify = true;
-pragma aborts_if_is_strict;
+pragma aborts_if_is_partial;
@@ -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 4c82eabb2d92b..eb1ed430767fd 100644
--- a/aptos-move/framework/aptos-framework/doc/coin.md
+++ b/aptos-move/framework/aptos-framework/doc/coin.md
@@ -57,6 +57,7 @@ This module provides the foundation for typesafe Coins.
- [Function `allow_supply_upgrades`](#0x1_coin_allow_supply_upgrades)
- [Function `calculate_amount_to_withdraw`](#0x1_coin_calculate_amount_to_withdraw)
- [Function `maybe_convert_to_fungible_store`](#0x1_coin_maybe_convert_to_fungible_store)
+- [Function `assert_signer_has_permission`](#0x1_coin_assert_signer_has_permission)
- [Function `migrate_to_fungible_store`](#0x1_coin_migrate_to_fungible_store)
- [Function `migrate_coin_store_to_fungible_store`](#0x1_coin_migrate_coin_store_to_fungible_store)
- [Function `coin_address`](#0x1_coin_coin_address)
@@ -74,6 +75,7 @@ This module provides the foundation for typesafe Coins.
- [Function `burn`](#0x1_coin_burn)
- [Function `burn_from`](#0x1_coin_burn_from)
- [Function `deposit`](#0x1_coin_deposit)
+- [Function `deposit_with_signer`](#0x1_coin_deposit_with_signer)
- [Function `can_receive_paired_fungible_asset`](#0x1_coin_can_receive_paired_fungible_asset)
- [Function `force_deposit`](#0x1_coin_force_deposit)
- [Function `destroy_zero`](#0x1_coin_destroy_zero)
@@ -147,6 +149,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;
@@ -2127,6 +2130,39 @@ or disallow upgradability of total supply.
+
+
+
+
+## Function `assert_signer_has_permission`
+
+
+
+fun assert_signer_has_permission<CoinType>(account: &signer)
+
+
+
+
+
+Implementation
+
+
+inline fun assert_signer_has_permission<CoinType>(account: &signer) {
+ if(permissioned_signer::is_permissioned_signer(account)) {
+ fungible_asset::withdraw_permission_check_by_address(
+ account,
+ primary_fungible_store::primary_store_address(
+ signer::address_of(account),
+ ensure_paired_metadata<CoinType>()
+ ),
+ 0
+ );
+ }
+}
+
+
+
+
@@ -2148,7 +2184,9 @@ Voluntarily migrate to fungible store for CoinType
if not yet.
public entry fun migrate_to_fungible_store<CoinType>(
account: &signer
) acquires CoinStore, CoinConversionMap, CoinInfo {
- maybe_convert_to_fungible_store<CoinType>(signer::address_of(account));
+ let account_addr = signer::address_of(account);
+ assert_signer_has_permission<CoinType>(account);
+ maybe_convert_to_fungible_store<CoinType>(account_addr);
}
@@ -2686,6 +2724,43 @@ Deposit the coin balance into the recipient's account and emit an event.
+
+
+
+
+## Function `deposit_with_signer`
+
+
+
+public fun deposit_with_signer<CoinType>(account: &signer, coin: coin::Coin<CoinType>)
+
+
+
+
+
+Implementation
+
+
+public fun deposit_with_signer<CoinType>(
+ account: &signer,
+ coin: Coin<CoinType>
+) acquires CoinStore, CoinConversionMap, CoinInfo {
+ let metadata = ensure_paired_metadata<CoinType>();
+ let account_address = signer::address_of(account);
+ fungible_asset::refill_permission(
+ account,
+ coin.value,
+ primary_fungible_store::primary_store_address_inlined(
+ account_address,
+ metadata,
+ )
+ );
+ deposit(account_address, coin);
+}
+
+
+
+
@@ -2973,7 +3048,7 @@ The given signer also becomes the account hosting the information about the coi
symbol: string::String,
decimals: u8,
monitor_supply: bool,
-): (BurnCapability<CoinType>, FreezeCapability<CoinType>, MintCapability<CoinType>) {
+): (BurnCapability<CoinType>, FreezeCapability<CoinType>, MintCapability<CoinType>) acquires CoinInfo, CoinConversionMap {
initialize_internal(account, name, symbol, decimals, monitor_supply, false)
}
@@ -3004,7 +3079,7 @@ Same as initialize
but supply can be initialized to parallelizable
symbol: string::String,
decimals: u8,
monitor_supply: bool,
-): (BurnCapability<CoinType>, FreezeCapability<CoinType>, MintCapability<CoinType>) {
+): (BurnCapability<CoinType>, FreezeCapability<CoinType>, MintCapability<CoinType>) acquires CoinInfo, CoinConversionMap {
system_addresses::assert_aptos_framework(account);
initialize_internal(account, name, symbol, decimals, monitor_supply, true)
}
@@ -3036,8 +3111,9 @@ Same as initialize
but supply can be initialized to parallelizable
decimals: u8,
monitor_supply: bool,
parallelizable: bool,
-): (BurnCapability<CoinType>, FreezeCapability<CoinType>, MintCapability<CoinType>) {
+): (BurnCapability<CoinType>, FreezeCapability<CoinType>, MintCapability<CoinType>) acquires CoinInfo, CoinConversionMap {
let account_addr = signer::address_of(account);
+ assert_signer_has_permission<CoinType>(account);
assert!(
coin_address<CoinType>() == account_addr,
@@ -3153,8 +3229,9 @@ Returns minted Coin
.
Implementation
-public fun register<CoinType>(account: &signer) acquires CoinConversionMap {
+public fun register<CoinType>(account: &signer) acquires CoinInfo, CoinConversionMap {
let account_addr = signer::address_of(account);
+ assert_signer_has_permission<CoinType>(account);
// Short-circuit and do nothing if account is already registered for CoinType.
if (is_account_registered<CoinType>(account_addr)) {
return
@@ -3257,6 +3334,17 @@ Withdraw specified amount
of coin CoinType
from the si
amount
);
let withdrawn_coin = if (coin_amount_to_withdraw > 0) {
+ let metadata = ensure_paired_metadata<CoinType>();
+ if(permissioned_signer::is_permissioned_signer(account)) {
+ // Perform the check only if the account is a permissioned signer to save the cost of
+ // computing the primary store location.
+ fungible_asset::withdraw_permission_check_by_address(
+ account,
+ primary_fungible_store::primary_store_address(account_addr, metadata),
+ coin_amount_to_withdraw
+ );
+ };
+
let coin_store = borrow_global_mut<CoinStore<CoinType>>(account_addr);
assert!(
!coin_store.frozen,
@@ -3581,6 +3669,7 @@ Destroy a burn capability.
pragma verify = true;
+pragma aborts_if_is_partial;
global supply<CoinType>: num;
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 944c283025880..bfe682fe63ce4 100644
--- a/aptos-move/framework/aptos-framework/doc/dispatchable_fungible_asset.md
+++ b/aptos-move/framework/aptos-framework/doc/dispatchable_fungible_asset.md
@@ -221,6 +221,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 af27435844e3d..9b9cfc806050a 100644
--- a/aptos-move/framework/aptos-framework/doc/fungible_asset.md
+++ b/aptos-move/framework/aptos-framework/doc/fungible_asset.md
@@ -22,6 +22,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;
@@ -626,6 +635,45 @@ MutateMetadataRef can be used to directly modify the fungible asset's Metadata.
+
+
+
+
+## Enum `WithdrawPermission`
+
+
+
+enum WithdrawPermission has copy, drop, store
+
+
+
+
+
+Variants
+
+
+
+ByStore
+
+
+
+Fields
+
+
+
+-
+
store_address: address
+
+-
+
+
+
+
+
+
+
+
+
@@ -1139,7 +1187,7 @@ The balance ref and the fungible asset do not match.
The supply ref and the fungible asset do not match.
-const ERAW_SUPPLY_REF_AND_FUNGIBLE_ASSET_MISMATCH: u64 = 34;
+const ERAW_SUPPLY_REF_AND_FUNGIBLE_ASSET_MISMATCH: u64 = 35;
@@ -1224,6 +1272,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 = 36;
+
+
+
+
@@ -2948,12 +3006,75 @@ that function unless you DO NOT want to support fungible assets with dispatchabl
amount: u64,
): FungibleAsset acquires FungibleStore, DispatchFunctionStore, ConcurrentFungibleBalance {
withdraw_sanity_check(owner, store, true);
+ withdraw_permission_check(owner, store, amount);
unchecked_withdraw(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,
+) {
+ assert!(permissioned_signer::check_permission_consume(owner, amount as u256, WithdrawPermission::ByStore {
+ store_address: object::object_address(&store),
+ }), 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, store_address: address, amount: u64)
+
+
+
+
+
+Implementation
+
+
+public(friend) fun withdraw_permission_check_by_address(
+ owner: &signer,
+ store_address: address,
+ amount: u64,
+) {
+ assert!(permissioned_signer::check_permission_consume(owner, amount as u256, WithdrawPermission::ByStore {
+ store_address,
+ }), error::permission_denied(EWITHDRAW_PERMISSION_DENIED));
+}
+
+
+
+
@@ -2977,7 +3098,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),
@@ -4112,6 +4265,138 @@ Ensure a known
+
+## Function `grant_permission_by_store`
+
+Permission management
+
+Master signer grant permissioned signer ability to withdraw a given amount of fungible asset.
+
+
+public fun grant_permission_by_store<T: key>(master: &signer, permissioned: &signer, store: object::Object<T>, amount: u64)
+
+
+
+
+
+Implementation
+
+
+public fun grant_permission_by_store<T: key>(
+ master: &signer,
+ permissioned: &signer,
+ store: Object<T>,
+ amount: u64
+) {
+ permissioned_signer::authorize_increase(
+ master,
+ permissioned,
+ amount as u256,
+ WithdrawPermission::ByStore {
+ store_address: object::object_address(&store),
+ }
+ )
+}
+
+
+
+
+
+
+
+
+## Function `grant_permission_by_address`
+
+
+
+public(friend) fun grant_permission_by_address(master: &signer, permissioned: &signer, store_address: address, amount: u64)
+
+
+
+
+
+Implementation
+
+
+public(friend) fun grant_permission_by_address(
+ master: &signer,
+ permissioned: &signer,
+ store_address: address,
+ amount: u64
+) {
+ permissioned_signer::authorize_increase(
+ master,
+ permissioned,
+ amount as u256,
+ WithdrawPermission::ByStore { store_address }
+ )
+}
+
+
+
+
+
+
+
+
+## Function `refill_permission`
+
+
+
+public(friend) fun refill_permission(permissioned: &signer, amount: u64, store_address: address)
+
+
+
+
+
+Implementation
+
+
+public(friend) fun refill_permission(
+ permissioned: &signer,
+ amount: u64,
+ store_address: address,
+) {
+ permissioned_signer::increase_limit(
+ permissioned,
+ amount as u256,
+ WithdrawPermission::ByStore { store_address }
+ )
+}
+
+
+
+
+
+
+
+
+## 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::ByStore {
+ store_address: object::object_address(&token_type),
+ })
+}
+
+
+
+
diff --git a/aptos-move/framework/aptos-framework/doc/managed_coin.md b/aptos-move/framework/aptos-framework/doc/managed_coin.md
index 50c2383fd111d..ede3c87a278f2 100644
--- a/aptos-move/framework/aptos-framework/doc/managed_coin.md
+++ b/aptos-move/framework/aptos-framework/doc/managed_coin.md
@@ -378,7 +378,7 @@ Removes capabilities from the account to be stored or destroyed elsewhere
pragma verify = true;
-pragma aborts_if_is_strict;
+pragma aborts_if_is_partial;
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 524024ea69f17..fdc63ca6edf5a 100644
--- a/aptos-move/framework/aptos-framework/doc/primary_fungible_store.md
+++ b/aptos-move/framework/aptos-framework/doc/primary_fungible_store.md
@@ -28,11 +28,14 @@ fungible asset to it. This emits an deposit event.
- [Function `primary_store_address_inlined`](#0x1_primary_fungible_store_primary_store_address_inlined)
- [Function `primary_store_inlined`](#0x1_primary_fungible_store_primary_store_inlined)
- [Function `primary_store_exists_inlined`](#0x1_primary_fungible_store_primary_store_exists_inlined)
+- [Function `grant_permission`](#0x1_primary_fungible_store_grant_permission)
+- [Function `grant_apt_permission`](#0x1_primary_fungible_store_grant_apt_permission)
- [Function `balance`](#0x1_primary_fungible_store_balance)
- [Function `is_balance_at_least`](#0x1_primary_fungible_store_is_balance_at_least)
- [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)
@@ -363,6 +366,73 @@ Use instead of the corresponding view functions for dispatchable hooks to avoid
+
+
+
+
+## Function `grant_permission`
+
+
+
+public fun grant_permission<T: key>(master: &signer, permissioned: &signer, metadata: object::Object<T>, amount: u64)
+
+
+
+
+
+Implementation
+
+
+public fun grant_permission<T: key>(
+ master: &signer,
+ permissioned: &signer,
+ metadata: Object<T>,
+ amount: u64
+) {
+ fungible_asset::grant_permission_by_address(
+ master,
+ permissioned,
+ primary_store_address_inlined(signer::address_of(permissioned), metadata),
+ amount
+ );
+}
+
+
+
+
+
+
+
+
+## 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
+) {
+ fungible_asset::grant_permission_by_address(
+ master,
+ permissioned,
+ object::create_user_derived_object_address(signer::address_of(permissioned), @aptos_fungible_asset),
+ amount
+ );
+}
+
+
+
+
@@ -507,6 +577,44 @@ 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(
+ owner,
+ fungible_asset::amount(&fa),
+ primary_store_address_inlined(
+ signer::address_of(owner),
+ fungible_asset::metadata_from_asset(&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/doc/stake.md b/aptos-move/framework/aptos-framework/doc/stake.md
index dedfe15027641..825dfe567c8f8 100644
--- a/aptos-move/framework/aptos-framework/doc/stake.md
+++ b/aptos-move/framework/aptos-framework/doc/stake.md
@@ -5517,7 +5517,8 @@ Returns validator's next epoch voting power, including pending_active, active, a
-include ResourceRequirement;
+pragma aborts_if_is_partial;
+include ResourceRequirement;
requires rewards_rate <= MAX_REWARDS_RATE;
requires rewards_rate_denominator > 0;
requires rewards_rate <= rewards_rate_denominator;
diff --git a/aptos-move/framework/aptos-framework/sources/aptos_account.move b/aptos-move/framework/aptos-framework/sources/aptos_account.move
index ddd0846b1596e..e9d7efdbf6307 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, sender_store, amount);
fungible_asset::unchecked_deposit(recipient_store, fungible_asset::unchecked_withdraw(sender_store, amount));
}
@@ -315,6 +316,27 @@ 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 (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_handle(&perm_handle);
+ primary_fungible_store::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[]);
@@ -353,6 +375,7 @@ module aptos_framework::aptos_account {
#[test(from = @0x1, to = @0x12)]
public fun test_direct_coin_transfers(from: &signer, to: &signer) acquires DirectTransferConfig {
+ coin::create_coin_conversion_map(from);
let (burn_cap, freeze_cap, mint_cap) = coin::initialize(
from,
utf8(b"FC"),
@@ -376,6 +399,7 @@ module aptos_framework::aptos_account {
#[test(from = @0x1, recipient_1 = @0x124, recipient_2 = @0x125)]
public fun test_batch_transfer_coins(
from: &signer, recipient_1: &signer, recipient_2: &signer) acquires DirectTransferConfig {
+ coin::create_coin_conversion_map(from);
let (burn_cap, freeze_cap, mint_cap) = coin::initialize(
from,
utf8(b"FC"),
@@ -417,6 +441,7 @@ module aptos_framework::aptos_account {
#[test(from = @0x1, to = @0x12)]
public fun test_direct_coin_transfers_with_explicit_direct_coin_transfer_config(
from: &signer, to: &signer) acquires DirectTransferConfig {
+ coin::create_coin_conversion_map(from);
let (burn_cap, freeze_cap, mint_cap) = coin::initialize(
from,
utf8(b"FC"),
@@ -442,6 +467,7 @@ module aptos_framework::aptos_account {
#[expected_failure(abort_code = 0x50003, location = Self)]
public fun test_direct_coin_transfers_fail_if_recipient_opted_out(
from: &signer, to: &signer) acquires DirectTransferConfig {
+ coin::create_coin_conversion_map(from);
let (burn_cap, freeze_cap, mint_cap) = coin::initialize(
from,
utf8(b"FC"),
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..5f0a5d85e5e72 100644
--- a/aptos-move/framework/aptos-framework/sources/aptos_coin.spec.move
+++ b/aptos-move/framework/aptos-framework/sources/aptos_coin.spec.move
@@ -31,12 +31,15 @@ spec aptos_framework::aptos_coin {
///
spec module {
pragma verify = true;
- pragma aborts_if_is_strict;
+ pragma aborts_if_is_partial;
}
spec initialize(aptos_framework: &signer): (BurnCapability, MintCapability) {
- use aptos_framework::aggregator_factory;
+ pragma verify = false;
+ 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 a396b121b82de..90e1bf5942a78 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};
@@ -612,11 +613,26 @@ module aptos_framework::coin {
};
}
+ inline fun assert_signer_has_permission(account: &signer) {
+ if(permissioned_signer::is_permissioned_signer(account)) {
+ fungible_asset::withdraw_permission_check_by_address(
+ account,
+ primary_fungible_store::primary_store_address(
+ signer::address_of(account),
+ ensure_paired_metadata()
+ ),
+ 0
+ );
+ }
+ }
+
/// Voluntarily migrate to fungible store for `CoinType` if not yet.
public entry fun migrate_to_fungible_store(
account: &signer
) acquires CoinStore, CoinConversionMap, CoinInfo {
- maybe_convert_to_fungible_store(signer::address_of(account));
+ let account_addr = signer::address_of(account);
+ assert_signer_has_permission(account);
+ maybe_convert_to_fungible_store(account_addr);
}
/// Migrate to fungible store for `CoinType` if not yet.
@@ -838,6 +854,23 @@ module aptos_framework::coin {
}
}
+ public fun deposit_with_signer(
+ account: &signer,
+ coin: Coin
+ ) acquires CoinStore, CoinConversionMap, CoinInfo {
+ let metadata = ensure_paired_metadata();
+ let account_address = signer::address_of(account);
+ fungible_asset::refill_permission(
+ account,
+ coin.value,
+ primary_fungible_store::primary_store_address_inlined(
+ account_address,
+ metadata,
+ )
+ );
+ deposit(account_address, coin);
+ }
+
inline fun can_receive_paired_fungible_asset(
account_address: address,
metadata: Object
@@ -948,7 +981,7 @@ module aptos_framework::coin {
symbol: string::String,
decimals: u8,
monitor_supply: bool,
- ): (BurnCapability, FreezeCapability, MintCapability) {
+ ): (BurnCapability, FreezeCapability, MintCapability) acquires CoinInfo, CoinConversionMap {
initialize_internal(account, name, symbol, decimals, monitor_supply, false)
}
@@ -959,7 +992,7 @@ module aptos_framework::coin {
symbol: string::String,
decimals: u8,
monitor_supply: bool,
- ): (BurnCapability, FreezeCapability, MintCapability) {
+ ): (BurnCapability, FreezeCapability, MintCapability) acquires CoinInfo, CoinConversionMap {
system_addresses::assert_aptos_framework(account);
initialize_internal(account, name, symbol, decimals, monitor_supply, true)
}
@@ -971,8 +1004,9 @@ module aptos_framework::coin {
decimals: u8,
monitor_supply: bool,
parallelizable: bool,
- ): (BurnCapability, FreezeCapability, MintCapability) {
+ ): (BurnCapability, FreezeCapability, MintCapability) acquires CoinInfo, CoinConversionMap {
let account_addr = signer::address_of(account);
+ assert_signer_has_permission(account);
assert!(
coin_address() == account_addr,
@@ -1028,8 +1062,9 @@ module aptos_framework::coin {
mint_internal(amount)
}
- public fun register(account: &signer) acquires CoinConversionMap {
+ public fun register(account: &signer) acquires CoinInfo, CoinConversionMap {
let account_addr = signer::address_of(account);
+ assert_signer_has_permission(account);
// Short-circuit and do nothing if account is already registered for CoinType.
if (is_account_registered(account_addr)) {
return
@@ -1072,6 +1107,17 @@ module aptos_framework::coin {
amount
);
let withdrawn_coin = if (coin_amount_to_withdraw > 0) {
+ let metadata = ensure_paired_metadata();
+ if(permissioned_signer::is_permissioned_signer(account)) {
+ // Perform the check only if the account is a permissioned signer to save the cost of
+ // computing the primary store location.
+ fungible_asset::withdraw_permission_check_by_address(
+ account,
+ primary_fungible_store::primary_store_address(account_addr, metadata),
+ coin_amount_to_withdraw
+ );
+ };
+
let coin_store = borrow_global_mut>(account_addr);
assert!(
!coin_store.frozen,
@@ -1216,7 +1262,7 @@ module aptos_framework::coin {
account: &signer,
decimals: u8,
monitor_supply: bool,
- ): (BurnCapability, FreezeCapability, MintCapability) {
+ ): (BurnCapability, FreezeCapability, MintCapability) acquires CoinInfo, CoinConversionMap {
aggregator_factory::initialize_aggregator_factory_for_test(account);
initialize(
account,
@@ -1232,7 +1278,7 @@ module aptos_framework::coin {
account: &signer,
decimals: u8,
monitor_supply: bool,
- ): (BurnCapability, FreezeCapability, MintCapability) {
+ ): (BurnCapability, FreezeCapability, MintCapability) acquires CoinInfo, CoinConversionMap {
let (burn_cap, freeze_cap, mint_cap) = initialize_fake_money(
account,
decimals,
@@ -1349,7 +1395,7 @@ module aptos_framework::coin {
#[test(source = @0x2, framework = @aptos_framework)]
#[expected_failure(abort_code = 0x10001, location = Self)]
- public fun fail_initialize(source: signer, framework: signer) {
+ public fun fail_initialize(source: signer, framework: signer) acquires CoinInfo, CoinConversionMap {
aggregator_factory::initialize_aggregator_factory_for_test(&framework);
let (burn_cap, freeze_cap, mint_cap) = initialize(
&source,
@@ -1446,7 +1492,7 @@ module aptos_framework::coin {
#[expected_failure(abort_code = 0x10007, location = Self)]
public fun test_destroy_non_zero(
source: signer,
- ) acquires CoinInfo {
+ ) acquires CoinInfo, CoinConversionMap {
account::create_account_for_test(signer::address_of(&source));
let (burn_cap, freeze_cap, mint_cap) = initialize_and_register_fake_money(&source, 1, true);
let coins_minted = mint(100, &mint_cap);
@@ -1486,7 +1532,7 @@ module aptos_framework::coin {
}
#[test(source = @0x1)]
- public fun test_is_coin_initialized(source: signer) {
+ public fun test_is_coin_initialized(source: signer) acquires CoinInfo, CoinConversionMap {
assert!(!is_coin_initialized(), 0);
let (burn_cap, freeze_cap, mint_cap) = initialize_fake_money(&source, 1, true);
@@ -1612,7 +1658,7 @@ module aptos_framework::coin {
}
#[test_only]
- fun initialize_with_aggregator(account: &signer) {
+ fun initialize_with_aggregator(account: &signer) acquires CoinInfo, CoinConversionMap {
let (burn_cap, freeze_cap, mint_cap) = initialize_with_parallelizable_supply(
account,
string::utf8(b"Fake money"),
@@ -1628,7 +1674,7 @@ module aptos_framework::coin {
}
#[test_only]
- fun initialize_with_integer(account: &signer) {
+ fun initialize_with_integer(account: &signer) acquires CoinInfo, CoinConversionMap {
let (burn_cap, freeze_cap, mint_cap) = initialize(
account,
string::utf8(b"Fake money"),
@@ -1646,14 +1692,14 @@ module aptos_framework::coin {
#[test(framework = @aptos_framework, other = @0x123)]
#[expected_failure(abort_code = 0x50003, location = aptos_framework::system_addresses)]
- fun test_supply_initialize_fails(framework: signer, other: signer) {
+ fun test_supply_initialize_fails(framework: signer, other: signer) acquires CoinInfo, CoinConversionMap {
aggregator_factory::initialize_aggregator_factory_for_test(&framework);
initialize_with_aggregator(&other);
}
#[test(other = @0x123)]
#[expected_failure(abort_code = 0x10003, location = Self)]
- fun test_create_coin_store_with_non_coin_type(other: signer) acquires CoinConversionMap {
+ fun test_create_coin_store_with_non_coin_type(other: signer) acquires CoinInfo, CoinConversionMap {
register(&other);
}
@@ -1664,7 +1710,7 @@ module aptos_framework::coin {
}
#[test(framework = @aptos_framework)]
- fun test_supply_initialize(framework: signer) acquires CoinInfo {
+ fun test_supply_initialize(framework: signer) acquires CoinInfo, CoinConversionMap {
aggregator_factory::initialize_aggregator_factory_for_test(&framework);
initialize_with_aggregator(&framework);
@@ -1686,7 +1732,7 @@ module aptos_framework::coin {
#[test(framework = @aptos_framework)]
#[expected_failure(abort_code = 0x20001, location = aptos_framework::aggregator)]
- fun test_supply_overflow(framework: signer) acquires CoinInfo {
+ fun test_supply_overflow(framework: signer) acquires CoinInfo, CoinConversionMap {
aggregator_factory::initialize_aggregator_factory_for_test(&framework);
initialize_with_aggregator(&framework);
@@ -2035,4 +2081,224 @@ module aptos_framework::coin {
#[resource_group_member(group = aptos_framework::object::ObjectGroup)]
/// The flag the existence of which indicates the primary fungible store is created by the migration from CoinStore.
struct MigrationFlag has key {}
+
+ #[test(account = @aptos_framework)]
+ #[expected_failure(abort_code = 0x50024, location = aptos_framework::fungible_asset)]
+ fun test_withdraw_with_permissioned_signer_no_migration(
+ account: &signer,
+ ) acquires CoinConversionMap, CoinInfo, CoinStore, PairedCoinType {
+ account::create_account_for_test(signer::address_of(account));
+ let account_addr = signer::address_of(account);
+ let (burn_cap, freeze_cap, mint_cap) = initialize_fake_money(account, 1, true);
+ create_coin_store(account);
+ create_coin_conversion_map(account);
+
+ let coin = mint(100, &mint_cap);
+ deposit(account_addr, coin);
+
+ let permissioned_handle = permissioned_signer::create_permissioned_handle(account);
+ let permissioned_signer = permissioned_signer::signer_from_permissioned_handle(&permissioned_handle);
+
+ // Withdraw from permissioned signer with no migration rules set
+ //
+ // Aborted with error.
+ let coin_2 = withdraw(&permissioned_signer, 10);
+ permissioned_signer::destroy_permissioned_handle(permissioned_handle);
+
+ burn(coin_2, &burn_cap);
+ move_to(account, FakeMoneyCapabilities {
+ burn_cap,
+ freeze_cap,
+ mint_cap,
+ });
+ }
+
+ #[test(account = @aptos_framework)]
+ #[expected_failure(abort_code = 0x50024, location = aptos_framework::fungible_asset)]
+ fun test_withdraw_with_permissioned_signer(
+ account: &signer,
+ ) acquires CoinConversionMap, CoinInfo, CoinStore, PairedCoinType {
+ account::create_account_for_test(signer::address_of(account));
+ let account_addr = signer::address_of(account);
+ let (burn_cap, freeze_cap, mint_cap) = initialize_fake_money(account, 1, true);
+ create_coin_store(account);
+ create_coin_conversion_map(account);
+
+ let coin = mint(100, &mint_cap);
+ deposit(account_addr, coin);
+
+ let permissioned_handle = permissioned_signer::create_permissioned_handle(account);
+ let permissioned_signer = permissioned_signer::signer_from_permissioned_handle(&permissioned_handle);
+
+ // Withdraw from permissioned signer with no migration rules set
+ //
+ // Aborted with error.
+ let coin_2 = withdraw(&permissioned_signer, 10);
+ permissioned_signer::destroy_permissioned_handle(permissioned_handle);
+
+ burn(coin_2, &burn_cap);
+ move_to(account, FakeMoneyCapabilities {
+ burn_cap,
+ freeze_cap,
+ mint_cap,
+ });
+ }
+
+ #[test(account = @aptos_framework)]
+ #[expected_failure(abort_code = 0x50024, location = aptos_framework::fungible_asset)]
+ fun test_withdraw_with_permissioned_signer_no_capacity(
+ account: &signer,
+ ) acquires CoinConversionMap, CoinInfo, CoinStore, PairedCoinType {
+ account::create_account_for_test(signer::address_of(account));
+ let account_addr = signer::address_of(account);
+ let (burn_cap, freeze_cap, mint_cap) = initialize_and_register_fake_money(account, 1, true);
+ ensure_paired_metadata();
+
+ let coin = mint(100, &mint_cap);
+ deposit(account_addr, coin);
+
+ let permissioned_handle = permissioned_signer::create_permissioned_handle(account);
+ let permissioned_signer = permissioned_signer::signer_from_permissioned_handle(&permissioned_handle);
+
+ // Withdraw from permissioned signer with no permissions granted.
+ let coin_2 = withdraw(&permissioned_signer, 10);
+ permissioned_signer::destroy_permissioned_handle(permissioned_handle);
+
+ burn(coin_2, &burn_cap);
+ move_to(account, FakeMoneyCapabilities {
+ burn_cap,
+ freeze_cap,
+ mint_cap,
+ });
+ }
+
+ #[test(account = @aptos_framework)]
+ fun test_e2e_withdraw_with_permissioned_signer_and_migration(
+ account: &signer,
+ ) acquires CoinConversionMap, CoinInfo, CoinStore, PairedCoinType {
+ account::create_account_for_test(signer::address_of(account));
+ let account_addr = signer::address_of(account);
+ let (burn_cap, freeze_cap, mint_cap) = initialize_and_register_fake_money(account, 1, true);
+ let metadata = ensure_paired_metadata();
+
+ let coin = mint(100, &mint_cap);
+ deposit(account_addr, coin);
+
+ let permissioned_handle = permissioned_signer::create_permissioned_handle(account);
+ let permissioned_signer = permissioned_signer::signer_from_permissioned_handle(&permissioned_handle);
+ primary_fungible_store::grant_permission(account, &permissioned_signer, metadata, 10);
+
+ // Withdraw from permissioned signer with proper permissions.
+ let coin_2 = withdraw(&permissioned_signer, 10);
+ burn(coin_2, &burn_cap);
+
+ // Withdraw with some funds from CoinStore and some from PFS.
+ primary_fungible_store::deposit(account_addr, coin_to_fungible_asset(mint(100, &mint_cap)));
+ primary_fungible_store::grant_permission(account, &permissioned_signer, metadata, 100);
+ let coin_2 = withdraw(&permissioned_signer, 100);
+ burn(coin_2, &burn_cap);
+
+ // Withdraw funds from PFS only.
+ assert!(coin_balance(account_addr) == 0, 1);
+ primary_fungible_store::grant_permission(account, &permissioned_signer, metadata, 10);
+ let coin_2 = withdraw(&permissioned_signer, 10);
+ burn(coin_2, &burn_cap);
+
+ permissioned_signer::destroy_permissioned_handle(permissioned_handle);
+ move_to(account, FakeMoneyCapabilities {
+ burn_cap,
+ freeze_cap,
+ mint_cap,
+ });
+ }
+
+ #[test(account = @aptos_framework)]
+ #[expected_failure(abort_code = 0x50024, location = aptos_framework::fungible_asset)]
+ fun test_e2e_withdraw_with_permissioned_signer_no_permission_1(
+ account: &signer,
+ ) acquires CoinConversionMap, CoinInfo, CoinStore, PairedCoinType {
+ account::create_account_for_test(signer::address_of(account));
+ let account_addr = signer::address_of(account);
+ let (burn_cap, freeze_cap, mint_cap) = initialize_and_register_fake_money(account, 1, true);
+ let metadata = ensure_paired_metadata();
+
+ let coin = mint(100, &mint_cap);
+ deposit(account_addr, coin);
+
+ let permissioned_handle = permissioned_signer::create_permissioned_handle(account);
+ let permissioned_signer = permissioned_signer::signer_from_permissioned_handle(&permissioned_handle);
+ primary_fungible_store::grant_permission(account, &permissioned_signer, metadata, 10);
+
+ let coin_2 = withdraw(&permissioned_signer, 20);
+ burn(coin_2, &burn_cap);
+
+ permissioned_signer::destroy_permissioned_handle(permissioned_handle);
+ move_to(account, FakeMoneyCapabilities {
+ burn_cap,
+ freeze_cap,
+ mint_cap,
+ });
+ }
+
+ #[test(account = @aptos_framework)]
+ #[expected_failure(abort_code = 0x50024, location = aptos_framework::fungible_asset)]
+ fun test_e2e_withdraw_with_permissioned_signer_no_permission_2(
+ account: &signer,
+ ) acquires CoinConversionMap, CoinInfo, CoinStore, PairedCoinType {
+ account::create_account_for_test(signer::address_of(account));
+ let account_addr = signer::address_of(account);
+ let (burn_cap, freeze_cap, mint_cap) = initialize_and_register_fake_money(account, 1, true);
+ let metadata = ensure_paired_metadata();
+
+ let coin = mint(100, &mint_cap);
+ deposit(account_addr, coin);
+
+ let permissioned_handle = permissioned_signer::create_permissioned_handle(account);
+ let permissioned_signer = permissioned_signer::signer_from_permissioned_handle(&permissioned_handle);
+ primary_fungible_store::grant_permission(account, &permissioned_signer, metadata, 10);
+
+ // Withdraw from permissioned signer with proper permissions.
+ let coin_2 = withdraw(&permissioned_signer, 10);
+ burn(coin_2, &burn_cap);
+
+ // Withdraw with some funds from CoinStore and some from PFS.
+ primary_fungible_store::deposit(account_addr, coin_to_fungible_asset(mint(100, &mint_cap)));
+ primary_fungible_store::grant_permission(account, &permissioned_signer, metadata, 90);
+ let coin_2 = withdraw(&permissioned_signer, 100);
+ burn(coin_2, &burn_cap);
+
+ permissioned_signer::destroy_permissioned_handle(permissioned_handle);
+ move_to(account, FakeMoneyCapabilities {
+ burn_cap,
+ freeze_cap,
+ mint_cap,
+ });
+ }
+
+ #[test(account = @aptos_framework)]
+ #[expected_failure(abort_code = 0x50024, location = aptos_framework::fungible_asset)]
+ fun test_e2e_withdraw_with_permissioned_signer_no_permission_3(
+ account: &signer,
+ ) acquires CoinConversionMap, CoinInfo, CoinStore, PairedCoinType {
+ account::create_account_for_test(signer::address_of(account));
+ let account_addr = signer::address_of(account);
+ let (burn_cap, freeze_cap, mint_cap) = initialize_and_register_fake_money(account, 1, true);
+ let metadata = ensure_paired_metadata();
+
+ let permissioned_handle = permissioned_signer::create_permissioned_handle(account);
+ let permissioned_signer = permissioned_signer::signer_from_permissioned_handle(&permissioned_handle);
+
+ // Withdraw with some funds from PFS only.
+ primary_fungible_store::deposit(account_addr, coin_to_fungible_asset(mint(100, &mint_cap)));
+ primary_fungible_store::grant_permission(account, &permissioned_signer, metadata, 90);
+ let coin_2 = withdraw(&permissioned_signer, 100);
+ burn(coin_2, &burn_cap);
+
+ permissioned_signer::destroy_permissioned_handle(permissioned_handle);
+ move_to(account, FakeMoneyCapabilities {
+ burn_cap,
+ freeze_cap,
+ mint_cap,
+ });
+ }
}
diff --git a/aptos-move/framework/aptos-framework/sources/coin.spec.move b/aptos-move/framework/aptos-framework/sources/coin.spec.move
index b43c5850ec882..0ccf01d2dd194 100644
--- a/aptos-move/framework/aptos-framework/sources/coin.spec.move
+++ b/aptos-move/framework/aptos-framework/sources/coin.spec.move
@@ -60,6 +60,7 @@ spec aptos_framework::coin {
///
spec module {
pragma verify = true;
+ pragma aborts_if_is_partial;
global supply: num;
global aggregate_supply: num;
apply TotalSupplyTracked to * except
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 496fdf610ab89..40087d979bf76 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 3573f3cf78980..4b4bd78393b37 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;
@@ -90,9 +91,10 @@ module aptos_framework::fungible_asset {
/// The balance ref and the fungible asset do not match.
const ERAW_BALANCE_REF_AND_FUNGIBLE_ASSET_MISMATCH: u64 = 34;
/// The supply ref and the fungible asset do not match.
- const ERAW_SUPPLY_REF_AND_FUNGIBLE_ASSET_MISMATCH: u64 = 34;
-
+ const ERAW_SUPPLY_REF_AND_FUNGIBLE_ASSET_MISMATCH: u64 = 35;
+ /// signer don't have the permission to perform withdraw operation
+ const EWITHDRAW_PERMISSION_DENIED: u64 = 36;
//
// Constants
//
@@ -209,6 +211,10 @@ module aptos_framework::fungible_asset {
metadata: Object
}
+ enum WithdrawPermission has copy, drop, store {
+ ByStore { store_address: address }
+ }
+
#[event]
/// Emitted when fungible assets are deposited into a store.
struct Deposit has drop, store {
@@ -864,16 +870,51 @@ module aptos_framework::fungible_asset {
amount: u64,
): FungibleAsset acquires FungibleStore, DispatchFunctionStore, ConcurrentFungibleBalance {
withdraw_sanity_check(owner, store, true);
+ withdraw_permission_check(owner, store, amount);
unchecked_withdraw(object::object_address(&store), amount)
}
+ /// Check the permission for withdraw operation.
+ public(friend) fun withdraw_permission_check(
+ owner: &signer,
+ store: Object,
+ amount: u64,
+ ) {
+ assert!(permissioned_signer::check_permission_consume(owner, amount as u256, WithdrawPermission::ByStore {
+ store_address: object::object_address(&store),
+ }), error::permission_denied(EWITHDRAW_PERMISSION_DENIED));
+ }
+
+ /// Check the permission for withdraw operation.
+ public(friend) fun withdraw_permission_check_by_address(
+ owner: &signer,
+ store_address: address,
+ amount: u64,
+ ) {
+ assert!(permissioned_signer::check_permission_consume(owner, amount as u256, WithdrawPermission::ByStore {
+ store_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),
@@ -1325,6 +1366,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_by_store(
+ master: &signer,
+ permissioned: &signer,
+ store: Object,
+ amount: u64
+ ) {
+ permissioned_signer::authorize_increase(
+ master,
+ permissioned,
+ amount as u256,
+ WithdrawPermission::ByStore {
+ store_address: object::object_address(&store),
+ }
+ )
+ }
+
+ public(friend) fun grant_permission_by_address(
+ master: &signer,
+ permissioned: &signer,
+ store_address: address,
+ amount: u64
+ ) {
+ permissioned_signer::authorize_increase(
+ master,
+ permissioned,
+ amount as u256,
+ WithdrawPermission::ByStore { store_address }
+ )
+ }
+
+ public(friend) fun refill_permission(
+ permissioned: &signer,
+ amount: u64,
+ store_address: address,
+ ) {
+ permissioned_signer::increase_limit(
+ permissioned,
+ amount as u256,
+ WithdrawPermission::ByStore { store_address }
+ )
+ }
+
+ /// Removing permissions from permissioned signer.
+ public fun revoke_permission(permissioned: &signer, token_type: Object) {
+ permissioned_signer::revoke_permission(permissioned, WithdrawPermission::ByStore {
+ store_address: object::object_address(&token_type),
+ })
+ }
+
#[test_only]
use aptos_framework::account;
@@ -1380,6 +1473,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);
@@ -1784,6 +1880,89 @@ 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 FA
+ grant_permission_by_store(aaron, &aaron_permission_signer, aaron_store, 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);
+ }
+
+ #[test(creator = @0xcafe, aaron = @0xface)]
+ #[expected_failure(abort_code = 0x50024, location = Self)]
+ fun test_e2e_withdraw_limit_exceeds(
+ 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 FA
+ grant_permission_by_store(aaron, &aaron_permission_signer, aaron_store, 10);
+
+ // Withdrawing more than 10 FA yield an error.
+ let fa = withdraw(&aaron_permission_signer, aaron_store, 11);
+ 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/managed_coin.move b/aptos-move/framework/aptos-framework/sources/managed_coin.move
index a5da4b24ad5c9..701f7d97e55d3 100644
--- a/aptos-move/framework/aptos-framework/sources/managed_coin.move
+++ b/aptos-move/framework/aptos-framework/sources/managed_coin.move
@@ -138,8 +138,9 @@ module aptos_framework::managed_coin {
#[test_only]
struct FakeMoney {}
- #[test(source = @0xa11ce, destination = @0xb0b, mod_account = @0x1)]
+ #[test(framework = @aptos_framework, source = @0xa11ce, destination = @0xb0b, mod_account = @0x1)]
public entry fun test_end_to_end(
+ framework: signer,
source: signer,
destination: signer,
mod_account: signer
@@ -150,6 +151,7 @@ module aptos_framework::managed_coin {
aptos_framework::account::create_account_for_test(destination_addr);
aptos_framework::account::create_account_for_test(signer::address_of(&mod_account));
aggregator_factory::initialize_aggregator_factory_for_test(&mod_account);
+ aptos_framework::coin::create_coin_conversion_map(&framework);
initialize(
&mod_account,
diff --git a/aptos-move/framework/aptos-framework/sources/managed_coin.spec.move b/aptos-move/framework/aptos-framework/sources/managed_coin.spec.move
index 344c9744f7c97..c85b4e70bbedd 100644
--- a/aptos-move/framework/aptos-framework/sources/managed_coin.spec.move
+++ b/aptos-move/framework/aptos-framework/sources/managed_coin.spec.move
@@ -47,7 +47,7 @@ spec aptos_framework::managed_coin {
///
spec module {
pragma verify = true;
- pragma aborts_if_is_strict;
+ pragma aborts_if_is_partial;
}
spec burn(
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 4a69b78937a1b..2fc6ae2b27d7b 100644
--- a/aptos-move/framework/aptos-framework/sources/primary_fungible_store.move
+++ b/aptos-move/framework/aptos-framework/sources/primary_fungible_store.move
@@ -20,6 +20,9 @@ module aptos_framework::primary_fungible_store {
use std::signer;
use std::string::String;
+ #[test_only]
+ use aptos_framework::permissioned_signer;
+
#[resource_group_member(group = aptos_framework::object::ObjectGroup)]
/// A resource that holds the derive ref for the fungible asset metadata object. This is used to create primary
/// stores for users with deterministic addresses so that users can easily deposit/withdraw/transfer fungible
@@ -124,6 +127,33 @@ module aptos_framework::primary_fungible_store {
fungible_asset::store_exists(primary_store_address_inlined(account, metadata))
}
+ public fun grant_permission(
+ master: &signer,
+ permissioned: &signer,
+ metadata: Object,
+ amount: u64
+ ) {
+ fungible_asset::grant_permission_by_address(
+ master,
+ permissioned,
+ primary_store_address_inlined(signer::address_of(permissioned), metadata),
+ amount
+ );
+ }
+
+ public fun grant_apt_permission(
+ master: &signer,
+ permissioned: &signer,
+ amount: u64
+ ) {
+ fungible_asset::grant_permission_by_address(
+ master,
+ permissioned,
+ object::create_user_derived_object_address(signer::address_of(permissioned), @aptos_fungible_asset),
+ amount
+ );
+ }
+
#[view]
/// Get the balance of `account`'s primary store.
public fun balance(account: address, metadata: Object): u64 {
@@ -168,6 +198,24 @@ 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(
+ owner,
+ fungible_asset::amount(&fa),
+ primary_store_address_inlined(
+ signer::address_of(owner),
+ fungible_asset::metadata_from_asset(&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);
@@ -402,4 +450,42 @@ module aptos_framework::primary_fungible_store {
assert!(balance(user_2_address, metadata) == 10, 0);
deposit(user_2_address, coins);
}
+
+ #[test(creator = @0xcafe, aaron = @0xface)]
+ fun test_permissioned_flow(
+ creator: &signer,
+ aaron: &signer,
+ ) acquires DeriveRefPod {
+ let (creator_ref, metadata) = create_test_token(creator);
+ let (mint_ref, _transfer_ref, _burn_ref) = init_test_metadata_with_primary_store_enabled(&creator_ref);
+ let creator_address = signer::address_of(creator);
+ let aaron_address = signer::address_of(aaron);
+ assert!(balance(creator_address, metadata) == 0, 1);
+ assert!(balance(aaron_address, metadata) == 0, 2);
+ mint(&mint_ref, creator_address, 100);
+ transfer(creator, metadata, aaron_address, 80);
+
+ let aaron_permission_handle = permissioned_signer::create_permissioned_handle(aaron);
+ let aaron_permission_signer = permissioned_signer::signer_from_permissioned_handle(&aaron_permission_handle);
+ grant_permission(aaron, &aaron_permission_signer, metadata, 10);
+
+ let fa = withdraw(&aaron_permission_signer, metadata, 10);
+ deposit(creator_address, fa);
+
+ assert!(balance(creator_address, metadata) == 30, 3);
+ assert!(balance(aaron_address, metadata) == 70, 4);
+
+ // Withdraw from creator and deposit back to aaron's account with permssioned signer.
+ let fa = withdraw(creator, metadata, 10);
+ deposit_with_signer(&aaron_permission_signer, fa);
+
+ // deposit_with_signer refills the permission, can now withdraw again.
+ let fa = withdraw(&aaron_permission_signer, metadata, 10);
+ deposit(creator_address, fa);
+
+ assert!(balance(creator_address, metadata) == 30, 3);
+ assert!(balance(aaron_address, metadata) == 70, 4);
+
+ permissioned_signer::destroy_permissioned_handle(aaron_permission_handle);
+ }
}
diff --git a/aptos-move/framework/aptos-framework/sources/stake.spec.move b/aptos-move/framework/aptos-framework/sources/stake.spec.move
index f922821e8ac83..3b7a01b8710a6 100644
--- a/aptos-move/framework/aptos-framework/sources/stake.spec.move
+++ b/aptos-move/framework/aptos-framework/sources/stake.spec.move
@@ -521,6 +521,7 @@ spec aptos_framework::stake {
}
spec distribute_rewards {
+ pragma aborts_if_is_partial;
include ResourceRequirement;
requires rewards_rate <= MAX_REWARDS_RATE;
requires rewards_rate_denominator > 0;