From 550a254eaed2b2c5befefad7466067c9d73c8a1d Mon Sep 17 00:00:00 2001 From: runtianz Date: Mon, 9 Sep 2024 10:02:05 -0700 Subject: [PATCH] add permissions for fungible assets operation --- .../src/tests/move_feature_gating.rs | 1 - .../aptos-framework/doc/aptos_account.md | 1 + .../aptos-framework/doc/aptos_coin.md | 5 +- .../framework/aptos-framework/doc/coin.md | 16 + .../doc/dispatchable_fungible_asset.md | 1 + .../aptos-framework/doc/fungible_asset.md | 289 +++++++++++++++++- .../aptos-framework/doc/managed_coin.md | 2 +- .../doc/primary_fungible_store.md | 108 +++++++ .../framework/aptos-framework/doc/stake.md | 3 +- .../sources/aptos_account.move | 26 ++ .../sources/aptos_coin.spec.move | 5 +- .../aptos-framework/sources/coin.move | 248 +++++++++++++++ .../aptos-framework/sources/coin.spec.move | 1 + .../sources/dispatchable_fungible_asset.move | 1 + .../sources/fungible_asset.move | 185 ++++++++++- .../aptos-framework/sources/managed_coin.move | 4 +- .../sources/managed_coin.spec.move | 2 +- .../sources/primary_fungible_store.move | 86 ++++++ .../aptos-framework/sources/stake.spec.move | 1 + 19 files changed, 971 insertions(+), 14 deletions(-) diff --git a/aptos-move/e2e-move-tests/src/tests/move_feature_gating.rs b/aptos-move/e2e-move-tests/src/tests/move_feature_gating.rs index 0445fcc1a05ee..3c325c6b87064 100644 --- a/aptos-move/e2e-move-tests/src/tests/move_feature_gating.rs +++ b/aptos-move/e2e-move-tests/src/tests/move_feature_gating.rs @@ -15,7 +15,6 @@ use move_core_types::vm_status::StatusCode; use rstest::rstest; #[rstest(enabled, disabled, - case(vec![], vec![FeatureFlag::ENABLE_ENUM_TYPES]), case(vec![FeatureFlag::ENABLE_ENUM_TYPES], vec![]), )] fn enum_types(enabled: Vec, disabled: Vec) { 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..f4fc9eb893ef6 100644
--- a/aptos-move/framework/aptos-framework/doc/coin.md
+++ b/aptos-move/framework/aptos-framework/doc/coin.md
@@ -147,6 +147,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;
@@ -2148,6 +2149,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));
 }
 
@@ -3037,6 +3039,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!( @@ -3154,6 +3157,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)) {
@@ -3257,6 +3261,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,
+                primary_fungible_store::primary_store_address(account_addr, 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,
@@ -3581,6 +3596,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..aef5d59339cc7 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,13 @@ 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;
-
+        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..bb402660c533c 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};
@@ -616,6 +617,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));
     }
 
@@ -838,6 +840,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
@@ -972,6 +991,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!(
@@ -1029,6 +1049,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)) {
@@ -1072,6 +1093,13 @@ module aptos_framework::coin {
             amount
         );
         let withdrawn_coin = if (coin_amount_to_withdraw > 0) {
+            let metadata = ensure_paired_metadata();
+            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,
@@ -2035,4 +2063,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;