From 3d2b9786d20862d9b9d9b6d82fbe77767605dce8 Mon Sep 17 00:00:00 2001 From: runtianz Date: Mon, 9 Sep 2024 10:38:26 -0700 Subject: [PATCH 1/4] TMP --- .../src/tests/move_feature_gating.rs | 1 - .../framework/aptos-framework/doc/account.md | 188 +++++++++++++++++- .../aptos-framework/doc/big_ordered_map.md | 28 +-- .../aptos-framework/doc/multisig_account.md | 3 +- .../aptos-framework/doc/ordered_map.md | 16 +- .../aptos-framework/doc/resource_account.md | 8 +- .../aptos-framework/doc/staking_contract.md | 44 ++-- .../aptos-framework/sources/account.move | 167 ++++++++++++++++ .../aptos-framework/sources/account.spec.move | 5 +- .../sources/multisig_account.spec.move | 1 + .../sources/resource_account.spec.move | 6 +- .../sources/staking_contract.spec.move | 4 + 12 files changed, 425 insertions(+), 46 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/account.md b/aptos-move/framework/aptos-framework/doc/account.md index 9566844a0f739..3957fd601e366 100644 --- a/aptos-move/framework/aptos-framework/doc/account.md +++ b/aptos-move/framework/aptos-framework/doc/account.md @@ -19,7 +19,12 @@ - [Struct `SignerCapabilityOfferProofChallenge`](#0x1_account_SignerCapabilityOfferProofChallenge) - [Struct `RotationCapabilityOfferProofChallengeV2`](#0x1_account_RotationCapabilityOfferProofChallengeV2) - [Struct `SignerCapabilityOfferProofChallengeV2`](#0x1_account_SignerCapabilityOfferProofChallengeV2) +- [Enum `AccountPermission`](#0x1_account_AccountPermission) - [Constants](#@Constants_0) +- [Function `check_rotation_permission`](#0x1_account_check_rotation_permission) +- [Function `check_offering_permission`](#0x1_account_check_offering_permission) +- [Function `grant_key_rotation_permission`](#0x1_account_grant_key_rotation_permission) +- [Function `grant_key_offering_permission`](#0x1_account_grant_key_offering_permission) - [Function `initialize`](#0x1_account_initialize) - [Function `create_account_if_does_not_exist`](#0x1_account_create_account_if_does_not_exist) - [Function `create_account`](#0x1_account_create_account) @@ -110,6 +115,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; @@ -639,6 +645,55 @@ This V2 struct adds the chain_id + + + + +## Enum `AccountPermission` + + + +
enum AccountPermission has copy, drop, store
+
+ + + +
+Variants + + +
+KeyRotation + + +
+Fields + + +
+
+ + +
+ +
+ +
+Offering + + +
+Fields + + +
+
+ + +
+ +
+
@@ -798,6 +853,16 @@ The current authentication key and the new authentication key are the same + + +Current permissioned signer cannot perform the privilaged operations. + + +
const ENO_ACCOUNT_PERMISSION: u64 = 23;
+
+ + + The caller does not have a digital-signature-based capability to call this function @@ -926,6 +991,115 @@ Scheme identifier for MultiEd25519 signatures used to derive authentication keys + + +## Function `check_rotation_permission` + +Permissions + + +
fun check_rotation_permission(s: &signer)
+
+ + + +
+Implementation + + +
inline fun check_rotation_permission(s: &signer) {
+    assert!(
+        permissioned_signer::check_permission_exists(s, AccountPermission::KeyRotation {}),
+        error::permission_denied(ENO_ACCOUNT_PERMISSION),
+    );
+}
+
+ + + +
+ + + +## Function `check_offering_permission` + + + +
fun check_offering_permission(s: &signer)
+
+ + + +
+Implementation + + +
inline fun check_offering_permission(s: &signer) {
+    assert!(
+        permissioned_signer::check_permission_exists(s, AccountPermission::Offering {}),
+        error::permission_denied(ENO_ACCOUNT_PERMISSION),
+    );
+}
+
+ + + +
+ + + +## Function `grant_key_rotation_permission` + +Grant permission to perform key rotations on behalf of the master signer. + +This is **extremely dangerous** and should be granted only when it's absolutely needed. + + +
public fun grant_key_rotation_permission(master: &signer, permissioned_signer: &signer)
+
+ + + +
+Implementation + + +
public fun grant_key_rotation_permission(master: &signer, permissioned_signer: &signer) {
+    permissioned_signer::authorize_unlimited(master, permissioned_signer, AccountPermission::KeyRotation {})
+}
+
+ + + +
+ + + +## Function `grant_key_offering_permission` + +Grant permission to use offered address's signer on behalf of the master signer. + +This is **extremely dangerous** and should be granted only when it's absolutely needed. + + +
public fun grant_key_offering_permission(master: &signer, permissioned_signer: &signer)
+
+ + + +
+Implementation + + +
public fun grant_key_offering_permission(master: &signer, permissioned_signer: &signer) {
+    permissioned_signer::authorize_unlimited(master, permissioned_signer, AccountPermission::Offering {})
+}
+
+ + + +
+ ## Function `initialize` @@ -1256,6 +1430,7 @@ many contexts: vector::length(&new_auth_key) == 32, error::invalid_argument(EMALFORMED_AUTHENTICATION_KEY) ); + check_rotation_permission(account); let account_resource = borrow_global_mut<Account>(addr); account_resource.authentication_key = new_auth_key; } @@ -1351,6 +1526,7 @@ 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)); + check_rotation_permission(account); let account_resource = borrow_global_mut<Account>(addr); // Verify the given `from_public_key_bytes` matches this account's current authentication key. @@ -1426,6 +1602,7 @@ 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 { + check_rotation_permission(delegate_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. @@ -1505,6 +1682,7 @@ offer, calling this function will replace the previous recipient_addressvector<u8>, recipient_address: address, ) acquires Account { + check_rotation_permission(account); let addr = signer::address_of(account); assert!(exists_at(recipient_address), error::not_found(EACCOUNT_DOES_NOT_EXIST)); @@ -1683,6 +1861,7 @@ Revoke the rotation capability offer given to to_be_revoked_recipient_addr
public entry fun revoke_rotation_capability(account: &signer, to_be_revoked_address: address) acquires Account {
     assert!(exists_at(to_be_revoked_address), error::not_found(EACCOUNT_DOES_NOT_EXIST));
+    check_rotation_permission(account);
     let addr = signer::address_of(account);
     let account_resource = borrow_global<Account>(addr);
     assert!(
@@ -1714,6 +1893,7 @@ Revoke any rotation capability offer in the specified account.
 
 
 
public entry fun revoke_any_rotation_capability(account: &signer) acquires Account {
+    check_rotation_permission(account);
     let account_resource = borrow_global_mut<Account>(signer::address_of(account));
     option::extract(&mut account_resource.rotation_capability_offer.for);
 }
@@ -1754,6 +1934,7 @@ to the account owner's signer capability).
     account_public_key_bytes: vector<u8>,
     recipient_address: address
 ) acquires Account {
+    check_offering_permission(account);
     let source_address = signer::address_of(account);
     assert!(exists_at(recipient_address), error::not_found(EACCOUNT_DOES_NOT_EXIST));
 
@@ -1853,6 +2034,7 @@ has a signer capability offer from accoun
 
 
public entry fun revoke_signer_capability(account: &signer, to_be_revoked_address: address) acquires Account {
     assert!(exists_at(to_be_revoked_address), error::not_found(EACCOUNT_DOES_NOT_EXIST));
+    check_offering_permission(account);
     let addr = signer::address_of(account);
     let account_resource = borrow_global<Account>(addr);
     assert!(
@@ -1884,6 +2066,7 @@ Revoke any signer capability offer in the specified account.
 
 
 
public entry fun revoke_any_signer_capability(account: &signer) acquires Account {
+    check_offering_permission(account);
     let account_resource = borrow_global_mut<Account>(signer::address_of(account));
     option::extract(&mut account_resource.signer_capability_offer.for);
 }
@@ -1911,6 +2094,7 @@ at the offerer's address.
 
 
 
public fun create_authorized_signer(account: &signer, offerer_address: address): signer acquires Account {
+    check_offering_permission(account);
     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.
@@ -2514,8 +2698,8 @@ Capability based functions for efficient use.
 ### Module-level Specification
 
 
-
pragma verify = true;
-pragma aborts_if_is_strict;
+
pragma verify = false;
+pragma aborts_if_is_partial;
 
diff --git a/aptos-move/framework/aptos-framework/doc/big_ordered_map.md b/aptos-move/framework/aptos-framework/doc/big_ordered_map.md index dac43b4483ca1..2a32bfce29e72 100644 --- a/aptos-move/framework/aptos-framework/doc/big_ordered_map.md +++ b/aptos-move/framework/aptos-framework/doc/big_ordered_map.md @@ -363,51 +363,51 @@ The BigOrderedMap data structure. ## Constants - + -Map key already exists +Internal errors. -
const EKEY_ALREADY_EXISTS: u64 = 1;
+
const EINTERNAL_INVARIANT_BROKEN: u64 = 20;
 
- + -Map key is not found -
const EKEY_NOT_FOUND: u64 = 2;
+
const NULL_INDEX: u64 = 0;
 
- + -Internal errors. +Trying to do an operation on an IteratorPtr that would go out of bounds -
const EINTERNAL_INVARIANT_BROKEN: u64 = 20;
+
const EITER_OUT_OF_BOUNDS: u64 = 3;
 
- + +Map key already exists -
const NULL_INDEX: u64 = 0;
+
const EKEY_ALREADY_EXISTS: u64 = 1;
 
- + -Trying to do an operation on an IteratorPtr that would go out of bounds +Map key is not found -
const EITER_OUT_OF_BOUNDS: u64 = 3;
+
const EKEY_NOT_FOUND: u64 = 2;
 
diff --git a/aptos-move/framework/aptos-framework/doc/multisig_account.md b/aptos-move/framework/aptos-framework/doc/multisig_account.md index 0b6aa0e44dd7a..4e9e9de51191b 100644 --- a/aptos-move/framework/aptos-framework/doc/multisig_account.md +++ b/aptos-move/framework/aptos-framework/doc/multisig_account.md @@ -4166,7 +4166,8 @@ Add new owners, remove owners to remove, update signatures required. -
aborts_if !exists<account::Account>(creator);
+
pragma aborts_if_is_partial;
+aborts_if !exists<account::Account>(creator);
 let owner_nonce = global<account::Account>(creator).sequence_number;
 
diff --git a/aptos-move/framework/aptos-framework/doc/ordered_map.md b/aptos-move/framework/aptos-framework/doc/ordered_map.md index 85c456769972d..cd90763cdbfe8 100644 --- a/aptos-move/framework/aptos-framework/doc/ordered_map.md +++ b/aptos-move/framework/aptos-framework/doc/ordered_map.md @@ -221,31 +221,31 @@ TODO: Once fields can be (mutable) references, this class will be deprecated. ## Constants - + -Map key already exists -
const EKEY_ALREADY_EXISTS: u64 = 1;
+
const EITER_OUT_OF_BOUNDS: u64 = 3;
 
- + -Map key is not found +Map key already exists -
const EKEY_NOT_FOUND: u64 = 2;
+
const EKEY_ALREADY_EXISTS: u64 = 1;
 
- + +Map key is not found -
const EITER_OUT_OF_BOUNDS: u64 = 3;
+
const EKEY_NOT_FOUND: u64 = 2;
 
diff --git a/aptos-move/framework/aptos-framework/doc/resource_account.md b/aptos-move/framework/aptos-framework/doc/resource_account.md index 318d15a785de7..45d7d6ebc4914 100644 --- a/aptos-move/framework/aptos-framework/doc/resource_account.md +++ b/aptos-move/framework/aptos-framework/doc/resource_account.md @@ -468,7 +468,7 @@ the SignerCapability.
pragma verify = true;
-pragma aborts_if_is_strict;
+pragma aborts_if_is_partial;
 
@@ -547,7 +547,8 @@ the SignerCapability. -
let resource_addr = signer::address_of(resource);
+
pragma aborts_if_is_partial;
+let resource_addr = signer::address_of(resource);
 // This enforces high-level requirement 1:
 include RotateAccountAuthenticationKeyAndStoreCapabilityAbortsIf;
 // This enforces high-level requirement 2:
@@ -618,7 +619,8 @@ the SignerCapability.
 
 
 
-
// This enforces high-level requirement 6:
+
pragma aborts_if_is_partial;
+// This enforces high-level requirement 6:
 aborts_if !exists<Container>(source_addr);
 let resource_addr = signer::address_of(resource);
 let container = global<Container>(source_addr);
diff --git a/aptos-move/framework/aptos-framework/doc/staking_contract.md b/aptos-move/framework/aptos-framework/doc/staking_contract.md
index 09ecf20686f86..43d969b13b988 100644
--- a/aptos-move/framework/aptos-framework/doc/staking_contract.md
+++ b/aptos-move/framework/aptos-framework/doc/staking_contract.md
@@ -99,6 +99,7 @@ pool.
     -  [Function `pending_distribution_counts`](#@Specification_1_pending_distribution_counts)
     -  [Function `staking_contract_exists`](#@Specification_1_staking_contract_exists)
     -  [Function `beneficiary_for_operator`](#@Specification_1_beneficiary_for_operator)
+    -  [Function `get_expected_stake_pool_address`](#@Specification_1_get_expected_stake_pool_address)
     -  [Function `create_staking_contract`](#@Specification_1_create_staking_contract)
     -  [Function `create_staking_contract_with_coins`](#@Specification_1_create_staking_contract_with_coins)
     -  [Function `add_stake`](#@Specification_1_add_stake)
@@ -2958,35 +2959,52 @@ Staking_contract exists the stacker/operator pair.
 
 
 
+
 
-
+### Function `beneficiary_for_operator`
 
 
-
fun spec_staking_contract_exists(staker: address, operator: address): bool {
-   if (!exists<Store>(staker)) {
-       false
-   } else {
-       let store = global<Store>(staker);
-       simple_map::spec_contains_key(store.staking_contracts, operator)
-   }
-}
+
#[view]
+public fun beneficiary_for_operator(operator: address): address
 
- -### Function `beneficiary_for_operator` +
pragma verify = false;
+
+ + + + + +### Function `get_expected_stake_pool_address`
#[view]
-public fun beneficiary_for_operator(operator: address): address
+public fun get_expected_stake_pool_address(staker: address, operator: address, contract_creation_seed: vector<u8>): address
 
-
pragma verify = false;
+
pragma aborts_if_is_partial;
+
+ + + + + + + +
fun spec_staking_contract_exists(staker: address, operator: address): bool {
+   if (!exists<Store>(staker)) {
+       false
+   } else {
+       let store = global<Store>(staker);
+       simple_map::spec_contains_key(store.staking_contracts, operator)
+   }
+}
 
diff --git a/aptos-move/framework/aptos-framework/sources/account.move b/aptos-move/framework/aptos-framework/sources/account.move index 744ffa18861e9..9af50f3817e53 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; @@ -179,6 +180,8 @@ module aptos_framework::account { const ENEW_AUTH_KEY_ALREADY_MAPPED: u64 = 21; /// The current authentication key and the new authentication key are the same const ENEW_AUTH_KEY_SAME_AS_CURRENT: u64 = 22; + /// Current permissioned signer cannot perform the privilaged operations. + const ENO_ACCOUNT_PERMISSION: u64 = 23; /// Explicitly separate the GUID space between Object and Account to prevent accidental overlap. const MAX_GUID_CREATION_NUM: u64 = 0x4000000000000; @@ -187,6 +190,43 @@ module aptos_framework::account { /// Create signer for testing, independently of an Aptos-style `Account`. public fun create_signer_for_test(addr: address): signer { create_signer(addr) } + enum AccountPermission has copy, drop, store { + /// Permission to rotate a key. + KeyRotation, + /// Permission to offer another address to act like your address + Offering, + } + + /// Permissions + /// + inline fun check_rotation_permission(s: &signer) { + assert!( + permissioned_signer::check_permission_exists(s, AccountPermission::KeyRotation {}), + error::permission_denied(ENO_ACCOUNT_PERMISSION), + ); + } + + inline fun check_offering_permission(s: &signer) { + assert!( + permissioned_signer::check_permission_exists(s, AccountPermission::Offering {}), + error::permission_denied(ENO_ACCOUNT_PERMISSION), + ); + } + + /// Grant permission to perform key rotations on behalf of the master signer. + /// + /// This is **extremely dangerous** and should be granted only when it's absolutely needed. + public fun grant_key_rotation_permission(master: &signer, permissioned_signer: &signer) { + permissioned_signer::authorize_unlimited(master, permissioned_signer, AccountPermission::KeyRotation {}) + } + + /// Grant permission to use offered address's signer on behalf of the master signer. + /// + /// This is **extremely dangerous** and should be granted only when it's absolutely needed. + public fun grant_key_offering_permission(master: &signer, permissioned_signer: &signer) { + permissioned_signer::authorize_unlimited(master, permissioned_signer, AccountPermission::Offering {}) + } + /// Only called during genesis to initialize system resources for this module. public(friend) fun initialize(aptos_framework: &signer) { system_addresses::assert_aptos_framework(aptos_framework); @@ -302,6 +342,7 @@ module aptos_framework::account { vector::length(&new_auth_key) == 32, error::invalid_argument(EMALFORMED_AUTHENTICATION_KEY) ); + check_rotation_permission(account); let account_resource = borrow_global_mut(addr); account_resource.authentication_key = new_auth_key; } @@ -357,6 +398,7 @@ module aptos_framework::account { ) acquires Account, OriginatingAddress { let addr = signer::address_of(account); assert!(exists_at(addr), error::not_found(EACCOUNT_DOES_NOT_EXIST)); + check_rotation_permission(account); let account_resource = borrow_global_mut(addr); // Verify the given `from_public_key_bytes` matches this account's current authentication key. @@ -412,6 +454,7 @@ module aptos_framework::account { new_public_key_bytes: vector, cap_update_table: vector ) acquires Account, OriginatingAddress { + check_rotation_permission(delegate_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. @@ -471,6 +514,7 @@ module aptos_framework::account { account_public_key_bytes: vector, recipient_address: address, ) acquires Account { + check_rotation_permission(account); let addr = signer::address_of(account); assert!(exists_at(recipient_address), error::not_found(EACCOUNT_DOES_NOT_EXIST)); @@ -569,6 +613,7 @@ module aptos_framework::account { /// Revoke the rotation capability offer given to `to_be_revoked_recipient_address` from `account` public entry fun revoke_rotation_capability(account: &signer, to_be_revoked_address: address) acquires Account { assert!(exists_at(to_be_revoked_address), error::not_found(EACCOUNT_DOES_NOT_EXIST)); + check_rotation_permission(account); let addr = signer::address_of(account); let account_resource = borrow_global(addr); assert!( @@ -580,6 +625,7 @@ module aptos_framework::account { /// Revoke any rotation capability offer in the specified account. public entry fun revoke_any_rotation_capability(account: &signer) acquires Account { + check_rotation_permission(account); let account_resource = borrow_global_mut(signer::address_of(account)); option::extract(&mut account_resource.rotation_capability_offer.for); } @@ -600,6 +646,7 @@ module aptos_framework::account { account_public_key_bytes: vector, recipient_address: address ) acquires Account { + check_offering_permission(account); let source_address = signer::address_of(account); assert!(exists_at(recipient_address), error::not_found(EACCOUNT_DOES_NOT_EXIST)); @@ -639,6 +686,7 @@ module aptos_framework::account { /// has a signer capability offer from `account` but will be revoked in this function). public entry fun revoke_signer_capability(account: &signer, to_be_revoked_address: address) acquires Account { assert!(exists_at(to_be_revoked_address), error::not_found(EACCOUNT_DOES_NOT_EXIST)); + check_offering_permission(account); let addr = signer::address_of(account); let account_resource = borrow_global(addr); assert!( @@ -650,6 +698,7 @@ module aptos_framework::account { /// Revoke any signer capability offer in the specified account. public entry fun revoke_any_signer_capability(account: &signer) acquires Account { + check_offering_permission(account); let account_resource = borrow_global_mut(signer::address_of(account)); option::extract(&mut account_resource.signer_capability_offer.for); } @@ -657,6 +706,7 @@ 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 { + check_offering_permission(account); 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. @@ -1202,6 +1252,123 @@ module aptos_framework::account { assert!(signer::address_of(&signer) == signer::address_of(&alice), 0); } + #[test(bob = @0x345)] + public entry fun test_valid_check_signer_capability_and_create_authorized_signer_with_permission(bob: signer) acquires Account { + let (alice_sk, alice_pk) = ed25519::generate_keys(); + let alice_pk_bytes = ed25519::validated_public_key_to_bytes(&alice_pk); + let alice = create_account_from_ed25519_public_key(alice_pk_bytes); + let alice_addr = signer::address_of(&alice); + + let bob_addr = signer::address_of(&bob); + create_account(bob_addr); + + let challenge = SignerCapabilityOfferProofChallengeV2 { + sequence_number: borrow_global(alice_addr).sequence_number, + source_address: alice_addr, + recipient_address: bob_addr, + }; + + let alice_signer_capability_offer_sig = ed25519::sign_struct(&alice_sk, challenge); + + let alice_permission_handle = permissioned_signer::create_permissioned_handle(&alice); + let alice_permission_signer = permissioned_signer::signer_from_permissioned_handle(&alice_permission_handle); + + grant_key_offering_permission(&alice, &alice_permission_signer); + + offer_signer_capability( + &alice_permission_signer, + ed25519::signature_to_bytes(&alice_signer_capability_offer_sig), + 0, + alice_pk_bytes, + bob_addr + ); + + assert!(option::contains(&borrow_global(alice_addr).signer_capability_offer.for, &bob_addr), 0); + + let signer = create_authorized_signer(&bob, alice_addr); + assert!(signer::address_of(&signer) == signer::address_of(&alice), 0); + + permissioned_signer::destroy_permissioned_handle(alice_permission_handle); + } + + #[test(bob = @0x345)] + #[expected_failure(abort_code = 0x50017, location = Self)] + public entry fun test_valid_check_signer_capability_and_create_authorized_signer_with_no_permission(bob: signer) acquires Account { + let (alice_sk, alice_pk) = ed25519::generate_keys(); + let alice_pk_bytes = ed25519::validated_public_key_to_bytes(&alice_pk); + let alice = create_account_from_ed25519_public_key(alice_pk_bytes); + let alice_addr = signer::address_of(&alice); + + let bob_addr = signer::address_of(&bob); + create_account(bob_addr); + + let challenge = SignerCapabilityOfferProofChallengeV2 { + sequence_number: borrow_global(alice_addr).sequence_number, + source_address: alice_addr, + recipient_address: bob_addr, + }; + + let alice_signer_capability_offer_sig = ed25519::sign_struct(&alice_sk, challenge); + + let alice_permission_handle = permissioned_signer::create_permissioned_handle(&alice); + let alice_permission_signer = permissioned_signer::signer_from_permissioned_handle(&alice_permission_handle); + + offer_signer_capability( + &alice_permission_signer, + ed25519::signature_to_bytes(&alice_signer_capability_offer_sig), + 0, + alice_pk_bytes, + bob_addr + ); + + assert!(option::contains(&borrow_global(alice_addr).signer_capability_offer.for, &bob_addr), 0); + + let signer = create_authorized_signer(&bob, alice_addr); + assert!(signer::address_of(&signer) == signer::address_of(&alice), 0); + + permissioned_signer::destroy_permissioned_handle(alice_permission_handle); + } + + #[test(bob = @0x345)] + #[expected_failure(abort_code = 0x50017, location = Self)] + public entry fun test_valid_check_signer_capability_and_create_authorized_signer_with_wrong_permission(bob: signer) acquires Account { + let (alice_sk, alice_pk) = ed25519::generate_keys(); + let alice_pk_bytes = ed25519::validated_public_key_to_bytes(&alice_pk); + let alice = create_account_from_ed25519_public_key(alice_pk_bytes); + let alice_addr = signer::address_of(&alice); + + let bob_addr = signer::address_of(&bob); + create_account(bob_addr); + + let challenge = SignerCapabilityOfferProofChallengeV2 { + sequence_number: borrow_global(alice_addr).sequence_number, + source_address: alice_addr, + recipient_address: bob_addr, + }; + + let alice_signer_capability_offer_sig = ed25519::sign_struct(&alice_sk, challenge); + + let alice_permission_handle = permissioned_signer::create_permissioned_handle(&alice); + let alice_permission_signer = permissioned_signer::signer_from_permissioned_handle(&alice_permission_handle); + + grant_key_rotation_permission(&alice, &alice_permission_signer); + + offer_signer_capability( + &alice_permission_signer, + ed25519::signature_to_bytes(&alice_signer_capability_offer_sig), + 0, + alice_pk_bytes, + bob_addr + ); + + assert!(option::contains(&borrow_global(alice_addr).signer_capability_offer.for, &bob_addr), 0); + + let signer = create_authorized_signer(&bob, alice_addr); + assert!(signer::address_of(&signer) == signer::address_of(&alice), 0); + + permissioned_signer::destroy_permissioned_handle(alice_permission_handle); + } + #[test(bob = @0x345)] public entry fun test_get_signer_cap_and_is_signer_cap(bob: signer) acquires Account { let (alice_sk, alice_pk) = ed25519::generate_keys(); diff --git a/aptos-move/framework/aptos-framework/sources/account.spec.move b/aptos-move/framework/aptos-framework/sources/account.spec.move index 36e7a5740027e..d872dd82b0278 100644 --- a/aptos-move/framework/aptos-framework/sources/account.spec.move +++ b/aptos-move/framework/aptos-framework/sources/account.spec.move @@ -105,8 +105,8 @@ spec aptos_framework::account { /// spec module { - pragma verify = true; - pragma aborts_if_is_strict; + pragma verify = false; + pragma aborts_if_is_partial; } /// Only the address `@aptos_framework` can call. @@ -390,7 +390,6 @@ spec aptos_framework::account { source_address, recipient_address, }; - aborts_if !exists(@aptos_framework); aborts_if !exists(recipient_address); aborts_if !exists(source_address); diff --git a/aptos-move/framework/aptos-framework/sources/multisig_account.spec.move b/aptos-move/framework/aptos-framework/sources/multisig_account.spec.move index 1e2b60c3724ed..f9ebf96b25838 100644 --- a/aptos-move/framework/aptos-framework/sources/multisig_account.spec.move +++ b/aptos-move/framework/aptos-framework/sources/multisig_account.spec.move @@ -207,6 +207,7 @@ spec aptos_framework::multisig_account { } spec get_next_multisig_account_address(creator: address): address { + pragma aborts_if_is_partial; aborts_if !exists(creator); let owner_nonce = global(creator).sequence_number; } diff --git a/aptos-move/framework/aptos-framework/sources/resource_account.spec.move b/aptos-move/framework/aptos-framework/sources/resource_account.spec.move index 847e77853bdc4..e5cce243cd84b 100644 --- a/aptos-move/framework/aptos-framework/sources/resource_account.spec.move +++ b/aptos-move/framework/aptos-framework/sources/resource_account.spec.move @@ -60,7 +60,7 @@ spec aptos_framework::resource_account { /// spec module { pragma verify = true; - pragma aborts_if_is_strict; + pragma aborts_if_is_partial; } spec create_resource_account( @@ -116,6 +116,8 @@ spec aptos_framework::resource_account { resource_signer_cap: account::SignerCapability, optional_auth_key: vector, ) { + pragma aborts_if_is_partial; + let resource_addr = signer::address_of(resource); /// [high-level-req-1] include RotateAccountAuthenticationKeyAndStoreCapabilityAbortsIf; @@ -172,6 +174,8 @@ spec aptos_framework::resource_account { resource: &signer, source_addr: address, ) : account::SignerCapability { + pragma aborts_if_is_partial; + /// [high-level-req-6] aborts_if !exists(source_addr); let resource_addr = signer::address_of(resource); diff --git a/aptos-move/framework/aptos-framework/sources/staking_contract.spec.move b/aptos-move/framework/aptos-framework/sources/staking_contract.spec.move index e5ad85e92ae43..29120af5ffe2a 100644 --- a/aptos-move/framework/aptos-framework/sources/staking_contract.spec.move +++ b/aptos-move/framework/aptos-framework/sources/staking_contract.spec.move @@ -135,6 +135,10 @@ spec aptos_framework::staking_contract { ensures result == spec_staking_contract_exists(staker, operator); } + spec get_expected_stake_pool_address { + pragma aborts_if_is_partial; + } + spec fun spec_staking_contract_exists(staker: address, operator: address): bool { if (!exists(staker)) { false From 000d361565a3044ee62ff82eed84480989902092 Mon Sep 17 00:00:00 2001 From: runtianz Date: Wed, 15 Jan 2025 07:30:50 -0800 Subject: [PATCH 2/4] Update account.spec.move --- aptos-move/framework/aptos-framework/doc/account.md | 1 - aptos-move/framework/aptos-framework/sources/account.spec.move | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/aptos-move/framework/aptos-framework/doc/account.md b/aptos-move/framework/aptos-framework/doc/account.md index 3957fd601e366..79323892386bd 100644 --- a/aptos-move/framework/aptos-framework/doc/account.md +++ b/aptos-move/framework/aptos-framework/doc/account.md @@ -2699,7 +2699,6 @@ Capability based functions for efficient use.
pragma verify = false;
-pragma aborts_if_is_partial;
 
diff --git a/aptos-move/framework/aptos-framework/sources/account.spec.move b/aptos-move/framework/aptos-framework/sources/account.spec.move index d872dd82b0278..336d42363f69b 100644 --- a/aptos-move/framework/aptos-framework/sources/account.spec.move +++ b/aptos-move/framework/aptos-framework/sources/account.spec.move @@ -106,7 +106,8 @@ spec aptos_framework::account { spec module { pragma verify = false; - pragma aborts_if_is_partial; + + } /// Only the address `@aptos_framework` can call. From 3a6c8a2492177a7d58ca50f49de72655310a3b0d Mon Sep 17 00:00:00 2001 From: runtianz Date: Mon, 9 Sep 2024 10:02:05 -0700 Subject: [PATCH 3/4] add permissions for fungible assets operation --- .../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 + 18 files changed, 971 insertions(+), 13 deletions(-) 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;

From 24c73462ed8c136f1d07147c43ce313cb4ca0d7e Mon Sep 17 00:00:00 2001
From: runtianz 
Date: Tue, 14 Jan 2025 19:54:19 -0800
Subject: [PATCH 4/4] Removing master signer checks in coin

---
 .../aptos-framework/sources/coin.move         | 52 ++++++++++++-------
 1 file changed, 33 insertions(+), 19 deletions(-)

diff --git a/aptos-move/framework/aptos-framework/sources/coin.move b/aptos-move/framework/aptos-framework/sources/coin.move
index bb402660c533c..e34ce890be937 100644
--- a/aptos-move/framework/aptos-framework/sources/coin.move
+++ b/aptos-move/framework/aptos-framework/sources/coin.move
@@ -613,12 +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 {
-        permissioned_signer::assert_master_signer(account);
-        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.
@@ -967,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)
     }
 
@@ -978,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)
     }
@@ -990,9 +1004,9 @@ module aptos_framework::coin {
         decimals: u8,
         monitor_supply: bool,
         parallelizable: bool,
-    ): (BurnCapability, FreezeCapability, MintCapability) {
-        permissioned_signer::assert_master_signer(account);
+    ): (BurnCapability, FreezeCapability, MintCapability) acquires CoinInfo, CoinConversionMap {
         let account_addr = signer::address_of(account);
+        assert_signer_has_permission(account);
 
         assert!(
             coin_address() == account_addr,
@@ -1048,9 +1062,9 @@ module aptos_framework::coin {
         mint_internal(amount)
     }
 
-    public fun register(account: &signer) acquires CoinConversionMap {
-        permissioned_signer::assert_master_signer(account);
+    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
@@ -1244,7 +1258,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,
@@ -1260,7 +1274,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,
@@ -1377,7 +1391,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,
@@ -1474,7 +1488,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);
@@ -1514,7 +1528,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);
@@ -1640,7 +1654,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"),
@@ -1656,7 +1670,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"),
@@ -1674,14 +1688,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);
     }
 
@@ -1692,7 +1706,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);
 
@@ -1714,7 +1728,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);