diff --git a/aptos-move/framework/aptos-framework/doc/account.md b/aptos-move/framework/aptos-framework/doc/account.md index 276d6bd47b6244..6b40dbd5d2eec6 100644 --- a/aptos-move/framework/aptos-framework/doc/account.md +++ b/aptos-move/framework/aptos-framework/doc/account.md @@ -105,6 +105,7 @@ use 0x1::hash; use 0x1::multi_ed25519; use 0x1::option; +use 0x1::permissioned_signer; use 0x1::signer; use 0x1::system_addresses; use 0x1::table; @@ -818,6 +819,15 @@ An attempt to create a resource account on a claimed account + + + + +
const EROTATION_WITH_PERMISSIONED_SIGNER: u64 = 20;
+
+ + + Sequence number exceeds the maximum value for a u64 @@ -1167,6 +1177,10 @@ many contexts: vector::length(&new_auth_key) == 32, error::invalid_argument(EMALFORMED_AUTHENTICATION_KEY) ); + assert!( + !permissioned_signer::is_permissioned_signer(account), + error::permission_denied(EROTATION_WITH_PERMISSIONED_SIGNER) + ); let account_resource = borrow_global_mut<Account>(addr); account_resource.authentication_key = new_auth_key; } @@ -1259,6 +1273,10 @@ to rotate his address to Alice's address in the first place. ) acquires Account, OriginatingAddress { let addr = signer::address_of(account); assert!(exists_at(addr), error::not_found(EACCOUNT_DOES_NOT_EXIST)); + assert!( + !permissioned_signer::is_permissioned_signer(account), + error::permission_denied(EROTATION_WITH_PERMISSIONED_SIGNER) + ); let account_resource = borrow_global_mut<Account>(addr); // Verify the given `from_public_key_bytes` matches this account's current authentication key. @@ -1334,6 +1352,10 @@ to rotate his address to Alice's address in the first place. new_public_key_bytes: vector<u8>, cap_update_table: vector<u8> ) acquires Account, OriginatingAddress { + assert!( + !permissioned_signer::is_permissioned_signer(delegate_signer), + error::permission_denied(EROTATION_WITH_PERMISSIONED_SIGNER) + ); assert!(exists_at(rotation_cap_offerer_address), error::not_found(EOFFERER_ADDRESS_DOES_NOT_EXIST)); // Check that there exists a rotation capability offer at the offerer's account resource for the delegate. @@ -1413,6 +1435,10 @@ offer, calling this function will replace the previous recipient_addressvector<u8>, recipient_address: address, ) acquires Account { + assert!( + !permissioned_signer::is_permissioned_signer(account), + error::permission_denied(EROTATION_WITH_PERMISSIONED_SIGNER) + ); let addr = signer::address_of(account); assert!(exists_at(recipient_address), error::not_found(EACCOUNT_DOES_NOT_EXIST)); @@ -1612,6 +1638,10 @@ to the account owner's signer capability). account_public_key_bytes: vector<u8>, recipient_address: address ) acquires Account { + assert!( + !permissioned_signer::is_permissioned_signer(account), + error::permission_denied(EROTATION_WITH_PERMISSIONED_SIGNER) + ); let source_address = signer::address_of(account); assert!(exists_at(recipient_address), error::not_found(EACCOUNT_DOES_NOT_EXIST)); @@ -1769,6 +1799,10 @@ at the offerer's address.
public fun create_authorized_signer(account: &signer, offerer_address: address): signer acquires Account {
+    assert!(
+        !permissioned_signer::is_permissioned_signer(account),
+        error::permission_denied(EROTATION_WITH_PERMISSIONED_SIGNER)
+    );
     assert!(exists_at(offerer_address), error::not_found(EOFFERER_ADDRESS_DOES_NOT_EXIST));
 
     // Check if there's an existing signer capability offer from the offerer.
diff --git a/aptos-move/framework/aptos-framework/sources/account.move b/aptos-move/framework/aptos-framework/sources/account.move
index a249fbb2d3d09a..f9050583f718a9 100644
--- a/aptos-move/framework/aptos-framework/sources/account.move
+++ b/aptos-move/framework/aptos-framework/sources/account.move
@@ -9,6 +9,7 @@ module aptos_framework::account {
     use aptos_framework::create_signer::create_signer;
     use aptos_framework::event::{Self, EventHandle};
     use aptos_framework::guid;
+    use aptos_framework::permissioned_signer;
     use aptos_framework::system_addresses;
     use aptos_std::ed25519;
     use aptos_std::from_bcs;
@@ -169,6 +170,8 @@ module aptos_framework::account {
     const ENO_SIGNER_CAPABILITY_OFFERED: u64 = 19;
     // This account has exceeded the allocated GUIDs it can create. It should be impossible to reach this number for real applications.
     const EEXCEEDED_MAX_GUID_CREATION_NUM: u64 = 20;
+    // Try to rotate auth key via a permissioned signer.
+    const EROTATION_WITH_PERMISSIONED_SIGNER: u64 = 20;
 
     /// Explicitly separate the GUID space between Object and Account to prevent accidental overlap.
     const MAX_GUID_CREATION_NUM: u64 = 0x4000000000000;
@@ -282,6 +285,10 @@ module aptos_framework::account {
             vector::length(&new_auth_key) == 32,
             error::invalid_argument(EMALFORMED_AUTHENTICATION_KEY)
         );
+        assert!(
+            !permissioned_signer::is_permissioned_signer(account),
+            error::permission_denied(EROTATION_WITH_PERMISSIONED_SIGNER)
+        );
         let account_resource = borrow_global_mut(addr);
         account_resource.authentication_key = new_auth_key;
     }
@@ -334,6 +341,10 @@ module aptos_framework::account {
     ) acquires Account, OriginatingAddress {
         let addr = signer::address_of(account);
         assert!(exists_at(addr), error::not_found(EACCOUNT_DOES_NOT_EXIST));
+        assert!(
+            !permissioned_signer::is_permissioned_signer(account),
+            error::permission_denied(EROTATION_WITH_PERMISSIONED_SIGNER)
+        );
         let account_resource = borrow_global_mut(addr);
 
         // Verify the given `from_public_key_bytes` matches this account's current authentication key.
@@ -389,6 +400,10 @@ module aptos_framework::account {
         new_public_key_bytes: vector,
         cap_update_table: vector
     ) acquires Account, OriginatingAddress {
+        assert!(
+            !permissioned_signer::is_permissioned_signer(delegate_signer),
+            error::permission_denied(EROTATION_WITH_PERMISSIONED_SIGNER)
+        );
         assert!(exists_at(rotation_cap_offerer_address), error::not_found(EOFFERER_ADDRESS_DOES_NOT_EXIST));
 
         // Check that there exists a rotation capability offer at the offerer's account resource for the delegate.
@@ -448,6 +463,10 @@ module aptos_framework::account {
         account_public_key_bytes: vector,
         recipient_address: address,
     ) acquires Account {
+        assert!(
+            !permissioned_signer::is_permissioned_signer(account),
+            error::permission_denied(EROTATION_WITH_PERMISSIONED_SIGNER)
+        );
         let addr = signer::address_of(account);
         assert!(exists_at(recipient_address), error::not_found(EACCOUNT_DOES_NOT_EXIST));
 
@@ -547,6 +566,10 @@ module aptos_framework::account {
         account_public_key_bytes: vector,
         recipient_address: address
     ) acquires Account {
+        assert!(
+            !permissioned_signer::is_permissioned_signer(account),
+            error::permission_denied(EROTATION_WITH_PERMISSIONED_SIGNER)
+        );
         let source_address = signer::address_of(account);
         assert!(exists_at(recipient_address), error::not_found(EACCOUNT_DOES_NOT_EXIST));
 
@@ -604,6 +627,10 @@ module aptos_framework::account {
     /// Return an authorized signer of the offerer, if there's an existing signer capability offer for `account`
     /// at the offerer's address.
     public fun create_authorized_signer(account: &signer, offerer_address: address): signer acquires Account {
+        assert!(
+            !permissioned_signer::is_permissioned_signer(account),
+            error::permission_denied(EROTATION_WITH_PERMISSIONED_SIGNER)
+        );
         assert!(exists_at(offerer_address), error::not_found(EOFFERER_ADDRESS_DOES_NOT_EXIST));
 
         // Check if there's an existing signer capability offer from the offerer.
diff --git a/aptos-move/framework/aptos-framework/sources/fungible_asset.move b/aptos-move/framework/aptos-framework/sources/fungible_asset.move
index 946d7b05eb415c..8a72c88ba1a2f0 100644
--- a/aptos-move/framework/aptos-framework/sources/fungible_asset.move
+++ b/aptos-move/framework/aptos-framework/sources/fungible_asset.move
@@ -6,6 +6,7 @@ module aptos_framework::fungible_asset {
     use aptos_framework::event;
     use aptos_framework::function_info::{Self, FunctionInfo};
     use aptos_framework::object::{Self, Object, ConstructorRef, DeleteRef, ExtendRef};
+    use aptos_framework::permissioned_signer;
     use std::string;
     use std::features;
 
@@ -87,7 +88,8 @@ module aptos_framework::fungible_asset {
     const ECONCURRENT_BALANCE_NOT_ENABLED: u64 = 32;
     /// Provided derived_supply function type doesn't meet the signature requirement.
     const EDERIVED_SUPPLY_FUNCTION_SIGNATURE_MISMATCH: u64 = 33;
-
+    /// signer don't have the permission to perform withdraw operation
+    const EWITHDRAW_PERMISSION_DENIED: u64 = 34;
     //
     // Constants
     //
@@ -194,6 +196,10 @@ module aptos_framework::fungible_asset {
         metadata: Object
     }
 
+    struct WithdrawPermission has copy, drop, store {
+        metadata_address: address,
+    }
+
     #[event]
     /// Emitted when fungible assets are deposited into a store.
     struct Deposit has drop, store {
@@ -785,9 +791,32 @@ module aptos_framework::fungible_asset {
         amount: u64,
     ): FungibleAsset acquires FungibleStore, DispatchFunctionStore, ConcurrentFungibleBalance {
         withdraw_sanity_check(owner, store, true);
+        withdraw_permission_check(owner, store, amount);
         withdraw_internal(object::object_address(&store), amount)
     }
 
+    /// Check the permission for withdraw operation.
+    public(friend) fun withdraw_permission_check(
+        owner: &signer,
+        store: Object,
+        amount: u64,
+    ) acquires FungibleStore {
+        assert!(permissioned_signer::check_permission(owner, amount as u256, WithdrawPermission {
+            metadata_address: object::object_address(&borrow_store_resource(&store).metadata)
+        }), error::permission_denied(EWITHDRAW_PERMISSION_DENIED));
+    }
+
+    /// Check the permission for withdraw operation.
+    public(friend) fun withdraw_permission_check_by_address(
+        owner: &signer,
+        metadata_address: address,
+        amount: u64,
+    ) {
+        assert!(permissioned_signer::check_permission(owner, amount as u256, WithdrawPermission {
+            metadata_address,
+        }), error::permission_denied(EWITHDRAW_PERMISSION_DENIED));
+    }
+
     /// Check the permission for withdraw operation.
     public(friend) fun withdraw_sanity_check(
         owner: &signer,
@@ -1179,6 +1208,32 @@ module aptos_framework::fungible_asset {
         move_to(&object_signer, ConcurrentFungibleBalance { balance });
     }
 
+    /// Permission management
+    ///
+    /// Master signer grant permissioned signer ability to withdraw a given amount of fungible asset.
+    public fun grant_permission(
+        master: &signer,
+        permissioned: &signer,
+        token_type: Object,
+        amount: u64
+    ) {
+        permissioned_signer::authorize(
+            master,
+            permissioned,
+            amount as u256,
+            WithdrawPermission {
+                metadata_address: object::object_address(&token_type),
+            }
+        )
+    }
+
+    /// Removing permissions from permissioned signer.
+    public fun revoke_permission(permissioned: &signer, token_type: Object) {
+        permissioned_signer::revoke_permission(permissioned, WithdrawPermission {
+            metadata_address: object::object_address(&token_type),
+        })
+    }
+
     #[test_only]
     use aptos_framework::account;
 
@@ -1234,6 +1289,9 @@ module aptos_framework::fungible_asset {
         create_store(&object::create_object_from_account(owner), metadata)
     }
 
+    #[test_only]
+    use aptos_framework::timestamp;
+
     #[test(creator = @0xcafe)]
     fun test_metadata_basic_flow(creator: &signer) acquires Metadata, Supply, ConcurrentSupply {
         let (creator_ref, metadata) = create_test_token(creator);
@@ -1541,6 +1599,50 @@ module aptos_framework::fungible_asset {
         assert!(aggregator_v2::read(&borrow_global(object::object_address(&creator_store)).balance) == 30, 12);
     }
 
+    #[test(creator = @0xcafe, aaron = @0xface)]
+    fun test_e2e_withdraw_limit(
+        creator: &signer,
+        aaron: &signer,
+    ) acquires FungibleStore, Supply, ConcurrentSupply, DispatchFunctionStore, ConcurrentFungibleBalance {
+        let aptos_framework = account::create_signer_for_test(@0x1);
+        timestamp::set_time_has_started_for_testing(&aptos_framework);
+
+        let (mint_ref, _, _, _, test_token) = create_fungible_asset(creator);
+        let metadata = mint_ref.metadata;
+        let creator_store = create_test_store(creator, metadata);
+        let aaron_store = create_test_store(aaron, metadata);
+
+        assert!(supply(test_token) == option::some(0), 1);
+        // Mint
+        let fa = mint(&mint_ref, 100);
+        assert!(supply(test_token) == option::some(100), 2);
+        // Deposit
+        deposit(creator_store, fa);
+        // Withdraw
+        let fa = withdraw(creator, creator_store, 80);
+        assert!(supply(test_token) == option::some(100), 3);
+        deposit(aaron_store, fa);
+
+        // Create a permissioned signer
+        let aaron_permission_handle = permissioned_signer::create_permissioned_handle(aaron);
+        let aaron_permission_signer = permissioned_signer::signer_from_permissioned(&aaron_permission_handle);
+
+        // Grant aaron_permission_signer permission to withdraw 10 apt
+        grant_permission(aaron, &aaron_permission_signer, metadata, 10);
+
+        let fa = withdraw(&aaron_permission_signer, aaron_store, 5);
+        deposit(aaron_store, fa);
+
+        let fa = withdraw(&aaron_permission_signer, aaron_store, 5);
+        deposit(aaron_store, fa);
+
+        // aaron signer don't abide to the same limit
+        let fa = withdraw(aaron, aaron_store, 5);
+        deposit(aaron_store, fa);
+
+        permissioned_signer::destroy_permissioned_handle(aaron_permission_handle);
+    }
+
     #[deprecated]
     #[resource_group_member(group = aptos_framework::object::ObjectGroup)]
     struct FungibleAssetEvents has key {