diff --git a/aptos-move/aptos-gas-schedule/src/gas_schedule/aptos_framework.rs b/aptos-move/aptos-gas-schedule/src/gas_schedule/aptos_framework.rs index d48c19c67e04b..b9bb7deccfe55 100644 --- a/aptos-move/aptos-gas-schedule/src/gas_schedule/aptos_framework.rs +++ b/aptos-move/aptos-gas-schedule/src/gas_schedule/aptos_framework.rs @@ -6,7 +6,7 @@ use crate::{ gas_feature_versions::{RELEASE_V1_14, RELEASE_V1_8, RELEASE_V1_9_SKIPPED}, gas_schedule::NativeGasParameters, - ver::gas_feature_versions::{RELEASE_V1_12, RELEASE_V1_13, RELEASE_V1_23}, + ver::gas_feature_versions::{RELEASE_V1_12, RELEASE_V1_13, RELEASE_V1_23, RELEASE_V1_26}, }; use aptos_gas_algebra::{ InternalGas, InternalGasPerAbstractValueUnit, InternalGasPerArg, InternalGasPerByte, @@ -20,6 +20,11 @@ crate::gas_schedule::macros::define_gas_parameters!( [account_create_address_base: InternalGas, "account.create_address.base", 1102], [account_create_signer_base: InternalGas, "account.create_signer.base", 1102], + // Permissioned signer gas parameters + [permission_address_base: InternalGas, { RELEASE_V1_26 => "permissioned_signer.permission_address.base"}, 1102], + [is_permissioned_signer_base: InternalGas, { RELEASE_V1_26 => "permissioned_signer.is_permissioned_signer.base"}, 1102], + [signer_from_permissioned_handle_base: InternalGas, { RELEASE_V1_26 => "permissioned_signer.signer_from_permissioned_handle.base"}, 1102], + // BN254 algebra gas parameters begin. // Generated at time 1701559125.5498126 by `scripts/algebra-gas/update_bn254_algebra_gas_params.py` with gas_per_ns=209.10511688369482. [algebra_ark_bn254_fq12_add: InternalGas, { 12.. => "algebra.ark_bn254_fq12_add" }, 809], diff --git a/aptos-move/aptos-gas-schedule/src/ver.rs b/aptos-move/aptos-gas-schedule/src/ver.rs index 9e40e1d833880..feb190e3b8e0e 100644 --- a/aptos-move/aptos-gas-schedule/src/ver.rs +++ b/aptos-move/aptos-gas-schedule/src/ver.rs @@ -89,4 +89,5 @@ pub mod gas_feature_versions { pub const RELEASE_V1_22: u64 = 26; pub const RELEASE_V1_23: u64 = 27; pub const RELEASE_V1_24: u64 = 28; + pub const RELEASE_V1_26: u64 = 29; } diff --git a/aptos-move/aptos-release-builder/data/permissioned_signer.yaml b/aptos-move/aptos-release-builder/data/permissioned_signer.yaml new file mode 100644 index 0000000000000..2212b4e38ca83 --- /dev/null +++ b/aptos-move/aptos-release-builder/data/permissioned_signer.yaml @@ -0,0 +1,13 @@ +--- +remote_endpoint: ~ +name: "v1.26-enable-permissioned_signer" +proposals: + - name: feature_flags + metadata: + title: "Enable permissioned signer feature flag" + description: "Enable permissioned signer in the aptos framework" + execution_mode: MultiStep + update_sequence: + - FeatureFlag: + enabled: + - permissioned_signer \ No newline at end of file diff --git a/aptos-move/aptos-release-builder/src/components/feature_flags.rs b/aptos-move/aptos-release-builder/src/components/feature_flags.rs index b4334bbcd1322..0b669d2198bf1 100644 --- a/aptos-move/aptos-release-builder/src/components/feature_flags.rs +++ b/aptos-move/aptos-release-builder/src/components/feature_flags.rs @@ -134,6 +134,7 @@ pub enum FeatureFlag { NativeMemoryOperations, EnableLoaderV2, DisallowInitModuleToPublishModules, + PermissionedSigner, } fn generate_features_blob(writer: &CodeWriter, data: &[u64]) { @@ -357,6 +358,7 @@ impl From for AptosFeatureFlag { FeatureFlag::DisallowInitModuleToPublishModules => { AptosFeatureFlag::DISALLOW_INIT_MODULE_TO_PUBLISH_MODULES }, + FeatureFlag::PermissionedSigner => AptosFeatureFlag::PERMISSIONED_SIGNER, } } } @@ -507,6 +509,7 @@ impl From for FeatureFlag { AptosFeatureFlag::DISALLOW_INIT_MODULE_TO_PUBLISH_MODULES => { FeatureFlag::DisallowInitModuleToPublishModules }, + AptosFeatureFlag::PERMISSIONED_SIGNER => FeatureFlag::PermissionedSigner, } } } diff --git a/aptos-move/e2e-benchmark/src/main.rs b/aptos-move/e2e-benchmark/src/main.rs index 7a9691e0e7e69..2c78740f10329 100644 --- a/aptos-move/e2e-benchmark/src/main.rs +++ b/aptos-move/e2e-benchmark/src/main.rs @@ -253,6 +253,8 @@ fn main() { repeats: 100, map_type: MapType::OrderedMap, }, + EntryPoints::APTPermissionedTransfer, + EntryPoints::APTTransfer, ]; let mut failures = Vec::new(); diff --git a/aptos-move/e2e-move-tests/src/tests/gas.rs b/aptos-move/e2e-move-tests/src/tests/gas.rs index ceab7b3508fa9..5af30e8956693 100644 --- a/aptos-move/e2e-move-tests/src/tests/gas.rs +++ b/aptos-move/e2e-move-tests/src/tests/gas.rs @@ -28,7 +28,7 @@ use aptos_types::{ transaction::{EntryFunction, TransactionPayload}, }; use aptos_vm_environment::prod_configs::set_paranoid_type_checks; -use move_core_types::{identifier::Identifier, language_storage::ModuleId}; +use move_core_types::{identifier::Identifier, language_storage::ModuleId, value::MoveValue}; use rand::{rngs::StdRng, SeedableRng}; use sha3::{Digest, Sha3_512}; use std::path::Path; @@ -55,7 +55,9 @@ fn test_modify_gas_schedule_check_hash() { "set_for_next_epoch_check_hash", vec![], vec![ - bcs::to_bytes(&CORE_CODE_ADDRESS).unwrap(), + MoveValue::Signer(CORE_CODE_ADDRESS) + .simple_serialize() + .unwrap(), bcs::to_bytes(&old_hash).unwrap(), bcs::to_bytes(&bcs::to_bytes(&gas_schedule).unwrap()).unwrap(), ], @@ -64,7 +66,9 @@ fn test_modify_gas_schedule_check_hash() { harness .executor .exec("reconfiguration_with_dkg", "finish", vec![], vec![ - bcs::to_bytes(&CORE_CODE_ADDRESS).unwrap(), + MoveValue::Signer(CORE_CODE_ADDRESS) + .simple_serialize() + .unwrap(), ]); let (_, gas_params) = harness.get_gas_params(); diff --git a/aptos-move/e2e-move-tests/src/tests/script_with_signer.data/pack/Move.toml b/aptos-move/e2e-move-tests/src/tests/script_with_signer.data/pack/Move.toml new file mode 100644 index 0000000000000..55c9545287791 --- /dev/null +++ b/aptos-move/e2e-move-tests/src/tests/script_with_signer.data/pack/Move.toml @@ -0,0 +1,5 @@ +[package] +name = "test" +version = "0.0.0" + +[dependencies] diff --git a/aptos-move/e2e-move-tests/src/tests/script_with_signer.data/pack/sources/test.move b/aptos-move/e2e-move-tests/src/tests/script_with_signer.data/pack/sources/test.move new file mode 100644 index 0000000000000..0282617af103e --- /dev/null +++ b/aptos-move/e2e-move-tests/src/tests/script_with_signer.data/pack/sources/test.move @@ -0,0 +1,3 @@ +script { + fun main(s1: &signer, u: u64, s2: &signer) {} +} diff --git a/aptos-move/e2e-move-tests/src/tests/scripts.rs b/aptos-move/e2e-move-tests/src/tests/scripts.rs index 40395dba22ea4..4970695c461ed 100644 --- a/aptos-move/e2e-move-tests/src/tests/scripts.rs +++ b/aptos-move/e2e-move-tests/src/tests/scripts.rs @@ -6,9 +6,9 @@ use aptos_language_e2e_tests::account::TransactionBuilder; use aptos_types::{ account_address::AccountAddress, on_chain_config::FeatureFlag, - transaction::{Script, TransactionArgument}, + transaction::{Script, TransactionArgument, TransactionStatus}, }; -use move_core_types::language_storage::TypeTag; +use move_core_types::{language_storage::TypeTag, value::MoveValue}; #[test] fn test_script_with_object_parameter() { @@ -146,6 +146,45 @@ fn test_script_with_type_parameter() { assert_success!(status); } +#[test] +fn test_script_with_signer_parameter() { + let mut h = MoveHarness::new(); + + let alice = h.new_account_at(AccountAddress::from_hex_literal("0xa11ce").unwrap()); + + let package = build_package( + common::test_dir_path("script_with_signer.data/pack"), + aptos_framework::BuildOptions::default(), + ) + .expect("building package must succeed"); + + let code = package.extract_script_code().into_iter().next().unwrap(); + + let txn = TransactionBuilder::new(alice.clone()) + .script(Script::new(code, vec![], vec![ + TransactionArgument::U64(0), + TransactionArgument::Serialized( + MoveValue::Signer(*alice.address()) + .simple_serialize() + .unwrap(), + ), + ])) + .sequence_number(10) + .max_gas_amount(1_000_000) + .gas_unit_price(1) + .sign(); + + let status = h.run(txn); + assert_eq!( + status, + TransactionStatus::Keep( + aptos_types::transaction::ExecutionStatus::MiscellaneousError(Some( + aptos_types::vm_status::StatusCode::INVALID_MAIN_FUNCTION_SIGNATURE + )) + ) + ); +} + #[test] fn test_two_to_two_transfer() { let mut h = MoveHarness::new(); diff --git a/aptos-move/e2e-tests/src/executor.rs b/aptos-move/e2e-tests/src/executor.rs index 2d3b78221b400..c2039a589d6d6 100644 --- a/aptos-move/e2e-tests/src/executor.rs +++ b/aptos-move/e2e-tests/src/executor.rs @@ -74,6 +74,7 @@ use move_core_types::{ identifier::Identifier, language_storage::{ModuleId, StructTag, TypeTag}, move_resource::{MoveResource, MoveStructType}, + value::MoveValue, }; use move_vm_runtime::{ module_traversal::{TraversalContext, TraversalStorage}, @@ -1042,13 +1043,23 @@ impl FakeExecutor { let mut arg = args.clone(); match &dynamic_args { ExecFuncTimerDynamicArgs::DistinctSigners => { - arg.insert(0, bcs::to_bytes(&extra_accounts.pop().unwrap()).unwrap()); + arg.insert( + 0, + MoveValue::Signer(extra_accounts.pop().unwrap()) + .simple_serialize() + .unwrap(), + ); }, ExecFuncTimerDynamicArgs::DistinctSignersAndFixed(signers) => { for signer in signers.iter().rev() { - arg.insert(0, bcs::to_bytes(&signer).unwrap()); + arg.insert(0, MoveValue::Signer(*signer).simple_serialize().unwrap()); } - arg.insert(0, bcs::to_bytes(&extra_accounts.pop().unwrap()).unwrap()); + arg.insert( + 0, + MoveValue::Signer(extra_accounts.pop().unwrap()) + .simple_serialize() + .unwrap(), + ); }, _ => {}, } diff --git a/aptos-move/framework/aptos-framework/doc/account.md b/aptos-move/framework/aptos-framework/doc/account.md index 9566844a0f739..447fb207802d1 100644 --- a/aptos-move/framework/aptos-framework/doc/account.md +++ b/aptos-move/framework/aptos-framework/doc/account.md @@ -19,7 +19,10 @@ - [Struct `SignerCapabilityOfferProofChallenge`](#0x1_account_SignerCapabilityOfferProofChallenge) - [Struct `RotationCapabilityOfferProofChallengeV2`](#0x1_account_RotationCapabilityOfferProofChallengeV2) - [Struct `SignerCapabilityOfferProofChallengeV2`](#0x1_account_SignerCapabilityOfferProofChallengeV2) +- [Struct `AccountPermission`](#0x1_account_AccountPermission) - [Constants](#@Constants_0) +- [Function `check_signer_permission`](#0x1_account_check_signer_permission) +- [Function `grant_permission`](#0x1_account_grant_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 +113,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 +643,33 @@ This V2 struct adds the chain_id + + + + +## Struct `AccountPermission` + + + +
struct AccountPermission has copy, drop, store
+
+ + + +
+Fields + + +
+
+dummy_field: bool +
+
+ +
+
+ +
@@ -798,6 +829,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 +967,61 @@ Scheme identifier for MultiEd25519 signatures used to derive authentication keys + + +## Function `check_signer_permission` + +Permissions + + +
fun check_signer_permission(s: &signer)
+
+ + + +
+Implementation + + +
inline fun check_signer_permission(s: &signer) {
+    assert!(
+        permissioned_signer::check_permission_exists(s, AccountPermission {}),
+        error::permission_denied(ENO_ACCOUNT_PERMISSION),
+    );
+}
+
+ + + +
+ + + +## Function `grant_permission` + +Grant permission to perform key rotations on behalf of the master signer. + +This is **extermely dangerous** and should be granted only when it's absolutely needed. + + +
public fun grant_permission(master: &signer, permissioned_signer: &signer)
+
+ + + +
+Implementation + + +
public fun grant_permission(master: &signer, permissioned_signer: &signer) {
+    permissioned_signer::authorize_unlimited(master, permissioned_signer, AccountPermission {})
+}
+
+ + + +
+ ## Function `initialize` @@ -1256,6 +1352,7 @@ many contexts: vector::length(&new_auth_key) == 32, error::invalid_argument(EMALFORMED_AUTHENTICATION_KEY) ); + check_signer_permission(account); let account_resource = borrow_global_mut<Account>(addr); account_resource.authentication_key = new_auth_key; } @@ -1351,6 +1448,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_signer_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 +1524,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_signer_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 +1604,7 @@ offer, calling this function will replace the previous recipient_addressvector<u8>, recipient_address: address, ) acquires Account { + check_signer_permission(account); let addr = signer::address_of(account); assert!(exists_at(recipient_address), error::not_found(EACCOUNT_DOES_NOT_EXIST)); @@ -1683,6 +1783,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_signer_permission(account);
     let addr = signer::address_of(account);
     let account_resource = borrow_global<Account>(addr);
     assert!(
@@ -1714,6 +1815,7 @@ Revoke any rotation capability offer in the specified account.
 
 
 
public entry fun revoke_any_rotation_capability(account: &signer) acquires Account {
+    check_signer_permission(account);
     let account_resource = borrow_global_mut<Account>(signer::address_of(account));
     option::extract(&mut account_resource.rotation_capability_offer.for);
 }
@@ -1754,6 +1856,7 @@ to the account owner's signer capability).
     account_public_key_bytes: vector<u8>,
     recipient_address: address
 ) acquires Account {
+    check_signer_permission(account);
     let source_address = signer::address_of(account);
     assert!(exists_at(recipient_address), error::not_found(EACCOUNT_DOES_NOT_EXIST));
 
@@ -1853,6 +1956,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_signer_permission(account);
     let addr = signer::address_of(account);
     let account_resource = borrow_global<Account>(addr);
     assert!(
@@ -1884,6 +1988,7 @@ Revoke any signer capability offer in the specified account.
 
 
 
public entry fun revoke_any_signer_capability(account: &signer) acquires Account {
+    check_signer_permission(account);
     let account_resource = borrow_global_mut<Account>(signer::address_of(account));
     option::extract(&mut account_resource.signer_capability_offer.for);
 }
@@ -1911,6 +2016,7 @@ at the offerer's address.
 
 
 
public fun create_authorized_signer(account: &signer, offerer_address: address): signer acquires Account {
+    check_signer_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.
@@ -2754,6 +2860,7 @@ The length of new_auth_key is 32.
 let post account_resource = global<Account>(addr);
 aborts_if !exists<Account>(addr);
 aborts_if vector::length(new_auth_key) != 32;
+aborts_if permissioned_signer::spec_is_permissioned_signer(account);
 modifies global<Account>(addr);
 ensures account_resource.authentication_key == new_auth_key;
 
@@ -2776,6 +2883,7 @@ The length of new_auth_key is 32. let post account_resource = global<Account>(addr); aborts_if !exists<Account>(addr); aborts_if vector::length(new_auth_key) != 32; +aborts_if permissioned_signer::spec_is_permissioned_signer(account); modifies global<Account>(addr); ensures account_resource.authentication_key == new_auth_key;
@@ -2807,6 +2915,7 @@ The authentication scheme is ED25519_SCHEME and MULTI_ED25519_SCHEME
let addr = signer::address_of(account);
 let account_resource = global<Account>(addr);
 aborts_if !exists<Account>(addr);
+aborts_if permissioned_signer::spec_is_permissioned_signer(account);
 // This enforces high-level requirement 6:
 include from_scheme == ED25519_SCHEME ==> ed25519::NewUnvalidatedPublicKeyFromBytesAbortsIf { bytes: from_public_key_bytes };
 aborts_if from_scheme == ED25519_SCHEME && ({
@@ -2871,7 +2980,8 @@ The authentication scheme is ED25519_SCHEME and MULTI_ED25519_SCHEME
 
 
 
-
aborts_if !exists<Account>(rotation_cap_offerer_address);
+
aborts_if permissioned_signer::spec_is_permissioned_signer(delegate_signer);
+aborts_if !exists<Account>(rotation_cap_offerer_address);
 let delegate_address = signer::address_of(delegate_signer);
 let offerer_account_resource = global<Account>(rotation_cap_offerer_address);
 aborts_if !from_bcs::deserializable<address>(offerer_account_resource.authentication_key);
@@ -2930,6 +3040,7 @@ The authentication scheme is ED25519_SCHEME and MULTI_ED25519_SCHEME
     source_address,
     recipient_address,
 };
+aborts_if permissioned_signer::spec_is_permissioned_signer(account);
 aborts_if !exists<chain_id::ChainId>(@aptos_framework);
 aborts_if !exists<Account>(recipient_address);
 aborts_if !exists<Account>(source_address);
@@ -3028,7 +3139,8 @@ The authentication scheme is ED25519_SCHEME and MULTI_ED25519_SCHEME
 
 
 
-
aborts_if !exists<Account>(to_be_revoked_address);
+
aborts_if permissioned_signer::spec_is_permissioned_signer(account);
+aborts_if !exists<Account>(to_be_revoked_address);
 let addr = signer::address_of(account);
 let account_resource = global<Account>(addr);
 aborts_if !exists<Account>(addr);
@@ -3052,7 +3164,8 @@ The authentication scheme is ED25519_SCHEME and MULTI_ED25519_SCHEME
 
 
 
-
let addr = signer::address_of(account);
+
aborts_if permissioned_signer::spec_is_permissioned_signer(account);
+let addr = signer::address_of(account);
 modifies global<Account>(addr);
 aborts_if !exists<Account>(addr);
 let account_resource = global<Account>(addr);
@@ -3084,6 +3197,7 @@ The authentication scheme is ED25519_SCHEME and MULTI_ED25519_SCHEME.
     source_address,
     recipient_address,
 };
+aborts_if permissioned_signer::spec_is_permissioned_signer(account);
 aborts_if !exists<Account>(recipient_address);
 aborts_if !exists<Account>(source_address);
 include account_scheme == ED25519_SCHEME ==> ed25519::NewUnvalidatedPublicKeyFromBytesAbortsIf { bytes: account_public_key_bytes };
@@ -3167,7 +3281,8 @@ The Account existed under the signer.
 The value of signer_capability_offer.for of Account resource under the signer is to_be_revoked_address.
 
 
-
aborts_if !exists<Account>(to_be_revoked_address);
+
aborts_if permissioned_signer::spec_is_permissioned_signer(account);
+aborts_if !exists<Account>(to_be_revoked_address);
 let addr = signer::address_of(account);
 let account_resource = global<Account>(addr);
 aborts_if !exists<Account>(addr);
@@ -3190,6 +3305,7 @@ The value of signer_capability_offer.for of Account resource under the signer is
 
 
 
modifies global<Account>(signer::address_of(account));
+aborts_if permissioned_signer::spec_is_permissioned_signer(account);
 // This enforces high-level requirement 7:
 aborts_if !exists<Account>(signer::address_of(account));
 let account_resource = global<Account>(signer::address_of(account));
@@ -3211,7 +3327,8 @@ The Account existed under the signer.
 The value of signer_capability_offer.for of Account resource under the signer is offerer_address.
 
 
-
// This enforces high-level requirement 8:
+
aborts_if permissioned_signer::spec_is_permissioned_signer(account);
+// This enforces high-level requirement 8:
 include AccountContainsAddr{
     account,
     address: offerer_address,
@@ -3372,6 +3489,8 @@ The value of signer_capability_offer.for of Account resource under the signer is
 
 
let source_addr = signer::address_of(source);
 let resource_addr = spec_create_resource_address(source_addr, seed);
+let resource = create_signer::spec_create_signer(resource_addr);
+aborts_if permissioned_signer::spec_is_permissioned_signer(resource);
 aborts_if len(ZERO_AUTH_KEY) != 32;
 include exists_at(resource_addr) ==> CreateResourceAccountAbortsIf;
 include !exists_at(resource_addr) ==> CreateAccountAbortsIf {addr: resource_addr};
diff --git a/aptos-move/framework/aptos-framework/doc/aptos_coin.md b/aptos-move/framework/aptos-framework/doc/aptos_coin.md
index 30f3eae067ed8..3e8e237398f2f 100644
--- a/aptos-move/framework/aptos-framework/doc/aptos_coin.md
+++ b/aptos-move/framework/aptos-framework/doc/aptos_coin.md
@@ -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/aptos_governance.md b/aptos-move/framework/aptos-framework/doc/aptos_governance.md
index 0edd157f955d2..ab21d466a4e3c 100644
--- a/aptos-move/framework/aptos-framework/doc/aptos_governance.md
+++ b/aptos-move/framework/aptos-framework/doc/aptos_governance.md
@@ -29,7 +29,10 @@ on a proposal multiple times as long as the total voting power of these votes do
 -  [Struct `CreateProposal`](#0x1_aptos_governance_CreateProposal)
 -  [Struct `Vote`](#0x1_aptos_governance_Vote)
 -  [Struct `UpdateConfig`](#0x1_aptos_governance_UpdateConfig)
+-  [Struct `GovernancePermission`](#0x1_aptos_governance_GovernancePermission)
 -  [Constants](#@Constants_0)
+-  [Function `check_signer_permission`](#0x1_aptos_governance_check_signer_permission)
+-  [Function `grant_permission`](#0x1_aptos_governance_grant_permission)
 -  [Function `store_signer_cap`](#0x1_aptos_governance_store_signer_cap)
 -  [Function `initialize`](#0x1_aptos_governance_initialize)
 -  [Function `update_governance_config`](#0x1_aptos_governance_update_governance_config)
@@ -109,6 +112,7 @@ on a proposal multiple times as long as the total voting power of these votes do
 use 0x1::governance_proposal;
 use 0x1::math64;
 use 0x1::option;
+use 0x1::permissioned_signer;
 use 0x1::randomness_config;
 use 0x1::reconfiguration_with_dkg;
 use 0x1::signer;
@@ -642,6 +646,33 @@ Event emitted when the governance configs are updated.
 
 
 
+
+
+
+
+## Struct `GovernancePermission`
+
+
+
+
struct GovernancePermission has copy, drop, store
+
+ + + +
+Fields + + +
+
+dummy_field: bool +
+
+ +
+
+ +
@@ -738,6 +769,16 @@ The proposal in the argument is not a partial voting proposal. + + +Current permissioned signer cannot perform governance operations. + + +
const ENO_GOVERNANCE_PERMISSION: u64 = 15;
+
+ + + The specified stake pool must be part of the validator set @@ -827,6 +868,59 @@ Proposal metadata attribute keys. + + +## Function `check_signer_permission` + +Permissions + + +
fun check_signer_permission(s: &signer)
+
+ + + +
+Implementation + + +
inline fun check_signer_permission(s: &signer) {
+    assert!(
+        permissioned_signer::check_permission_exists(s, GovernancePermission {}),
+        error::permission_denied(ENO_GOVERNANCE_PERMISSION),
+    );
+}
+
+ + + +
+ + + +## Function `grant_permission` + +Grant permission to perform governance operations on behalf of the master signer. + + +
public fun grant_permission(master: &signer, permissioned_signer: &signer)
+
+ + + +
+Implementation + + +
public fun grant_permission(master: &signer, permissioned_signer: &signer) {
+    permissioned_signer::authorize_unlimited(master, permissioned_signer, GovernancePermission {})
+}
+
+ + + +
+ ## Function `store_signer_cap` @@ -1310,6 +1404,7 @@ Return proposal_id when a proposal is successfully created. metadata_hash: vector<u8>, is_multi_step_proposal: bool, ): u64 acquires GovernanceConfig, GovernanceEvents { + check_signer_permission(proposer); let proposer_address = signer::address_of(proposer); assert!( stake::get_delegated_voter(stake_pool) == proposer_address, @@ -1542,6 +1637,7 @@ cannot vote on the proposal even after partial governance voting is enabled. voting_power: u64, should_pass: bool, ) acquires ApprovedExecutionHashes, VotingRecords, VotingRecordsV2, GovernanceEvents { + permissioned_signer::assert_master_signer(voter); let voter_address = signer::address_of(voter); assert!(stake::get_delegated_voter(stake_pool) == voter_address, error::invalid_argument(ENOT_DELEGATED_VOTER)); @@ -2163,6 +2259,7 @@ Limit addition overflow.
let addr = signer::address_of(aptos_framework);
 let register_account = global<account::Account>(addr);
+aborts_if permissioned_signer::spec_is_permissioned_signer(aptos_framework);
 aborts_if exists<voting::VotingForum<GovernanceProposal>>(addr);
 aborts_if !exists<account::Account>(addr);
 aborts_if register_account.guid_creation_num + 7 > MAX_U64;
diff --git a/aptos-move/framework/aptos-framework/doc/code.md b/aptos-move/framework/aptos-framework/doc/code.md
index ce18af9e16fbc..8f08922aae674 100644
--- a/aptos-move/framework/aptos-framework/doc/code.md
+++ b/aptos-move/framework/aptos-framework/doc/code.md
@@ -12,8 +12,11 @@ This module supports functionality related to code management.
 -  [Struct `ModuleMetadata`](#0x1_code_ModuleMetadata)
 -  [Struct `UpgradePolicy`](#0x1_code_UpgradePolicy)
 -  [Struct `PublishPackage`](#0x1_code_PublishPackage)
+-  [Struct `CodePermission`](#0x1_code_CodePermission)
 -  [Struct `AllowedDep`](#0x1_code_AllowedDep)
 -  [Constants](#@Constants_0)
+-  [Function `check_signer_permission`](#0x1_code_check_signer_permission)
+-  [Function `grant_permission`](#0x1_code_grant_permission)
 -  [Function `upgrade_policy_arbitrary`](#0x1_code_upgrade_policy_arbitrary)
 -  [Function `upgrade_policy_compat`](#0x1_code_upgrade_policy_compat)
 -  [Function `upgrade_policy_immutable`](#0x1_code_upgrade_policy_immutable)
@@ -50,6 +53,7 @@ This module supports functionality related to code management.
 use 0x1::features;
 use 0x1::object;
 use 0x1::option;
+use 0x1::permissioned_signer;
 use 0x1::signer;
 use 0x1::string;
 use 0x1::system_addresses;
@@ -300,6 +304,33 @@ Event emitted when code is published to an address.
 
 
 
+
+
+
+
+## Struct `CodePermission`
+
+
+
+
struct CodePermission has copy, drop, store
+
+ + + +
+Fields + + +
+
+dummy_field: bool +
+
+ +
+
+ +
@@ -413,6 +444,16 @@ Not the owner of the package registry. + + +Current permissioned signer cannot publish codes. + + +
const ENO_CODE_PERMISSION: u64 = 11;
+
+ + + Dependency could not be resolved to any published package. @@ -443,6 +484,59 @@ Cannot downgrade a package's upgradability policy + + +## Function `check_signer_permission` + +Permissions + + +
fun check_signer_permission(s: &signer)
+
+ + + +
+Implementation + + +
inline fun check_signer_permission(s: &signer) {
+    assert!(
+        permissioned_signer::check_permission_exists(s, CodePermission {}),
+        error::permission_denied(ENO_CODE_PERMISSION),
+    );
+}
+
+ + + +
+ + + +## Function `grant_permission` + +Grant permission to publish code on behalf of the master signer. + + +
public fun grant_permission(master: &signer, permissioned_signer: &signer)
+
+ + + +
+Implementation + + +
public fun grant_permission(master: &signer, permissioned_signer: &signer) {
+    permissioned_signer::authorize_unlimited(master, permissioned_signer, CodePermission {})
+}
+
+ + + +
+ ## Function `upgrade_policy_arbitrary` @@ -598,6 +692,7 @@ package.
public fun publish_package(owner: &signer, pack: PackageMetadata, code: vector<vector<u8>>) acquires PackageRegistry {
+    check_signer_permission(owner);
     // Disallow incompatible upgrade mode. Governance can decide later if this should be reconsidered.
     assert!(
         pack.upgrade_policy.policy > upgrade_policy_arbitrary().policy,
@@ -679,6 +774,7 @@ package.
 
 
 
public fun freeze_code_object(publisher: &signer, code_object: Object<PackageRegistry>) acquires PackageRegistry {
+    check_signer_permission(publisher);
     let code_object_addr = object::object_address(&code_object);
     assert!(exists<PackageRegistry>(code_object_addr), error::not_found(ECODE_OBJECT_DOES_NOT_EXIST));
     assert!(
diff --git a/aptos-move/framework/aptos-framework/doc/coin.md b/aptos-move/framework/aptos-framework/doc/coin.md
index bbd15e38568ae..f1f40ee4f68b5 100644
--- a/aptos-move/framework/aptos-framework/doc/coin.md
+++ b/aptos-move/framework/aptos-framework/doc/coin.md
@@ -146,6 +146,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;
@@ -2096,6 +2097,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));
 }
 
@@ -2985,6 +2987,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!( @@ -3102,6 +3105,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)) {
@@ -3205,6 +3209,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,
+                object::object_address(&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,
@@ -4195,7 +4210,8 @@ The creator of CoinType must be @aptos_framework.
 
 
 
-
let account_addr = signer::address_of(account);
+
aborts_if permissioned_signer::spec_is_permissioned_signer(account);
+let account_addr = signer::address_of(account);
 // This enforces high-level requirement 1:
 aborts_if type_info::type_of<CoinType>().account_address != account_addr;
 // This enforces high-level requirement 2:
@@ -4217,7 +4233,8 @@ The creator of CoinType must be @aptos_framework.
 
 
 
-
let addr = signer::address_of(account);
+
aborts_if permissioned_signer::spec_is_permissioned_signer(account);
+let addr = signer::address_of(account);
 aborts_if addr != @aptos_framework;
 aborts_if monitor_supply && !exists<aggregator_factory::AggregatorFactory>(@aptos_framework);
 include InitializeInternalSchema<CoinType> {
@@ -4240,7 +4257,8 @@ The creator of CoinType must be @aptos_framework.
 
 
 
-
include InitializeInternalSchema<CoinType> {
+
aborts_if permissioned_signer::spec_is_permissioned_signer(account);
+include InitializeInternalSchema<CoinType> {
     name: name.bytes,
     symbol: symbol.bytes
 };
diff --git a/aptos-move/framework/aptos-framework/doc/delegation_pool.md b/aptos-move/framework/aptos-framework/doc/delegation_pool.md
index c17ab31779be0..dc3d6cd8ad0e4 100644
--- a/aptos-move/framework/aptos-framework/doc/delegation_pool.md
+++ b/aptos-move/framework/aptos-framework/doc/delegation_pool.md
@@ -124,6 +124,7 @@ transferred to A
 -  [Resource `BeneficiaryForOperator`](#0x1_delegation_pool_BeneficiaryForOperator)
 -  [Resource `NextCommissionPercentage`](#0x1_delegation_pool_NextCommissionPercentage)
 -  [Resource `DelegationPoolAllowlisting`](#0x1_delegation_pool_DelegationPoolAllowlisting)
+-  [Struct `DelegationPermission`](#0x1_delegation_pool_DelegationPermission)
 -  [Struct `AddStake`](#0x1_delegation_pool_AddStake)
 -  [Struct `AddStakeEvent`](#0x1_delegation_pool_AddStakeEvent)
 -  [Struct `ReactivateStake`](#0x1_delegation_pool_ReactivateStake)
@@ -171,6 +172,8 @@ transferred to A
 -  [Function `allowlisting_enabled`](#0x1_delegation_pool_allowlisting_enabled)
 -  [Function `delegator_allowlisted`](#0x1_delegation_pool_delegator_allowlisted)
 -  [Function `get_delegators_allowlist`](#0x1_delegation_pool_get_delegators_allowlist)
+-  [Function `check_signer_permission`](#0x1_delegation_pool_check_signer_permission)
+-  [Function `grant_permission`](#0x1_delegation_pool_grant_permission)
 -  [Function `initialize_delegation_pool`](#0x1_delegation_pool_initialize_delegation_pool)
 -  [Function `beneficiary_for_operator`](#0x1_delegation_pool_beneficiary_for_operator)
 -  [Function `enable_partial_governance_voting`](#0x1_delegation_pool_enable_partial_governance_voting)
@@ -245,6 +248,7 @@ transferred to A
 use 0x1::error;
 use 0x1::event;
 use 0x1::features;
+use 0x1::permissioned_signer;
 use 0x1::pool_u64_unbound;
 use 0x1::signer;
 use 0x1::smart_table;
@@ -678,6 +682,33 @@ evicted later by the pool owner.
 
 
 
+
+
+
+
+## Struct `DelegationPermission`
+
+
+
+
struct DelegationPermission has copy, drop, store
+
+ + + +
+Fields + + +
+
+dummy_field: bool +
+
+ +
+
+ +
@@ -1828,6 +1859,16 @@ There is not enough active stake on the stake pool to unlock< + + +Signer does not have permission to perform delegation logic. + + +
const ENO_DELEGATION_PERMISSION: u64 = 28;
+
+ + + Changing beneficiaries for operators is not supported. @@ -2756,6 +2797,58 @@ Return allowlist or revert if allowlisting is not enabled for the provided deleg + + + + +## Function `check_signer_permission` + +Permissions + + +
fun check_signer_permission(s: &signer)
+
+ + + +
+Implementation + + +
inline fun check_signer_permission(s: &signer) {
+    assert!(
+        permissioned_signer::check_permission_exists(s, DelegationPermission {}),
+        error::permission_denied(ENO_DELEGATION_PERMISSION),
+    );
+}
+
+ + + +
+ + + +## Function `grant_permission` + + + +
public fun grant_permission(master: &signer, permissioned_signer: &signer)
+
+ + + +
+Implementation + + +
public fun grant_permission(master: &signer, permissioned_signer: &signer) {
+    permissioned_signer::authorize_unlimited(master, permissioned_signer, DelegationPermission {})
+}
+
+ + +
@@ -2782,6 +2875,7 @@ Ownership over setting the operator/voter is granted to owner who h operator_commission_percentage: u64, delegation_pool_creation_seed: vector<u8>, ) acquires DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage { + check_signer_permission(owner); assert!(features::delegation_pools_enabled(), error::invalid_state(EDELEGATION_POOLS_DISABLED)); let owner_address = signer::address_of(owner); assert!(!owner_cap_exists(owner_address), error::already_exists(EOWNER_CAP_ALREADY_EXISTS)); @@ -2942,6 +3036,7 @@ Vote on a proposal with a voter's voting power. To successfully vote, the follow voting_power: u64, should_pass: bool ) acquires DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage { + check_signer_permission(voter); assert_partial_governance_voting_enabled(pool_address); // synchronize delegation and stake pools before any user operation. synchronize_delegation_pool(pool_address); @@ -3022,6 +3117,7 @@ voting power in THIS delegation pool must be not less than the minimum required metadata_hash: vector<u8>, is_multi_step_proposal: bool, ) acquires DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage { + check_signer_permission(voter); assert_partial_governance_voting_enabled(pool_address); // synchronize delegation and stake pools before any user operation @@ -3794,6 +3890,7 @@ Allows an owner to change the operator of the underlying stake pool. owner: &signer, new_operator: address ) acquires DelegationPoolOwnership, DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage { + check_signer_permission(owner); let pool_address = get_owned_pool_address(signer::address_of(owner)); // synchronize delegation and stake pools before any user operation // ensure the old operator is paid its uncommitted commission rewards @@ -3829,6 +3926,7 @@ one for each pool. operator: &signer, new_beneficiary: address ) acquires BeneficiaryForOperator { + check_signer_permission(operator); assert!(features::operator_beneficiary_change_enabled(), std::error::invalid_state( EOPERATOR_BENEFICIARY_CHANGE_NOT_SUPPORTED )); @@ -3874,6 +3972,7 @@ Allows an owner to update the commission percentage for the operator of the unde owner: &signer, new_commission_percentage: u64 ) acquires DelegationPoolOwnership, DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage { + check_signer_permission(owner); assert!(features::commission_change_delegation_pool_enabled(), error::invalid_state( ECOMMISSION_RATE_CHANGE_NOT_SUPPORTED )); @@ -3939,6 +4038,7 @@ Allows an owner to change the delegated voter of the underlying stake pool. owner: &signer, new_voter: address ) acquires DelegationPoolOwnership, DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage { + check_signer_permission(owner); // No one can change delegated_voter once the partial governance voting feature is enabled. assert!( !features::delegation_pool_partial_governance_voting_enabled(), @@ -3977,6 +4077,7 @@ this change won't take effects until the next lockup period. pool_address: address, new_voter: address ) acquires DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage { + check_signer_permission(delegator); assert_partial_governance_voting_enabled(pool_address); // synchronize delegation and stake pools before any user operation @@ -4054,6 +4155,7 @@ Enable delegators allowlisting as the pool owner.
public entry fun enable_delegators_allowlisting(
     owner: &signer,
 ) acquires DelegationPoolOwnership, DelegationPool {
+    check_signer_permission(owner);
     assert!(
         features::delegation_pool_allowlisting_enabled(),
         error::invalid_state(EDELEGATORS_ALLOWLISTING_NOT_SUPPORTED)
@@ -4092,6 +4194,7 @@ Disable delegators allowlisting as the pool owner. The existing allowlist will b
 
public entry fun disable_delegators_allowlisting(
     owner: &signer,
 ) acquires DelegationPoolOwnership, DelegationPoolAllowlisting {
+    check_signer_permission(owner);
     let pool_address = get_owned_pool_address(signer::address_of(owner));
     assert_allowlisting_enabled(pool_address);
 
@@ -4127,6 +4230,7 @@ Allowlist a delegator as the pool owner.
     owner: &signer,
     delegator_address: address,
 ) acquires DelegationPoolOwnership, DelegationPoolAllowlisting {
+    check_signer_permission(owner);
     let pool_address = get_owned_pool_address(signer::address_of(owner));
     assert_allowlisting_enabled(pool_address);
 
@@ -4162,6 +4266,7 @@ Remove a delegator from the allowlist as the pool owner, but do not unlock their
     owner: &signer,
     delegator_address: address,
 ) acquires DelegationPoolOwnership, DelegationPoolAllowlisting {
+    check_signer_permission(owner);
     let pool_address = get_owned_pool_address(signer::address_of(owner));
     assert_allowlisting_enabled(pool_address);
 
@@ -4197,6 +4302,7 @@ Evict a delegator that is not allowlisted by unlocking their entire stake.
     owner: &signer,
     delegator_address: address,
 ) acquires DelegationPoolOwnership, DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage, DelegationPoolAllowlisting {
+    check_signer_permission(owner);
     let pool_address = get_owned_pool_address(signer::address_of(owner));
     assert_allowlisting_enabled(pool_address);
     assert!(
@@ -4241,6 +4347,7 @@ Add amount of coins to the delegation pool pool_addressaddress,
     amount: u64
 ) acquires DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage, DelegationPoolAllowlisting {
+    check_signer_permission(delegator);
     // short-circuit if amount to add is 0 so no event is emitted
     if (amount == 0) { return };
 
@@ -4318,6 +4425,7 @@ at most how much active stake there is on the stake pool.
     pool_address: address,
     amount: u64
 ) acquires DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage {
+    check_signer_permission(delegator);
     // short-circuit if amount to unlock is 0 so no event is emitted
     if (amount == 0) { return };
 
@@ -4419,6 +4527,7 @@ Move amount of coins from pending_inactive to active.
     pool_address: address,
     amount: u64
 ) acquires DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage, DelegationPoolAllowlisting {
+    check_signer_permission(delegator);
     // short-circuit if amount to reactivate is 0 so no event is emitted
     if (amount == 0) { return };
 
@@ -4489,6 +4598,7 @@ Withdraw amount of owned inactive stake from the delegation pool at
     pool_address: address,
     amount: u64
 ) acquires DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage {
+    check_signer_permission(delegator);
     assert!(amount > 0, error::invalid_argument(EWITHDRAW_ZERO_STAKE));
     // synchronize delegation and stake pools before any user operation
     synchronize_delegation_pool(pool_address);
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..e01da41d8b51c 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 +636,33 @@ MutateMetadataRef can be used to directly modify the fungible asset's Metadata. + + + + +## Struct `WithdrawPermission` + + + +
struct WithdrawPermission has copy, drop, store
+
+ + + +
+Fields + + +
+
+metadata_address: address +
+
+ +
+
+ +
@@ -1224,6 +1261,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 = 34;
+
+ + + @@ -2954,6 +3001,103 @@ that function unless you DO NOT want to support fungible assets with dispatchabl + + + + +## Function `withdraw_with_permission` + + + +
public fun withdraw_with_permission<T: key>(perm: &mut permissioned_signer::Permission<fungible_asset::WithdrawPermission>, store: object::Object<T>, amount: u64): fungible_asset::FungibleAsset
+
+ + + +
+Implementation + + +
public fun withdraw_with_permission<T: key>(
+    perm: &mut Permission<WithdrawPermission>,
+    store: Object<T>,
+    amount: u64,
+): FungibleAsset acquires FungibleStore, DispatchFunctionStore, ConcurrentFungibleBalance {
+    withdraw_sanity_check_impl(permissioned_signer::address_of(perm), store, true);
+    assert!(
+        permissioned_signer::consume_permission(perm, amount as u256, WithdrawPermission {
+            metadata_address: object::object_address(&borrow_store_resource(&store).metadata)
+        }),
+        error::permission_denied(EWITHDRAW_PERMISSION_DENIED)
+    );
+    withdraw_internal(object::object_address(&store), amount)
+}
+
+ + + +
+ + + +## Function `withdraw_permission_check` + +Check the permission for withdraw operation. + + +
public(friend) fun withdraw_permission_check<T: key>(owner: &signer, store: object::Object<T>, amount: u64)
+
+ + + +
+Implementation + + +
public(friend) fun withdraw_permission_check<T: key>(
+    owner: &signer,
+    store: Object<T>,
+    amount: u64,
+) acquires FungibleStore {
+    assert!(permissioned_signer::check_permission_consume(owner, amount as u256, WithdrawPermission {
+        metadata_address: object::object_address(&borrow_store_resource(&store).metadata)
+    }), 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, metadata_address: address, amount: u64)
+
+ + + +
+Implementation + + +
public(friend) fun withdraw_permission_check_by_address(
+    owner: &signer,
+    metadata_address: address,
+    amount: u64,
+) {
+    assert!(permissioned_signer::check_permission_consume(owner, amount as u256, WithdrawPermission {
+        metadata_address,
+    }), error::permission_denied(EWITHDRAW_PERMISSION_DENIED));
+}
+
+ + +
@@ -2977,7 +3121,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 +4288,138 @@ Ensure a known 
+
+## Function `grant_permission`
+
+Permission management
+
+Master signer grant permissioned signer ability to withdraw a given amount of fungible asset.
+
+
+
public fun grant_permission(master: &signer, permissioned: &signer, token_type: object::Object<fungible_asset::Metadata>, amount: u64)
+
+ + + +
+Implementation + + +
public fun grant_permission(
+    master: &signer,
+    permissioned: &signer,
+    token_type: Object<Metadata>,
+    amount: u64
+) {
+    permissioned_signer::authorize(
+        master,
+        permissioned,
+        amount as u256,
+        WithdrawPermission {
+            metadata_address: object::object_address(&token_type),
+        }
+    )
+}
+
+ + + +
+ + + +## Function `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
+) {
+    permissioned_signer::authorize(
+        master,
+        permissioned,
+        amount as u256,
+        WithdrawPermission { metadata_address: @aptos_fungible_asset }
+    )
+}
+
+ + + +
+ + + +## Function `refill_permission_with_fa` + + + +
public(friend) fun refill_permission_with_fa(permissioned: &signer, fa: &fungible_asset::FungibleAsset)
+
+ + + +
+Implementation + + +
public(friend) fun refill_permission_with_fa(
+    permissioned: &signer,
+    fa: &FungibleAsset
+) {
+    permissioned_signer::increase_limit(
+        permissioned,
+        amount(fa) as u256,
+        WithdrawPermission {
+            metadata_address: object::object_address(&metadata_from_asset(fa)),
+        }
+    )
+}
+
+ + + +
+ + + +## Function `revoke_permission` + +Removing permissions from permissioned signer. + + +
public fun revoke_permission(permissioned: &signer, token_type: object::Object<fungible_asset::Metadata>)
+
+ + + +
+Implementation + + +
public fun revoke_permission(permissioned: &signer, token_type: Object<Metadata>) {
+    permissioned_signer::revoke_permission(permissioned, WithdrawPermission {
+        metadata_address: object::object_address(&token_type),
+    })
+}
+
+ + +
diff --git a/aptos-move/framework/aptos-framework/doc/managed_coin.md b/aptos-move/framework/aptos-framework/doc/managed_coin.md index 50c2383fd111d..5f1821115f381 100644 --- a/aptos-move/framework/aptos-framework/doc/managed_coin.md +++ b/aptos-move/framework/aptos-framework/doc/managed_coin.md @@ -429,7 +429,8 @@ The Capabilities should not be under the signer before creating; The Capabilities should be under the signer after creating; -
include coin::InitializeInternalSchema<CoinType>;
+
aborts_if permissioned_signer::spec_is_permissioned_signer(account);
+include coin::InitializeInternalSchema<CoinType>;
 aborts_if !string::spec_internal_check_utf8(name);
 aborts_if !string::spec_internal_check_utf8(symbol);
 aborts_if exists<Capabilities<CoinType>>(signer::address_of(account));
diff --git a/aptos-move/framework/aptos-framework/doc/object.md b/aptos-move/framework/aptos-framework/doc/object.md
index 7ae33a425ee25..86e1e410ffc47 100644
--- a/aptos-move/framework/aptos-framework/doc/object.md
+++ b/aptos-move/framework/aptos-framework/doc/object.md
@@ -32,6 +32,7 @@ make it so that a reference to a global object can be returned from a function.
 -  [Struct `TransferRef`](#0x1_object_TransferRef)
 -  [Struct `LinearTransferRef`](#0x1_object_LinearTransferRef)
 -  [Struct `DeriveRef`](#0x1_object_DeriveRef)
+-  [Struct `TransferPermission`](#0x1_object_TransferPermission)
 -  [Struct `TransferEvent`](#0x1_object_TransferEvent)
 -  [Struct `Transfer`](#0x1_object_Transfer)
 -  [Constants](#@Constants_0)
@@ -89,6 +90,7 @@ make it so that a reference to a global object can be returned from a function.
 -  [Function `is_owner`](#0x1_object_is_owner)
 -  [Function `owns`](#0x1_object_owns)
 -  [Function `root_owner`](#0x1_object_root_owner)
+-  [Function `grant_permission`](#0x1_object_grant_permission)
 -  [Specification](#@Specification_1)
     -  [High-level Requirements](#high-level-req)
     -  [Module-level Specification](#module-level-spec)
@@ -133,6 +135,7 @@ make it so that a reference to a global object can be returned from a function.
     -  [Function `is_owner`](#@Specification_1_is_owner)
     -  [Function `owns`](#@Specification_1_owns)
     -  [Function `root_owner`](#@Specification_1_root_owner)
+    -  [Function `grant_permission`](#@Specification_1_grant_permission)
 
 
 
use 0x1::account;
@@ -144,6 +147,7 @@ make it so that a reference to a global object can be returned from a function.
 use 0x1::from_bcs;
 use 0x1::guid;
 use 0x1::hash;
+use 0x1::permissioned_signer;
 use 0x1::signer;
 use 0x1::transaction_context;
 use 0x1::vector;
@@ -496,6 +500,34 @@ Used to create derived objects from a given objects.
 
 
 
+
+ + + +## Struct `TransferPermission` + +Permission to transfer object with permissioned signer. + + +
struct TransferPermission has copy, drop, store
+
+ + + +
+Fields + + +
+
+object: address +
+
+ +
+
+ +
@@ -1999,6 +2031,10 @@ hierarchy. to: address, ) acquires ObjectCore { let owner_address = signer::address_of(owner); + assert!( + permissioned_signer::check_permission_exists(owner, TransferPermission { object }), + error::permission_denied(EOBJECT_NOT_TRANSFERRABLE) + ); verify_ungated_and_descendant(owner_address, object); transfer_raw_inner(object, to); } @@ -2188,6 +2224,10 @@ Allow origin owners to reclaim any objects they previous burnt. ) acquires TombStone, ObjectCore { let object_addr = object.inner; assert!(exists<TombStone>(object_addr), error::invalid_argument(EOBJECT_NOT_BURNT)); + assert!( + permissioned_signer::check_permission_exists(original_owner, TransferPermission { object: object_addr }), + error::permission_denied(EOBJECT_NOT_TRANSFERRABLE) + ); let TombStone { original_owner: original_owner_addr } = move_from<TombStone>(object_addr); assert!(original_owner_addr == signer::address_of(original_owner), error::permission_denied(ENOT_OBJECT_OWNER)); @@ -2361,6 +2401,38 @@ to determine the identity of the starting point of ownership. + + + + +## Function `grant_permission` + + + +
public fun grant_permission<T>(master: &signer, permissioned_signer: &signer, object: object::Object<T>)
+
+ + + +
+Implementation + + +
public fun grant_permission<T>(
+    master: &signer,
+    permissioned_signer: &signer,
+    object: Object<T>,
+) {
+    permissioned_signer::authorize_unlimited(
+        master,
+        permissioned_signer,
+        TransferPermission { object: object.inner }
+    )
+}
+
+ + +
@@ -2442,15 +2514,6 @@ to determine the identity of the starting point of ownership. - - - - -
fun spec_exists_at<T: key>(object: address): bool;
-
- - - ### Function `address_to_object` @@ -3402,4 +3465,32 @@ to determine the identity of the starting point of ownership.
+ + + +### Function `grant_permission` + + +
public fun grant_permission<T>(master: &signer, permissioned_signer: &signer, object: object::Object<T>)
+
+ + + + +
pragma aborts_if_is_partial;
+aborts_if !permissioned_signer::spec_is_permissioned_signer(permissioned_signer);
+aborts_if permissioned_signer::spec_is_permissioned_signer(master);
+aborts_if signer::address_of(master) != signer::address_of(permissioned_signer);
+
+ + + + + + + +
fun spec_exists_at<T: key>(object: address): bool;
+
+ + [move-book]: https://aptos.dev/move/book/SUMMARY diff --git a/aptos-move/framework/aptos-framework/doc/object_code_deployment.md b/aptos-move/framework/aptos-framework/doc/object_code_deployment.md index e4caf56a77c74..e7d56007cacc4 100644 --- a/aptos-move/framework/aptos-framework/doc/object_code_deployment.md +++ b/aptos-move/framework/aptos-framework/doc/object_code_deployment.md @@ -39,7 +39,10 @@ Once modules are marked as immutable, they cannot be made mutable again. - [Struct `Publish`](#0x1_object_code_deployment_Publish) - [Struct `Upgrade`](#0x1_object_code_deployment_Upgrade) - [Struct `Freeze`](#0x1_object_code_deployment_Freeze) +- [Struct `ObjectCodePermission`](#0x1_object_code_deployment_ObjectCodePermission) - [Constants](#@Constants_0) +- [Function `check_signer_permission`](#0x1_object_code_deployment_check_signer_permission) +- [Function `grant_permission`](#0x1_object_code_deployment_grant_permission) - [Function `publish`](#0x1_object_code_deployment_publish) - [Function `object_seed`](#0x1_object_code_deployment_object_seed) - [Function `upgrade`](#0x1_object_code_deployment_upgrade) @@ -53,6 +56,7 @@ Once modules are marked as immutable, they cannot be made mutable again. use 0x1::event; use 0x1::features; use 0x1::object; +use 0x1::permissioned_signer; use 0x1::signer; use 0x1::vector;
@@ -173,6 +177,33 @@ Event emitted when code in an existing object is made immutable. + + + + +## Struct `ObjectCodePermission` + + + +
struct ObjectCodePermission has copy, drop, store
+
+ + + +
+Fields + + +
+
+dummy_field: bool +
+
+ +
+
+ +
@@ -190,6 +221,16 @@ Event emitted when code in an existing object is made immutable. + + +Current permissioned signer cannot deploy object code. + + +
const ENO_CODE_PERMISSION: u64 = 4;
+
+ + + Not the owner of the code_object @@ -219,6 +260,59 @@ Object code deployment feature not supported. + + +## Function `check_signer_permission` + +Permissions + + +
fun check_signer_permission(s: &signer)
+
+ + + +
+Implementation + + +
inline fun check_signer_permission(s: &signer) {
+    assert!(
+        permissioned_signer::check_permission_exists(s, ObjectCodePermission {}),
+        error::permission_denied(ENO_CODE_PERMISSION),
+    );
+}
+
+ + + +
+ + + +## Function `grant_permission` + +Grant permission to publish code on behalf of the master signer. + + +
public fun grant_permission(master: &signer, permissioned_signer: &signer)
+
+ + + +
+Implementation + + +
public fun grant_permission(master: &signer, permissioned_signer: &signer) {
+    permissioned_signer::authorize_unlimited(master, permissioned_signer, ObjectCodePermission {})
+}
+
+ + + +
+ ## Function `publish` @@ -243,6 +337,7 @@ the code to be published via code. T metadata_serialized: vector<u8>, code: vector<vector<u8>>, ) { + check_signer_permission(publisher); assert!( features::is_object_code_deployment_enabled(), error::unavailable(EOBJECT_CODE_DEPLOYMENT_NOT_SUPPORTED), @@ -319,6 +414,7 @@ Requires the publisher to be the owner of the code_object. code: vector<vector<u8>>, code_object: Object<PackageRegistry>, ) acquires ManagingRefs { + check_signer_permission(publisher); let publisher_address = signer::address_of(publisher); assert!( object::is_owner(code_object, publisher_address), diff --git a/aptos-move/framework/aptos-framework/doc/overview.md b/aptos-move/framework/aptos-framework/doc/overview.md index 314baa3612ba9..7ad32f3ea8ec0 100644 --- a/aptos-move/framework/aptos-framework/doc/overview.md +++ b/aptos-move/framework/aptos-framework/doc/overview.md @@ -46,6 +46,7 @@ This is the reference documentation of the Aptos framework. - [`0x1::object`](object.md#0x1_object) - [`0x1::object_code_deployment`](object_code_deployment.md#0x1_object_code_deployment) - [`0x1::optional_aggregator`](optional_aggregator.md#0x1_optional_aggregator) +- [`0x1::permissioned_signer`](permissioned_signer.md#0x1_permissioned_signer) - [`0x1::primary_fungible_store`](primary_fungible_store.md#0x1_primary_fungible_store) - [`0x1::randomness`](randomness.md#0x1_randomness) - [`0x1::randomness_api_v0_config`](randomness_api_v0_config.md#0x1_randomness_api_v0_config) diff --git a/aptos-move/framework/aptos-framework/doc/permissioned_signer.md b/aptos-move/framework/aptos-framework/doc/permissioned_signer.md new file mode 100644 index 0000000000000..3baecf3e6a383 --- /dev/null +++ b/aptos-move/framework/aptos-framework/doc/permissioned_signer.md @@ -0,0 +1,1798 @@ + + + +# Module `0x1::permissioned_signer` + +A _permissioned signer_ consists of a pair of the original signer and a generated +address which is used to store information about associated permissions. + +A permissioned signer is a restricted version of a signer. Functions move_to and +address_of behave the same, and can be passed wherever signer is needed. However, +code can internally query for the permissions to assert additional restrictions on +the use of the signer. + +A client which is interested in restricting access granted via a signer can create a permissioned signer +and pass on to other existing code without changes to existing APIs. Core functions in the framework, for +example account functions, can then assert availability of permissions, effectively restricting +existing code in a compatible way. + +After introducing the core functionality, examples are provided for withdraw limit on accounts, and +for blind signing. + + +- [Struct `RevokePermissionHandlePermission`](#0x1_permissioned_signer_RevokePermissionHandlePermission) +- [Resource `GrantedPermissionHandles`](#0x1_permissioned_signer_GrantedPermissionHandles) +- [Enum `PermissionedHandle`](#0x1_permissioned_signer_PermissionedHandle) +- [Enum `StorablePermissionedHandle`](#0x1_permissioned_signer_StorablePermissionedHandle) +- [Enum Resource `PermissionStorage`](#0x1_permissioned_signer_PermissionStorage) +- [Enum `StoredPermission`](#0x1_permissioned_signer_StoredPermission) +- [Constants](#@Constants_0) +- [Function `create_permissioned_handle`](#0x1_permissioned_signer_create_permissioned_handle) +- [Function `create_storable_permissioned_handle`](#0x1_permissioned_signer_create_storable_permissioned_handle) +- [Function `destroy_permissioned_handle`](#0x1_permissioned_signer_destroy_permissioned_handle) +- [Function `destroy_storable_permissioned_handle`](#0x1_permissioned_signer_destroy_storable_permissioned_handle) +- [Function `destroy_permissions_storage_address`](#0x1_permissioned_signer_destroy_permissions_storage_address) +- [Function `signer_from_permissioned_handle`](#0x1_permissioned_signer_signer_from_permissioned_handle) +- [Function `signer_from_storable_permissioned_handle`](#0x1_permissioned_signer_signer_from_storable_permissioned_handle) +- [Function `grant_revoke_permission`](#0x1_permissioned_signer_grant_revoke_permission) +- [Function `revoke_permission_storage_address`](#0x1_permissioned_signer_revoke_permission_storage_address) +- [Function `revoke_all_handles`](#0x1_permissioned_signer_revoke_all_handles) +- [Function `permissions_storage_address`](#0x1_permissioned_signer_permissions_storage_address) +- [Function `assert_master_signer`](#0x1_permissioned_signer_assert_master_signer) +- [Function `is_above`](#0x1_permissioned_signer_is_above) +- [Function `consume_capacity`](#0x1_permissioned_signer_consume_capacity) +- [Function `increase_capacity`](#0x1_permissioned_signer_increase_capacity) +- [Function `merge`](#0x1_permissioned_signer_merge) +- [Function `map_or`](#0x1_permissioned_signer_map_or) +- [Function `insert_or`](#0x1_permissioned_signer_insert_or) +- [Function `authorize_increase`](#0x1_permissioned_signer_authorize_increase) +- [Function `authorize_unlimited`](#0x1_permissioned_signer_authorize_unlimited) +- [Function `increase_limit`](#0x1_permissioned_signer_increase_limit) +- [Function `check_permission_exists`](#0x1_permissioned_signer_check_permission_exists) +- [Function `check_permission_capacity_above`](#0x1_permissioned_signer_check_permission_capacity_above) +- [Function `check_permission_consume`](#0x1_permissioned_signer_check_permission_consume) +- [Function `capacity`](#0x1_permissioned_signer_capacity) +- [Function `revoke_permission`](#0x1_permissioned_signer_revoke_permission) +- [Function `is_permissioned_signer`](#0x1_permissioned_signer_is_permissioned_signer) +- [Function `permission_address`](#0x1_permissioned_signer_permission_address) +- [Function `signer_from_permissioned_handle_impl`](#0x1_permissioned_signer_signer_from_permissioned_handle_impl) +- [Specification](#@Specification_1) + - [Function `create_permissioned_handle`](#@Specification_1_create_permissioned_handle) + - [Function `create_storable_permissioned_handle`](#@Specification_1_create_storable_permissioned_handle) + - [Function `destroy_permissioned_handle`](#@Specification_1_destroy_permissioned_handle) + - [Function `destroy_storable_permissioned_handle`](#@Specification_1_destroy_storable_permissioned_handle) + - [Function `revoke_permission_storage_address`](#@Specification_1_revoke_permission_storage_address) + - [Function `authorize_increase`](#@Specification_1_authorize_increase) + - [Function `check_permission_exists`](#@Specification_1_check_permission_exists) + - [Function `check_permission_capacity_above`](#@Specification_1_check_permission_capacity_above) + - [Function `check_permission_consume`](#@Specification_1_check_permission_consume) + - [Function `capacity`](#@Specification_1_capacity) + - [Function `is_permissioned_signer`](#@Specification_1_is_permissioned_signer) + - [Function `permission_address`](#@Specification_1_permission_address) + - [Function `signer_from_permissioned_handle_impl`](#@Specification_1_signer_from_permissioned_handle_impl) + + +
use 0x1::copyable_any;
+use 0x1::create_signer;
+use 0x1::error;
+use 0x1::option;
+use 0x1::signer;
+use 0x1::simple_map;
+use 0x1::timestamp;
+use 0x1::transaction_context;
+use 0x1::vector;
+
+ + + + + +## Struct `RevokePermissionHandlePermission` + +If a permissioned signer has this permission, it would be able to revoke other granted +permission handles in the same signer. + + +
struct RevokePermissionHandlePermission has copy, drop, store
+
+ + + +
+Fields + + +
+
+dummy_field: bool +
+
+ +
+
+ + +
+ + + +## Resource `GrantedPermissionHandles` + +Stores the list of granted permission handles for a given account. + + +
struct GrantedPermissionHandles has key
+
+ + + +
+Fields + + +
+
+active_handles: vector<address> +
+
+ Each address refers to a permissions_storage_addr that stores the PermissionStorage. +
+
+ + +
+ + + +## Enum `PermissionedHandle` + +A ephermeral permission handle that can be used to generate a permissioned signer with permission +configuration stored within. + + +
enum PermissionedHandle
+
+ + + +
+Variants + + +
+V1 + + +
+Fields + + +
+
+master_account_addr: address +
+
+ Address of the signer that creates this handle. +
+
+permissions_storage_addr: address +
+
+ Address that stores PermissionStorage. +
+
+ + +
+ +
+ +
+ + + +## Enum `StorablePermissionedHandle` + +A permission handle that can be used to generate a permissioned signer. + +This handle is storable and thus should be treated very carefully as it serves similar functionality +as signer delegation. + + +
enum StorablePermissionedHandle has store
+
+ + + +
+Variants + + +
+V1 + + +
+Fields + + +
+
+master_account_addr: address +
+
+ Address of the signer that creates this handle. +
+
+permissions_storage_addr: address +
+
+ Address that stores PermissionStorage. +
+
+expiration_time: u64 +
+
+ Permissioned signer can no longer be generated from this handle after expiration_time. +
+
+ + +
+ +
+ +
+ + + +## Enum Resource `PermissionStorage` + +The actual permission configuration stored on-chain. + +The address that holds PermissionStorage will be generated freshly every time a permission +handle gets created. + + +
enum PermissionStorage has key
+
+ + + +
+Variants + + +
+V1 + + +
+Fields + + +
+
+perms: simple_map::SimpleMap<copyable_any::Any, permissioned_signer::StoredPermission> +
+
+ A hetherogenous map from Permission structs defined by each different modules to + its permission capacity. +
+
+ + +
+ +
+ +
+ + + +## Enum `StoredPermission` + +Types of permission capacity stored on chain. + + +
enum StoredPermission has copy, drop, store
+
+ + + +
+Variants + + +
+Unlimited + + +
+Fields + + +
+
+ + +
+ +
+ +
+Capacity + + +
+Fields + + +
+
+0: u256 +
+
+ +
+
+ + +
+ +
+ +
+ + + +## Constants + + + + +Cannot authorize a permission. + + +
const ECANNOT_AUTHORIZE: u64 = 2;
+
+ + + + + +signer doesn't have enough capacity to extract permission. + + +
const ECANNOT_EXTRACT_PERMISSION: u64 = 4;
+
+ + + + + +Trying to grant permission using non-master signer. + + +
const ENOT_MASTER_SIGNER: u64 = 1;
+
+ + + + + +Access permission information from a master signer. + + +
const ENOT_PERMISSIONED_SIGNER: u64 = 3;
+
+ + + + + +destroying permission handle that has already been revoked or not owned by the +given master signer. + + +
const E_NOT_ACTIVE: u64 = 8;
+
+ + + + + +permission handle has expired. + + +
const E_PERMISSION_EXPIRED: u64 = 5;
+
+ + + + + +storing extracted permission into a different signer. + + +
const E_PERMISSION_MISMATCH: u64 = 6;
+
+ + + + + +permission handle has been revoked by the original signer. + + +
const E_PERMISSION_REVOKED: u64 = 7;
+
+ + + + + + + +
const U256_MAX: u256 = 115792089237316195423570985008687907853269984665640564039457584007913129639935;
+
+ + + + + +## Function `create_permissioned_handle` + +Create an ephermeral permission handle based on the master signer. + +This handle can be used to derive a signer that can be used in the context of +the current transaction. + + +
public fun create_permissioned_handle(master: &signer): permissioned_signer::PermissionedHandle
+
+ + + +
+Implementation + + +
public fun create_permissioned_handle(master: &signer): PermissionedHandle {
+    assert_master_signer(master);
+    let permissions_storage_addr = generate_auid_address();
+    let master_account_addr = signer::address_of(master);
+
+    move_to(
+        &create_signer(permissions_storage_addr),
+        PermissionStorage::V1 { perms: simple_map::new() }
+    );
+
+    PermissionedHandle::V1 { master_account_addr, permissions_storage_addr }
+}
+
+ + + +
+ + + +## Function `create_storable_permissioned_handle` + +Create an storable permission handle based on the master signer. + +This handle can be used to derive a signer that can be stored by a smart contract. +This is as dangerous as key delegation, thus it remains public(package) for now. + +The caller should check if expiration_time is not too far in the future. + + +
public(friend) fun create_storable_permissioned_handle(master: &signer, expiration_time: u64): permissioned_signer::StorablePermissionedHandle
+
+ + + +
+Implementation + + +
public(package) fun create_storable_permissioned_handle(
+    master: &signer, expiration_time: u64
+): StorablePermissionedHandle acquires GrantedPermissionHandles {
+    assert_master_signer(master);
+    let permissions_storage_addr = generate_auid_address();
+    let master_account_addr = signer::address_of(master);
+
+    assert!(
+        timestamp::now_seconds() < expiration_time,
+        error::permission_denied(E_PERMISSION_EXPIRED)
+    );
+
+    if (!exists<GrantedPermissionHandles>(master_account_addr)) {
+        move_to<GrantedPermissionHandles>(
+            master, GrantedPermissionHandles { active_handles: vector::empty() }
+        );
+    };
+
+    GrantedPermissionHandles[master_account_addr]
+        .active_handles.push_back(permissions_storage_addr);
+
+    move_to(
+        &create_signer(permissions_storage_addr),
+        PermissionStorage::V1 { perms: simple_map::new() }
+    );
+
+    StorablePermissionedHandle::V1 {
+        master_account_addr,
+        permissions_storage_addr,
+        expiration_time
+    }
+}
+
+ + + +
+ + + +## Function `destroy_permissioned_handle` + +Destroys an ephermeral permission handle. Clean up the permission stored in that handle + + +
public fun destroy_permissioned_handle(p: permissioned_signer::PermissionedHandle)
+
+ + + +
+Implementation + + +
public fun destroy_permissioned_handle(p: PermissionedHandle) acquires PermissionStorage {
+    let PermissionedHandle::V1 { master_account_addr: _, permissions_storage_addr } =
+        p;
+    destroy_permissions_storage_address(permissions_storage_addr);
+}
+
+ + + +
+ + + +## Function `destroy_storable_permissioned_handle` + +Destroys a storable permission handle. Clean up the permission stored in that handle + + +
public(friend) fun destroy_storable_permissioned_handle(p: permissioned_signer::StorablePermissionedHandle)
+
+ + + +
+Implementation + + +
public(package) fun destroy_storable_permissioned_handle(
+    p: StorablePermissionedHandle
+) acquires PermissionStorage, GrantedPermissionHandles {
+    let StorablePermissionedHandle::V1 {
+        master_account_addr,
+        permissions_storage_addr,
+        expiration_time: _
+    } = p;
+
+    assert!(
+        exists<GrantedPermissionHandles>(master_account_addr),
+        error::permission_denied(E_PERMISSION_REVOKED),
+    );
+    let active_handles = &mut GrantedPermissionHandles[master_account_addr].active_handles;
+
+    let (found, idx) = active_handles.index_of(&permissions_storage_addr);
+
+    // Removing the address from the active handle list if it's still active.
+    if(found) {
+        active_handles.swap_remove(idx);
+    };
+
+    destroy_permissions_storage_address(permissions_storage_addr);
+}
+
+ + + +
+ + + +## Function `destroy_permissions_storage_address` + + + +
fun destroy_permissions_storage_address(permissions_storage_addr: address)
+
+ + + +
+Implementation + + +
inline fun destroy_permissions_storage_address(
+    permissions_storage_addr: address
+) acquires PermissionStorage {
+    if (exists<PermissionStorage>(permissions_storage_addr)) {
+        let PermissionStorage::V1 { perms } =
+            move_from<PermissionStorage>(permissions_storage_addr);
+        simple_map::destroy(
+            perms,
+            |_dk| {},
+            |_dv| {}
+        );
+    }
+}
+
+ + + +
+ + + +## Function `signer_from_permissioned_handle` + +Generate the permissioned signer based on the ephermeral permission handle. + +This signer can be used as a regular signer for other smart contracts. However when such +signer interacts with various framework functions, it would subject to permission checks +and would abort if check fails. + + +
public fun signer_from_permissioned_handle(p: &permissioned_signer::PermissionedHandle): signer
+
+ + + +
+Implementation + + +
public fun signer_from_permissioned_handle(p: &PermissionedHandle): signer {
+    signer_from_permissioned_handle_impl(
+        p.master_account_addr, p.permissions_storage_addr
+    )
+}
+
+ + + +
+ + + +## Function `signer_from_storable_permissioned_handle` + +Generate the permissioned signer based on the storable permission handle. + + +
public(friend) fun signer_from_storable_permissioned_handle(p: &permissioned_signer::StorablePermissionedHandle): signer
+
+ + + +
+Implementation + + +
public(package) fun signer_from_storable_permissioned_handle(
+    p: &StorablePermissionedHandle
+): signer {
+    assert!(
+        timestamp::now_seconds() < p.expiration_time,
+        error::permission_denied(E_PERMISSION_EXPIRED)
+    );
+    assert!(
+        exists<PermissionStorage>(p.permissions_storage_addr),
+        error::permission_denied(E_PERMISSION_REVOKED)
+    );
+    signer_from_permissioned_handle_impl(
+        p.master_account_addr, p.permissions_storage_addr
+    )
+}
+
+ + + +
+ + + +## Function `grant_revoke_permission` + + + +
public fun grant_revoke_permission(master: &signer, permissioned: &signer)
+
+ + + +
+Implementation + + +
public fun grant_revoke_permission(
+    master: &signer,
+    permissioned: &signer,
+) acquires PermissionStorage {
+    authorize_unlimited(master, permissioned, RevokePermissionHandlePermission {});
+}
+
+ + + +
+ + + +## Function `revoke_permission_storage_address` + +Revoke a specific storable permission handle immediately. This will disallow owner of +the storable permission handle to derive signer from it anymore. + + +
public entry fun revoke_permission_storage_address(s: &signer, permissions_storage_addr: address)
+
+ + + +
+Implementation + + +
public entry fun revoke_permission_storage_address(
+    s: &signer, permissions_storage_addr: address
+) acquires GrantedPermissionHandles, PermissionStorage {
+    assert!(
+        check_permission_exists(s, RevokePermissionHandlePermission {}),
+        error::permission_denied(ENOT_MASTER_SIGNER)
+    );
+    let master_account_addr = signer::address_of(s);
+
+    assert!(
+        exists<GrantedPermissionHandles>(master_account_addr),
+        error::permission_denied(E_PERMISSION_REVOKED),
+    );
+    let active_handles = &mut GrantedPermissionHandles[master_account_addr].active_handles;
+    let (found, idx) = active_handles.index_of(&permissions_storage_addr);
+
+    // The address has to be in the activated list in the master account address.
+    assert!(found, error::permission_denied(E_NOT_ACTIVE));
+    active_handles.swap_remove(idx);
+    destroy_permissions_storage_address(permissions_storage_addr);
+}
+
+ + + +
+ + + +## Function `revoke_all_handles` + +Revoke all storable permission handle of the signer immediately. + + +
public entry fun revoke_all_handles(s: &signer)
+
+ + + +
+Implementation + + +
public entry fun revoke_all_handles(s: &signer) acquires GrantedPermissionHandles, PermissionStorage {
+    assert!(
+        check_permission_exists(s, RevokePermissionHandlePermission {}),
+        error::permission_denied(ENOT_MASTER_SIGNER)
+    );
+    let master_account_addr = signer::address_of(s);
+    if (!exists<GrantedPermissionHandles>(master_account_addr)) { return };
+
+    let granted_permissions =
+        borrow_global_mut<GrantedPermissionHandles>(master_account_addr);
+    let delete_list = vector::trim_reverse(
+        &mut granted_permissions.active_handles, 0
+    );
+    vector::destroy(
+        delete_list,
+        |address| {
+            destroy_permissions_storage_address(address);
+        }
+    )
+}
+
+ + + +
+ + + +## Function `permissions_storage_address` + +Return the permission handle address so that it could be used for revocation purpose. + + +
public(friend) fun permissions_storage_address(p: &permissioned_signer::StorablePermissionedHandle): address
+
+ + + +
+Implementation + + +
public(package) fun permissions_storage_address(
+    p: &StorablePermissionedHandle
+): address {
+    p.permissions_storage_addr
+}
+
+ + + +
+ + + +## Function `assert_master_signer` + +Helper function that would abort if the signer passed in is a permissioned signer. + + +
public(friend) fun assert_master_signer(s: &signer)
+
+ + + +
+Implementation + + +
public(package) fun assert_master_signer(s: &signer) {
+    assert!(
+        !is_permissioned_signer(s), error::permission_denied(ENOT_MASTER_SIGNER)
+    );
+}
+
+ + + +
+ + + +## Function `is_above` + +===================================================================================================== +StoredPermission operations + +check if StoredPermission has at least threshold capacity. + + +
fun is_above(perm: &permissioned_signer::StoredPermission, threshold: u256): bool
+
+ + + +
+Implementation + + +
fun is_above(perm: &StoredPermission, threshold: u256): bool {
+    match (perm) {
+        StoredPermission::Capacity(capacity) => *capacity > threshold,
+        StoredPermission::Unlimited => true,
+    }
+}
+
+ + + +
+ + + +## Function `consume_capacity` + +consume threshold capacity from StoredPermission + + +
fun consume_capacity(perm: &mut permissioned_signer::StoredPermission, threshold: u256): bool
+
+ + + +
+Implementation + + +
fun consume_capacity(perm: &mut StoredPermission, threshold: u256): bool {
+    match (perm) {
+        StoredPermission::Capacity(current_capacity) => {
+            if (*current_capacity >= threshold) {
+                *current_capacity = *current_capacity - threshold;
+                true
+            } else { false }
+        }
+        StoredPermission::Unlimited => true
+    }
+}
+
+ + + +
+ + + +## Function `increase_capacity` + +increase threshold capacity from StoredPermission + + +
fun increase_capacity(perm: &mut permissioned_signer::StoredPermission, threshold: u256)
+
+ + + +
+Implementation + + +
fun increase_capacity(perm: &mut StoredPermission, threshold: u256) {
+    match (perm) {
+        StoredPermission::Capacity(current_capacity) => {
+            *current_capacity = *current_capacity + threshold;
+        }
+        StoredPermission::Unlimited => (),
+    }
+}
+
+ + + +
+ + + +## Function `merge` + +merge the two stored permission + + +
fun merge(lhs: &mut permissioned_signer::StoredPermission, rhs: permissioned_signer::StoredPermission)
+
+ + + +
+Implementation + + +
fun merge(lhs: &mut StoredPermission, rhs: StoredPermission) {
+    match (rhs) {
+        StoredPermission::Capacity(new_capacity) => {
+            match (lhs) {
+                StoredPermission::Capacity(current_capacity) => {
+                    *current_capacity = *current_capacity + new_capacity;
+                }
+                StoredPermission::Unlimited => (),
+            }
+        }
+        StoredPermission::Unlimited => *lhs = StoredPermission::Unlimited,
+    }
+}
+
+ + + +
+ + + +## Function `map_or` + +===================================================================================================== +Permission Management + +Authorizes permissioned with the given permission. This requires to have access to the master +signer. + + +
fun map_or<PermKey: copy, drop, store, T>(permissioned: &signer, perm: PermKey, mutate: |&mut permissioned_signer::StoredPermission|T, default: T): T
+
+ + + +
+Implementation + + +
inline fun map_or<PermKey: copy + drop + store, T>(
+    permissioned: &signer,
+    perm: PermKey,
+    mutate: |&mut StoredPermission| T,
+    default: T,
+): T {
+    let permission_signer_addr = permission_address(permissioned);
+    assert!(
+        exists<PermissionStorage>(permission_signer_addr),
+        error::permission_denied(E_NOT_ACTIVE)
+    );
+    let perms =
+        &mut borrow_global_mut<PermissionStorage>(permission_signer_addr).perms;
+    let key = copyable_any::pack(perm);
+    if (simple_map::contains_key(perms, &key)) {
+        mutate(simple_map::borrow_mut(perms, &key))
+    } else {
+        default
+    }
+}
+
+ + + +
+ + + +## Function `insert_or` + + + +
fun insert_or<PermKey: copy, drop, store>(permissioned: &signer, perm: PermKey, mutate: |&mut permissioned_signer::StoredPermission|, default: permissioned_signer::StoredPermission)
+
+ + + +
+Implementation + + +
inline fun insert_or<PermKey: copy + drop + store>(
+    permissioned: &signer,
+    perm: PermKey,
+    mutate: |&mut StoredPermission|,
+    default: StoredPermission,
+) {
+    let permission_signer_addr = permission_address(permissioned);
+    assert!(
+        exists<PermissionStorage>(permission_signer_addr),
+        error::permission_denied(E_NOT_ACTIVE)
+    );
+    let perms =
+        &mut borrow_global_mut<PermissionStorage>(permission_signer_addr).perms;
+    let key = copyable_any::pack(perm);
+    if (simple_map::contains_key(perms, &key)) {
+        mutate(simple_map::borrow_mut(perms, &key));
+    } else {
+        simple_map::add(perms, key, default);
+    }
+}
+
+ + + +
+ + + +## Function `authorize_increase` + +Authorizes permissioned with a given capacity and increment the existing capacity if present. + +Consumption using check_permission_consume will deduct the capacity. + + +
public(friend) fun authorize_increase<PermKey: copy, drop, store>(master: &signer, permissioned: &signer, capacity: u256, perm: PermKey)
+
+ + + +
+Implementation + + +
public(package) fun authorize_increase<PermKey: copy + drop + store>(
+    master: &signer,
+    permissioned: &signer,
+    capacity: u256,
+    perm: PermKey
+) acquires PermissionStorage {
+    assert!(
+        is_permissioned_signer(permissioned)
+            && !is_permissioned_signer(master)
+            && signer::address_of(master) == signer::address_of(permissioned),
+        error::permission_denied(ECANNOT_AUTHORIZE)
+    );
+    insert_or(
+        permissioned,
+        perm,
+        |stored_permission| {
+            increase_capacity(stored_permission, capacity);
+        },
+        StoredPermission::Capacity(capacity),
+    )
+}
+
+ + + +
+ + + +## Function `authorize_unlimited` + +Authorizes permissioned with the given unlimited permission. +Unlimited permission can be consumed however many times. + + +
public(friend) fun authorize_unlimited<PermKey: copy, drop, store>(master: &signer, permissioned: &signer, perm: PermKey)
+
+ + + +
+Implementation + + +
public(package) fun authorize_unlimited<PermKey: copy + drop + store>(
+    master: &signer,
+    permissioned: &signer,
+    perm: PermKey
+) acquires PermissionStorage {
+    assert!(
+        is_permissioned_signer(permissioned)
+            && !is_permissioned_signer(master)
+            && signer::address_of(master) == signer::address_of(permissioned),
+        error::permission_denied(ECANNOT_AUTHORIZE)
+    );
+    insert_or(
+        permissioned,
+        perm,
+        |stored_permission| {
+            *stored_permission = StoredPermission::Unlimited;
+        },
+        StoredPermission::Unlimited,
+    )
+}
+
+ + + +
+ + + +## Function `increase_limit` + +Increase the capacity of a permissioned signer **without** master signer's approvoal. + +The caller of the module will need to make sure the witness type PermKey can only be +constructed within its own module, otherwise attackers can refill the permission for itself +to bypass the checks. + + +
public(friend) fun increase_limit<PermKey: copy, drop, store>(permissioned: &signer, capacity: u256, perm: PermKey)
+
+ + + +
+Implementation + + +
public(package) fun increase_limit<PermKey: copy + drop + store>(
+    permissioned: &signer,
+    capacity: u256,
+    perm: PermKey
+) acquires PermissionStorage {
+    if(!is_permissioned_signer(permissioned)) {
+        return;
+    };
+    insert_or(
+        permissioned,
+        perm,
+        |stored_permission| {
+            increase_capacity(stored_permission, capacity);
+        },
+        StoredPermission::Capacity(capacity),
+    )
+}
+
+ + + +
+ + + +## Function `check_permission_exists` + + + +
public(friend) fun check_permission_exists<PermKey: copy, drop, store>(s: &signer, perm: PermKey): bool
+
+ + + +
+Implementation + + +
public(package) fun check_permission_exists<PermKey: copy + drop + store>(
+    s: &signer, perm: PermKey
+): bool acquires PermissionStorage {
+    check_permission_capacity_above(s, 0, perm)
+}
+
+ + + +
+ + + +## Function `check_permission_capacity_above` + + + +
public(friend) fun check_permission_capacity_above<PermKey: copy, drop, store>(s: &signer, threshold: u256, perm: PermKey): bool
+
+ + + +
+Implementation + + +
public(package) fun check_permission_capacity_above<PermKey: copy + drop + store>(
+    s: &signer, threshold: u256, perm: PermKey
+): bool acquires PermissionStorage {
+    if (!is_permissioned_signer(s)) {
+        // master signer has all permissions
+        return true
+    };
+    map_or(
+        s,
+        perm,
+        |stored_permission| {
+            is_above(stored_permission, threshold)
+        },
+        false,
+    )
+}
+
+ + + +
+ + + +## Function `check_permission_consume` + + + +
public(friend) fun check_permission_consume<PermKey: copy, drop, store>(s: &signer, threshold: u256, perm: PermKey): bool
+
+ + + +
+Implementation + + +
public(package) fun check_permission_consume<PermKey: copy + drop + store>(
+    s: &signer, threshold: u256, perm: PermKey
+): bool acquires PermissionStorage {
+    if (!is_permissioned_signer(s)) {
+        // master signer has all permissions
+        return true
+    };
+    map_or(
+        s,
+        perm,
+        |stored_permission| {
+             consume_capacity(stored_permission, threshold)
+        },
+        false,
+    )
+}
+
+ + + +
+ + + +## Function `capacity` + + + +
public(friend) fun capacity<PermKey: copy, drop, store>(s: &signer, perm: PermKey): option::Option<u256>
+
+ + + +
+Implementation + + +
public(package) fun capacity<PermKey: copy + drop + store>(
+    s: &signer, perm: PermKey
+): Option<u256> acquires PermissionStorage {
+    if (!is_permissioned_signer(s)) {
+        return option::some(U256_MAX)
+    };
+    map_or(
+        s,
+        perm,
+        |stored_permission: &mut StoredPermission| {
+            option::some(match (stored_permission) {
+                StoredPermission::Capacity(capacity) => *capacity,
+                StoredPermission::Unlimited => U256_MAX,
+            })
+        },
+        option::none(),
+    )
+}
+
+ + + +
+ + + +## Function `revoke_permission` + + + +
public(friend) fun revoke_permission<PermKey: copy, drop, store>(permissioned: &signer, perm: PermKey)
+
+ + + +
+Implementation + + +
public(package) fun revoke_permission<PermKey: copy + drop + store>(
+    permissioned: &signer, perm: PermKey
+) acquires PermissionStorage {
+    if (!is_permissioned_signer(permissioned)) {
+        // Master signer has no permissions associated with it.
+        return
+    };
+    let addr = permission_address(permissioned);
+    if (!exists<PermissionStorage>(addr)) { return };
+    let perm_storage = &mut borrow_global_mut<PermissionStorage>(addr).perms;
+    let key = copyable_any::pack(perm);
+    if (simple_map::contains_key(perm_storage, &key)) {
+        simple_map::remove(
+            &mut borrow_global_mut<PermissionStorage>(addr).perms,
+            &copyable_any::pack(perm)
+        );
+    }
+}
+
+ + + +
+ + + +## Function `is_permissioned_signer` + + +Check whether this is a permissioned signer. + + +
public fun is_permissioned_signer(s: &signer): bool
+
+ + + +
+Implementation + + +
public native fun is_permissioned_signer(s: &signer): bool;
+
+ + + +
+ + + +## Function `permission_address` + +Return the address used for storing permissions. Aborts if not a permissioned signer. + + +
fun permission_address(permissioned: &signer): address
+
+ + + +
+Implementation + + +
native fun permission_address(permissioned: &signer): address;
+
+ + + +
+ + + +## Function `signer_from_permissioned_handle_impl` + +Creates a permissioned signer from an existing universal signer. The function aborts if the +given signer is already a permissioned signer. + +The implementation of this function requires to extend the value representation for signers in the VM. +invariants: +signer::address_of(master) == signer::address_of(signer_from_permissioned_handle(create_permissioned_handle(master))), + + +
fun signer_from_permissioned_handle_impl(master_account_addr: address, permissions_storage_addr: address): signer
+
+ + + +
+Implementation + + +
native fun signer_from_permissioned_handle_impl(
+    master_account_addr: address, permissions_storage_addr: address
+): signer;
+
+ + + +
+ + + +## Specification + + + +
pragma verify = false;
+axiom forall a: GrantedPermissionHandles:
+    (
+        forall i in 0..len(a.active_handles):
+            forall j in 0..len(a.active_handles):
+                i != j ==>
+                    a.active_handles[i] != a.active_handles[j]
+    );
+
+ + + + + + + +
fun spec_is_permissioned_signer(s: signer): bool;
+
+ + + + + +### Function `create_permissioned_handle` + + +
public fun create_permissioned_handle(master: &signer): permissioned_signer::PermissionedHandle
+
+ + + + +
pragma opaque;
+aborts_if [abstract] spec_is_permissioned_signer(master);
+let permissions_storage_addr = transaction_context::spec_generate_unique_address();
+modifies global<PermissionStorage>(permissions_storage_addr);
+let master_account_addr = signer::address_of(master);
+ensures result.master_account_addr == master_account_addr;
+ensures result.permissions_storage_addr == permissions_storage_addr;
+
+ + + + + +### Function `create_storable_permissioned_handle` + + +
public(friend) fun create_storable_permissioned_handle(master: &signer, expiration_time: u64): permissioned_signer::StorablePermissionedHandle
+
+ + + + +
pragma opaque;
+aborts_if [abstract] spec_is_permissioned_signer(master);
+let permissions_storage_addr = transaction_context::spec_generate_unique_address();
+modifies global<PermissionStorage>(permissions_storage_addr);
+let master_account_addr = signer::address_of(master);
+modifies global<GrantedPermissionHandles>(master_account_addr);
+ensures result.master_account_addr == master_account_addr;
+ensures result.permissions_storage_addr == permissions_storage_addr;
+ensures result.expiration_time == expiration_time;
+ensures vector::spec_contains(
+    global<GrantedPermissionHandles>(master_account_addr).active_handles,
+    permissions_storage_addr
+);
+ensures exists<GrantedPermissionHandles>(master_account_addr);
+
+ + + + + +### Function `destroy_permissioned_handle` + + +
public fun destroy_permissioned_handle(p: permissioned_signer::PermissionedHandle)
+
+ + + + +
ensures !exists<PermissionStorage>(p.permissions_storage_addr);
+
+ + + + + +### Function `destroy_storable_permissioned_handle` + + +
public(friend) fun destroy_storable_permissioned_handle(p: permissioned_signer::StorablePermissionedHandle)
+
+ + + + +
ensures !exists<PermissionStorage>(p.permissions_storage_addr);
+let post granted_permissions = global<GrantedPermissionHandles>(
+    p.master_account_addr
+);
+
+ + + + + +### Function `revoke_permission_storage_address` + + +
public entry fun revoke_permission_storage_address(s: &signer, permissions_storage_addr: address)
+
+ + + + + + +### Function `authorize_increase` + + +
public(friend) fun authorize_increase<PermKey: copy, drop, store>(master: &signer, permissioned: &signer, capacity: u256, perm: PermKey)
+
+ + + + +
pragma aborts_if_is_partial;
+aborts_if !spec_is_permissioned_signer(permissioned);
+aborts_if spec_is_permissioned_signer(master);
+aborts_if signer::address_of(permissioned) != signer::address_of(master);
+ensures exists<PermissionStorage>(
+    spec_permission_address(permissioned)
+);
+
+ + + + + +### Function `check_permission_exists` + + +
public(friend) fun check_permission_exists<PermKey: copy, drop, store>(s: &signer, perm: PermKey): bool
+
+ + + + +
pragma verify = false;
+
+ + + + + + + +
fun spec_check_permission_exists<PermKey: copy + drop + store>(s: signer, perm: PermKey): bool {
+   use aptos_std::type_info;
+   use std::bcs;
+   let addr = spec_permission_address(s);
+   let key = Any {
+       type_name: type_info::type_name<PermKey>(),
+       data: bcs::serialize(perm)
+   };
+   if (!spec_is_permissioned_signer(s)) { true }
+   else if (!exists<PermissionStorage>(addr)) { false }
+   else {
+       simple_map::spec_contains_key(global<PermissionStorage>(addr).perms, key)
+   }
+}
+
+ + + + + +### Function `check_permission_capacity_above` + + +
public(friend) fun check_permission_capacity_above<PermKey: copy, drop, store>(s: &signer, threshold: u256, perm: PermKey): bool
+
+ + + + +
let permissioned_signer_addr = spec_permission_address(s);
+ensures !spec_is_permissioned_signer(s) ==> result == true;
+ensures (
+    spec_is_permissioned_signer(s)
+        && !exists<PermissionStorage>(permissioned_signer_addr)
+) ==> result == false;
+let key = Any {
+    type_name: type_info::type_name<SimpleMap<Any, u256>>(),
+    data: bcs::serialize(perm)
+};
+
+ + + + + +### Function `check_permission_consume` + + +
public(friend) fun check_permission_consume<PermKey: copy, drop, store>(s: &signer, threshold: u256, perm: PermKey): bool
+
+ + + + +
let permissioned_signer_addr = spec_permission_address(s);
+ensures !spec_is_permissioned_signer(s) ==> result == true;
+ensures (
+    spec_is_permissioned_signer(s)
+        && !exists<PermissionStorage>(permissioned_signer_addr)
+) ==> result == false;
+
+ + + + + +### Function `capacity` + + +
public(friend) fun capacity<PermKey: copy, drop, store>(s: &signer, perm: PermKey): option::Option<u256>
+
+ + + + + + +### Function `is_permissioned_signer` + + +
public fun is_permissioned_signer(s: &signer): bool
+
+ + + + +
pragma opaque;
+aborts_if [abstract] false;
+ensures [abstract] result == spec_is_permissioned_signer(s);
+
+ + + + + + + +
fun spec_permission_address(s: signer): address;
+
+ + + + + +### Function `permission_address` + + +
fun permission_address(permissioned: &signer): address
+
+ + + + +
pragma opaque;
+aborts_if [abstract]!spec_is_permissioned_signer(permissioned);
+ensures [abstract] result == spec_permission_address(permissioned);
+
+ + + + + + + +
fun spec_signer_from_permissioned_handle_impl(
+   master_account_addr: address, permissions_storage_addr: address
+): signer;
+
+ + + + + +### Function `signer_from_permissioned_handle_impl` + + +
fun signer_from_permissioned_handle_impl(master_account_addr: address, permissions_storage_addr: address): signer
+
+ + + + +
pragma opaque;
+ensures [abstract] result
+    == spec_signer_from_permissioned_handle_impl(
+        master_account_addr, permissions_storage_addr
+    );
+
+ + +[move-book]: https://aptos.dev/move/book/SUMMARY 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..eab14b4fa26e6 100644 --- a/aptos-move/framework/aptos-framework/doc/primary_fungible_store.md +++ b/aptos-move/framework/aptos-framework/doc/primary_fungible_store.md @@ -33,6 +33,7 @@ fungible asset to it. This emits an deposit event. - [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) @@ -507,6 +508,37 @@ 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_with_fa(owner, &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/resource_account.md b/aptos-move/framework/aptos-framework/doc/resource_account.md index 318d15a785de7..b292e11870952 100644 --- a/aptos-move/framework/aptos-framework/doc/resource_account.md +++ b/aptos-move/framework/aptos-framework/doc/resource_account.md @@ -486,6 +486,8 @@ the SignerCapability.
let source_addr = signer::address_of(origin);
 let resource_addr = account::spec_create_resource_address(source_addr, seed);
+let resource = create_signer::spec_create_signer(resource_addr);
+aborts_if permissioned_signer::spec_is_permissioned_signer(resource);
 include RotateAccountAuthenticationKeyAndStoreCapabilityAbortsIfWithoutAccountLimit;
 
@@ -547,7 +549,8 @@ the SignerCapability. -
let resource_addr = signer::address_of(resource);
+
aborts_if permissioned_signer::spec_is_permissioned_signer(resource);
+let resource_addr = signer::address_of(resource);
 // This enforces high-level requirement 1:
 include RotateAccountAuthenticationKeyAndStoreCapabilityAbortsIf;
 // This enforces high-level requirement 2:
@@ -618,7 +621,8 @@ the SignerCapability.
 
 
 
-
// This enforces high-level requirement 6:
+
aborts_if permissioned_signer::spec_is_permissioned_signer(resource);
+// 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/stake.md b/aptos-move/framework/aptos-framework/doc/stake.md
index dedfe15027641..8bad1999f929c 100644
--- a/aptos-move/framework/aptos-framework/doc/stake.md
+++ b/aptos-move/framework/aptos-framework/doc/stake.md
@@ -33,6 +33,7 @@ or if their stake drops below the min required, they would get removed at the en
 -  [Struct `IndividualValidatorPerformance`](#0x1_stake_IndividualValidatorPerformance)
 -  [Resource `ValidatorPerformance`](#0x1_stake_ValidatorPerformance)
 -  [Struct `RegisterValidatorCandidateEvent`](#0x1_stake_RegisterValidatorCandidateEvent)
+-  [Struct `StakePermission`](#0x1_stake_StakePermission)
 -  [Struct `RegisterValidatorCandidate`](#0x1_stake_RegisterValidatorCandidate)
 -  [Struct `SetOperatorEvent`](#0x1_stake_SetOperatorEvent)
 -  [Struct `SetOperator`](#0x1_stake_SetOperator)
@@ -63,6 +64,8 @@ or if their stake drops below the min required, they would get removed at the en
 -  [Resource `Ghost$ghost_active_num`](#0x1_stake_Ghost$ghost_active_num)
 -  [Resource `Ghost$ghost_pending_inactive_num`](#0x1_stake_Ghost$ghost_pending_inactive_num)
 -  [Constants](#@Constants_0)
+-  [Function `check_signer_permission`](#0x1_stake_check_signer_permission)
+-  [Function `grant_permission`](#0x1_stake_grant_permission)
 -  [Function `get_lockup_secs`](#0x1_stake_get_lockup_secs)
 -  [Function `get_remaining_lockup_secs`](#0x1_stake_get_remaining_lockup_secs)
 -  [Function `get_stake`](#0x1_stake_get_stake)
@@ -175,6 +178,7 @@ or if their stake drops below the min required, they would get removed at the en
 use 0x1::fixed_point64;
 use 0x1::math64;
 use 0x1::option;
+use 0x1::permissioned_signer;
 use 0x1::reconfiguration_state;
 use 0x1::signer;
 use 0x1::staking_config;
@@ -624,6 +628,33 @@ This allows the Stake module to mint rewards to stakers.
 
 
 
+
+
+
+
+## Struct `StakePermission`
+
+
+
+
struct StakePermission has copy, drop, store
+
+ + + +
+Fields + + +
+
+dummy_field: bool +
+
+ +
+
+ +
@@ -1730,6 +1761,16 @@ Validators cannot join or leave post genesis on this test network. + + +Signer does not have permission to perform stake logic. + + +
const ENO_STAKE_PERMISSION: u64 = 28;
+
+ + + An account cannot own more than one owner capability. @@ -1878,6 +1919,59 @@ Validator status enum. We can switch to proper enum later once Move supports it. + + +## Function `check_signer_permission` + +Permissions + + +
fun check_signer_permission(s: &signer)
+
+ + + +
+Implementation + + +
inline fun check_signer_permission(s: &signer) {
+    assert!(
+        permissioned_signer::check_permission_exists(s, StakePermission {}),
+        error::permission_denied(ENO_STAKE_PERMISSION),
+    );
+}
+
+ + + +
+ + + +## Function `grant_permission` + +Grant permission to mutate staking on behalf of the master signer. + + +
public fun grant_permission(master: &signer, permissioned_signer: &signer)
+
+ + + +
+Implementation + + +
public fun grant_permission(master: &signer, permissioned_signer: &signer) {
+    permissioned_signer::authorize_unlimited(master, permissioned_signer, StakePermission {})
+}
+
+ + + +
+ ## Function `get_lockup_secs` @@ -2385,6 +2479,7 @@ to set later. operator: address, voter: address, ) acquires AllowedValidators, OwnerCapability, StakePool, ValidatorSet { + check_signer_permission(owner); initialize_owner(owner); move_to(owner, ValidatorConfig { consensus_pubkey: vector::empty(), @@ -2434,6 +2529,7 @@ Initialize the validator account and give ownership to the signing account. network_addresses: vector<u8>, fullnode_addresses: vector<u8>, ) acquires AllowedValidators { + check_signer_permission(account); // Checks the public key has a valid proof-of-possession to prevent rogue-key attacks. let pubkey_from_pop = &bls12381::public_key_from_bytes_with_pop( consensus_pubkey, @@ -2471,6 +2567,7 @@ Initialize the validator account and give ownership to the signing account.
fun initialize_owner(owner: &signer) acquires AllowedValidators {
+    check_signer_permission(owner);
     let owner_address = signer::address_of(owner);
     assert!(is_allowed(owner_address), error::not_found(EINELIGIBLE_VALIDATOR));
     assert!(!stake_pool_exists(owner_address), error::already_exists(EALREADY_REGISTERED));
@@ -2525,6 +2622,7 @@ Extract and return owner capability from the signing account.
 
 
 
public fun extract_owner_cap(owner: &signer): OwnerCapability acquires OwnerCapability {
+    check_signer_permission(owner);
     let owner_address = signer::address_of(owner);
     assert_owner_cap_exists(owner_address);
     move_from<OwnerCapability>(owner_address)
@@ -2553,6 +2651,7 @@ staking pool.
 
 
 
public fun deposit_owner_cap(owner: &signer, owner_cap: OwnerCapability) {
+    check_signer_permission(owner);
     assert!(!exists<OwnerCapability>(signer::address_of(owner)), error::not_found(EOWNER_CAP_ALREADY_EXISTS));
     move_to(owner, owner_cap);
 }
@@ -2604,6 +2703,7 @@ Allows an owner to change the operator of the stake pool.
 
 
 
public entry fun set_operator(owner: &signer, new_operator: address) acquires OwnerCapability, StakePool {
+    check_signer_permission(owner);
     let owner_address = signer::address_of(owner);
     assert_owner_cap_exists(owner_address);
     let ownership_cap = borrow_global<OwnerCapability>(owner_address);
@@ -2680,6 +2780,7 @@ Allows an owner to change the delegated voter of the stake pool.
 
 
 
public entry fun set_delegated_voter(owner: &signer, new_voter: address) acquires OwnerCapability, StakePool {
+    check_signer_permission(owner);
     let owner_address = signer::address_of(owner);
     assert_owner_cap_exists(owner_address);
     let ownership_cap = borrow_global<OwnerCapability>(owner_address);
@@ -2736,6 +2837,7 @@ Add amount of coins from the public entry fun add_stake(owner: &signer, amount: u64) acquires OwnerCapability, StakePool, ValidatorSet {
+    check_signer_permission(owner);
     let owner_address = signer::address_of(owner);
     assert_owner_cap_exists(owner_address);
     let ownership_cap = borrow_global<OwnerCapability>(owner_address);
@@ -2837,6 +2939,7 @@ Move amount of coins from pending_inactive to active.
 
 
 
public entry fun reactivate_stake(owner: &signer, amount: u64) acquires OwnerCapability, StakePool {
+    check_signer_permission(owner);
     assert_reconfig_not_in_progress();
     let owner_address = signer::address_of(owner);
     assert_owner_cap_exists(owner_address);
@@ -2925,6 +3028,7 @@ Rotate the consensus key of the validator, it'll take effect in next epoch.
     new_consensus_pubkey: vector<u8>,
     proof_of_possession: vector<u8>,
 ) acquires StakePool, ValidatorConfig {
+    check_signer_permission(operator);
     assert_reconfig_not_in_progress();
     assert_stake_pool_exists(pool_address);
 
@@ -2989,6 +3093,7 @@ Update the network and full node addresses of the validator. This only takes eff
     new_network_addresses: vector<u8>,
     new_fullnode_addresses: vector<u8>,
 ) acquires StakePool, ValidatorConfig {
+    check_signer_permission(operator);
     assert_reconfig_not_in_progress();
     assert_stake_pool_exists(pool_address);
     let stake_pool = borrow_global_mut<StakePool>(pool_address);
@@ -3046,6 +3151,7 @@ Similar to increase_lockup_with_cap but will use ownership capability from the s
 
 
 
public entry fun increase_lockup(owner: &signer) acquires OwnerCapability, StakePool {
+    check_signer_permission(owner);
     let owner_address = signer::address_of(owner);
     assert_owner_cap_exists(owner_address);
     let ownership_cap = borrow_global<OwnerCapability>(owner_address);
@@ -3130,6 +3236,7 @@ This can only called by the operator of the validator/staking pool.
     operator: &signer,
     pool_address: address
 ) acquires StakePool, ValidatorConfig, ValidatorSet {
+    check_signer_permission(operator);
     assert!(
         staking_config::get_allow_validator_set_change(&staking_config::get()),
         error::invalid_argument(ENO_POST_GENESIS_VALIDATOR_SET_CHANGE_ALLOWED),
@@ -3233,6 +3340,7 @@ Similar to unlock_with_cap but will use ownership capability from the signing ac
 
 
 
public entry fun unlock(owner: &signer, amount: u64) acquires OwnerCapability, StakePool {
+    check_signer_permission(owner);
     assert_reconfig_not_in_progress();
     let owner_address = signer::address_of(owner);
     assert_owner_cap_exists(owner_address);
@@ -3321,6 +3429,7 @@ Withdraw from account's inacti
     owner: &signer,
     withdraw_amount: u64
 ) acquires OwnerCapability, StakePool, ValidatorSet {
+    check_signer_permission(owner);
     let owner_address = signer::address_of(owner);
     assert_owner_cap_exists(owner_address);
     let ownership_cap = borrow_global<OwnerCapability>(owner_address);
@@ -3420,6 +3529,7 @@ Can only be called by the operator of the validator/staking pool.
     operator: &signer,
     pool_address: address
 ) acquires StakePool, ValidatorSet {
+    check_signer_permission(operator);
     assert_reconfig_not_in_progress();
     let config = staking_config::get();
     assert!(
@@ -4792,6 +4902,11 @@ Returns validator's next epoch voting power, including pending_active, active, a
 
 
 
pragma verify_duration_estimate = 120;
+pragma verify = false;
+pragma aborts_if_is_partial;
+include AbortsIfSignerPermissionStake {
+    s: owner
+};
 include ResourceRequirement;
 let addr = signer::address_of(owner);
 ensures global<ValidatorConfig>(addr) == ValidatorConfig {
@@ -4823,7 +4938,10 @@ Returns validator's next epoch voting power, including pending_active, active, a
 
 
 
-
let pubkey_from_pop = bls12381::spec_public_key_from_bytes_with_pop(
+
include AbortsIfSignerPermissionStake {
+    s: account
+};
+let pubkey_from_pop = bls12381::spec_public_key_from_bytes_with_pop(
     consensus_pubkey,
     proof_of_possession_from_bytes(proof_of_possession)
 );
@@ -4862,6 +4980,9 @@ Returns validator's next epoch voting power, including pending_active, active, a
 
 
 
pragma verify_duration_estimate = 300;
+include AbortsIfSignerPermissionStake {
+    s: owner
+};
 let owner_address = signer::address_of(owner);
 aborts_if !exists<OwnerCapability>(owner_address);
 ensures !exists<OwnerCapability>(owner_address);
@@ -4880,7 +5001,10 @@ Returns validator's next epoch voting power, including pending_active, active, a
 
 
 
-
let owner_address = signer::address_of(owner);
+
include AbortsIfSignerPermissionStake {
+    s: owner
+};
+let owner_address = signer::address_of(owner);
 aborts_if exists<OwnerCapability>(owner_address);
 ensures exists<OwnerCapability>(owner_address);
 ensures global<OwnerCapability>(owner_address) == owner_cap;
@@ -4940,8 +5064,11 @@ Returns validator's next epoch voting power, including pending_active, active, a
 
 
 
-
pragma verify_duration_estimate = 120;
+
pragma verify = false;
 pragma aborts_if_is_partial;
+include AbortsIfSignerPermissionStake {
+    s: owner
+};
 aborts_if reconfiguration_state::spec_is_in_progress();
 include ResourceRequirement;
 include AddStakeAbortsIfAndEnsures;
@@ -4961,7 +5088,7 @@ Returns validator's next epoch voting power, including pending_active, active, a
 
 
 
pragma disable_invariants_in_body;
-pragma verify_duration_estimate = 300;
+pragma verify = false;
 include ResourceRequirement;
 let amount = coins.value;
 aborts_if reconfiguration_state::spec_is_in_progress();
@@ -5006,7 +5133,10 @@ Returns validator's next epoch voting power, including pending_active, active, a
 
 
 
-
let pre_stake_pool = global<StakePool>(pool_address);
+
include AbortsIfSignerPermissionStake {
+    s: operator
+};
+let pre_stake_pool = global<StakePool>(pool_address);
 let post validator_info = global<ValidatorConfig>(pool_address);
 aborts_if reconfiguration_state::spec_is_in_progress();
 aborts_if !exists<StakePool>(pool_address);
@@ -5035,7 +5165,10 @@ Returns validator's next epoch voting power, including pending_active, active, a
 
 
 
-
let pre_stake_pool = global<StakePool>(pool_address);
+
include AbortsIfSignerPermissionStake {
+    s: operator
+};
+let pre_stake_pool = global<StakePool>(pool_address);
 let post validator_info = global<ValidatorConfig>(pool_address);
 modifies global<ValidatorConfig>(pool_address);
 include StakedValueNochange;
@@ -5091,6 +5224,9 @@ Returns validator's next epoch voting power, including pending_active, active, a
 
 
pragma verify_duration_estimate = 60;
 pragma disable_invariants_in_body;
+include AbortsIfSignerPermissionStake {
+    s: operator
+};
 aborts_if !staking_config::get_allow_validator_set_change(staking_config::get());
 aborts_if !exists<StakePool>(pool_address);
 aborts_if !exists<ValidatorConfig>(pool_address);
@@ -5168,6 +5304,9 @@ Returns validator's next epoch voting power, including pending_active, active, a
 
 
 
pragma verify = false;
+include AbortsIfSignerPermissionStake {
+    s: owner
+};
 aborts_if reconfiguration_state::spec_is_in_progress();
 let addr = signer::address_of(owner);
 let ownership_cap = global<OwnerCapability>(addr);
@@ -5214,6 +5353,9 @@ Returns validator's next epoch voting power, including pending_active, active, a
 
 
pragma disable_invariants_in_body;
 requires chain_status::is_operating();
+include AbortsIfSignerPermissionStake {
+    s: operator
+};
 aborts_if reconfiguration_state::spec_is_in_progress();
 let config = staking_config::get();
 aborts_if !staking_config::get_allow_validator_set_change(config);
@@ -5399,25 +5541,6 @@ Returns validator's next epoch voting power, including pending_active, active, a
 
 
 
-
-
-
-
-
schema UpdateStakePoolAbortsIf {
-    pool_address: address;
-    validator_perf: ValidatorPerformance;
-    aborts_if !exists<StakePool>(pool_address);
-    aborts_if !exists<ValidatorConfig>(pool_address);
-    aborts_if global<ValidatorConfig>(pool_address).validator_index >= len(validator_perf.validators);
-    let aptos_addr = type_info::type_of<AptosCoin>().account_address;
-    let stake_pool = global<StakePool>(pool_address);
-    include DistributeRewardsAbortsIf {stake: stake_pool.active};
-    include DistributeRewardsAbortsIf {stake: stake_pool.pending_inactive};
-}
-
- - - ### Function `get_reconfig_start_time_secs` @@ -5485,6 +5608,7 @@ Returns validator's next epoch voting power, including pending_active, active, a
pragma opaque;
 pragma verify_duration_estimate = 300;
+pragma verify = false;
 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/doc/staking_proxy.md b/aptos-move/framework/aptos-framework/doc/staking_proxy.md
index c05adb7f26803..c6c767f8af19c 100644
--- a/aptos-move/framework/aptos-framework/doc/staking_proxy.md
+++ b/aptos-move/framework/aptos-framework/doc/staking_proxy.md
@@ -5,6 +5,10 @@
 
 
 
+-  [Struct `StakeProxyPermission`](#0x1_staking_proxy_StakeProxyPermission)
+-  [Constants](#@Constants_0)
+-  [Function `check_signer_permission`](#0x1_staking_proxy_check_signer_permission)
+-  [Function `grant_permission`](#0x1_staking_proxy_grant_permission)
 -  [Function `set_operator`](#0x1_staking_proxy_set_operator)
 -  [Function `set_voter`](#0x1_staking_proxy_set_voter)
 -  [Function `set_vesting_contract_operator`](#0x1_staking_proxy_set_vesting_contract_operator)
@@ -13,20 +17,23 @@
 -  [Function `set_vesting_contract_voter`](#0x1_staking_proxy_set_vesting_contract_voter)
 -  [Function `set_staking_contract_voter`](#0x1_staking_proxy_set_staking_contract_voter)
 -  [Function `set_stake_pool_voter`](#0x1_staking_proxy_set_stake_pool_voter)
--  [Specification](#@Specification_0)
+-  [Specification](#@Specification_1)
     -  [High-level Requirements](#high-level-req)
     -  [Module-level Specification](#module-level-spec)
-    -  [Function `set_operator`](#@Specification_0_set_operator)
-    -  [Function `set_voter`](#@Specification_0_set_voter)
-    -  [Function `set_vesting_contract_operator`](#@Specification_0_set_vesting_contract_operator)
-    -  [Function `set_staking_contract_operator`](#@Specification_0_set_staking_contract_operator)
-    -  [Function `set_stake_pool_operator`](#@Specification_0_set_stake_pool_operator)
-    -  [Function `set_vesting_contract_voter`](#@Specification_0_set_vesting_contract_voter)
-    -  [Function `set_staking_contract_voter`](#@Specification_0_set_staking_contract_voter)
-    -  [Function `set_stake_pool_voter`](#@Specification_0_set_stake_pool_voter)
-
-
-
use 0x1::signer;
+    -  [Function `grant_permission`](#@Specification_1_grant_permission)
+    -  [Function `set_operator`](#@Specification_1_set_operator)
+    -  [Function `set_voter`](#@Specification_1_set_voter)
+    -  [Function `set_vesting_contract_operator`](#@Specification_1_set_vesting_contract_operator)
+    -  [Function `set_staking_contract_operator`](#@Specification_1_set_staking_contract_operator)
+    -  [Function `set_stake_pool_operator`](#@Specification_1_set_stake_pool_operator)
+    -  [Function `set_vesting_contract_voter`](#@Specification_1_set_vesting_contract_voter)
+    -  [Function `set_staking_contract_voter`](#@Specification_1_set_staking_contract_voter)
+    -  [Function `set_stake_pool_voter`](#@Specification_1_set_stake_pool_voter)
+
+
+
use 0x1::error;
+use 0x1::permissioned_signer;
+use 0x1::signer;
 use 0x1::stake;
 use 0x1::staking_contract;
 use 0x1::vesting;
@@ -34,6 +41,101 @@
 
 
 
+
+
+## Struct `StakeProxyPermission`
+
+
+
+
struct StakeProxyPermission has copy, drop, store
+
+ + + +
+Fields + + +
+
+dummy_field: bool +
+
+ +
+
+ + +
+ + + +## Constants + + + + +Signer does not have permission to perform stake proxy logic. + + +
const ENO_STAKE_PERMISSION: u64 = 28;
+
+ + + + + +## Function `check_signer_permission` + +Permissions + + +
fun check_signer_permission(s: &signer)
+
+ + + +
+Implementation + + +
inline fun check_signer_permission(s: &signer) {
+    assert!(
+        permissioned_signer::check_permission_exists(s, StakeProxyPermission {}),
+        error::permission_denied(ENO_STAKE_PERMISSION),
+    );
+}
+
+ + + +
+ + + +## Function `grant_permission` + +Grant permission to mutate staking on behalf of the master signer. + + +
public fun grant_permission(master: &signer, permissioned_signer: &signer)
+
+ + + +
+Implementation + + +
public fun grant_permission(master: &signer, permissioned_signer: &signer) {
+    permissioned_signer::authorize_unlimited(master, permissioned_signer, StakeProxyPermission {})
+}
+
+ + + +
+ ## Function `set_operator` @@ -102,6 +204,7 @@
public entry fun set_vesting_contract_operator(owner: &signer, old_operator: address, new_operator: address) {
+    check_signer_permission(owner);
     let owner_address = signer::address_of(owner);
     let vesting_contracts = &vesting::vesting_contracts(owner_address);
     vector::for_each_ref(vesting_contracts, |vesting_contract| {
@@ -134,6 +237,7 @@
 
 
 
public entry fun set_staking_contract_operator(owner: &signer, old_operator: address, new_operator: address) {
+    check_signer_permission(owner);
     let owner_address = signer::address_of(owner);
     if (staking_contract::staking_contract_exists(owner_address, old_operator)) {
         let current_commission_percentage = staking_contract::commission_percentage(owner_address, old_operator);
@@ -162,6 +266,7 @@
 
 
 
public entry fun set_stake_pool_operator(owner: &signer, new_operator: address) {
+    check_signer_permission(owner);
     let owner_address = signer::address_of(owner);
     if (stake::stake_pool_exists(owner_address)) {
         stake::set_operator(owner, new_operator);
@@ -189,6 +294,7 @@
 
 
 
public entry fun set_vesting_contract_voter(owner: &signer, operator: address, new_voter: address) {
+    check_signer_permission(owner);
     let owner_address = signer::address_of(owner);
     let vesting_contracts = &vesting::vesting_contracts(owner_address);
     vector::for_each_ref(vesting_contracts, |vesting_contract| {
@@ -220,6 +326,7 @@
 
 
 
public entry fun set_staking_contract_voter(owner: &signer, operator: address, new_voter: address) {
+    check_signer_permission(owner);
     let owner_address = signer::address_of(owner);
     if (staking_contract::staking_contract_exists(owner_address, operator)) {
         staking_contract::update_voter(owner, operator, new_voter);
@@ -247,6 +354,7 @@
 
 
 
public entry fun set_stake_pool_voter(owner: &signer, new_voter: address) {
+    check_signer_permission(owner);
     if (stake::stake_pool_exists(signer::address_of(owner))) {
         stake::set_delegated_voter(owner, new_voter);
     };
@@ -257,7 +365,7 @@
 
 
 
-
+
 
 ## Specification
 
@@ -329,7 +437,26 @@
 
 
 
-
+
+
+### Function `grant_permission`
+
+
+
public fun grant_permission(master: &signer, permissioned_signer: &signer)
+
+ + + + +
pragma aborts_if_is_partial;
+aborts_if !permissioned_signer::spec_is_permissioned_signer(permissioned_signer);
+aborts_if permissioned_signer::spec_is_permissioned_signer(master);
+aborts_if signer::address_of(master) != signer::address_of(permissioned_signer);
+
+ + + + ### Function `set_operator` @@ -349,7 +476,7 @@ Aborts if conditions of SetStakePoolOperator are not met - + ### Function `set_voter` @@ -368,7 +495,7 @@ Aborts if conditions of SetStackingContractVoter and SetStackPoolVoterAbortsIf a - + ### Function `set_vesting_contract_operator` @@ -384,7 +511,7 @@ Aborts if conditions of SetStackingContractVoter and SetStackPoolVoterAbortsIf a - + ### Function `set_staking_contract_operator` @@ -438,7 +565,7 @@ Aborts if conditions of SetStackingContractVoter and SetStackPoolVoterAbortsIf a - + ### Function `set_stake_pool_operator` @@ -452,6 +579,12 @@ One of them are not exists
include SetStakePoolOperator;
+include AbortsIfSignerPermissionStakeProxy {
+    s: owner
+};
+include exists<stake::StakePool>(signer::address_of(owner)) ==> stake::AbortsIfSignerPermissionStake {
+    s:owner
+};
 
@@ -463,6 +596,9 @@ One of them are not exists
schema SetStakePoolOperator {
     owner: &signer;
     new_operator: address;
+    include AbortsIfSignerPermissionStakeProxy {
+        s: owner
+    };
     let owner_address = signer::address_of(owner);
     let ownership_cap = borrow_global<stake::OwnerCapability>(owner_address);
     let pool_address = ownership_cap.pool_address;
@@ -473,7 +609,7 @@ One of them are not exists
 
 
 
-
+
 
 ### Function `set_vesting_contract_voter`
 
@@ -489,7 +625,7 @@ One of them are not exists
 
 
 
-
+
 
 ### Function `set_staking_contract_voter`
 
@@ -501,6 +637,9 @@ One of them are not exists
 
 
 
include SetStakingContractVoter;
+include AbortsIfSignerPermissionStakeProxy {
+    s: owner
+};
 
@@ -531,7 +670,7 @@ Then abort if the resource is not exist - + ### Function `set_stake_pool_voter` @@ -543,6 +682,12 @@ Then abort if the resource is not exist
include SetStakePoolVoterAbortsIf;
+include AbortsIfSignerPermissionStakeProxy {
+    s: owner
+};
+include exists<stake::StakePool>(signer::address_of(owner)) ==> stake::AbortsIfSignerPermissionStake {
+    s:owner
+};
 
@@ -554,6 +699,9 @@ Then abort if the resource is not exist
schema SetStakePoolVoterAbortsIf {
     owner: &signer;
     new_voter: address;
+    include AbortsIfSignerPermissionStakeProxy {
+        s: owner
+    };
     let owner_address = signer::address_of(owner);
     let ownership_cap = global<stake::OwnerCapability>(owner_address);
     let pool_address = ownership_cap.pool_address;
@@ -563,4 +711,17 @@ Then abort if the resource is not exist
 
+ + + + + +
schema AbortsIfSignerPermissionStakeProxy {
+    s: signer;
+    let perm = StakeProxyPermission {};
+    aborts_if !permissioned_signer::spec_check_permission_exists(s, perm);
+}
+
+ + [move-book]: https://aptos.dev/move/book/SUMMARY diff --git a/aptos-move/framework/aptos-framework/doc/transaction_context.md b/aptos-move/framework/aptos-framework/doc/transaction_context.md index cc7d9010ffc74..d4dbf2d635811 100644 --- a/aptos-move/framework/aptos-framework/doc/transaction_context.md +++ b/aptos-move/framework/aptos-framework/doc/transaction_context.md @@ -1029,6 +1029,7 @@ Returns the inner entry function payload of the multisig payload.
pragma opaque;
+aborts_if [abstract] false;
 ensures [abstract] result == spec_generate_unique_address();
 
@@ -1055,6 +1056,7 @@ Returns the inner entry function payload of the multisig payload.
pragma opaque;
+aborts_if [abstract] false;
 // This enforces high-level requirement 3:
 ensures [abstract] result == spec_generate_unique_address();
 
diff --git a/aptos-move/framework/aptos-framework/doc/vesting.md b/aptos-move/framework/aptos-framework/doc/vesting.md index 95b80f3be991a..61f57cb5517c7 100644 --- a/aptos-move/framework/aptos-framework/doc/vesting.md +++ b/aptos-move/framework/aptos-framework/doc/vesting.md @@ -65,7 +65,10 @@ withdrawable, admin can call admin_withdraw to withdraw all funds to the vesting - [Struct `DistributeEvent`](#0x1_vesting_DistributeEvent) - [Struct `TerminateEvent`](#0x1_vesting_TerminateEvent) - [Struct `AdminWithdrawEvent`](#0x1_vesting_AdminWithdrawEvent) +- [Struct `VestPermission`](#0x1_vesting_VestPermission) - [Constants](#@Constants_0) +- [Function `check_signer_permission`](#0x1_vesting_check_signer_permission) +- [Function `grant_permission`](#0x1_vesting_grant_permission) - [Function `stake_pool_address`](#0x1_vesting_stake_pool_address) - [Function `vesting_start_secs`](#0x1_vesting_vesting_start_secs) - [Function `period_duration_secs`](#0x1_vesting_period_duration_secs) @@ -169,6 +172,7 @@ withdrawable, admin can call admin_withdraw to withdraw all funds to the vesting use 0x1::features; use 0x1::fixed_point32; use 0x1::math64; +use 0x1::permissioned_signer; use 0x1::pool_u64; use 0x1::signer; use 0x1::simple_map; @@ -1423,6 +1427,33 @@ withdrawable, admin can call admin_withdraw to withdraw all funds to the vesting + + + + +## Struct `VestPermission` + + + +
struct VestPermission has copy, drop, store
+
+ + + +
+Fields + + +
+
+dummy_field: bool +
+
+ +
+
+ +
@@ -1470,6 +1501,16 @@ Shareholders list cannot be empty. + + +Current permissioned signer cannot perform vesting operations. + + +
const ENO_VESTING_PERMISSION: u64 = 17;
+
+ + + Cannot terminate the vesting contract with pending active stake. Need to wait until next epoch. @@ -1640,6 +1681,59 @@ Vesting contract has been terminated and all funds have been released back to th + + +## Function `check_signer_permission` + +Permissions + + +
fun check_signer_permission(s: &signer)
+
+ + + +
+Implementation + + +
inline fun check_signer_permission(s: &signer) {
+    assert!(
+        permissioned_signer::check_permission_exists(s, VestPermission {}),
+        error::permission_denied(ENO_VESTING_PERMISSION),
+    );
+}
+
+ + + +
+ + + +## Function `grant_permission` + +Grant permission to perform vesting operations on behalf of the master signer. + + +
public fun grant_permission(master: &signer, permissioned_signer: &signer)
+
+ + + +
+Implementation + + +
public fun grant_permission(master: &signer, permissioned_signer: &signer) {
+    permissioned_signer::authorize_unlimited(master, permissioned_signer, VestPermission {})
+}
+
+ + + +
+ ## Function `stake_pool_address` @@ -3238,6 +3332,7 @@ This address should be deterministic for the same admin and vesting contract cre
fun verify_admin(admin: &signer, vesting_contract: &VestingContract) {
+    check_signer_permission(admin);
     assert!(signer::address_of(admin) == vesting_contract.admin, error::unauthenticated(ENOT_ADMIN));
 }
 
@@ -4327,7 +4422,8 @@ This address should be deterministic for the same admin and vesting contract cre -
// This enforces high-level requirement 9:
+
aborts_if permissioned_signer::spec_is_permissioned_signer(admin);
+// This enforces high-level requirement 9:
 aborts_if signer::address_of(admin) != vesting_contract.admin;
 
@@ -4508,6 +4604,7 @@ This address should be deterministic for the same admin and vesting contract cre
schema VerifyAdminAbortsIf {
     contract_address: address;
     admin: signer;
+    aborts_if permissioned_signer::spec_is_permissioned_signer(admin);
     aborts_if !exists<VestingContract>(contract_address);
     let vesting_contract = global<VestingContract>(contract_address);
     aborts_if signer::address_of(admin) != vesting_contract.admin;
diff --git a/aptos-move/framework/aptos-framework/doc/voting.md b/aptos-move/framework/aptos-framework/doc/voting.md
index 2a4cb6cb28387..82a7e7b44c28c 100644
--- a/aptos-move/framework/aptos-framework/doc/voting.md
+++ b/aptos-move/framework/aptos-framework/doc/voting.md
@@ -36,7 +36,10 @@ the resolution process.
 -  [Struct `CreateProposalEvent`](#0x1_voting_CreateProposalEvent)
 -  [Struct `RegisterForumEvent`](#0x1_voting_RegisterForumEvent)
 -  [Struct `VoteEvent`](#0x1_voting_VoteEvent)
+-  [Struct `VotePermission`](#0x1_voting_VotePermission)
 -  [Constants](#@Constants_0)
+-  [Function `check_signer_permission`](#0x1_voting_check_signer_permission)
+-  [Function `grant_permission`](#0x1_voting_grant_permission)
 -  [Function `register`](#0x1_voting_register)
 -  [Function `create_proposal`](#0x1_voting_create_proposal)
 -  [Function `create_proposal_v2`](#0x1_voting_create_proposal_v2)
@@ -98,6 +101,7 @@ the resolution process.
 use 0x1::features;
 use 0x1::from_bcs;
 use 0x1::option;
+use 0x1::permissioned_signer;
 use 0x1::signer;
 use 0x1::simple_map;
 use 0x1::string;
@@ -593,6 +597,33 @@ Extra metadata (e.g. description, code url) can be part of the ProposalType stru
 
 
 
+
+
+
+
+## Struct `VotePermission`
+
+
+
+
struct VotePermission has copy, drop, store
+
+ + + +
+Fields + + +
+
+dummy_field: bool +
+
+ +
+
+ +
@@ -631,6 +662,16 @@ Cannot vote if the specified multi-step proposal is in execution. + + +Cannot call is_multi_step_proposal_in_execution() on single-step proposals. + + +
const ENO_VOTE_PERMISSION: u64 = 13;
+
+ + + Proposal cannot be resolved more than once @@ -780,6 +821,59 @@ Key used to track the resolvable time in the proposal's metadata. + + +## Function `check_signer_permission` + +Permissions + + +
fun check_signer_permission(s: &signer)
+
+ + + +
+Implementation + + +
inline fun check_signer_permission(s: &signer) {
+    assert!(
+        permissioned_signer::check_permission_exists(s, VotePermission {}),
+        error::permission_denied(ENO_VOTE_PERMISSION),
+    );
+}
+
+ + + +
+ + + +## Function `grant_permission` + +Grant permission to vote on behalf of the master signer. + + +
public fun grant_permission(master: &signer, permissioned_signer: &signer)
+
+ + + +
+Implementation + + +
public fun grant_permission(master: &signer, permissioned_signer: &signer) {
+    permissioned_signer::authorize_unlimited(master, permissioned_signer, VotePermission {})
+}
+
+ + + +
+ ## Function `register` @@ -796,6 +890,7 @@ Key used to track the resolvable time in the proposal's metadata.
public fun register<ProposalType: store>(account: &signer) {
+    check_signer_permission(account);
     let addr = signer::address_of(account);
     assert!(!exists<VotingForum<ProposalType>>(addr), error::already_exists(EVOTING_FORUM_ALREADY_REGISTERED));
 
@@ -1926,7 +2021,8 @@ Return true if the voting period of the given proposal has already ended.
 
 
 
-
let addr = signer::address_of(account);
+
aborts_if permissioned_signer::spec_is_permissioned_signer(account);
+let addr = signer::address_of(account);
 aborts_if exists<VotingForum<ProposalType>>(addr);
 aborts_if !exists<account::Account>(addr);
 let register_account = global<account::Account>(addr);
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/aptos_account.move b/aptos-move/framework/aptos-framework/sources/aptos_account.move
index ddd0846b1596e..64c7dfc867303 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[]);
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/aptos_governance.move b/aptos-move/framework/aptos-framework/sources/aptos_governance.move
index f5de768d05864..ecd8144689ffd 100644
--- a/aptos-move/framework/aptos-framework/sources/aptos_governance.move
+++ b/aptos-move/framework/aptos-framework/sources/aptos_governance.move
@@ -31,6 +31,7 @@ module aptos_framework::aptos_governance {
     use aptos_framework::system_addresses;
     use aptos_framework::aptos_coin::{Self, AptosCoin};
     use aptos_framework::consensus_config;
+    use aptos_framework::permissioned_signer;
     use aptos_framework::randomness_config;
     use aptos_framework::reconfiguration_with_dkg;
     use aptos_framework::timestamp;
@@ -64,6 +65,8 @@ module aptos_framework::aptos_governance {
     const ENOT_PARTIAL_VOTING_PROPOSAL: u64 = 14;
     /// The proposal has expired.
     const EPROPOSAL_EXPIRED: u64 = 15;
+    /// Current permissioned signer cannot perform governance operations.
+    const ENO_GOVERNANCE_PERMISSION: u64 = 16;
 
     /// This matches the same enum const in voting. We have to duplicate it as Move doesn't have support for enums yet.
     const PROPOSAL_STATE_SUCCEEDED: u64 = 1;
@@ -168,6 +171,21 @@ module aptos_framework::aptos_governance {
         voting_duration_secs: u64,
     }
 
+    struct GovernancePermission has copy, drop, store {}
+
+    /// Permissions
+    inline fun check_governance_permission(s: &signer) {
+        assert!(
+            permissioned_signer::check_permission_exists(s, GovernancePermission {}),
+            error::permission_denied(ENO_GOVERNANCE_PERMISSION),
+        );
+    }
+
+    /// Grant permission to perform governance operations on behalf of the master signer.
+    public fun grant_permission(master: &signer, permissioned_signer: &signer) {
+        permissioned_signer::authorize_unlimited(master, permissioned_signer, GovernancePermission {})
+    }
+
     /// Can be called during genesis or by the governance itself.
     /// Stores the signer capability for a given address.
     public fun store_signer_cap(
@@ -396,6 +414,7 @@ module aptos_framework::aptos_governance {
         metadata_hash: vector,
         is_multi_step_proposal: bool,
     ): u64 acquires GovernanceConfig, GovernanceEvents {
+        check_governance_permission(proposer);
         let proposer_address = signer::address_of(proposer);
         assert!(
             stake::get_delegated_voter(stake_pool) == proposer_address,
@@ -528,6 +547,7 @@ module aptos_framework::aptos_governance {
         voting_power: u64,
         should_pass: bool,
     ) acquires ApprovedExecutionHashes, VotingRecords, VotingRecordsV2, GovernanceEvents {
+        permissioned_signer::assert_master_signer(voter);
         let voter_address = signer::address_of(voter);
         assert!(stake::get_delegated_voter(stake_pool) == voter_address, error::invalid_argument(ENOT_DELEGATED_VOTER));
 
diff --git a/aptos-move/framework/aptos-framework/sources/aptos_governance.spec.move b/aptos-move/framework/aptos-framework/sources/aptos_governance.spec.move
index 3ea2572dd8fea..7126b01c0d76f 100644
--- a/aptos-move/framework/aptos-framework/sources/aptos_governance.spec.move
+++ b/aptos-move/framework/aptos-framework/sources/aptos_governance.spec.move
@@ -29,7 +29,14 @@ spec aptos_framework::aptos_governance {
     ///
     spec module {
         pragma verify = true;
-        pragma aborts_if_is_strict;
+        pragma aborts_if_is_partial;
+    }
+
+    spec schema AbortsIfPermissionedSigner {
+        use aptos_framework::permissioned_signer;
+        s: signer;
+        let perm = GovernancePermission {};
+        aborts_if !permissioned_signer::spec_check_permission_exists(s, perm);
     }
 
     spec store_signer_cap(
@@ -215,6 +222,7 @@ spec aptos_framework::aptos_governance {
         pragma verify_duration_estimate = 60;
         requires chain_status::is_operating();
         include CreateProposalAbortsIf;
+        // include AbortsIfPermissionedSigner { s: proposer };
     }
 
     /// `stake_pool` must exist StakePool.
diff --git a/aptos-move/framework/aptos-framework/sources/code.move b/aptos-move/framework/aptos-framework/sources/code.move
index ef884c9695d1c..56a0b13d0056c 100644
--- a/aptos-move/framework/aptos-framework/sources/code.move
+++ b/aptos-move/framework/aptos-framework/sources/code.move
@@ -13,6 +13,9 @@ module aptos_framework::code {
     use std::string;
     use aptos_framework::event;
     use aptos_framework::object::{Self, Object};
+    use aptos_framework::permissioned_signer;
+
+    friend aptos_framework::object_code_deployment;
 
     // ----------------------------------------------------------------------
     // Code Publishing
@@ -105,6 +108,24 @@ module aptos_framework::code {
     /// `code_object` does not exist.
     const ECODE_OBJECT_DOES_NOT_EXIST: u64 = 0xA;
 
+    /// Current permissioned signer cannot publish codes.
+    const ENO_CODE_PERMISSION: u64 = 0xB;
+
+    struct CodePublishingPermission has copy, drop, store {}
+
+    /// Permissions
+    public(friend) fun check_code_publishing_permission(s: &signer) {
+        assert!(
+            permissioned_signer::check_permission_exists(s, CodePublishingPermission {}),
+            error::permission_denied(ENO_CODE_PERMISSION),
+        );
+    }
+
+    /// Grant permission to publish code on behalf of the master signer.
+    public fun grant_permission(master: &signer, permissioned_signer: &signer) {
+        permissioned_signer::authorize_unlimited(master, permissioned_signer, CodePublishingPermission {})
+    }
+
     /// Whether unconditional code upgrade with no compatibility check is allowed. This
     /// publication mode should only be used for modules which aren't shared with user others.
     /// The developer is responsible for not breaking memory layout of any resources he already
@@ -145,6 +166,7 @@ module aptos_framework::code {
     /// Publishes a package at the given signer's address. The caller must provide package metadata describing the
     /// package.
     public fun publish_package(owner: &signer, pack: PackageMetadata, code: vector>) acquires PackageRegistry {
+        check_code_publishing_permission(owner);
         // Disallow incompatible upgrade mode. Governance can decide later if this should be reconsidered.
         assert!(
             pack.upgrade_policy.policy > upgrade_policy_arbitrary().policy,
@@ -206,6 +228,7 @@ module aptos_framework::code {
     }
 
     public fun freeze_code_object(publisher: &signer, code_object: Object) acquires PackageRegistry {
+        check_code_publishing_permission(publisher);
         let code_object_addr = object::object_address(&code_object);
         assert!(exists(code_object_addr), error::not_found(ECODE_OBJECT_DOES_NOT_EXIST));
         assert!(
diff --git a/aptos-move/framework/aptos-framework/sources/code.spec.move b/aptos-move/framework/aptos-framework/sources/code.spec.move
index f968e0dbbddc3..88c63ccb927d0 100644
--- a/aptos-move/framework/aptos-framework/sources/code.spec.move
+++ b/aptos-move/framework/aptos-framework/sources/code.spec.move
@@ -59,7 +59,7 @@ spec aptos_framework::code {
     ///
     spec module {
         pragma verify = true;
-        pragma aborts_if_is_strict;
+        pragma aborts_if_is_partial;
     }
 
     spec request_publish {
@@ -72,6 +72,13 @@ spec aptos_framework::code {
         pragma opaque;
     }
 
+    spec schema AbortsIfPermissionedSigner {
+        use aptos_framework::permissioned_signer;
+        s: signer;
+        let perm = CodePublishingPermission {};
+        aborts_if !permissioned_signer::spec_check_permission_exists(s, perm);
+    }
+
     spec initialize(aptos_framework: &signer, package_owner: &signer, metadata: PackageMetadata) {
         let aptos_addr = signer::address_of(aptos_framework);
         let owner_addr = signer::address_of(package_owner);
@@ -86,6 +93,7 @@ spec aptos_framework::code {
         let addr = signer::address_of(owner);
         modifies global(addr);
         aborts_if pack.upgrade_policy.policy <= upgrade_policy_arbitrary().policy;
+        // include AbortsIfPermissionedSigner { s: owner };
     }
 
     spec publish_package_txn {
@@ -125,6 +133,7 @@ spec aptos_framework::code {
         aborts_if !exists(code_object_addr);
         aborts_if !exists(code_object_addr);
         aborts_if !object::is_owner(code_object, signer::address_of(publisher));
+        // include AbortsIfPermissionedSigner { s: publisher };
 
         modifies global(code_object_addr);
     }
diff --git a/aptos-move/framework/aptos-framework/sources/coin.move b/aptos-move/framework/aptos-framework/sources/coin.move
index ce9d5b8230ca9..d5d8009515e78 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};
@@ -603,6 +604,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));
     }
 
@@ -959,6 +961,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!(
@@ -1016,6 +1019,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)) {
@@ -1059,6 +1063,17 @@ module aptos_framework::coin {
             amount
         );
         let withdrawn_coin = if (coin_amount_to_withdraw > 0) {
+            let metadata = paired_metadata();
+            if(option::is_some(&metadata)) {
+                fungible_asset::withdraw_permission_check_by_address(
+                    account,
+                    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>(account_addr);
             assert!(
                 !coin_store.frozen,
@@ -2022,4 +2037,222 @@ 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 = 0x50001, location = aptos_framework::permissioned_signer)]
+    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);
+
+        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 = 0x50001, location = aptos_framework::permissioned_signer)]
+    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);
+
+        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..d7fb0608e6901 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
@@ -385,6 +386,7 @@ spec aptos_framework::coin {
     }
 
     spec initialize {
+        aborts_if permissioned_signer::spec_is_permissioned_signer(account);
         let account_addr = signer::address_of(account);
         /// [high-level-req-1.2]
         aborts_if type_info::type_of().account_address != account_addr;
@@ -403,6 +405,7 @@ spec aptos_framework::coin {
     monitor_supply: bool,
     ): (BurnCapability, FreezeCapability, MintCapability) {
         use aptos_framework::aggregator_factory;
+        aborts_if permissioned_signer::spec_is_permissioned_signer(account);
         let addr = signer::address_of(account);
         aborts_if addr != @aptos_framework;
         aborts_if monitor_supply && !exists(@aptos_framework);
@@ -435,6 +438,7 @@ spec aptos_framework::coin {
     monitor_supply: bool,
     parallelizable: bool,
     ): (BurnCapability, FreezeCapability, MintCapability) {
+        aborts_if permissioned_signer::spec_is_permissioned_signer(account);
         include InitializeInternalSchema {
             name: name.bytes,
             symbol: symbol.bytes
diff --git a/aptos-move/framework/aptos-framework/sources/create_signer.move b/aptos-move/framework/aptos-framework/sources/create_signer.move
index 3da0c50c904f0..a92dd88845568 100644
--- a/aptos-move/framework/aptos-framework/sources/create_signer.move
+++ b/aptos-move/framework/aptos-framework/sources/create_signer.move
@@ -16,6 +16,7 @@ module aptos_framework::create_signer {
     friend aptos_framework::genesis;
     friend aptos_framework::multisig_account;
     friend aptos_framework::object;
+    friend aptos_framework::permissioned_signer;
 
     public(friend) native fun create_signer(addr: address): signer;
 }
diff --git a/aptos-move/framework/aptos-framework/sources/create_signer.spec.move b/aptos-move/framework/aptos-framework/sources/create_signer.spec.move
index 1bb4c0ffa9fd6..dab59d30da2db 100644
--- a/aptos-move/framework/aptos-framework/sources/create_signer.spec.move
+++ b/aptos-move/framework/aptos-framework/sources/create_signer.spec.move
@@ -41,5 +41,8 @@ spec aptos_framework::create_signer {
         pragma opaque;
         aborts_if [abstract] false;
         ensures [abstract] signer::address_of(result) == addr;
+        ensures [abstract] result == spec_create_signer(addr);
     }
+
+    spec fun spec_create_signer(addr: address): signer;
 }
diff --git a/aptos-move/framework/aptos-framework/sources/delegation_pool.move b/aptos-move/framework/aptos-framework/sources/delegation_pool.move
index 3b5cc7600f881..b02129fd40a3e 100644
--- a/aptos-move/framework/aptos-framework/sources/delegation_pool.move
+++ b/aptos-move/framework/aptos-framework/sources/delegation_pool.move
@@ -124,6 +124,7 @@ module aptos_framework::delegation_pool {
     use aptos_framework::aptos_governance;
     use aptos_framework::coin;
     use aptos_framework::event::{Self, EventHandle, emit};
+    use aptos_framework::permissioned_signer;
     use aptos_framework::stake;
     use aptos_framework::stake::get_operator;
     use aptos_framework::staking_config;
@@ -215,6 +216,9 @@ module aptos_framework::delegation_pool {
     /// Cannot unlock the accumulated active stake of NULL_SHAREHOLDER(0x0).
     const ECANNOT_UNLOCK_NULL_SHAREHOLDER: u64 = 27;
 
+    /// Signer does not have permission to perform delegation logic.
+    const ENO_DELEGATION_PERMISSION: u64 = 28;
+
     const MAX_U64: u64 = 18446744073709551615;
 
     /// Maximum operator percentage fee(of double digit precision): 22.85% is represented as 2285
@@ -346,6 +350,11 @@ module aptos_framework::delegation_pool {
         allowlist: SmartTable,
     }
 
+    enum DelegationPermission has copy, drop, store {
+        DelegationPoolManagementPermission,
+        StakeManagementPermission,
+    }
+
     #[event]
     struct AddStake has drop, store {
         pool_address: address,
@@ -832,6 +841,29 @@ module aptos_framework::delegation_pool {
         allowlist
     }
 
+    /// Permissions
+    inline fun check_delegation_pool_management_permission(s: &signer) {
+        assert!(
+            permissioned_signer::check_permission_exists(s, DelegationPermission::DelegationPoolManagementPermission {}),
+            error::permission_denied(ENO_DELEGATION_PERMISSION),
+        );
+    }
+
+    public fun grant_delegation_pool_management_permission(master: &signer, permissioned_signer: &signer) {
+        permissioned_signer::authorize_unlimited(master, permissioned_signer, DelegationPermission::DelegationPoolManagementPermission {})
+    }
+
+    inline fun check_stake_management_permission(s: &signer) {
+        assert!(
+            permissioned_signer::check_permission_exists(s, DelegationPermission::StakeManagementPermission {}),
+            error::permission_denied(ENO_DELEGATION_PERMISSION),
+        );
+    }
+
+    public fun grant_stake_management_permission(master: &signer, permissioned_signer: &signer) {
+        permissioned_signer::authorize_unlimited(master, permissioned_signer, DelegationPermission::StakeManagementPermission {})
+    }
+
     /// Initialize a delegation pool of custom fixed `operator_commission_percentage`.
     /// A resource account is created from `owner` signer and its supplied `delegation_pool_creation_seed`
     /// to host the delegation pool resource and own the underlying stake pool.
@@ -841,6 +873,7 @@ module aptos_framework::delegation_pool {
         operator_commission_percentage: u64,
         delegation_pool_creation_seed: vector,
     ) acquires DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage {
+        check_delegation_pool_management_permission(owner);
         assert!(features::delegation_pools_enabled(), error::invalid_state(EDELEGATION_POOLS_DISABLED));
         let owner_address = signer::address_of(owner);
         assert!(!owner_cap_exists(owner_address), error::already_exists(EOWNER_CAP_ALREADY_EXISTS));
@@ -941,6 +974,7 @@ module aptos_framework::delegation_pool {
         voting_power: u64,
         should_pass: bool
     ) acquires DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage {
+        check_stake_management_permission(voter);
         assert_partial_governance_voting_enabled(pool_address);
         // synchronize delegation and stake pools before any user operation.
         synchronize_delegation_pool(pool_address);
@@ -1001,6 +1035,7 @@ module aptos_framework::delegation_pool {
         metadata_hash: vector,
         is_multi_step_proposal: bool,
     ) acquires DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage {
+        check_stake_management_permission(voter);
         assert_partial_governance_voting_enabled(pool_address);
 
         // synchronize delegation and stake pools before any user operation
@@ -1293,6 +1328,7 @@ module aptos_framework::delegation_pool {
         owner: &signer,
         new_operator: address
     ) acquires DelegationPoolOwnership, DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage {
+        check_delegation_pool_management_permission(owner);
         let pool_address = get_owned_pool_address(signer::address_of(owner));
         // synchronize delegation and stake pools before any user operation
         // ensure the old operator is paid its uncommitted commission rewards
@@ -1308,6 +1344,7 @@ module aptos_framework::delegation_pool {
         operator: &signer,
         new_beneficiary: address
     ) acquires BeneficiaryForOperator {
+        check_stake_management_permission(operator);
         assert!(features::operator_beneficiary_change_enabled(), std::error::invalid_state(
             EOPERATOR_BENEFICIARY_CHANGE_NOT_SUPPORTED
         ));
@@ -1333,6 +1370,7 @@ module aptos_framework::delegation_pool {
         owner: &signer,
         new_commission_percentage: u64
     ) acquires DelegationPoolOwnership, DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage {
+        check_delegation_pool_management_permission(owner);
         assert!(features::commission_change_delegation_pool_enabled(), error::invalid_state(
             ECOMMISSION_RATE_CHANGE_NOT_SUPPORTED
         ));
@@ -1378,6 +1416,7 @@ module aptos_framework::delegation_pool {
         owner: &signer,
         new_voter: address
     ) acquires DelegationPoolOwnership, DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage {
+        check_delegation_pool_management_permission(owner);
         // No one can change delegated_voter once the partial governance voting feature is enabled.
         assert!(
             !features::delegation_pool_partial_governance_voting_enabled(),
@@ -1396,6 +1435,7 @@ module aptos_framework::delegation_pool {
         pool_address: address,
         new_voter: address
     ) acquires DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage {
+        check_stake_management_permission(delegator);
         assert_partial_governance_voting_enabled(pool_address);
 
         // synchronize delegation and stake pools before any user operation
@@ -1453,6 +1493,7 @@ module aptos_framework::delegation_pool {
     public entry fun enable_delegators_allowlisting(
         owner: &signer,
     ) acquires DelegationPoolOwnership, DelegationPool {
+        check_delegation_pool_management_permission(owner);
         assert!(
             features::delegation_pool_allowlisting_enabled(),
             error::invalid_state(EDELEGATORS_ALLOWLISTING_NOT_SUPPORTED)
@@ -1471,6 +1512,7 @@ module aptos_framework::delegation_pool {
     public entry fun disable_delegators_allowlisting(
         owner: &signer,
     ) acquires DelegationPoolOwnership, DelegationPoolAllowlisting {
+        check_delegation_pool_management_permission(owner);
         let pool_address = get_owned_pool_address(signer::address_of(owner));
         assert_allowlisting_enabled(pool_address);
 
@@ -1486,6 +1528,7 @@ module aptos_framework::delegation_pool {
         owner: &signer,
         delegator_address: address,
     ) acquires DelegationPoolOwnership, DelegationPoolAllowlisting {
+        check_delegation_pool_management_permission(owner);
         let pool_address = get_owned_pool_address(signer::address_of(owner));
         assert_allowlisting_enabled(pool_address);
 
@@ -1501,6 +1544,7 @@ module aptos_framework::delegation_pool {
         owner: &signer,
         delegator_address: address,
     ) acquires DelegationPoolOwnership, DelegationPoolAllowlisting {
+        check_delegation_pool_management_permission(owner);
         let pool_address = get_owned_pool_address(signer::address_of(owner));
         assert_allowlisting_enabled(pool_address);
 
@@ -1516,6 +1560,7 @@ module aptos_framework::delegation_pool {
         owner: &signer,
         delegator_address: address,
     ) acquires DelegationPoolOwnership, DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage, DelegationPoolAllowlisting {
+        check_delegation_pool_management_permission(owner);
         let pool_address = get_owned_pool_address(signer::address_of(owner));
         assert_allowlisting_enabled(pool_address);
         assert!(
@@ -1540,6 +1585,7 @@ module aptos_framework::delegation_pool {
         pool_address: address,
         amount: u64
     ) acquires DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage, DelegationPoolAllowlisting {
+        check_stake_management_permission(delegator);
         // short-circuit if amount to add is 0 so no event is emitted
         if (amount == 0) { return };
 
@@ -1597,6 +1643,7 @@ module aptos_framework::delegation_pool {
         pool_address: address,
         amount: u64
     ) acquires DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage {
+        check_stake_management_permission(delegator);
         // short-circuit if amount to unlock is 0 so no event is emitted
         if (amount == 0) { return };
 
@@ -1658,6 +1705,7 @@ module aptos_framework::delegation_pool {
         pool_address: address,
         amount: u64
     ) acquires DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage, DelegationPoolAllowlisting {
+        check_stake_management_permission(delegator);
         // short-circuit if amount to reactivate is 0 so no event is emitted
         if (amount == 0) { return };
 
@@ -1708,6 +1756,7 @@ module aptos_framework::delegation_pool {
         pool_address: address,
         amount: u64
     ) acquires DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage {
+        check_stake_management_permission(delegator);
         assert!(amount > 0, error::invalid_argument(EWITHDRAW_ZERO_STAKE));
         // synchronize delegation and stake pools before any user operation
         synchronize_delegation_pool(pool_address);
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..e6aeea50a8262 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::{Self, Permission};
     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,66 @@ 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)
+    }
+
+    public fun withdraw_with_permission(
+        perm: &mut Permission,
+        store: Object,
+        amount: u64,
+    ): FungibleAsset acquires FungibleStore, DispatchFunctionStore, ConcurrentFungibleBalance {
+        withdraw_sanity_check_impl(permissioned_signer::address_of(perm), store, true);
+        assert!(
+            permissioned_signer::consume_permission(perm, amount as u256, WithdrawPermission::ByStore {
+                store_address: object::object_address(&store),
+            }),
+            error::permission_denied(EWITHDRAW_PERMISSION_DENIED)
+        );
         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 +1381,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 +1488,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 +1895,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.spec.move b/aptos-move/framework/aptos-framework/sources/managed_coin.spec.move
index 344c9744f7c97..b36de742d8da4 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(
@@ -97,6 +97,8 @@ spec aptos_framework::managed_coin {
         decimals: u8,
         monitor_supply: bool,
     ) {
+        use aptos_framework::permissioned_signer;
+        aborts_if permissioned_signer::spec_is_permissioned_signer(account);
         include coin::InitializeInternalSchema;
         aborts_if !string::spec_internal_check_utf8(name);
         aborts_if !string::spec_internal_check_utf8(symbol);
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..7b29812efeaf4 100644
--- a/aptos-move/framework/aptos-framework/sources/multisig_account.spec.move
+++ b/aptos-move/framework/aptos-framework/sources/multisig_account.spec.move
@@ -166,6 +166,7 @@ spec aptos_framework::multisig_account {
     /// 
 
     spec module {
+        pragma aborts_if_is_partial;
     }
 
     spec metadata(multisig_account: address): SimpleMap> {
@@ -207,6 +208,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/object.move b/aptos-move/framework/aptos-framework/sources/object.move
index bb6684ff6f430..09e724318e942 100644
--- a/aptos-move/framework/aptos-framework/sources/object.move
+++ b/aptos-move/framework/aptos-framework/sources/object.move
@@ -28,6 +28,7 @@ module aptos_framework::object {
     use aptos_framework::create_signer::create_signer;
     use aptos_framework::event;
     use aptos_framework::guid;
+    use aptos_framework::permissioned_signer;
 
     friend aptos_framework::coin;
     friend aptos_framework::primary_fungible_store;
@@ -165,6 +166,11 @@ module aptos_framework::object {
         self: address,
     }
 
+    /// Permission to transfer object with permissioned signer.
+    struct TransferPermission has copy, drop, store {
+        object: address,
+    }
+
     /// Emitted whenever the object's owner field is changed.
     struct TransferEvent has drop, store {
         object: address,
@@ -540,6 +546,10 @@ module aptos_framework::object {
         to: address,
     ) acquires ObjectCore {
         let owner_address = signer::address_of(owner);
+        assert!(
+            permissioned_signer::check_permission_exists(owner, TransferPermission { object }),
+            error::permission_denied(EOBJECT_NOT_TRANSFERRABLE)
+        );
         verify_ungated_and_descendant(owner_address, object);
         transfer_raw_inner(object, to);
     }
@@ -629,6 +639,10 @@ module aptos_framework::object {
     ) acquires TombStone, ObjectCore {
         let object_addr = object.inner;
         assert!(exists(object_addr), error::invalid_argument(EOBJECT_NOT_BURNT));
+        assert!(
+            permissioned_signer::check_permission_exists(original_owner, TransferPermission { object: object_addr }),
+            error::permission_denied(EOBJECT_NOT_TRANSFERRABLE)
+        );
 
         let TombStone { original_owner: original_owner_addr } = move_from(object_addr);
         assert!(original_owner_addr == signer::address_of(original_owner), error::permission_denied(ENOT_OBJECT_OWNER));
@@ -699,6 +713,30 @@ module aptos_framework::object {
         obj_owner
     }
 
+    /// Master signer offers a transfer permission of an object to a permissioned signer.
+    public fun grant_permission(
+        master: &signer,
+        permissioned_signer: &signer,
+        object: Object,
+    ) {
+        permissioned_signer::authorize_unlimited(
+            master,
+            permissioned_signer,
+            TransferPermission { object: object.inner }
+        )
+    }
+
+    /// Grant a transfer permission to the permissioned signer using TransferRef.
+    public fun grant_permission_with_transfer_ref(
+        permissioned_signer: &signer,
+        ref: &TransferRef,
+    ) {
+        permissioned_signer::grant_unlimited_with_permissioned_signer(
+            permissioned_signer,
+            TransferPermission { object: ref.self }
+        )
+    }
+
     #[test_only]
     use std::option::{Self, Option};
 
@@ -1093,4 +1131,69 @@ module aptos_framework::object {
         set_untransferable(&weapon_constructor_ref);
         transfer_with_ref(linear_transfer_ref, @0x456);
     }
+
+    #[test_only]
+    use aptos_framework::timestamp;
+
+    #[test(creator = @0x123)]
+    fun test_transfer_permission_e2e(
+        creator: &signer,
+    ) acquires ObjectCore {
+        let aptos_framework = account::create_signer_for_test(@0x1);
+        timestamp::set_time_has_started_for_testing(&aptos_framework);
+
+        let (_, hero) = create_hero(creator);
+        let (_, weapon) = create_weapon(creator);
+
+        // Create a permissioned signer
+        let creator_permission_handle = permissioned_signer::create_permissioned_handle(creator);
+        let creator_permission_signer = permissioned_signer::signer_from_permissioned_handle(&creator_permission_handle);
+
+        // Grant aaron_permission_signer permission to transfer weapon object
+        grant_permission(creator, &creator_permission_signer, weapon);
+        transfer_to_object(&creator_permission_signer, weapon, hero);
+
+        permissioned_signer::destroy_permissioned_handle(creator_permission_handle);
+    }
+
+    #[test(creator = @0x123)]
+    #[expected_failure(abort_code = 327689, location = Self)]
+    fun test_transfer_no_permission(
+        creator: &signer,
+    ) acquires ObjectCore {
+        let aptos_framework = account::create_signer_for_test(@0x1);
+        timestamp::set_time_has_started_for_testing(&aptos_framework);
+
+        let (_, hero) = create_hero(creator);
+        let (_, weapon) = create_weapon(creator);
+
+        // Create a permissioned signer
+        let creator_permission_handle = permissioned_signer::create_permissioned_handle(creator);
+        let creator_permission_signer = permissioned_signer::signer_from_permissioned_handle(&creator_permission_handle);
+
+        transfer_to_object(&creator_permission_signer, weapon, hero);
+
+        permissioned_signer::destroy_permissioned_handle(creator_permission_handle);
+    }
+
+    #[test(creator = @0x123)]
+    fun test_create_and_transfer(
+        creator: &signer,
+    ) acquires ObjectCore {
+        let aptos_framework = account::create_signer_for_test(@0x1);
+        timestamp::set_time_has_started_for_testing(&aptos_framework);
+
+        let (_, hero) = create_hero(creator);
+        let (weapon_ref, weapon) = create_weapon(creator);
+        let t_ref = generate_transfer_ref(&weapon_ref);
+
+        // Create a permissioned signer
+        let creator_permission_handle = permissioned_signer::create_permissioned_handle(creator);
+        let creator_permission_signer = permissioned_signer::signer_from_permissioned_handle(&creator_permission_handle);
+
+        grant_permission_with_transfer_ref(&creator_permission_signer, &t_ref);
+        transfer_to_object(&creator_permission_signer, weapon, hero);
+
+        permissioned_signer::destroy_permissioned_handle(creator_permission_handle);
+    }
 }
diff --git a/aptos-move/framework/aptos-framework/sources/object.spec.move b/aptos-move/framework/aptos-framework/sources/object.spec.move
index 51ae05b568368..6ddef5f6124bb 100644
--- a/aptos-move/framework/aptos-framework/sources/object.spec.move
+++ b/aptos-move/framework/aptos-framework/sources/object.spec.move
@@ -46,7 +46,14 @@ spec aptos_framework::object {
     /// 
     ///
     spec module {
-        pragma aborts_if_is_strict;
+        pragma aborts_if_is_partial;
+    }
+
+    spec grant_permission {
+        pragma aborts_if_is_partial;
+        aborts_if !permissioned_signer::spec_is_permissioned_signer(permissioned_signer);
+        aborts_if permissioned_signer::spec_is_permissioned_signer(master);
+        aborts_if signer::address_of(master) != signer::address_of(permissioned_signer);
     }
 
     spec fun spec_exists_at(object: address): bool;
diff --git a/aptos-move/framework/aptos-framework/sources/object_code_deployment.move b/aptos-move/framework/aptos-framework/sources/object_code_deployment.move
index ef9e7d37fe9df..f611d1003573d 100644
--- a/aptos-move/framework/aptos-framework/sources/object_code_deployment.move
+++ b/aptos-move/framework/aptos-framework/sources/object_code_deployment.move
@@ -47,6 +47,8 @@ module aptos_framework::object_code_deployment {
     const ENOT_CODE_OBJECT_OWNER: u64 = 2;
     /// `code_object` does not exist.
     const ECODE_OBJECT_DOES_NOT_EXIST: u64 = 3;
+    /// Current permissioned signer cannot deploy object code.
+    const ENO_CODE_PERMISSION: u64 = 4;
 
     const OBJECT_CODE_DEPLOYMENT_DOMAIN_SEPARATOR: vector = b"aptos_framework::object_code_deployment";
 
@@ -84,6 +86,7 @@ module aptos_framework::object_code_deployment {
         metadata_serialized: vector,
         code: vector>,
     ) {
+        code::check_code_publishing_permission(publisher);
         assert!(
             features::is_object_code_deployment_enabled(),
             error::unavailable(EOBJECT_CODE_DEPLOYMENT_NOT_SUPPORTED),
@@ -120,6 +123,7 @@ module aptos_framework::object_code_deployment {
         code: vector>,
         code_object: Object,
     ) acquires ManagingRefs {
+        code::check_code_publishing_permission(publisher);
         let publisher_address = signer::address_of(publisher);
         assert!(
             object::is_owner(code_object, publisher_address),
diff --git a/aptos-move/framework/aptos-framework/sources/permissioned_signer.move b/aptos-move/framework/aptos-framework/sources/permissioned_signer.move
new file mode 100644
index 0000000000000..5b21a00fd8509
--- /dev/null
+++ b/aptos-move/framework/aptos-framework/sources/permissioned_signer.move
@@ -0,0 +1,739 @@
+/// A _permissioned signer_ consists of a pair of the original signer and a generated
+/// address which is used to store information about associated permissions.
+///
+/// A permissioned signer is a restricted version of a signer. Functions `move_to` and
+/// `address_of` behave the same, and can be passed wherever signer is needed. However,
+/// code can internally query for the permissions to assert additional restrictions on
+/// the use of the signer.
+///
+/// A client which is interested in restricting access granted via a signer can create a permissioned signer
+/// and pass on to other existing code without changes to existing APIs. Core functions in the framework, for
+/// example account functions, can then assert availability of permissions, effectively restricting
+/// existing code in a compatible way.
+///
+/// After introducing the core functionality, examples are provided for withdraw limit on accounts, and
+/// for blind signing.
+module aptos_framework::permissioned_signer {
+    use std::features;
+    use std::signer;
+    use std::error;
+    use std::vector;
+    use std::option::{Option, Self};
+    use aptos_std::copyable_any::{Self, Any};
+    use aptos_std::ordered_map::{Self, OrderedMap};
+    use aptos_framework::create_signer::create_signer;
+    use aptos_framework::transaction_context::generate_auid_address;
+    use aptos_framework::timestamp;
+
+    /// Trying to grant permission using non-master signer.
+    const ENOT_MASTER_SIGNER: u64 = 1;
+
+    /// Cannot authorize a permission.
+    const ECANNOT_AUTHORIZE: u64 = 2;
+
+    /// Access permission information from a master signer.
+    const ENOT_PERMISSIONED_SIGNER: u64 = 3;
+
+    /// signer doesn't have enough capacity to extract permission.
+    const ECANNOT_EXTRACT_PERMISSION: u64 = 4;
+
+    /// permission handle has expired.
+    const E_PERMISSION_EXPIRED: u64 = 5;
+
+    /// storing extracted permission into a different signer.
+    const E_PERMISSION_MISMATCH: u64 = 6;
+
+    /// permission handle has been revoked by the original signer.
+    const E_PERMISSION_REVOKED: u64 = 7;
+
+    /// destroying permission handle that has already been revoked or not owned by the
+    /// given master signer.
+    const E_NOT_ACTIVE: u64 = 8;
+
+    /// Permissioned signer feature is not activated.
+    const EPERMISSION_SIGNER_DISABLED: u64 = 9;
+
+    const U256_MAX: u256 =
+        115792089237316195423570985008687907853269984665640564039457584007913129639935;
+
+    /// If a permissioned signer has this permission, it would be able to revoke other granted
+    /// permission handles in the same signer.
+    struct RevokePermissionHandlePermission has copy, store, drop {}
+
+    /// Stores the list of granted permission handles for a given account.
+    struct GrantedPermissionHandles has key {
+        /// Each address refers to a `permissions_storage_addr` that stores the `PermissionStorage`.
+        active_handles: vector
+ } + + /// A ephermeral permission handle that can be used to generate a permissioned signer with permission + /// configuration stored within. + enum PermissionedHandle { + V1 { + /// Address of the signer that creates this handle. + master_account_addr: address, + /// Address that stores `PermissionStorage`. + permissions_storage_addr: address + } + } + + /// A permission handle that can be used to generate a permissioned signer. + /// + /// This handle is storable and thus should be treated very carefully as it serves similar functionality + /// as signer delegation. + enum StorablePermissionedHandle has store { + V1 { + /// Address of the signer that creates this handle. + master_account_addr: address, + /// Address that stores `PermissionStorage`. + permissions_storage_addr: address, + /// Permissioned signer can no longer be generated from this handle after `expiration_time`. + expiration_time: u64 + } + } + + /// The actual permission configuration stored on-chain. + /// + /// The address that holds `PermissionStorage` will be generated freshly every time a permission + /// handle gets created. + enum PermissionStorage has key { + V1 { + /// A hetherogenous map from `Permission` structs defined by each different modules to + /// its permission capacity. + perms: OrderedMap + } + } + + /// Types of permission capacity stored on chain. + enum StoredPermission has store, copy, drop { + /// Unlimited capacity. + Unlimited, + /// Fixed capacity, will be deducted when permission is used. + Capacity(u256), + } + + enum Permission { + V1 { + owner_address: address, + key: K, + perm: StoredPermission, + } + } + + /// Create an ephermeral permission handle based on the master signer. + /// + /// This handle can be used to derive a signer that can be used in the context of + /// the current transaction. + public fun create_permissioned_handle(master: &signer): PermissionedHandle { + assert_master_signer(master); + let permissions_storage_addr = generate_auid_address(); + let master_account_addr = signer::address_of(master); + + move_to( + &create_signer(permissions_storage_addr), + PermissionStorage::V1 { perms: ordered_map::new() } + ); + + PermissionedHandle::V1 { master_account_addr, permissions_storage_addr } + } + + /// Create an storable permission handle based on the master signer. + /// + /// This handle can be used to derive a signer that can be stored by a smart contract. + /// This is as dangerous as key delegation, thus it remains public(package) for now. + /// + /// The caller should check if `expiration_time` is not too far in the future. + public(package) fun create_storable_permissioned_handle( + master: &signer, expiration_time: u64 + ): StorablePermissionedHandle acquires GrantedPermissionHandles { + assert_master_signer(master); + let permissions_storage_addr = generate_auid_address(); + let master_account_addr = signer::address_of(master); + + assert!( + timestamp::now_seconds() < expiration_time, + error::permission_denied(E_PERMISSION_EXPIRED) + ); + + if (!exists(master_account_addr)) { + move_to( + master, GrantedPermissionHandles { active_handles: vector::empty() } + ); + }; + + GrantedPermissionHandles[master_account_addr] + .active_handles.push_back(permissions_storage_addr); + + move_to( + &create_signer(permissions_storage_addr), + PermissionStorage::V1 { perms: ordered_map::new() } + ); + + StorablePermissionedHandle::V1 { + master_account_addr, + permissions_storage_addr, + expiration_time + } + } + + /// Destroys an ephermeral permission handle. Clean up the permission stored in that handle + public fun destroy_permissioned_handle(p: PermissionedHandle) acquires PermissionStorage { + let PermissionedHandle::V1 { master_account_addr: _, permissions_storage_addr } = + p; + destroy_permissions_storage_address(permissions_storage_addr); + } + + /// Destroys a storable permission handle. Clean up the permission stored in that handle + public(package) fun destroy_storable_permissioned_handle( + p: StorablePermissionedHandle + ) acquires PermissionStorage, GrantedPermissionHandles { + let StorablePermissionedHandle::V1 { + master_account_addr, + permissions_storage_addr, + expiration_time: _ + } = p; + + assert!( + exists(master_account_addr), + error::permission_denied(E_PERMISSION_REVOKED), + ); + let active_handles = &mut GrantedPermissionHandles[master_account_addr].active_handles; + + let (found, idx) = active_handles.index_of(&permissions_storage_addr); + + // Removing the address from the active handle list if it's still active. + if(found) { + active_handles.swap_remove(idx); + }; + + destroy_permissions_storage_address(permissions_storage_addr); + } + + inline fun destroy_permissions_storage_address( + permissions_storage_addr: address + ) acquires PermissionStorage { + if (exists(permissions_storage_addr)) { + let PermissionStorage::V1 { perms } = + move_from(permissions_storage_addr); + ordered_map::destroy( + perms, + |_dk| {}, + |_dv| {} + ); + } + } + + /// Generate the permissioned signer based on the ephermeral permission handle. + /// + /// This signer can be used as a regular signer for other smart contracts. However when such + /// signer interacts with various framework functions, it would subject to permission checks + /// and would abort if check fails. + public fun signer_from_permissioned_handle(p: &PermissionedHandle): signer { + assert!( + features::is_permissioned_signer_enabled(), + error::permission_denied(EPERMISSION_SIGNER_DISABLED) + ); + signer_from_permissioned_handle_impl( + p.master_account_addr, p.permissions_storage_addr + ) + } + + /// Generate the permissioned signer based on the storable permission handle. + public(package) fun signer_from_storable_permissioned_handle( + p: &StorablePermissionedHandle + ): signer { + assert!( + features::is_permissioned_signer_enabled(), + error::permission_denied(EPERMISSION_SIGNER_DISABLED) + ); + assert!( + timestamp::now_seconds() < p.expiration_time, + error::permission_denied(E_PERMISSION_EXPIRED) + ); + assert!( + exists(p.permissions_storage_addr), + error::permission_denied(E_PERMISSION_REVOKED) + ); + signer_from_permissioned_handle_impl( + p.master_account_addr, p.permissions_storage_addr + ) + } + + public fun grant_revoke_permission( + master: &signer, + permissioned: &signer, + ) acquires PermissionStorage { + authorize_unlimited(master, permissioned, RevokePermissionHandlePermission {}); + } + + /// Revoke a specific storable permission handle immediately. This will disallow owner of + /// the storable permission handle to derive signer from it anymore. + public entry fun revoke_permission_storage_address( + s: &signer, permissions_storage_addr: address + ) acquires GrantedPermissionHandles, PermissionStorage { + assert!( + check_permission_exists(s, RevokePermissionHandlePermission {}), + error::permission_denied(ENOT_MASTER_SIGNER) + ); + let master_account_addr = signer::address_of(s); + + assert!( + exists(master_account_addr), + error::permission_denied(E_PERMISSION_REVOKED), + ); + let active_handles = &mut GrantedPermissionHandles[master_account_addr].active_handles; + let (found, idx) = active_handles.index_of(&permissions_storage_addr); + + // The address has to be in the activated list in the master account address. + assert!(found, error::permission_denied(E_NOT_ACTIVE)); + active_handles.swap_remove(idx); + destroy_permissions_storage_address(permissions_storage_addr); + } + + /// Revoke all storable permission handle of the signer immediately. + public entry fun revoke_all_handles(s: &signer) acquires GrantedPermissionHandles, PermissionStorage { + assert!( + check_permission_exists(s, RevokePermissionHandlePermission {}), + error::permission_denied(ENOT_MASTER_SIGNER) + ); + let master_account_addr = signer::address_of(s); + if (!exists(master_account_addr)) { return }; + + let granted_permissions = + borrow_global_mut(master_account_addr); + let delete_list = vector::trim_reverse( + &mut granted_permissions.active_handles, 0 + ); + vector::destroy( + delete_list, + |address| { + destroy_permissions_storage_address(address); + } + ) + } + + /// Return the permission handle address so that it could be used for revocation purpose. + public(package) fun permissions_storage_address( + p: &StorablePermissionedHandle + ): address { + p.permissions_storage_addr + } + + /// Helper function that would abort if the signer passed in is a permissioned signer. + public(package) fun assert_master_signer(s: &signer) { + assert!( + !is_permissioned_signer(s), error::permission_denied(ENOT_MASTER_SIGNER) + ); + } + + /// ===================================================================================================== + /// StoredPermission operations + /// + /// check if StoredPermission has at least `threshold` capacity. + fun is_above(perm: &StoredPermission, threshold: u256): bool { + match (perm) { + StoredPermission::Capacity(capacity) => *capacity > threshold, + StoredPermission::Unlimited => true, + } + } + + /// consume `threshold` capacity from StoredPermission + fun consume_capacity(perm: &mut StoredPermission, threshold: u256): bool { + match (perm) { + StoredPermission::Capacity(current_capacity) => { + if (*current_capacity >= threshold) { + *current_capacity = *current_capacity - threshold; + true + } else { false } + } + StoredPermission::Unlimited => true + } + } + + /// increase `threshold` capacity from StoredPermission + fun increase_capacity(perm: &mut StoredPermission, threshold: u256) { + match (perm) { + StoredPermission::Capacity(current_capacity) => { + *current_capacity = *current_capacity + threshold; + } + StoredPermission::Unlimited => (), + } + } + + /// merge the two stored permission + fun merge(lhs: &mut StoredPermission, rhs: StoredPermission) { + match (rhs) { + StoredPermission::Capacity(new_capacity) => { + match (lhs) { + StoredPermission::Capacity(current_capacity) => { + *current_capacity = *current_capacity + new_capacity; + } + StoredPermission::Unlimited => (), + } + } + StoredPermission::Unlimited => *lhs = StoredPermission::Unlimited, + } + } + + /// ===================================================================================================== + /// Permission Management + /// + /// Authorizes `permissioned` with the given permission. This requires to have access to the `master` + /// signer. + + inline fun map_or( + permissioned: &signer, + perm: PermKey, + mutate: |&mut StoredPermission| T, + default: T, + ): T { + let permission_signer_addr = permission_address(permissioned); + assert!( + exists(permission_signer_addr), + error::permission_denied(E_NOT_ACTIVE) + ); + let perms = + &mut borrow_global_mut(permission_signer_addr).perms; + let key = copyable_any::pack(perm); + if (ordered_map::contains(perms, &key)) { + mutate(ordered_map::borrow_mut(perms, &key)) + } else { + default + } + } + + inline fun insert_or( + permissioned: &signer, + perm: PermKey, + mutate: |&mut StoredPermission|, + default: StoredPermission, + ) { + let permission_signer_addr = permission_address(permissioned); + assert!( + exists(permission_signer_addr), + error::permission_denied(E_NOT_ACTIVE) + ); + let perms = + &mut borrow_global_mut(permission_signer_addr).perms; + let key = copyable_any::pack(perm); + if (ordered_map::contains(perms, &key)) { + mutate(ordered_map::borrow_mut(perms, &key)); + } else { + ordered_map::add(perms, key, default); + } + } + + /// Authorizes `permissioned` with a given capacity and increment the existing capacity if present. + /// + /// Consumption using `check_permission_consume` will deduct the capacity. + public(package) fun authorize_increase( + master: &signer, + permissioned: &signer, + capacity: u256, + perm: PermKey + ) acquires PermissionStorage { + assert!( + is_permissioned_signer(permissioned) + && !is_permissioned_signer(master) + && signer::address_of(master) == signer::address_of(permissioned), + error::permission_denied(ECANNOT_AUTHORIZE) + ); + insert_or( + permissioned, + perm, + |stored_permission| { + increase_capacity(stored_permission, capacity); + }, + StoredPermission::Capacity(capacity), + ) + } + + /// Authorizes `permissioned` with the given unlimited permission. + /// Unlimited permission can be consumed however many times. + public(package) fun authorize_unlimited( + master: &signer, + permissioned: &signer, + perm: PermKey + ) acquires PermissionStorage { + assert!( + is_permissioned_signer(permissioned) + && !is_permissioned_signer(master) + && signer::address_of(master) == signer::address_of(permissioned), + error::permission_denied(ECANNOT_AUTHORIZE) + ); + insert_or( + permissioned, + perm, + |stored_permission| { + *stored_permission = StoredPermission::Unlimited; + }, + StoredPermission::Unlimited, + ) + } + + /// Grant an unlimited permission to a permissioned signer **without** master signer's approvoal. + public(package) fun grant_unlimited_with_permissioned_signer( + permissioned: &signer, + perm: PermKey + ) acquires PermissionStorage { + if(!is_permissioned_signer(permissioned)) { + return; + }; + insert_or( + permissioned, + perm, + |stored_permission| { + *stored_permission = StoredPermission::Unlimited; + }, + StoredPermission::Unlimited, + ) + } + + /// Increase the `capacity` of a permissioned signer **without** master signer's approvoal. + /// + /// The caller of the module will need to make sure the witness type `PermKey` can only be + /// constructed within its own module, otherwise attackers can refill the permission for itself + /// to bypass the checks. + public(package) fun increase_limit( + permissioned: &signer, + capacity: u256, + perm: PermKey + ) acquires PermissionStorage { + if(!is_permissioned_signer(permissioned)) { + return; + }; + insert_or( + permissioned, + perm, + |stored_permission| { + increase_capacity(stored_permission, capacity); + }, + StoredPermission::Capacity(capacity), + ) + } + + public(package) fun check_permission_exists( + s: &signer, perm: PermKey + ): bool acquires PermissionStorage { + check_permission_capacity_above(s, 0, perm) + } + + public(package) fun check_permission_capacity_above( + s: &signer, threshold: u256, perm: PermKey + ): bool acquires PermissionStorage { + if (!is_permissioned_signer(s)) { + // master signer has all permissions + return true + }; + map_or( + s, + perm, + |stored_permission| { + is_above(stored_permission, threshold) + }, + false, + ) + } + + public(package) fun check_permission_consume( + s: &signer, threshold: u256, perm: PermKey + ): bool acquires PermissionStorage { + if (!is_permissioned_signer(s)) { + // master signer has all permissions + return true + }; + map_or( + s, + perm, + |stored_permission| { + consume_capacity(stored_permission, threshold) + }, + false, + ) + } + + public(package) fun capacity( + s: &signer, perm: PermKey + ): Option acquires PermissionStorage { + if (!is_permissioned_signer(s)) { + return option::some(U256_MAX) + }; + map_or( + s, + perm, + |stored_permission: &mut StoredPermission| { + option::some(match (stored_permission) { + StoredPermission::Capacity(capacity) => *capacity, + StoredPermission::Unlimited => U256_MAX, + }) + }, + option::none(), + ) + } + + public(package) fun revoke_permission( + permissioned: &signer, perm: PermKey + ) acquires PermissionStorage { + if (!is_permissioned_signer(permissioned)) { + // Master signer has no permissions associated with it. + return + }; + let addr = permission_address(permissioned); + if (!exists(addr)) { return }; + let perm_storage = &mut borrow_global_mut(addr).perms; + let key = copyable_any::pack(perm); + if (ordered_map::contains(perm_storage, &key)) { + ordered_map::remove( + &mut borrow_global_mut(addr).perms, + ©able_any::pack(perm) + ); + } + } + + /// ===================================================================================================== + /// Another flavor of api to extract and store permissions + /// + public(package) fun extract_permission( + s: &signer, weight: u256, perm: PermKey + ): Permission acquires PermissionStorage { + assert!( + check_permission_consume(s, weight, perm), + error::permission_denied(ECANNOT_EXTRACT_PERMISSION) + ); + Permission::V1 { + owner_address: signer::address_of(s), + key: perm, + perm: StoredPermission::Capacity(weight), + } + } + + public(package) fun extract_all_permission( + s: &signer, perm_key: PermKey + ): Permission acquires PermissionStorage { + assert!( + is_permissioned_signer(s), + error::permission_denied(ECANNOT_EXTRACT_PERMISSION) + ); + let addr = permission_address(s); + assert!( + exists(addr), + error::permission_denied(ECANNOT_EXTRACT_PERMISSION) + ); + let key = copyable_any::pack(perm_key); + let storage = &mut borrow_global_mut(addr).perms; + let value = ordered_map::remove(storage, &key); + + Permission::V1 { + owner_address: signer::address_of(s), + key: perm_key, + perm: value, + } + } + + public(package) fun address_of(perm: &Permission): address { + perm.owner_address + } + + public(package) fun consume_permission( + perm: &mut Permission, weight: u256, perm_key: PermKey + ): bool { + if (perm.key != perm_key) { + return false + }; + consume_capacity(&mut perm.perm, weight) + } + + public(package) fun store_permission( + s: &signer, perm: Permission + ) acquires PermissionStorage { + assert!( + is_permissioned_signer(s), + error::permission_denied(ENOT_PERMISSIONED_SIGNER) + ); + let Permission::V1 { key, perm, owner_address } = perm; + + assert!( + signer::address_of(s) == owner_address, + error::permission_denied(E_PERMISSION_MISMATCH) + ); + + insert_or( + s, + key, + |stored_permission| { + merge(stored_permission, perm); + }, + perm, + ) + } + + // ===================================================================================================== + // Native Functions + /// + /// Check whether this is a permissioned signer. + public native fun is_permissioned_signer(s: &signer): bool; + /// Return the address used for storing permissions. Aborts if not a permissioned signer. + native fun permission_address(permissioned: &signer): address; + /// Creates a permissioned signer from an existing universal signer. The function aborts if the + /// given signer is already a permissioned signer. + /// + /// The implementation of this function requires to extend the value representation for signers in the VM. + /// invariants: + /// signer::address_of(master) == signer::address_of(signer_from_permissioned_handle(create_permissioned_handle(master))), + /// + native fun signer_from_permissioned_handle_impl( + master_account_addr: address, permissions_storage_addr: address + ): signer; + + #[test(creator = @0xcafe)] + fun signer_address_roundtrip( + creator: &signer + ) acquires PermissionStorage, GrantedPermissionHandles { + let aptos_framework = create_signer(@0x1); + timestamp::set_time_has_started_for_testing(&aptos_framework); + + let handle = create_permissioned_handle(creator); + let perm_signer = signer_from_permissioned_handle(&handle); + assert!(signer::address_of(&perm_signer) == signer::address_of(creator), 1); + assert!( + permission_address(&perm_signer) + == handle.permissions_storage_addr, + 1 + ); + assert!(exists(handle.permissions_storage_addr), 1); + + destroy_permissioned_handle(handle); + + let handle = create_storable_permissioned_handle(creator, 60); + let perm_signer = signer_from_storable_permissioned_handle(&handle); + assert!(signer::address_of(&perm_signer) == signer::address_of(creator), 1); + assert!( + permission_address(&perm_signer) + == handle.permissions_storage_addr, + 1 + ); + assert!(exists(handle.permissions_storage_addr), 1); + + destroy_storable_permissioned_handle(handle); + } + + #[test_only] + use aptos_std::bcs; + + #[test(creator = @0xcafe)] + #[expected_failure(abort_code = 0x1C5, location = aptos_std::bcs)] + fun signer_serialization( + creator: &signer + ) acquires PermissionStorage { + let aptos_framework = create_signer(@0x1); + timestamp::set_time_has_started_for_testing(&aptos_framework); + + let handle = create_permissioned_handle(creator); + let perm_signer = signer_from_permissioned_handle(&handle); + + assert!(bcs::to_bytes(creator) == bcs::to_bytes(&signer::address_of(creator)), 1); + bcs::to_bytes(&perm_signer); + + destroy_permissioned_handle(handle); + } +} diff --git a/aptos-move/framework/aptos-framework/sources/permissioned_signer.spec.move b/aptos-move/framework/aptos-framework/sources/permissioned_signer.spec.move new file mode 100644 index 0000000000000..9e94c8952bccc --- /dev/null +++ b/aptos-move/framework/aptos-framework/sources/permissioned_signer.spec.move @@ -0,0 +1,170 @@ +spec aptos_framework::permissioned_signer { + + spec module { + pragma verify = false; + axiom forall a: GrantedPermissionHandles: + ( + forall i in 0..len(a.active_handles): + forall j in 0..len(a.active_handles): + i != j ==> + a.active_handles[i] != a.active_handles[j] + ); + } + + spec fun spec_is_permissioned_signer(s: signer): bool; + + spec is_permissioned_signer(s: &signer): bool { + pragma opaque; + aborts_if [abstract] false; + ensures [abstract] result == spec_is_permissioned_signer(s); + } + + spec fun spec_permission_address(s: signer): address; + + spec permission_address(permissioned: &signer): address { + pragma opaque; + aborts_if [abstract]!spec_is_permissioned_signer(permissioned); + ensures [abstract] result == spec_permission_address(permissioned); + } + + spec fun spec_signer_from_permissioned_handle_impl( + master_account_addr: address, permissions_storage_addr: address + ): signer; + + spec signer_from_permissioned_handle_impl( + master_account_addr: address, permissions_storage_addr: address + ): signer { + pragma opaque; + ensures [abstract] result + == spec_signer_from_permissioned_handle_impl( + master_account_addr, permissions_storage_addr + ); + } + + spec create_permissioned_handle(master: &signer): PermissionedHandle { + use aptos_framework::transaction_context; + pragma opaque; + aborts_if [abstract] spec_is_permissioned_signer(master); + let permissions_storage_addr = transaction_context::spec_generate_unique_address(); + modifies global(permissions_storage_addr); + let master_account_addr = signer::address_of(master); + ensures result.master_account_addr == master_account_addr; + ensures result.permissions_storage_addr == permissions_storage_addr; + } + + spec create_storable_permissioned_handle(master: &signer, expiration_time: u64): StorablePermissionedHandle { + use aptos_framework::transaction_context; + pragma opaque; + aborts_if [abstract] spec_is_permissioned_signer(master); + let permissions_storage_addr = transaction_context::spec_generate_unique_address(); + modifies global(permissions_storage_addr); + let master_account_addr = signer::address_of(master); + modifies global(master_account_addr); + ensures result.master_account_addr == master_account_addr; + ensures result.permissions_storage_addr == permissions_storage_addr; + ensures result.expiration_time == expiration_time; + ensures vector::spec_contains( + global(master_account_addr).active_handles, + permissions_storage_addr + ); + ensures exists(master_account_addr); + } + + spec destroy_permissioned_handle(p: PermissionedHandle) { + ensures !exists(p.permissions_storage_addr); + } + + spec destroy_storable_permissioned_handle(p: StorablePermissionedHandle) { + ensures !exists(p.permissions_storage_addr); + let post granted_permissions = global( + p.master_account_addr + ); + // ensures [abstract] !vector::spec_contains(granted_permissions.active_handles, p.permissions_storage_addr); + } + + spec revoke_permission_storage_address(s: &signer, permissions_storage_addr: address) { + // aborts_if spec_is_permissioned_signer(s); + } + + spec authorize_increase( + master: &signer, permissioned: &signer, capacity: u256, perm: PermKey + ) { + + // use aptos_std::type_info; + // use std::bcs; + pragma aborts_if_is_partial; + aborts_if !spec_is_permissioned_signer(permissioned); + aborts_if spec_is_permissioned_signer(master); + aborts_if signer::address_of(permissioned) != signer::address_of(master); + ensures exists( + spec_permission_address(permissioned) + ); + // let perms = global(permission_signer_addr).perms; + // let post post_perms = global(permission_signer_addr).perms; + // let key = Any { + // type_name: type_info::type_name>(), + // data: bcs::serialize(perm) + // }; + // ensures smart_table::spec_contains(perms, key) ==> + // smart_table::spec_get(post_perms, key) == old(smart_table::spec_get(perms, key)) + capacity; + // ensures !smart_table::spec_contains(perms, key) ==> + // smart_table::spec_get(post_perms, key) == capacity; + } + + spec check_permission_exists(s: &signer, perm: PermKey): bool { + pragma verify = false; + // pragma opaque; + // aborts_if false; + // ensures [abstract] result == spec_check_permission_exists(s, perm); + } + + spec fun spec_check_permission_exists(s: signer, perm: PermKey): bool { + use aptos_std::type_info; + use std::bcs; + let addr = spec_permission_address(s); + let key = Any { + type_name: type_info::type_name(), + data: bcs::serialize(perm) + }; + if (!spec_is_permissioned_signer(s)) { true } + else if (!exists(addr)) { false } + else { + // ordered_map::spec_contains_key(global(addr).perms, key) + // FIXME: ordered map spec doesn't exist yet. + true + } + } + + spec check_permission_capacity_above( + s: &signer, threshold: u256, perm: PermKey + ): bool { + let permissioned_signer_addr = spec_permission_address(s); + ensures !spec_is_permissioned_signer(s) ==> result == true; + ensures ( + spec_is_permissioned_signer(s) + && !exists(permissioned_signer_addr) + ) ==> result == false; + // ensures (spec_is_permissioned_signer(s) && exists(permissioned_signer_addr) && !smart_table::spec_contains(global(permissioned_signer_addr).perms, key)) ==> + // result == false; + // ensures (spec_is_permissioned_signer(s) && exists(permissioned_signer_addr) && smart_table::spec_contains(global(permissioned_signer_addr).perms, key)) ==> + // result == (smart_table::spec_get(global(permissioned_signer_addr).perms, key) > threshold); + } + + spec check_permission_consume( + s: &signer, threshold: u256, perm: PermKey + ): bool { + let permissioned_signer_addr = spec_permission_address(s); + ensures !spec_is_permissioned_signer(s) ==> result == true; + ensures ( + spec_is_permissioned_signer(s) + && !exists(permissioned_signer_addr) + ) ==> result == false; + + } + + spec capacity(s: &signer, perm: PermKey): Option { + // let permissioned_signer_addr = signer::address_of(spec_permission_address(s)); + // ensures !exists(permissioned_signer_addr) ==> + // option::is_none(result); + } +} 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/resource_account.spec.move b/aptos-move/framework/aptos-framework/sources/resource_account.spec.move index 847e77853bdc4..2bd5dd294f2e0 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( @@ -68,8 +68,10 @@ spec aptos_framework::resource_account { seed: vector, optional_auth_key: vector, ) { + use aptos_framework::create_signer; let source_addr = signer::address_of(origin); let resource_addr = account::spec_create_resource_address(source_addr, seed); + let resource = create_signer::spec_create_signer(resource_addr); include RotateAccountAuthenticationKeyAndStoreCapabilityAbortsIfWithoutAccountLimit; } @@ -116,6 +118,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 +176,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/stake.move b/aptos-move/framework/aptos-framework/sources/stake.move index b4f62d3df4004..190ceda38cb1e 100644 --- a/aptos-move/framework/aptos-framework/sources/stake.move +++ b/aptos-move/framework/aptos-framework/sources/stake.move @@ -34,6 +34,7 @@ module aptos_framework::stake { use aptos_framework::system_addresses; use aptos_framework::staking_config::{Self, StakingConfig, StakingRewardsConfig}; use aptos_framework::chain_status; + use aptos_framework::permissioned_signer; friend aptos_framework::block; friend aptos_framework::genesis; @@ -81,6 +82,8 @@ module aptos_framework::stake { const EFEES_TABLE_ALREADY_EXISTS: u64 = 19; /// Validator set change temporarily disabled because of in-progress reconfiguration. Please retry after 1 minute. const ERECONFIGURATION_IN_PROGRESS: u64 = 20; + /// Signer does not have permission to perform stake logic. + const ENO_STAKE_PERMISSION: u64 = 28; /// Validator status enum. We can switch to proper enum later once Move supports it. const VALIDATOR_STATUS_PENDING_ACTIVE: u64 = 1; @@ -204,6 +207,8 @@ module aptos_framework::stake { pool_address: address, } + struct StakeManagementPermission has copy, drop, store {} + #[event] struct RegisterValidatorCandidate has drop, store { pool_address: address, @@ -344,6 +349,19 @@ module aptos_framework::stake { fees_table: Table>, } + /// Permissions + inline fun check_stake_permission(s: &signer) { + assert!( + permissioned_signer::check_permission_exists(s, StakeManagementPermission {}), + error::permission_denied(ENO_STAKE_PERMISSION), + ); + } + + /// Grant permission to mutate staking on behalf of the master signer. + public fun grant_permission(master: &signer, permissioned_signer: &signer) { + permissioned_signer::authorize_unlimited(master, permissioned_signer, StakeManagementPermission {}) + } + #[view] /// Return the lockup expiration of the stake pool at `pool_address`. /// This will throw an error if there's no stake pool at `pool_address`. @@ -536,6 +554,7 @@ module aptos_framework::stake { operator: address, voter: address, ) acquires AllowedValidators, OwnerCapability, StakePool, ValidatorSet { + check_stake_permission(owner); initialize_owner(owner); move_to(owner, ValidatorConfig { consensus_pubkey: vector::empty(), @@ -565,6 +584,7 @@ module aptos_framework::stake { network_addresses: vector, fullnode_addresses: vector, ) acquires AllowedValidators { + check_stake_permission(account); // Checks the public key has a valid proof-of-possession to prevent rogue-key attacks. let pubkey_from_pop = &bls12381::public_key_from_bytes_with_pop( consensus_pubkey, @@ -582,6 +602,7 @@ module aptos_framework::stake { } fun initialize_owner(owner: &signer) acquires AllowedValidators { + check_stake_permission(owner); let owner_address = signer::address_of(owner); assert!(is_allowed(owner_address), error::not_found(EINELIGIBLE_VALIDATOR)); assert!(!stake_pool_exists(owner_address), error::already_exists(EALREADY_REGISTERED)); @@ -616,6 +637,7 @@ module aptos_framework::stake { /// Extract and return owner capability from the signing account. public fun extract_owner_cap(owner: &signer): OwnerCapability acquires OwnerCapability { + check_stake_permission(owner); let owner_address = signer::address_of(owner); assert_owner_cap_exists(owner_address); move_from(owner_address) @@ -624,6 +646,7 @@ module aptos_framework::stake { /// Deposit `owner_cap` into `account`. This requires `account` to not already have ownership of another /// staking pool. public fun deposit_owner_cap(owner: &signer, owner_cap: OwnerCapability) { + check_stake_permission(owner); assert!(!exists(signer::address_of(owner)), error::not_found(EOWNER_CAP_ALREADY_EXISTS)); move_to(owner, owner_cap); } @@ -635,6 +658,7 @@ module aptos_framework::stake { /// Allows an owner to change the operator of the stake pool. public entry fun set_operator(owner: &signer, new_operator: address) acquires OwnerCapability, StakePool { + check_stake_permission(owner); let owner_address = signer::address_of(owner); assert_owner_cap_exists(owner_address); let ownership_cap = borrow_global(owner_address); @@ -671,6 +695,7 @@ module aptos_framework::stake { /// Allows an owner to change the delegated voter of the stake pool. public entry fun set_delegated_voter(owner: &signer, new_voter: address) acquires OwnerCapability, StakePool { + check_stake_permission(owner); let owner_address = signer::address_of(owner); assert_owner_cap_exists(owner_address); let ownership_cap = borrow_global(owner_address); @@ -687,6 +712,7 @@ module aptos_framework::stake { /// Add `amount` of coins from the `account` owning the StakePool. public entry fun add_stake(owner: &signer, amount: u64) acquires OwnerCapability, StakePool, ValidatorSet { + check_stake_permission(owner); let owner_address = signer::address_of(owner); assert_owner_cap_exists(owner_address); let ownership_cap = borrow_global(owner_address); @@ -748,6 +774,7 @@ module aptos_framework::stake { /// Move `amount` of coins from pending_inactive to active. public entry fun reactivate_stake(owner: &signer, amount: u64) acquires OwnerCapability, StakePool { + check_stake_permission(owner); assert_reconfig_not_in_progress(); let owner_address = signer::address_of(owner); assert_owner_cap_exists(owner_address); @@ -796,6 +823,7 @@ module aptos_framework::stake { new_consensus_pubkey: vector, proof_of_possession: vector, ) acquires StakePool, ValidatorConfig { + check_stake_permission(operator); assert_reconfig_not_in_progress(); assert_stake_pool_exists(pool_address); @@ -840,6 +868,7 @@ module aptos_framework::stake { new_network_addresses: vector, new_fullnode_addresses: vector, ) acquires StakePool, ValidatorConfig { + check_stake_permission(operator); assert_reconfig_not_in_progress(); assert_stake_pool_exists(pool_address); let stake_pool = borrow_global_mut(pool_address); @@ -877,6 +906,7 @@ module aptos_framework::stake { /// Similar to increase_lockup_with_cap but will use ownership capability from the signing account. public entry fun increase_lockup(owner: &signer) acquires OwnerCapability, StakePool { + check_stake_permission(owner); let owner_address = signer::address_of(owner); assert_owner_cap_exists(owner_address); let ownership_cap = borrow_global(owner_address); @@ -921,6 +951,7 @@ module aptos_framework::stake { operator: &signer, pool_address: address ) acquires StakePool, ValidatorConfig, ValidatorSet { + check_stake_permission(operator); assert!( staking_config::get_allow_validator_set_change(&staking_config::get()), error::invalid_argument(ENO_POST_GENESIS_VALIDATOR_SET_CHANGE_ALLOWED), @@ -984,6 +1015,7 @@ module aptos_framework::stake { /// Similar to unlock_with_cap but will use ownership capability from the signing account. public entry fun unlock(owner: &signer, amount: u64) acquires OwnerCapability, StakePool { + check_stake_permission(owner); assert_reconfig_not_in_progress(); let owner_address = signer::address_of(owner); assert_owner_cap_exists(owner_address); @@ -1032,6 +1064,7 @@ module aptos_framework::stake { owner: &signer, withdraw_amount: u64 ) acquires OwnerCapability, StakePool, ValidatorSet { + check_stake_permission(owner); let owner_address = signer::address_of(owner); assert_owner_cap_exists(owner_address); let ownership_cap = borrow_global(owner_address); @@ -1091,6 +1124,7 @@ module aptos_framework::stake { operator: &signer, pool_address: address ) acquires StakePool, ValidatorSet { + check_stake_permission(operator); assert_reconfig_not_in_progress(); let config = staking_config::get(); assert!( diff --git a/aptos-move/framework/aptos-framework/sources/stake.spec.move b/aptos-move/framework/aptos-framework/sources/stake.spec.move index f922821e8ac83..b63804de27dc0 100644 --- a/aptos-move/framework/aptos-framework/sources/stake.spec.move +++ b/aptos-move/framework/aptos-framework/sources/stake.spec.move @@ -42,6 +42,7 @@ spec aptos_framework::stake { // ----------------- spec module { pragma verify = true; + pragma aborts_if_is_partial; // The validator set should satisfy its desired invariant. invariant [suspendable] exists(@aptos_framework) ==> validator_set_is_valid(); // After genesis, `AptosCoinCapabilities`, `ValidatorPerformance` and `ValidatorSet` exist. @@ -125,6 +126,9 @@ spec aptos_framework::stake { network_addresses: vector, fullnode_addresses: vector, ){ + include AbortsIfSignerPermissionStake { + s: account + }; let pubkey_from_pop = bls12381::spec_public_key_from_bytes_with_pop( consensus_pubkey, proof_of_possession_from_bytes(proof_of_possession) @@ -170,6 +174,9 @@ spec aptos_framework::stake { // This function casue timeout (property proved) pragma verify_duration_estimate = 60; pragma disable_invariants_in_body; + include AbortsIfSignerPermissionStake { + s: operator + }; aborts_if !staking_config::get_allow_validator_set_change(staking_config::get()); aborts_if !exists(pool_address); aborts_if !exists(pool_address); @@ -223,6 +230,9 @@ spec aptos_framework::stake { { // TODO(fa_migration) pragma verify = false; + include AbortsIfSignerPermissionStake { + s: owner + }; aborts_if reconfiguration_state::spec_is_in_progress(); let addr = signer::address_of(owner); let ownership_cap = global(addr); @@ -262,6 +272,9 @@ spec aptos_framework::stake { ) { pragma disable_invariants_in_body; requires chain_status::is_operating(); + include AbortsIfSignerPermissionStake { + s: operator + }; aborts_if reconfiguration_state::spec_is_in_progress(); let config = staking_config::get(); aborts_if !staking_config::get_allow_validator_set_change(config); @@ -297,12 +310,18 @@ spec aptos_framework::stake { spec extract_owner_cap(owner: &signer): OwnerCapability { // TODO: set because of timeout (property proved) pragma verify_duration_estimate = 300; + include AbortsIfSignerPermissionStake { + s: owner + }; let owner_address = signer::address_of(owner); aborts_if !exists(owner_address); ensures !exists(owner_address); } spec deposit_owner_cap(owner: &signer, owner_cap: OwnerCapability) { + include AbortsIfSignerPermissionStake { + s: owner + }; let owner_address = signer::address_of(owner); aborts_if exists(owner_address); ensures exists(owner_address); @@ -351,6 +370,9 @@ spec aptos_framework::stake { new_network_addresses: vector, new_fullnode_addresses: vector, ) { + include AbortsIfSignerPermissionStake { + s: operator + }; let pre_stake_pool = global(pool_address); let post validator_info = global(pool_address); modifies global(pool_address); @@ -397,6 +419,9 @@ spec aptos_framework::stake { new_consensus_pubkey: vector, proof_of_possession: vector, ) { + include AbortsIfSignerPermissionStake { + s: operator + }; let pre_stake_pool = global(pool_address); let post validator_info = global(pool_address); aborts_if reconfiguration_state::spec_is_in_progress(); @@ -502,6 +527,13 @@ spec aptos_framework::stake { }; } + spec schema AbortsIfSignerPermissionStake { + use aptos_framework::permissioned_signer; + s: signer; + let perm = StakeManagementPermission {}; + aborts_if !permissioned_signer::spec_check_permission_exists(s, perm); + } + spec schema UpdateStakePoolAbortsIf { use aptos_std::type_info; @@ -521,6 +553,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; @@ -594,6 +627,7 @@ spec aptos_framework::stake { pragma opaque; // TODO: set because of timeout (property proved) pragma verify_duration_estimate = 300; + pragma verify = false; requires rewards_rate <= MAX_REWARDS_RATE; requires rewards_rate_denominator > 0; requires rewards_rate <= rewards_rate_denominator; @@ -671,7 +705,7 @@ spec aptos_framework::stake { spec add_stake_with_cap { pragma disable_invariants_in_body; - pragma verify_duration_estimate = 300; + pragma verify = false; include ResourceRequirement; let amount = coins.value; aborts_if reconfiguration_state::spec_is_in_progress(); @@ -679,10 +713,13 @@ spec aptos_framework::stake { } spec add_stake { - // TODO: These function passed locally however failed in github CI - pragma verify_duration_estimate = 120; + // TODO: fix + pragma verify = false; // TODO(fa_migration) pragma aborts_if_is_partial; + include AbortsIfSignerPermissionStake { + s: owner + }; aborts_if reconfiguration_state::spec_is_in_progress(); include ResourceRequirement; include AddStakeAbortsIfAndEnsures; @@ -696,7 +733,11 @@ spec aptos_framework::stake { ) { // TODO: These function failed in github CI pragma verify_duration_estimate = 120; - + pragma verify = false; + pragma aborts_if_is_partial; + include AbortsIfSignerPermissionStake { + s: owner + }; include ResourceRequirement; let addr = signer::address_of(owner); ensures global(addr) == ValidatorConfig { 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 diff --git a/aptos-move/framework/aptos-framework/sources/staking_proxy.move b/aptos-move/framework/aptos-framework/sources/staking_proxy.move index 26d1aa33372ce..76ffbd2182a63 100644 --- a/aptos-move/framework/aptos-framework/sources/staking_proxy.move +++ b/aptos-move/framework/aptos-framework/sources/staking_proxy.move @@ -1,11 +1,31 @@ module aptos_framework::staking_proxy { + use std::error; use std::signer; use std::vector; + use aptos_framework::permissioned_signer; use aptos_framework::stake; use aptos_framework::staking_contract; use aptos_framework::vesting; + struct StakeProxyPermission has copy, drop, store {} + + /// Signer does not have permission to perform stake proxy logic. + const ENO_STAKE_PERMISSION: u64 = 28; + + /// Permissions + inline fun check_stake_proxy_permission(s: &signer) { + assert!( + permissioned_signer::check_permission_exists(s, StakeProxyPermission {}), + error::permission_denied(ENO_STAKE_PERMISSION), + ); + } + + /// Grant permission to mutate staking on behalf of the master signer. + public fun grant_permission(master: &signer, permissioned_signer: &signer) { + permissioned_signer::authorize_unlimited(master, permissioned_signer, StakeProxyPermission {}) + } + public entry fun set_operator(owner: &signer, old_operator: address, new_operator: address) { set_vesting_contract_operator(owner, old_operator, new_operator); set_staking_contract_operator(owner, old_operator, new_operator); @@ -19,6 +39,7 @@ module aptos_framework::staking_proxy { } public entry fun set_vesting_contract_operator(owner: &signer, old_operator: address, new_operator: address) { + check_stake_proxy_permission(owner); let owner_address = signer::address_of(owner); let vesting_contracts = &vesting::vesting_contracts(owner_address); vector::for_each_ref(vesting_contracts, |vesting_contract| { @@ -31,6 +52,7 @@ module aptos_framework::staking_proxy { } public entry fun set_staking_contract_operator(owner: &signer, old_operator: address, new_operator: address) { + check_stake_proxy_permission(owner); let owner_address = signer::address_of(owner); if (staking_contract::staking_contract_exists(owner_address, old_operator)) { let current_commission_percentage = staking_contract::commission_percentage(owner_address, old_operator); @@ -39,6 +61,7 @@ module aptos_framework::staking_proxy { } public entry fun set_stake_pool_operator(owner: &signer, new_operator: address) { + check_stake_proxy_permission(owner); let owner_address = signer::address_of(owner); if (stake::stake_pool_exists(owner_address)) { stake::set_operator(owner, new_operator); @@ -46,6 +69,7 @@ module aptos_framework::staking_proxy { } public entry fun set_vesting_contract_voter(owner: &signer, operator: address, new_voter: address) { + check_stake_proxy_permission(owner); let owner_address = signer::address_of(owner); let vesting_contracts = &vesting::vesting_contracts(owner_address); vector::for_each_ref(vesting_contracts, |vesting_contract| { @@ -57,6 +81,7 @@ module aptos_framework::staking_proxy { } public entry fun set_staking_contract_voter(owner: &signer, operator: address, new_voter: address) { + check_stake_proxy_permission(owner); let owner_address = signer::address_of(owner); if (staking_contract::staking_contract_exists(owner_address, operator)) { staking_contract::update_voter(owner, operator, new_voter); @@ -64,6 +89,7 @@ module aptos_framework::staking_proxy { } public entry fun set_stake_pool_voter(owner: &signer, new_voter: address) { + check_stake_proxy_permission(owner); if (stake::stake_pool_exists(signer::address_of(owner))) { stake::set_delegated_voter(owner, new_voter); }; diff --git a/aptos-move/framework/aptos-framework/sources/staking_proxy.spec.move b/aptos-move/framework/aptos-framework/sources/staking_proxy.spec.move index a1da120f0eed4..9f72369b39c0c 100644 --- a/aptos-move/framework/aptos-framework/sources/staking_proxy.spec.move +++ b/aptos-move/framework/aptos-framework/sources/staking_proxy.spec.move @@ -41,7 +41,14 @@ spec aptos_framework::staking_proxy { /// spec module { pragma verify = true; - pragma aborts_if_is_strict; + pragma aborts_if_is_partial; + } + + spec grant_permission { + pragma aborts_if_is_partial; + aborts_if !permissioned_signer::spec_is_permissioned_signer(permissioned_signer); + aborts_if permissioned_signer::spec_is_permissioned_signer(master); + aborts_if signer::address_of(master) != signer::address_of(permissioned_signer); } /// Aborts if conditions of SetStakePoolOperator are not met @@ -58,6 +65,7 @@ spec aptos_framework::staking_proxy { spec set_voter(owner: &signer, operator: address, new_voter: address) { // TODO: Can't verify `set_vesting_contract_voter` pragma aborts_if_is_partial; + pragma verify_duration_estimate = 120; include SetStakingContractVoter; include SetStakePoolVoterAbortsIf; } @@ -122,12 +130,21 @@ spec aptos_framework::staking_proxy { /// One of them are not exists spec set_stake_pool_operator(owner: &signer, new_operator: address) { include SetStakePoolOperator; + include AbortsIfSignerPermissionStakeProxy { + s: owner + }; + include exists(signer::address_of(owner)) ==> stake::AbortsIfSignerPermissionStake { + s:owner + }; } spec schema SetStakePoolOperator { owner: &signer; new_operator: address; + include AbortsIfSignerPermissionStakeProxy { + s: owner + }; let owner_address = signer::address_of(owner); let ownership_cap = borrow_global(owner_address); let pool_address = ownership_cap.pool_address; @@ -137,6 +154,9 @@ spec aptos_framework::staking_proxy { spec set_staking_contract_voter(owner: &signer, operator: address, new_voter: address) { include SetStakingContractVoter; + include AbortsIfSignerPermissionStakeProxy { + s: owner + }; } /// Make sure staking_contract_exists first @@ -166,16 +186,32 @@ spec aptos_framework::staking_proxy { spec set_stake_pool_voter(owner: &signer, new_voter: address) { include SetStakePoolVoterAbortsIf; + include AbortsIfSignerPermissionStakeProxy { + s: owner + }; + include exists(signer::address_of(owner)) ==> stake::AbortsIfSignerPermissionStake { + s:owner + }; } spec schema SetStakePoolVoterAbortsIf { owner: &signer; new_voter: address; + include AbortsIfSignerPermissionStakeProxy { + s: owner + }; let owner_address = signer::address_of(owner); let ownership_cap = global(owner_address); let pool_address = ownership_cap.pool_address; aborts_if stake::stake_pool_exists(owner_address) && !(exists(owner_address) && stake::stake_pool_exists(pool_address)); ensures stake::stake_pool_exists(owner_address) ==> global(pool_address).delegated_voter == new_voter; } + + spec schema AbortsIfSignerPermissionStakeProxy { + use aptos_framework::permissioned_signer; + s: signer; + let perm = StakeProxyPermission {}; + aborts_if !permissioned_signer::spec_check_permission_exists(s, perm); + } } diff --git a/aptos-move/framework/aptos-framework/sources/transaction_context.spec.move b/aptos-move/framework/aptos-framework/sources/transaction_context.spec.move index f9837e26e6a75..07487cb0919ae 100644 --- a/aptos-move/framework/aptos-framework/sources/transaction_context.spec.move +++ b/aptos-move/framework/aptos-framework/sources/transaction_context.spec.move @@ -58,11 +58,13 @@ spec aptos_framework::transaction_context { } spec generate_unique_address(): address { pragma opaque; + aborts_if [abstract] false; ensures [abstract] result == spec_generate_unique_address(); } spec fun spec_generate_unique_address(): address; spec generate_auid_address(): address { pragma opaque; + aborts_if [abstract] false; // property 3: Generating the unique address should return a vector with 32 bytes, if the auid feature flag is enabled. /// [high-level-req-3] ensures [abstract] result == spec_generate_unique_address(); diff --git a/aptos-move/framework/aptos-framework/sources/vesting.move b/aptos-move/framework/aptos-framework/sources/vesting.move index 34424b882a7e3..069cd18abbc6d 100644 --- a/aptos-move/framework/aptos-framework/sources/vesting.move +++ b/aptos-move/framework/aptos-framework/sources/vesting.move @@ -53,6 +53,7 @@ module aptos_framework::vesting { use aptos_framework::staking_contract; use aptos_framework::system_addresses; use aptos_framework::timestamp; + use aptos_framework::permissioned_signer; friend aptos_framework::genesis; @@ -90,6 +91,8 @@ module aptos_framework::vesting { const EPERMISSION_DENIED: u64 = 15; /// Zero items were provided to a *_many function. const EVEC_EMPTY_FOR_MANY_FUNCTION: u64 = 16; + /// Current permissioned signer cannot perform vesting operations. + const ENO_VESTING_PERMISSION: u64 = 17; /// Maximum number of shareholders a vesting pool can support. const MAXIMUM_SHAREHOLDERS: u64 = 30; @@ -328,6 +331,22 @@ module aptos_framework::vesting { amount: u64, } + /// Permissions to mutate the vesting config for a given account. + struct VestPermission has copy, drop, store {} + + /// Permissions + inline fun check_vest_permission(s: &signer) { + assert!( + permissioned_signer::check_permission_exists(s, VestPermission {}), + error::permission_denied(ENO_VESTING_PERMISSION), + ); + } + + /// Grant permission to perform vesting operations on behalf of the master signer. + public fun grant_permission(master: &signer, permissioned_signer: &signer) { + permissioned_signer::authorize_unlimited(master, permissioned_signer, VestPermission {}) + } + #[view] /// Return the address of the underlying stake pool (separate resource account) of the vesting contract. /// @@ -535,6 +554,7 @@ module aptos_framework::vesting { // Optional seed used when creating the staking contract account. contract_creation_seed: vector, ): address acquires AdminStore { + check_vest_permission(admin); assert!( !system_addresses::is_reserved_address(withdrawal_address), error::invalid_argument(EINVALID_WITHDRAWAL_ADDRESS), @@ -1053,6 +1073,7 @@ module aptos_framework::vesting { contract_address: address, shareholder: address, ) acquires VestingAccountManagement, VestingContract { + check_vest_permission(account); let vesting_contract = borrow_global_mut(contract_address); let addr = signer::address_of(account); assert!( @@ -1132,6 +1153,7 @@ module aptos_framework::vesting { admin: &signer, contract_creation_seed: vector, ): (signer, SignerCapability) acquires AdminStore { + check_vest_permission(admin); let admin_store = borrow_global_mut(signer::address_of(admin)); let seed = bcs::to_bytes(&signer::address_of(admin)); vector::append(&mut seed, bcs::to_bytes(&admin_store.nonce)); @@ -1151,6 +1173,7 @@ module aptos_framework::vesting { } fun verify_admin(admin: &signer, vesting_contract: &VestingContract) { + check_vest_permission(admin); assert!(signer::address_of(admin) == vesting_contract.admin, error::unauthenticated(ENOT_ADMIN)); } diff --git a/aptos-move/framework/aptos-framework/sources/vesting.spec.move b/aptos-move/framework/aptos-framework/sources/vesting.spec.move index 3bcbcbb11c0de..66244215a7ea7 100644 --- a/aptos-move/framework/aptos-framework/sources/vesting.spec.move +++ b/aptos-move/framework/aptos-framework/sources/vesting.spec.move @@ -105,13 +105,20 @@ spec aptos_framework::vesting { /// spec module { pragma verify = true; - pragma aborts_if_is_strict; + pragma aborts_if_is_partial; // property 2: The vesting pool should not exceed a maximum of 30 shareholders. /// [high-level-spec-2] invariant forall a: address where exists(a): global(a).grant_pool.shareholders_limit <= MAXIMUM_SHAREHOLDERS; } + spec schema AbortsIfPermissionedSigner { + use aptos_framework::permissioned_signer; + s: signer; + let perm = VestPermission {}; + aborts_if !permissioned_signer::spec_check_permission_exists(s, perm); + } + spec stake_pool_address(vesting_contract_address: address): address { aborts_if !exists(vesting_contract_address); } @@ -487,6 +494,7 @@ spec aptos_framework::vesting { } spec get_vesting_account_signer(admin: &signer, contract_address: address): signer { + pragma verify_duration_estimate = 120; include VerifyAdminAbortsIf; } @@ -530,8 +538,11 @@ spec aptos_framework::vesting { } spec verify_admin(admin: &signer, vesting_contract: &VestingContract) { + pragma verify_duration_estimate = 120; + aborts_if permissioned_signer::spec_is_permissioned_signer(admin); /// [high-level-req-9] aborts_if signer::address_of(admin) != vesting_contract.admin; + // include AbortsIfPermissionedSigner { s: admin }; } spec assert_vesting_contract_exists(contract_address: address) { @@ -630,6 +641,8 @@ spec aptos_framework::vesting { spec schema VerifyAdminAbortsIf { contract_address: address; admin: signer; + + aborts_if permissioned_signer::spec_is_permissioned_signer(admin); aborts_if !exists(contract_address); let vesting_contract = global(contract_address); aborts_if signer::address_of(admin) != vesting_contract.admin; diff --git a/aptos-move/framework/aptos-framework/sources/voting.move b/aptos-move/framework/aptos-framework/sources/voting.move index 8312ac17b7619..8dd1b0cb2e9a5 100644 --- a/aptos-move/framework/aptos-framework/sources/voting.move +++ b/aptos-move/framework/aptos-framework/sources/voting.move @@ -34,6 +34,7 @@ module aptos_framework::voting { use aptos_framework::account; use aptos_framework::event::{Self, EventHandle}; + use aptos_framework::permissioned_signer; use aptos_framework::timestamp; use aptos_framework::transaction_context; use aptos_std::from_bcs; @@ -63,6 +64,8 @@ module aptos_framework::voting { const ESINGLE_STEP_PROPOSAL_CANNOT_HAVE_NEXT_EXECUTION_HASH: u64 = 11; /// Cannot call `is_multi_step_proposal_in_execution()` on single-step proposals. const EPROPOSAL_IS_SINGLE_STEP: u64 = 12; + /// Cannot call `is_multi_step_proposal_in_execution()` on single-step proposals. + const ENO_VOTE_PERMISSION: u64 = 13; /// ProposalStateEnum representing proposal state. const PROPOSAL_STATE_PENDING: u64 = 0; @@ -188,7 +191,23 @@ module aptos_framework::voting { num_votes: u64, } + struct VotePermission has copy, drop, store {} + + /// Permissions + inline fun check_vote_permission(s: &signer) { + assert!( + permissioned_signer::check_permission_exists(s, VotePermission {}), + error::permission_denied(ENO_VOTE_PERMISSION), + ); + } + + /// Grant permission to vote on behalf of the master signer. + public fun grant_permission(master: &signer, permissioned_signer: &signer) { + permissioned_signer::authorize_unlimited(master, permissioned_signer, VotePermission {}) + } + public fun register(account: &signer) { + check_vote_permission(account); let addr = signer::address_of(account); assert!(!exists>(addr), error::already_exists(EVOTING_FORUM_ALREADY_REGISTERED)); diff --git a/aptos-move/framework/aptos-framework/sources/voting.spec.move b/aptos-move/framework/aptos-framework/sources/voting.spec.move index a1c05091e54b6..296593c5c9f25 100644 --- a/aptos-move/framework/aptos-framework/sources/voting.spec.move +++ b/aptos-move/framework/aptos-framework/sources/voting.spec.move @@ -40,10 +40,18 @@ spec aptos_framework::voting { /// spec module { pragma verify = true; - pragma aborts_if_is_strict; + pragma aborts_if_is_partial; + } + + spec schema AbortsIfPermissionedSigner { + use aptos_framework::permissioned_signer; + s: signer; + let perm = VotePermission {}; + aborts_if !permissioned_signer::spec_check_permission_exists(s, perm); } spec register(account: &signer) { + // include AbortsIfPermissionedSigner { s: account }; let addr = signer::address_of(account); // Will abort if there's already a `VotingForum` under addr diff --git a/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/object_code_deployment.md b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/object_code_deployment.md index 210d0a1e6b892..354117d123444 100644 --- a/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/object_code_deployment.md +++ b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/object_code_deployment.md @@ -39,7 +39,10 @@ Once modules are marked as immutable, they cannot be made mutable again. - [Struct `Publish`](#0x1_object_code_deployment_Publish) - [Struct `Upgrade`](#0x1_object_code_deployment_Upgrade) - [Struct `Freeze`](#0x1_object_code_deployment_Freeze) +- [Struct `ObjectCodePermission`](#0x1_object_code_deployment_ObjectCodePermission) - [Constants](#@Constants_0) +- [Function `check_signer_permission`](#0x1_object_code_deployment_check_signer_permission) +- [Function `grant_permission`](#0x1_object_code_deployment_grant_permission) - [Function `publish`](#0x1_object_code_deployment_publish) - [Function `object_seed`](#0x1_object_code_deployment_object_seed) - [Function `upgrade`](#0x1_object_code_deployment_upgrade) @@ -53,6 +56,7 @@ Once modules are marked as immutable, they cannot be made mutable again. use 0x1::event; use 0x1::features; use 0x1::object; +use 0x1::permissioned_signer; use 0x1::signer; use 0x1::vector;
@@ -173,6 +177,33 @@ Event emitted when code in an existing object is made immutable. + + + + +## Struct `ObjectCodePermission` + + + +
struct ObjectCodePermission has copy, drop, store
+
+ + + +
+Fields + + +
+
+dummy_field: bool +
+
+ +
+
+ +
@@ -190,6 +221,16 @@ Event emitted when code in an existing object is made immutable. + + +Current permissioned signer cannot deploy object code. + + +
const ENO_CODE_PERMISSION: u64 = 4;
+
+ + + Not the owner of the code_object @@ -219,6 +260,59 @@ Object code deployment feature not supported. + + +## Function `check_signer_permission` + +Permissions + + +
fun check_signer_permission(s: &signer)
+
+ + + +
+Implementation + + +
inline fun check_signer_permission(s: &signer) {
+    assert!(
+        permissioned_signer::check_permission_exists(s, ObjectCodePermission {}),
+        error::permission_denied(ENO_CODE_PERMISSION),
+    );
+}
+
+ + + +
+ + + +## Function `grant_permission` + +Grant permission to publish code on behalf of the master signer. + + +
public fun grant_permission(master: &signer, permissioned_signer: &signer)
+
+ + + +
+Implementation + + +
public fun grant_permission(master: &signer, permissioned_signer: &signer) {
+    permissioned_signer::authorize_unlimited(master, permissioned_signer, ObjectCodePermission {})
+}
+
+ + + +
+ ## Function `publish` @@ -243,6 +337,7 @@ the code to be published via code. T metadata_serialized: vector<u8>, code: vector<vector<u8>>, ) { + check_signer_permission(publisher); assert!( features::is_object_code_deployment_enabled(), error::unavailable(EOBJECT_CODE_DEPLOYMENT_NOT_SUPPORTED), @@ -319,6 +414,7 @@ Requires the publisher to be the owner of the code_object. code: vector<vector<u8>>, code_object: Object<PackageRegistry>, ) acquires ManagingRefs { + check_signer_permission(publisher); let publisher_address = signer::address_of(publisher); assert!( object::is_owner(code_object, publisher_address), diff --git a/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/permissioned_signer.md b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/permissioned_signer.md new file mode 100644 index 0000000000000..c4c76a6d0bea9 --- /dev/null +++ b/aptos-move/framework/aptos-framework/tests/compiler-v2-doc/permissioned_signer.md @@ -0,0 +1,1930 @@ + + + +# Module `0x1::permissioned_signer` + +A _permissioned signer_ consists of a pair of the original signer and a generated +address which is used store information about associated permissions. + +A permissioned signer is a restricted version of a signer. Functions move_to and +address_of behave the same, and can be passed wherever signer is needed. However, +code can internally query for the permissions to assert additional restrictions on +the use of the signer. + +A client which is interested in restricting access granted via a signer can create a permissioned signer +and pass on to other existing code without changes to existing APIs. Core functions in the framework, for +example account functions, can then assert availability of permissions, effectively restricting +existing code in a compatible way. + +After introducing the core functionality, examples are provided for withdraw limit on accounts, and +for blind signing. + + +- [Resource `GrantedPermissionHandles`](#0x1_permissioned_signer_GrantedPermissionHandles) +- [Enum `PermissionedHandle`](#0x1_permissioned_signer_PermissionedHandle) +- [Enum `StorablePermissionedHandle`](#0x1_permissioned_signer_StorablePermissionedHandle) +- [Enum Resource `PermissionStorage`](#0x1_permissioned_signer_PermissionStorage) +- [Enum `StoredPermission`](#0x1_permissioned_signer_StoredPermission) +- [Enum `Permission`](#0x1_permissioned_signer_Permission) +- [Constants](#@Constants_0) +- [Function `create_permissioned_handle`](#0x1_permissioned_signer_create_permissioned_handle) +- [Function `create_storable_permissioned_handle`](#0x1_permissioned_signer_create_storable_permissioned_handle) +- [Function `destroy_permissioned_handle`](#0x1_permissioned_signer_destroy_permissioned_handle) +- [Function `destroy_storable_permissioned_handle`](#0x1_permissioned_signer_destroy_storable_permissioned_handle) +- [Function `destroy_permissions_storage_address`](#0x1_permissioned_signer_destroy_permissions_storage_address) +- [Function `signer_from_permissioned_handle`](#0x1_permissioned_signer_signer_from_permissioned_handle) +- [Function `signer_from_storable_permissioned_handle`](#0x1_permissioned_signer_signer_from_storable_permissioned_handle) +- [Function `revoke_permission_storage_address`](#0x1_permissioned_signer_revoke_permission_storage_address) +- [Function `revoke_all_handles`](#0x1_permissioned_signer_revoke_all_handles) +- [Function `permissions_storage_address`](#0x1_permissioned_signer_permissions_storage_address) +- [Function `assert_master_signer`](#0x1_permissioned_signer_assert_master_signer) +- [Function `is_above`](#0x1_permissioned_signer_is_above) +- [Function `consume_capacity`](#0x1_permissioned_signer_consume_capacity) +- [Function `increase_capacity`](#0x1_permissioned_signer_increase_capacity) +- [Function `merge`](#0x1_permissioned_signer_merge) +- [Function `map_or`](#0x1_permissioned_signer_map_or) +- [Function `insert_or`](#0x1_permissioned_signer_insert_or) +- [Function `authorize`](#0x1_permissioned_signer_authorize) +- [Function `authorize_unlimited`](#0x1_permissioned_signer_authorize_unlimited) +- [Function `check_permission_exists`](#0x1_permissioned_signer_check_permission_exists) +- [Function `check_permission_capacity_above`](#0x1_permissioned_signer_check_permission_capacity_above) +- [Function `check_permission_consume`](#0x1_permissioned_signer_check_permission_consume) +- [Function `capacity`](#0x1_permissioned_signer_capacity) +- [Function `revoke_permission`](#0x1_permissioned_signer_revoke_permission) +- [Function `extract_permission`](#0x1_permissioned_signer_extract_permission) +- [Function `extract_all_permission`](#0x1_permissioned_signer_extract_all_permission) +- [Function `address_of`](#0x1_permissioned_signer_address_of) +- [Function `consume_permission`](#0x1_permissioned_signer_consume_permission) +- [Function `store_permission`](#0x1_permissioned_signer_store_permission) +- [Function `is_permissioned_signer`](#0x1_permissioned_signer_is_permissioned_signer) +- [Function `permission_address`](#0x1_permissioned_signer_permission_address) +- [Function `signer_from_permissioned_handle_impl`](#0x1_permissioned_signer_signer_from_permissioned_handle_impl) +- [Specification](#@Specification_1) + - [Function `create_permissioned_handle`](#@Specification_1_create_permissioned_handle) + - [Function `create_storable_permissioned_handle`](#@Specification_1_create_storable_permissioned_handle) + - [Function `destroy_permissioned_handle`](#@Specification_1_destroy_permissioned_handle) + - [Function `destroy_storable_permissioned_handle`](#@Specification_1_destroy_storable_permissioned_handle) + - [Function `revoke_permission_storage_address`](#@Specification_1_revoke_permission_storage_address) + - [Function `authorize`](#@Specification_1_authorize) + - [Function `check_permission_exists`](#@Specification_1_check_permission_exists) + - [Function `check_permission_capacity_above`](#@Specification_1_check_permission_capacity_above) + - [Function `check_permission_consume`](#@Specification_1_check_permission_consume) + - [Function `capacity`](#@Specification_1_capacity) + - [Function `consume_permission`](#@Specification_1_consume_permission) + - [Function `is_permissioned_signer`](#@Specification_1_is_permissioned_signer) + - [Function `permission_address`](#@Specification_1_permission_address) + - [Function `signer_from_permissioned_handle_impl`](#@Specification_1_signer_from_permissioned_handle_impl) + + +
use 0x1::copyable_any;
+use 0x1::create_signer;
+use 0x1::error;
+use 0x1::option;
+use 0x1::signer;
+use 0x1::simple_map;
+use 0x1::timestamp;
+use 0x1::transaction_context;
+use 0x1::vector;
+
+ + + + + +## Resource `GrantedPermissionHandles` + + + +
struct GrantedPermissionHandles has key
+
+ + + +
+Fields + + +
+
+active_handles: vector<address> +
+
+ +
+
+ + +
+ + + +## Enum `PermissionedHandle` + + + +
enum PermissionedHandle
+
+ + + +
+Variants + + +
+V1 + + +
+Fields + + +
+
+master_account_addr: address +
+
+ +
+
+permissions_storage_addr: address +
+
+ +
+
+ + +
+ +
+ +
+ + + +## Enum `StorablePermissionedHandle` + + + +
enum StorablePermissionedHandle has store
+
+ + + +
+Variants + + +
+V1 + + +
+Fields + + +
+
+master_account_addr: address +
+
+ +
+
+permissions_storage_addr: address +
+
+ +
+
+expiration_time: u64 +
+
+ +
+
+ + +
+ +
+ +
+ + + +## Enum Resource `PermissionStorage` + + + +
enum PermissionStorage has key
+
+ + + +
+Variants + + +
+V1 + + +
+Fields + + +
+
+perms: simple_map::SimpleMap<copyable_any::Any, permissioned_signer::StoredPermission> +
+
+ +
+
+ + +
+ +
+ +
+ + + +## Enum `StoredPermission` + + + +
enum StoredPermission has copy, drop, store
+
+ + + +
+Variants + + +
+Unlimited + + +
+Fields + + +
+
+ + +
+ +
+ +
+Capacity + + +
+Fields + + +
+
+0: u256 +
+
+ +
+
+ + +
+ +
+ +
+ + + +## Enum `Permission` + + + +
enum Permission<K>
+
+ + + +
+Variants + + +
+V1 + + +
+Fields + + +
+
+owner_address: address +
+
+ +
+
+key: K +
+
+ +
+
+perm: permissioned_signer::StoredPermission +
+
+ +
+
+ + +
+ +
+ +
+ + + +## Constants + + + + +Cannot authorize a permission. + + +
const ECANNOT_AUTHORIZE: u64 = 2;
+
+ + + + + +signer doesn't have enough capacity to extract permission. + + +
const ECANNOT_EXTRACT_PERMISSION: u64 = 4;
+
+ + + + + +Trying to grant permission using master signer. + + +
const ENOT_MASTER_SIGNER: u64 = 1;
+
+ + + + + +Access permission information from a master signer. + + +
const ENOT_PERMISSIONED_SIGNER: u64 = 3;
+
+ + + + + +destroying permission handle that has already been revoked or not owned by the +given master signer. + + +
const E_NOT_ACTIVE: u64 = 8;
+
+ + + + + +permission handle has expired. + + +
const E_PERMISSION_EXPIRED: u64 = 5;
+
+ + + + + +storing extracted permission into a different signer. + + +
const E_PERMISSION_MISMATCH: u64 = 6;
+
+ + + + + +permission handle has been revoked by the original signer. + + +
const E_PERMISSION_REVOKED: u64 = 7;
+
+ + + + + + + +
const U256_MAX: u256 = 115792089237316195423570985008687907853269984665640564039457584007913129639935;
+
+ + + + + +## Function `create_permissioned_handle` + +Create an ephermeral permission handle based on the master signer. + +This handle can be used to derive a signer that can be used in the context of +the current transaction. + + +
public fun create_permissioned_handle(master: &signer): permissioned_signer::PermissionedHandle
+
+ + + +
+Implementation + + +
public fun create_permissioned_handle(master: &signer): PermissionedHandle {
+    assert_master_signer(master);
+    let permissions_storage_addr = generate_auid_address();
+    let master_account_addr = signer::address_of(master);
+
+    move_to(
+        &create_signer(permissions_storage_addr),
+        PermissionStorage::V1 { perms: simple_map::new() }
+    );
+
+    PermissionedHandle::V1 { master_account_addr, permissions_storage_addr }
+}
+
+ + + +
+ + + +## Function `create_storable_permissioned_handle` + +Create an storable permission handle based on the master signer. + +This handle can be used to derive a signer that can be stored by a smart contract. +This is as dangerous as key delegation, thus it remains public(package) for now. + +The caller should check if expiration_time is not too far in the future. + + +
public(friend) fun create_storable_permissioned_handle(master: &signer, expiration_time: u64): permissioned_signer::StorablePermissionedHandle
+
+ + + +
+Implementation + + +
public(package) fun create_storable_permissioned_handle(
+    master: &signer, expiration_time: u64
+): StorablePermissionedHandle acquires GrantedPermissionHandles {
+    assert_master_signer(master);
+    let permissions_storage_addr = generate_auid_address();
+    let master_account_addr = signer::address_of(master);
+
+    assert!(
+        timestamp::now_seconds() < expiration_time,
+        error::permission_denied(E_PERMISSION_EXPIRED)
+    );
+
+    if (!exists<GrantedPermissionHandles>(master_account_addr)) {
+        move_to<GrantedPermissionHandles>(
+            master, GrantedPermissionHandles { active_handles: vector::empty() }
+        );
+    };
+
+    vector::push_back(
+        &mut borrow_global_mut<GrantedPermissionHandles>(master_account_addr).active_handles,
+        permissions_storage_addr
+    );
+
+    move_to(
+        &create_signer(permissions_storage_addr),
+        PermissionStorage::V1 { perms: simple_map::new() }
+    );
+
+    StorablePermissionedHandle::V1 {
+        master_account_addr,
+        permissions_storage_addr,
+        expiration_time
+    }
+}
+
+ + + +
+ + + +## Function `destroy_permissioned_handle` + +Destroys an ephermeral permission handle. Clean up the permission stored in that handle + + +
public fun destroy_permissioned_handle(p: permissioned_signer::PermissionedHandle)
+
+ + + +
+Implementation + + +
public fun destroy_permissioned_handle(p: PermissionedHandle) acquires PermissionStorage {
+    let PermissionedHandle::V1 { master_account_addr: _, permissions_storage_addr } =
+        p;
+    destroy_permissions_storage_address(permissions_storage_addr);
+}
+
+ + + +
+ + + +## Function `destroy_storable_permissioned_handle` + +Destroys a storable permission handle. Clean up the permission stored in that handle + + +
public(friend) fun destroy_storable_permissioned_handle(p: permissioned_signer::StorablePermissionedHandle)
+
+ + + +
+Implementation + + +
public(package) fun destroy_storable_permissioned_handle(
+    p: StorablePermissionedHandle
+) acquires PermissionStorage, GrantedPermissionHandles {
+    let StorablePermissionedHandle::V1 {
+        master_account_addr,
+        permissions_storage_addr,
+        expiration_time: _
+    } = p;
+
+    assert!(
+        exists<GrantedPermissionHandles>(master_account_addr),
+        error::permission_denied(E_PERMISSION_REVOKED),
+    );
+    let granted_permissions =
+        borrow_global_mut<GrantedPermissionHandles>(master_account_addr);
+    let (found, idx) = vector::index_of(
+        &granted_permissions.active_handles, &permissions_storage_addr
+    );
+
+    // Removing the address from the active handle list if it's still active.
+    if(found) {
+        vector::swap_remove(&mut granted_permissions.active_handles, idx);
+    };
+
+    destroy_permissions_storage_address(permissions_storage_addr);
+}
+
+ + + +
+ + + +## Function `destroy_permissions_storage_address` + + + +
fun destroy_permissions_storage_address(permissions_storage_addr: address)
+
+ + + +
+Implementation + + +
inline fun destroy_permissions_storage_address(
+    permissions_storage_addr: address
+) acquires PermissionStorage {
+    if (exists<PermissionStorage>(permissions_storage_addr)) {
+        let PermissionStorage::V1 { perms } =
+            move_from<PermissionStorage>(permissions_storage_addr);
+        simple_map::destroy(
+            perms,
+            |_dk| {},
+            |_dv| {}
+        );
+    }
+}
+
+ + + +
+ + + +## Function `signer_from_permissioned_handle` + +Generate the permissioned signer based on the ephermeral permission handle. + +This signer can be used as a regular signer for other smart contracts. However when such +signer interacts with various framework functions, it would subject to permission checks +and would abort if check fails. + + +
public fun signer_from_permissioned_handle(p: &permissioned_signer::PermissionedHandle): signer
+
+ + + +
+Implementation + + +
public fun signer_from_permissioned_handle(p: &PermissionedHandle): signer {
+    signer_from_permissioned_handle_impl(
+        p.master_account_addr, p.permissions_storage_addr
+    )
+}
+
+ + + +
+ + + +## Function `signer_from_storable_permissioned_handle` + +Generate the permissioned signer based on the storable permission handle. + + +
public(friend) fun signer_from_storable_permissioned_handle(p: &permissioned_signer::StorablePermissionedHandle): signer
+
+ + + +
+Implementation + + +
public(package) fun signer_from_storable_permissioned_handle(
+    p: &StorablePermissionedHandle
+): signer {
+    assert!(
+        timestamp::now_seconds() < p.expiration_time,
+        error::permission_denied(E_PERMISSION_EXPIRED)
+    );
+    assert!(
+        exists<PermissionStorage>(p.permissions_storage_addr),
+        error::permission_denied(E_PERMISSION_REVOKED)
+    );
+    signer_from_permissioned_handle_impl(
+        p.master_account_addr, p.permissions_storage_addr
+    )
+}
+
+ + + +
+ + + +## Function `revoke_permission_storage_address` + +Revoke a specific storable permission handle immediately. This would disallow owner of +the storable permission handle to derive signer from it anymore. + + +
public entry fun revoke_permission_storage_address(s: &signer, permissions_storage_addr: address)
+
+ + + +
+Implementation + + +
public entry fun revoke_permission_storage_address(
+    s: &signer, permissions_storage_addr: address
+) acquires GrantedPermissionHandles, PermissionStorage {
+    assert!(
+        !is_permissioned_signer(s), error::permission_denied(ENOT_MASTER_SIGNER)
+    );
+    let master_account_addr = signer::address_of(s);
+
+    assert!(
+        exists<GrantedPermissionHandles>(master_account_addr),
+        error::permission_denied(E_PERMISSION_REVOKED),
+    );
+    let granted_permissions =
+        borrow_global_mut<GrantedPermissionHandles>(master_account_addr);
+    let (found, idx) = vector::index_of(
+        &granted_permissions.active_handles, &permissions_storage_addr
+    );
+
+    // The address has to be in the activated list in the master account address.
+    assert!(found, error::permission_denied(E_NOT_ACTIVE));
+    vector::swap_remove(&mut granted_permissions.active_handles, idx);
+    destroy_permissions_storage_address(permissions_storage_addr);
+}
+
+ + + +
+ + + +## Function `revoke_all_handles` + +Revoke all storable permission handle of the signer immediately. + + +
public entry fun revoke_all_handles(s: &signer)
+
+ + + +
+Implementation + + +
public entry fun revoke_all_handles(s: &signer) acquires GrantedPermissionHandles, PermissionStorage {
+    assert!(
+        !is_permissioned_signer(s), error::permission_denied(ENOT_MASTER_SIGNER)
+    );
+    let master_account_addr = signer::address_of(s);
+    if (!exists<GrantedPermissionHandles>(master_account_addr)) { return };
+
+    let granted_permissions =
+        borrow_global_mut<GrantedPermissionHandles>(master_account_addr);
+    let delete_list = vector::trim_reverse(
+        &mut granted_permissions.active_handles, 0
+    );
+    vector::destroy(
+        delete_list,
+        |address| {
+            destroy_permissions_storage_address(address);
+        }
+    )
+}
+
+ + + +
+ + + +## Function `permissions_storage_address` + +Return the permission handle address so that it could be used for revocation purpose. + + +
public(friend) fun permissions_storage_address(p: &permissioned_signer::StorablePermissionedHandle): address
+
+ + + +
+Implementation + + +
public(package) fun permissions_storage_address(
+    p: &StorablePermissionedHandle
+): address {
+    p.permissions_storage_addr
+}
+
+ + + +
+ + + +## Function `assert_master_signer` + +Helper function that would abort if the signer passed in is a permissioned signer. + + +
public fun assert_master_signer(s: &signer)
+
+ + + +
+Implementation + + +
public fun assert_master_signer(s: &signer) {
+    assert!(
+        !is_permissioned_signer(s), error::permission_denied(ENOT_MASTER_SIGNER)
+    );
+}
+
+ + + +
+ + + +## Function `is_above` + +===================================================================================================== +StoredPermission operations + +check if StoredPermission has at least threshold capacity. + + +
fun is_above(perm: &permissioned_signer::StoredPermission, threshold: u256): bool
+
+ + + +
+Implementation + + +
fun is_above(perm: &StoredPermission, threshold: u256): bool {
+    match (perm) {
+        StoredPermission::Capacity(capacity) => *capacity > threshold,
+        StoredPermission::Unlimited => true,
+    }
+}
+
+ + + +
+ + + +## Function `consume_capacity` + +consume threshold capacity from StoredPermission + + +
fun consume_capacity(perm: &mut permissioned_signer::StoredPermission, threshold: u256): bool
+
+ + + +
+Implementation + + +
fun consume_capacity(perm: &mut StoredPermission, threshold: u256): bool {
+    match (perm) {
+        StoredPermission::Capacity(current_capacity) => {
+            if (*current_capacity >= threshold) {
+                *current_capacity = *current_capacity - threshold;
+                true
+            } else { false }
+        }
+        StoredPermission::Unlimited => true
+    }
+}
+
+ + + +
+ + + +## Function `increase_capacity` + +increase threshold capacity from StoredPermission + + +
fun increase_capacity(perm: &mut permissioned_signer::StoredPermission, threshold: u256)
+
+ + + +
+Implementation + + +
fun increase_capacity(perm: &mut StoredPermission, threshold: u256) {
+    match (perm) {
+        StoredPermission::Capacity(current_capacity) => {
+            *current_capacity = *current_capacity + threshold;
+        }
+        StoredPermission::Unlimited => (),
+    }
+}
+
+ + + +
+ + + +## Function `merge` + +merge the two stored permission + + +
fun merge(lhs: &mut permissioned_signer::StoredPermission, rhs: permissioned_signer::StoredPermission)
+
+ + + +
+Implementation + + +
fun merge(lhs: &mut StoredPermission, rhs: StoredPermission) {
+    match (rhs) {
+        StoredPermission::Capacity(new_capacity) => {
+            match (lhs) {
+                StoredPermission::Capacity(current_capacity) => {
+                    *current_capacity = *current_capacity + new_capacity;
+                }
+                StoredPermission::Unlimited => (),
+            }
+        }
+        StoredPermission::Unlimited => *lhs = StoredPermission::Unlimited,
+    }
+}
+
+ + + +
+ + + +## Function `map_or` + +===================================================================================================== +Permission Management + +Authorizes permissioned with the given permission. This requires to have access to the master +signer. + + +
fun map_or<PermKey: copy, drop, store, T>(permissioned: &signer, perm: PermKey, mutate: |&mut permissioned_signer::StoredPermission|T, default: T): T
+
+ + + +
+Implementation + + +
inline fun map_or<PermKey: copy + drop + store, T>(
+    permissioned: &signer,
+    perm: PermKey,
+    mutate: |&mut StoredPermission| T,
+    default: T,
+): T {
+    let permission_signer_addr = permission_address(permissioned);
+    assert!(
+        exists<PermissionStorage>(permission_signer_addr),
+        error::permission_denied(E_NOT_ACTIVE)
+    );
+    let perms =
+        &mut borrow_global_mut<PermissionStorage>(permission_signer_addr).perms;
+    let key = copyable_any::pack(perm);
+    if (simple_map::contains_key(perms, &key)) {
+        mutate(simple_map::borrow_mut(perms, &key))
+    } else {
+        default
+    }
+}
+
+ + + +
+ + + +## Function `insert_or` + + + +
fun insert_or<PermKey: copy, drop, store>(permissioned: &signer, perm: PermKey, mutate: |&mut permissioned_signer::StoredPermission|, default: permissioned_signer::StoredPermission)
+
+ + + +
+Implementation + + +
inline fun insert_or<PermKey: copy + drop + store>(
+    permissioned: &signer,
+    perm: PermKey,
+    mutate: |&mut StoredPermission|,
+    default: StoredPermission,
+) {
+    let permission_signer_addr = permission_address(permissioned);
+    assert!(
+        exists<PermissionStorage>(permission_signer_addr),
+        error::permission_denied(E_NOT_ACTIVE)
+    );
+    let perms =
+        &mut borrow_global_mut<PermissionStorage>(permission_signer_addr).perms;
+    let key = copyable_any::pack(perm);
+    if (simple_map::contains_key(perms, &key)) {
+        mutate(simple_map::borrow_mut(perms, &key));
+    } else {
+        simple_map::add(perms, key, default);
+    }
+}
+
+ + + +
+ + + +## Function `authorize` + + + +
public fun authorize<PermKey: copy, drop, store>(master: &signer, permissioned: &signer, capacity: u256, perm: PermKey)
+
+ + + +
+Implementation + + +
public fun authorize<PermKey: copy + drop + store>(
+    master: &signer,
+    permissioned: &signer,
+    capacity: u256,
+    perm: PermKey
+) acquires PermissionStorage {
+    assert!(
+        is_permissioned_signer(permissioned)
+            && !is_permissioned_signer(master)
+            && signer::address_of(master) == signer::address_of(permissioned),
+        error::permission_denied(ECANNOT_AUTHORIZE)
+    );
+    insert_or(
+        permissioned,
+        perm,
+        |stored_permission| {
+            increase_capacity(stored_permission, capacity);
+        },
+        StoredPermission::Capacity(capacity),
+    )
+}
+
+ + + +
+ + + +## Function `authorize_unlimited` + +Authorizes permissioned with the given unlimited permission. +Unlimited permission can be consumed however many times. + + +
public fun authorize_unlimited<PermKey: copy, drop, store>(master: &signer, permissioned: &signer, perm: PermKey)
+
+ + + +
+Implementation + + +
public fun authorize_unlimited<PermKey: copy + drop + store>(
+    master: &signer,
+    permissioned: &signer,
+    perm: PermKey
+) acquires PermissionStorage {
+    assert!(
+        is_permissioned_signer(permissioned)
+            && !is_permissioned_signer(master)
+            && signer::address_of(master) == signer::address_of(permissioned),
+        error::permission_denied(ECANNOT_AUTHORIZE)
+    );
+    insert_or(
+        permissioned,
+        perm,
+        |stored_permission| {
+            *stored_permission = StoredPermission::Unlimited;
+        },
+        StoredPermission::Unlimited,
+    )
+}
+
+ + + +
+ + + +## Function `check_permission_exists` + + + +
public fun check_permission_exists<PermKey: copy, drop, store>(s: &signer, perm: PermKey): bool
+
+ + + +
+Implementation + + +
public fun check_permission_exists<PermKey: copy + drop + store>(
+    s: &signer, perm: PermKey
+): bool acquires PermissionStorage {
+    check_permission_capacity_above(s, 0, perm)
+}
+
+ + + +
+ + + +## Function `check_permission_capacity_above` + + + +
public fun check_permission_capacity_above<PermKey: copy, drop, store>(s: &signer, threshold: u256, perm: PermKey): bool
+
+ + + +
+Implementation + + +
public fun check_permission_capacity_above<PermKey: copy + drop + store>(
+    s: &signer, threshold: u256, perm: PermKey
+): bool acquires PermissionStorage {
+    if (!is_permissioned_signer(s)) {
+        // master signer has all permissions
+        return true
+    };
+    map_or(
+        s,
+        perm,
+        |stored_permission| {
+            is_above(stored_permission, threshold)
+        },
+        false,
+    )
+}
+
+ + + +
+ + + +## Function `check_permission_consume` + + + +
public fun check_permission_consume<PermKey: copy, drop, store>(s: &signer, threshold: u256, perm: PermKey): bool
+
+ + + +
+Implementation + + +
public fun check_permission_consume<PermKey: copy + drop + store>(
+    s: &signer, threshold: u256, perm: PermKey
+): bool acquires PermissionStorage {
+    if (!is_permissioned_signer(s)) {
+        // master signer has all permissions
+        return true
+    };
+    map_or(
+        s,
+        perm,
+        |stored_permission| {
+             consume_capacity(stored_permission, threshold)
+        },
+        false,
+    )
+}
+
+ + + +
+ + + +## Function `capacity` + + + +
public fun capacity<PermKey: copy, drop, store>(s: &signer, perm: PermKey): option::Option<u256>
+
+ + + +
+Implementation + + +
public fun capacity<PermKey: copy + drop + store>(
+    s: &signer, perm: PermKey
+): Option<u256> acquires PermissionStorage {
+    if (!is_permissioned_signer(s)) {
+        return option::some(U256_MAX)
+    };
+    map_or(
+        s,
+        perm,
+        |stored_permission: &mut StoredPermission| {
+            option::some(match (stored_permission) {
+                StoredPermission::Capacity(capacity) => *capacity,
+                StoredPermission::Unlimited => U256_MAX,
+            })
+        },
+        option::none(),
+    )
+}
+
+ + + +
+ + + +## Function `revoke_permission` + + + +
public fun revoke_permission<PermKey: copy, drop, store>(permissioned: &signer, perm: PermKey)
+
+ + + +
+Implementation + + +
public fun revoke_permission<PermKey: copy + drop + store>(
+    permissioned: &signer, perm: PermKey
+) acquires PermissionStorage {
+    if (!is_permissioned_signer(permissioned)) {
+        // Master signer has no permissions associated with it.
+        return
+    };
+    let addr = permission_address(permissioned);
+    if (!exists<PermissionStorage>(addr)) { return };
+    let perm_storage = &mut borrow_global_mut<PermissionStorage>(addr).perms;
+    let key = copyable_any::pack(perm);
+    if (simple_map::contains_key(perm_storage, &key)) {
+        simple_map::remove(
+            &mut borrow_global_mut<PermissionStorage>(addr).perms,
+            &copyable_any::pack(perm)
+        );
+    }
+}
+
+ + + +
+ + + +## Function `extract_permission` + +===================================================================================================== +Another flavor of api to extract and store permissions + + +
public(friend) fun extract_permission<PermKey: copy, drop, store>(s: &signer, weight: u256, perm: PermKey): permissioned_signer::Permission<PermKey>
+
+ + + +
+Implementation + + +
public(package) fun extract_permission<PermKey: copy + drop + store>(
+    s: &signer, weight: u256, perm: PermKey
+): Permission<PermKey> acquires PermissionStorage {
+    assert!(
+        check_permission_consume(s, weight, perm),
+        error::permission_denied(ECANNOT_EXTRACT_PERMISSION)
+    );
+    Permission::V1 {
+        owner_address: signer::address_of(s),
+        key: perm,
+        perm: StoredPermission::Capacity(weight),
+    }
+}
+
+ + + +
+ + + +## Function `extract_all_permission` + + + +
public(friend) fun extract_all_permission<PermKey: copy, drop, store>(s: &signer, perm_key: PermKey): permissioned_signer::Permission<PermKey>
+
+ + + +
+Implementation + + +
public(package) fun extract_all_permission<PermKey: copy + drop + store>(
+    s: &signer, perm_key: PermKey
+): Permission<PermKey> acquires PermissionStorage {
+    assert!(
+        is_permissioned_signer(s),
+        error::permission_denied(ECANNOT_EXTRACT_PERMISSION)
+    );
+    let addr = permission_address(s);
+    assert!(
+        exists<PermissionStorage>(addr),
+        error::permission_denied(ECANNOT_EXTRACT_PERMISSION)
+    );
+    let key = copyable_any::pack(perm_key);
+    let storage = &mut borrow_global_mut<PermissionStorage>(addr).perms;
+    let (_, value) = simple_map::remove(storage, &key);
+
+    Permission::V1 {
+        owner_address: signer::address_of(s),
+        key: perm_key,
+        perm: value,
+    }
+}
+
+ + + +
+ + + +## Function `address_of` + + + +
public(friend) fun address_of<PermKey>(perm: &permissioned_signer::Permission<PermKey>): address
+
+ + + +
+Implementation + + +
public(package) fun address_of<PermKey>(perm: &Permission<PermKey>): address {
+    perm.owner_address
+}
+
+ + + +
+ + + +## Function `consume_permission` + + + +
public(friend) fun consume_permission<PermKey: copy, drop, store>(perm: &mut permissioned_signer::Permission<PermKey>, weight: u256, perm_key: PermKey): bool
+
+ + + +
+Implementation + + +
public(package) fun consume_permission<PermKey: copy + drop + store>(
+    perm: &mut Permission<PermKey>, weight: u256, perm_key: PermKey
+): bool {
+    if (perm.key != perm_key) {
+        return false
+    };
+    consume_capacity(&mut perm.perm, weight)
+}
+
+ + + +
+ + + +## Function `store_permission` + + + +
public(friend) fun store_permission<PermKey: copy, drop, store>(s: &signer, perm: permissioned_signer::Permission<PermKey>)
+
+ + + +
+Implementation + + +
public(package) fun store_permission<PermKey: copy + drop + store>(
+    s: &signer, perm: Permission<PermKey>
+) acquires PermissionStorage {
+    assert!(
+        is_permissioned_signer(s),
+        error::permission_denied(ENOT_PERMISSIONED_SIGNER)
+    );
+    let Permission::V1 { key, perm, owner_address } = perm;
+
+    assert!(
+        signer::address_of(s) == owner_address,
+        error::permission_denied(E_PERMISSION_MISMATCH)
+    );
+
+    insert_or(
+        s,
+        key,
+        |stored_permission| {
+            merge(stored_permission, perm);
+        },
+        perm,
+    )
+}
+
+ + + +
+ + + +## Function `is_permissioned_signer` + + +Check whether this is a permissioned signer. + + +
public fun is_permissioned_signer(s: &signer): bool
+
+ + + +
+Implementation + + +
public native fun is_permissioned_signer(s: &signer): bool;
+
+ + + +
+ + + +## Function `permission_address` + +Return the address used for storing permissions. Aborts if not a permissioned signer. + + +
fun permission_address(permissioned: &signer): address
+
+ + + +
+Implementation + + +
native fun permission_address(permissioned: &signer): address;
+
+ + + +
+ + + +## Function `signer_from_permissioned_handle_impl` + +Creates a permissioned signer from an existing universal signer. The function aborts if the +given signer is already a permissioned signer. + +The implementation of this function requires to extend the value representation for signers in the VM. +invariants: +signer::address_of(master) == signer::address_of(signer_from_permissioned_handle(create_permissioned_handle(master))), + + +
fun signer_from_permissioned_handle_impl(master_account_addr: address, permissions_storage_addr: address): signer
+
+ + + +
+Implementation + + +
native fun signer_from_permissioned_handle_impl(
+    master_account_addr: address, permissions_storage_addr: address
+): signer;
+
+ + + +
+ + + +## Specification + + + +
pragma verify = false;
+axiom forall a: GrantedPermissionHandles:
+    (
+        forall i in 0..len(a.active_handles):
+            forall j in 0..len(a.active_handles):
+                i != j ==>
+                    a.active_handles[i] != a.active_handles[j]
+    );
+
+ + + + + + + +
fun spec_is_permissioned_signer(s: signer): bool;
+
+ + + + + +### Function `create_permissioned_handle` + + +
public fun create_permissioned_handle(master: &signer): permissioned_signer::PermissionedHandle
+
+ + + + +
pragma opaque;
+aborts_if [abstract] spec_is_permissioned_signer(master);
+let permissions_storage_addr = transaction_context::spec_generate_unique_address();
+modifies global<PermissionStorage>(permissions_storage_addr);
+let master_account_addr = signer::address_of(master);
+ensures result.master_account_addr == master_account_addr;
+ensures result.permissions_storage_addr == permissions_storage_addr;
+
+ + + + + +### Function `create_storable_permissioned_handle` + + +
public(friend) fun create_storable_permissioned_handle(master: &signer, expiration_time: u64): permissioned_signer::StorablePermissionedHandle
+
+ + + + +
pragma opaque;
+aborts_if [abstract] spec_is_permissioned_signer(master);
+let permissions_storage_addr = transaction_context::spec_generate_unique_address();
+modifies global<PermissionStorage>(permissions_storage_addr);
+let master_account_addr = signer::address_of(master);
+modifies global<GrantedPermissionHandles>(master_account_addr);
+ensures result.master_account_addr == master_account_addr;
+ensures result.permissions_storage_addr == permissions_storage_addr;
+ensures result.expiration_time == expiration_time;
+ensures vector::spec_contains(
+    global<GrantedPermissionHandles>(master_account_addr).active_handles,
+    permissions_storage_addr
+);
+ensures exists<GrantedPermissionHandles>(master_account_addr);
+
+ + + + + +### Function `destroy_permissioned_handle` + + +
public fun destroy_permissioned_handle(p: permissioned_signer::PermissionedHandle)
+
+ + + + +
ensures !exists<PermissionStorage>(p.permissions_storage_addr);
+
+ + + + + +### Function `destroy_storable_permissioned_handle` + + +
public(friend) fun destroy_storable_permissioned_handle(p: permissioned_signer::StorablePermissionedHandle)
+
+ + + + +
ensures !exists<PermissionStorage>(p.permissions_storage_addr);
+let post granted_permissions = global<GrantedPermissionHandles>(
+    p.master_account_addr
+);
+
+ + + + + +### Function `revoke_permission_storage_address` + + +
public entry fun revoke_permission_storage_address(s: &signer, permissions_storage_addr: address)
+
+ + + + + + +### Function `authorize` + + +
public fun authorize<PermKey: copy, drop, store>(master: &signer, permissioned: &signer, capacity: u256, perm: PermKey)
+
+ + + + +
pragma aborts_if_is_partial;
+aborts_if !spec_is_permissioned_signer(permissioned);
+aborts_if spec_is_permissioned_signer(master);
+aborts_if signer::address_of(permissioned) != signer::address_of(master);
+ensures exists<PermissionStorage>(
+    spec_permission_address(permissioned)
+);
+
+ + + + + +### Function `check_permission_exists` + + +
public fun check_permission_exists<PermKey: copy, drop, store>(s: &signer, perm: PermKey): bool
+
+ + + + +
pragma verify = false;
+
+ + + + + + + +
fun spec_check_permission_exists<PermKey: copy + drop + store>(s: signer, perm: PermKey): bool {
+   use aptos_std::type_info;
+   use std::bcs;
+   let addr = spec_permission_address(s);
+   let key = Any {
+       type_name: type_info::type_name<PermKey>(),
+       data: bcs::serialize(perm)
+   };
+   if (!spec_is_permissioned_signer(s)) { true }
+   else if (!exists<PermissionStorage>(addr)) { false }
+   else {
+       simple_map::spec_contains_key(global<PermissionStorage>(addr).perms, key)
+   }
+}
+
+ + + + + +### Function `check_permission_capacity_above` + + +
public fun check_permission_capacity_above<PermKey: copy, drop, store>(s: &signer, threshold: u256, perm: PermKey): bool
+
+ + + + +
let permissioned_signer_addr = spec_permission_address(s);
+ensures !spec_is_permissioned_signer(s) ==> result == true;
+ensures (
+    spec_is_permissioned_signer(s)
+        && !exists<PermissionStorage>(permissioned_signer_addr)
+) ==> result == false;
+let key = Any {
+    type_name: type_info::type_name<SimpleMap<Any, u256>>(),
+    data: bcs::serialize(perm)
+};
+
+ + + + + +### Function `check_permission_consume` + + +
public fun check_permission_consume<PermKey: copy, drop, store>(s: &signer, threshold: u256, perm: PermKey): bool
+
+ + + + +
let permissioned_signer_addr = spec_permission_address(s);
+ensures !spec_is_permissioned_signer(s) ==> result == true;
+ensures (
+    spec_is_permissioned_signer(s)
+        && !exists<PermissionStorage>(permissioned_signer_addr)
+) ==> result == false;
+
+ + + + + +### Function `capacity` + + +
public fun capacity<PermKey: copy, drop, store>(s: &signer, perm: PermKey): option::Option<u256>
+
+ + + + + + +### Function `consume_permission` + + +
public(friend) fun consume_permission<PermKey: copy, drop, store>(perm: &mut permissioned_signer::Permission<PermKey>, weight: u256, perm_key: PermKey): bool
+
+ + + + + + +### Function `is_permissioned_signer` + + +
public fun is_permissioned_signer(s: &signer): bool
+
+ + + + +
pragma opaque;
+aborts_if [abstract] false;
+ensures [abstract] result == spec_is_permissioned_signer(s);
+
+ + + + + + + +
fun spec_permission_address(s: signer): address;
+
+ + + + + +### Function `permission_address` + + +
fun permission_address(permissioned: &signer): address
+
+ + + + +
pragma opaque;
+aborts_if [abstract]!spec_is_permissioned_signer(permissioned);
+ensures [abstract] result == spec_permission_address(permissioned);
+
+ + + + + + + +
fun spec_signer_from_permissioned_handle_impl(
+   master_account_addr: address, permissions_storage_addr: address
+): signer;
+
+ + + + + +### Function `signer_from_permissioned_handle_impl` + + +
fun signer_from_permissioned_handle_impl(master_account_addr: address, permissions_storage_addr: address): signer
+
+ + + + +
pragma opaque;
+ensures [abstract] result
+    == spec_signer_from_permissioned_handle_impl(
+        master_account_addr, permissions_storage_addr
+    );
+
+ + +[move-book]: https://aptos.dev/move/book/SUMMARY diff --git a/aptos-move/framework/aptos-framework/tests/permissioned_signer_tests.move b/aptos-move/framework/aptos-framework/tests/permissioned_signer_tests.move new file mode 100644 index 0000000000000..b8b4e6f90f524 --- /dev/null +++ b/aptos-move/framework/aptos-framework/tests/permissioned_signer_tests.move @@ -0,0 +1,341 @@ +#[test_only] +module aptos_framework::permissioned_signer_tests { + use aptos_framework::account::create_signer_for_test; + use aptos_framework::permissioned_signer; + use aptos_framework::timestamp; + use std::option; + use std::signer; + + struct OnePermission has copy, drop, store {} + + struct AddressPermission has copy, drop, store { + addr: address + } + + #[test(creator = @0xcafe)] + fun test_permission_e2e(creator: &signer) { + let aptos_framework = create_signer_for_test(@0x1); + timestamp::set_time_has_started_for_testing(&aptos_framework); + + let perm_handle = permissioned_signer::create_permissioned_handle(creator); + let perm_signer = permissioned_signer::signer_from_permissioned_handle(&perm_handle); + + assert!(permissioned_signer::is_permissioned_signer(&perm_signer), 1); + assert!(!permissioned_signer::is_permissioned_signer(creator), 1); + assert!(signer::address_of(&perm_signer) == signer::address_of(creator), 1); + + permissioned_signer::authorize_increase(creator, &perm_signer, 100, OnePermission {}); + assert!( + permissioned_signer::capacity(&perm_signer, OnePermission {}) + == option::some(100), + 1 + ); + + assert!( + permissioned_signer::check_permission_consume( + &perm_signer, 10, OnePermission {} + ), + 1 + ); + assert!( + permissioned_signer::capacity(&perm_signer, OnePermission {}) + == option::some(90), + 1 + ); + + permissioned_signer::authorize_increase( + creator, + &perm_signer, + 5, + AddressPermission { addr: @0x1 } + ); + + assert!( + permissioned_signer::capacity(&perm_signer, AddressPermission { addr: @0x1 }) + == option::some(5), + 1 + ); + assert!( + permissioned_signer::capacity(&perm_signer, AddressPermission { addr: @0x2 }) + == option::none(), + 1 + ); + + // Not enough capacity, check permission should return false + assert!( + !permissioned_signer::check_permission_consume( + &perm_signer, 10, AddressPermission { addr: @0x1 } + ), + 1 + ); + + assert!( + permissioned_signer::check_permission_consume( + &perm_signer, 5, AddressPermission { addr: @0x1 } + ), + 1 + ); + + // Remaining capacity is 0, should be viewed as non-exist. + assert!( + !permissioned_signer::check_permission_exists( + &perm_signer, AddressPermission { addr: @0x1 } + ), + 1 + ); + + permissioned_signer::revoke_permission(&perm_signer, OnePermission {}); + assert!( + permissioned_signer::capacity(&perm_signer, OnePermission {}) + == option::none(), + 1 + ); + + permissioned_signer::destroy_permissioned_handle(perm_handle); + } + + #[test(creator = @0xcafe)] + fun test_storable_permission_e2e(creator: &signer) { + let aptos_framework = create_signer_for_test(@0x1); + timestamp::set_time_has_started_for_testing(&aptos_framework); + + let perm_handle = + permissioned_signer::create_storable_permissioned_handle(creator, 60); + let perm_signer = + permissioned_signer::signer_from_storable_permissioned_handle(&perm_handle); + + assert!(permissioned_signer::is_permissioned_signer(&perm_signer), 1); + assert!(!permissioned_signer::is_permissioned_signer(creator), 1); + assert!(signer::address_of(&perm_signer) == signer::address_of(creator), 1); + + permissioned_signer::authorize_increase(creator, &perm_signer, 100, OnePermission {}); + assert!( + permissioned_signer::capacity(&perm_signer, OnePermission {}) + == option::some(100), + 1 + ); + + assert!( + permissioned_signer::check_permission_consume( + &perm_signer, 10, OnePermission {} + ), + 1 + ); + assert!( + permissioned_signer::capacity(&perm_signer, OnePermission {}) + == option::some(90), + 1 + ); + + permissioned_signer::authorize_increase( + creator, + &perm_signer, + 5, + AddressPermission { addr: @0x1 } + ); + + assert!( + permissioned_signer::capacity(&perm_signer, AddressPermission { addr: @0x1 }) + == option::some(5), + 1 + ); + assert!( + permissioned_signer::capacity(&perm_signer, AddressPermission { addr: @0x2 }) + == option::none(), + 1 + ); + + // Not enough capacity, check permission should return false + assert!( + !permissioned_signer::check_permission_consume( + &perm_signer, 10, AddressPermission { addr: @0x1 } + ), + 1 + ); + + permissioned_signer::revoke_permission(&perm_signer, OnePermission {}); + assert!( + permissioned_signer::capacity(&perm_signer, OnePermission {}) + == option::none(), + 1 + ); + + permissioned_signer::destroy_storable_permissioned_handle(perm_handle); + } + + #[test(creator = @0xcafe)] + #[expected_failure( + abort_code = 0x50005, location = aptos_framework::permissioned_signer + )] + fun test_permission_expiration(creator: &signer) { + let aptos_framework = create_signer_for_test(@0x1); + timestamp::set_time_has_started_for_testing(&aptos_framework); + + let perm_handle = + permissioned_signer::create_storable_permissioned_handle(creator, 60); + let _perm_signer = + permissioned_signer::signer_from_storable_permissioned_handle(&perm_handle); + + timestamp::fast_forward_seconds(60); + let _perm_signer = + permissioned_signer::signer_from_storable_permissioned_handle(&perm_handle); + + permissioned_signer::destroy_storable_permissioned_handle(perm_handle); + } + + // invalid authorization + // 1. master signer is a permissioned signer + // 2. permissioned signer is a master signer + // 3. permissioned and main signer address mismatch + #[test(creator = @0xcafe)] + #[expected_failure( + abort_code = 0x50002, location = aptos_framework::permissioned_signer + )] + fun test_auth_1(creator: &signer) { + let aptos_framework = create_signer_for_test(@0x1); + timestamp::set_time_has_started_for_testing(&aptos_framework); + + let perm_handle = permissioned_signer::create_permissioned_handle(creator); + let perm_signer = permissioned_signer::signer_from_permissioned_handle(&perm_handle); + + permissioned_signer::authorize_increase( + &perm_signer, + &perm_signer, + 100, + OnePermission {} + ); + permissioned_signer::destroy_permissioned_handle(perm_handle); + } + + #[test(creator = @0xcafe)] + #[expected_failure( + abort_code = 0x50002, location = aptos_framework::permissioned_signer + )] + fun test_auth_2(creator: &signer) { + permissioned_signer::authorize_increase(creator, creator, 100, OnePermission {}); + } + + #[test(creator = @0xcafe, creator2 = @0xbeef)] + #[expected_failure( + abort_code = 0x50002, location = aptos_framework::permissioned_signer + )] + fun test_auth_3(creator: &signer, creator2: &signer) { + let aptos_framework = create_signer_for_test(@0x1); + timestamp::set_time_has_started_for_testing(&aptos_framework); + + let perm_handle = permissioned_signer::create_permissioned_handle(creator); + let perm_signer = permissioned_signer::signer_from_permissioned_handle(&perm_handle); + + permissioned_signer::authorize_increase(creator2, &perm_signer, 100, OnePermission {}); + permissioned_signer::destroy_permissioned_handle(perm_handle); + } + + // Accessing capacity on a master signer + #[test(creator = @0xcafe)] + fun test_invalid_capacity(creator: &signer) { + assert!( + permissioned_signer::capacity(creator, OnePermission {}) + == option::some( + 115792089237316195423570985008687907853269984665640564039457584007913129639935 + ), + 1 + ); + } + + // creating permission using a permissioned signer + #[test(creator = @0xcafe)] + #[expected_failure( + abort_code = 0x50001, location = aptos_framework::permissioned_signer + )] + fun test_invalid_creation(creator: &signer) { + let aptos_framework = create_signer_for_test(@0x1); + timestamp::set_time_has_started_for_testing(&aptos_framework); + + let perm_handle = permissioned_signer::create_permissioned_handle(creator); + let perm_signer = permissioned_signer::signer_from_permissioned_handle(&perm_handle); + + let perm_handle_2 = permissioned_signer::create_permissioned_handle(&perm_signer); + permissioned_signer::destroy_permissioned_handle(perm_handle); + permissioned_signer::destroy_permissioned_handle(perm_handle_2); + } + + #[test(creator = @0xcafe)] + fun test_permission_revocation_success(creator: &signer) { + let aptos_framework = create_signer_for_test(@0x1); + timestamp::set_time_has_started_for_testing(&aptos_framework); + + let perm_handle = + permissioned_signer::create_storable_permissioned_handle(creator, 60); + let _perm_signer = + permissioned_signer::signer_from_storable_permissioned_handle(&perm_handle); + + permissioned_signer::revoke_permission_storage_address( + creator, permissioned_signer::permissions_storage_address(&perm_handle) + ); + + permissioned_signer::destroy_storable_permissioned_handle(perm_handle); + } + + #[test(creator = @0xcafe)] + fun test_permission_revocation_success_with_permissioned_signer(creator: &signer) { + let aptos_framework = create_signer_for_test(@0x1); + timestamp::set_time_has_started_for_testing(&aptos_framework); + + let perm_handle = + permissioned_signer::create_storable_permissioned_handle(creator, 60); + let perm_signer = + permissioned_signer::signer_from_storable_permissioned_handle(&perm_handle); + + permissioned_signer::grant_revoke_permission(creator, &perm_signer); + + permissioned_signer::revoke_permission_storage_address( + &perm_signer, permissioned_signer::permissions_storage_address(&perm_handle) + ); + + permissioned_signer::destroy_storable_permissioned_handle(perm_handle); + } + + #[test(creator = @0xcafe)] + #[expected_failure( + abort_code = 0x50007, location = aptos_framework::permissioned_signer + )] + fun test_permission_revocation_and_access(creator: &signer) { + let aptos_framework = create_signer_for_test(@0x1); + timestamp::set_time_has_started_for_testing(&aptos_framework); + + let perm_handle = + permissioned_signer::create_storable_permissioned_handle(creator, 60); + let _perm_signer = + permissioned_signer::signer_from_storable_permissioned_handle(&perm_handle); + + permissioned_signer::revoke_permission_storage_address( + creator, permissioned_signer::permissions_storage_address(&perm_handle) + ); + let _perm_signer = + permissioned_signer::signer_from_storable_permissioned_handle(&perm_handle); + + permissioned_signer::destroy_storable_permissioned_handle(perm_handle); + } + + #[test(creator1 = @0xcafe, creator2 = @0xbafe)] + #[expected_failure( + abort_code = 0x50008, location = aptos_framework::permissioned_signer + )] + fun test_permission_revoke_other(creator1: &signer, creator2: &signer) { + let aptos_framework = create_signer_for_test(@0x1); + timestamp::set_time_has_started_for_testing(&aptos_framework); + + let perm_handle_1 = + permissioned_signer::create_storable_permissioned_handle(creator1, 60); + + let perm_handle_2 = + permissioned_signer::create_storable_permissioned_handle(creator2, 60); + + permissioned_signer::revoke_permission_storage_address( + creator1, permissioned_signer::permissions_storage_address(&perm_handle_2) + ); + + permissioned_signer::destroy_storable_permissioned_handle(perm_handle_1); + permissioned_signer::destroy_storable_permissioned_handle(perm_handle_2); + } +} diff --git a/aptos-move/framework/aptos-stdlib/doc/smart_table.md b/aptos-move/framework/aptos-stdlib/doc/smart_table.md index ac5388661f2ca..332646552eb04 100644 --- a/aptos-move/framework/aptos-stdlib/doc/smart_table.md +++ b/aptos-move/framework/aptos-stdlib/doc/smart_table.md @@ -1479,6 +1479,8 @@ map_spec_has_key = spec_contains;
pragma verify = false;
+pragma opaque;
+aborts_if [abstract] false;
 
@@ -1495,6 +1497,8 @@ map_spec_has_key = spec_contains;
pragma verify = false;
+pragma opaque;
+aborts_if false;
 
diff --git a/aptos-move/framework/aptos-stdlib/sources/data_structures/smart_table.spec.move b/aptos-move/framework/aptos-stdlib/sources/data_structures/smart_table.spec.move index d905a0a40bb3a..18d0cc53056d6 100644 --- a/aptos-move/framework/aptos-stdlib/sources/data_structures/smart_table.spec.move +++ b/aptos-move/framework/aptos-stdlib/sources/data_structures/smart_table.spec.move @@ -24,10 +24,14 @@ spec aptos_std::smart_table { spec destroy(self: SmartTable) { pragma verify = false; + pragma opaque; + aborts_if [abstract] false; } spec clear(self: &mut SmartTable) { pragma verify = false; + pragma opaque; + aborts_if false; } spec split_one_bucket(self: &mut SmartTable) { diff --git a/aptos-move/framework/aptos-stdlib/sources/from_bcs.move b/aptos-move/framework/aptos-stdlib/sources/from_bcs.move index 3eb6e2177d2f4..74e9876f0dc29 100644 --- a/aptos-move/framework/aptos-stdlib/sources/from_bcs.move +++ b/aptos-move/framework/aptos-stdlib/sources/from_bcs.move @@ -88,4 +88,12 @@ module aptos_std::from_bcs { let bad_vec = b"01"; to_address(bad_vec); } + + #[test(s1 = @0x123)] + fun test_signer_roundtrip(s1: signer) { + assert!( + from_bytes(bcs::to_bytes(&s1)) == s1, + 1 + ) + } } diff --git a/aptos-move/framework/aptos-token-objects/doc/aptos_token.md b/aptos-move/framework/aptos-token-objects/doc/aptos_token.md index b3f124c43686e..25cd7b3ac64d9 100644 --- a/aptos-move/framework/aptos-token-objects/doc/aptos_token.md +++ b/aptos-move/framework/aptos-token-objects/doc/aptos_token.md @@ -15,6 +15,8 @@ The key features are: - [Resource `AptosCollection`](#0x4_aptos_token_AptosCollection) - [Resource `AptosToken`](#0x4_aptos_token_AptosToken) +- [Struct `TokenUpdatePermission`](#0x4_aptos_token_TokenUpdatePermission) +- [Struct `CollectionUpdatePermission`](#0x4_aptos_token_CollectionUpdatePermission) - [Constants](#@Constants_0) - [Function `create_collection`](#0x4_aptos_token_create_collection) - [Function `create_collection_object`](#0x4_aptos_token_create_collection_object) @@ -58,11 +60,16 @@ The key features are: - [Function `set_collection_royalties`](#0x4_aptos_token_set_collection_royalties) - [Function `set_collection_royalties_call`](#0x4_aptos_token_set_collection_royalties_call) - [Function `set_collection_uri`](#0x4_aptos_token_set_collection_uri) +- [Function `authorize_token_mutation`](#0x4_aptos_token_authorize_token_mutation) +- [Function `revoke_token_mutation`](#0x4_aptos_token_revoke_token_mutation) +- [Function `authorize_collection_mutation`](#0x4_aptos_token_authorize_collection_mutation) +- [Function `revoke_collection_mutation`](#0x4_aptos_token_revoke_collection_mutation)
use 0x1::error;
 use 0x1::object;
 use 0x1::option;
+use 0x1::permissioned_signer;
 use 0x1::signer;
 use 0x1::string;
 use 0x4::collection;
@@ -201,6 +208,60 @@ Storage state for managing the no-code Token.
 
 
 
+
+
+
+
+## Struct `TokenUpdatePermission`
+
+
+
+
struct TokenUpdatePermission has copy, drop, store
+
+ + + +
+Fields + + +
+
+token_address: address +
+
+ +
+
+ + +
+ + + +## Struct `CollectionUpdatePermission` + + + +
struct CollectionUpdatePermission has copy, drop, store
+
+ + + +
+Fields + + +
+
+collection_address: address +
+
+ +
+
+ +
@@ -864,6 +925,11 @@ With an existing collection, directly mint a soul bound token into the recipient token::creator(*token) == signer::address_of(creator), error::permission_denied(ENOT_CREATOR), ); + + assert!( + permissioned_signer::check_permission_capacity_above(creator, 0, TokenUpdatePermission { token_address }), + error::permission_denied(ENOT_CREATOR), + ); borrow_global<AptosToken>(token_address) }
@@ -1561,6 +1627,11 @@ With an existing collection, directly mint a soul bound token into the recipient collection::creator(*collection) == signer::address_of(creator), error::permission_denied(ENOT_CREATOR), ); + + assert!( + permissioned_signer::check_permission_capacity_above(creator, 0, CollectionUpdatePermission { collection_address }), + error::permission_denied(ENOT_CREATOR), + ); borrow_global<AptosCollection>(collection_address) }
@@ -1697,6 +1768,140 @@ With an existing collection, directly mint a soul bound token into the recipient + + + + +## Function `authorize_token_mutation` + + + +
public fun authorize_token_mutation<T: key>(creator: &signer, permissioned_creator: &signer, token: object::Object<T>)
+
+ + + +
+Implementation + + +
public fun authorize_token_mutation<T: key>(
+    creator: &signer,
+    permissioned_creator: &signer,
+    token: Object<T>,
+) {
+    let token_address = object::object_address(&token);
+    assert!(
+        exists<AptosToken>(token_address),
+        error::not_found(ETOKEN_DOES_NOT_EXIST),
+    );
+    permissioned_signer::authorize_unlimited(
+        creator,
+        permissioned_creator,
+        TokenUpdatePermission { token_address },
+    )
+}
+
+ + + +
+ + + +## Function `revoke_token_mutation` + + + +
public fun revoke_token_mutation<T: key>(permissioned_signer: &signer, token: object::Object<T>)
+
+ + + +
+Implementation + + +
public fun revoke_token_mutation<T: key>(
+    permissioned_signer: &signer,
+    token: Object<T>,
+) {
+    permissioned_signer::revoke_permission(
+        permissioned_signer,
+        TokenUpdatePermission { token_address: object::object_address(&token) },
+    )
+}
+
+ + + +
+ + + +## Function `authorize_collection_mutation` + + + +
public fun authorize_collection_mutation<T: key>(creator: &signer, permissioned_signer: &signer, collection: object::Object<T>)
+
+ + + +
+Implementation + + +
public fun authorize_collection_mutation<T: key>(
+    creator: &signer,
+    permissioned_signer: &signer,
+    collection: Object<T>,
+) {
+    let collection_address = object::object_address(&collection);
+    assert!(
+        exists<AptosCollection>(collection_address),
+        error::not_found(ETOKEN_DOES_NOT_EXIST),
+    );
+    permissioned_signer::authorize_unlimited(
+        creator,
+        permissioned_signer,
+        CollectionUpdatePermission { collection_address },
+    )
+}
+
+ + + +
+ + + +## Function `revoke_collection_mutation` + + + +
public fun revoke_collection_mutation<T: key>(permissioned_signer: &signer, collection: object::Object<T>)
+
+ + + +
+Implementation + + +
public fun revoke_collection_mutation<T: key>(
+    permissioned_signer: &signer,
+    collection: Object<T>,
+) {
+    permissioned_signer::revoke_permission(
+        permissioned_signer,
+        CollectionUpdatePermission { collection_address: object::object_address(&collection) },
+    )
+}
+
+ + +
diff --git a/aptos-move/framework/aptos-token-objects/sources/aptos_token.move b/aptos-move/framework/aptos-token-objects/sources/aptos_token.move index 5fe04786cd1a9..fc955cae413d8 100644 --- a/aptos-move/framework/aptos-token-objects/sources/aptos_token.move +++ b/aptos-move/framework/aptos-token-objects/sources/aptos_token.move @@ -373,6 +373,7 @@ module aptos_token_objects::aptos_token { token::creator(*token) == signer::address_of(creator), error::permission_denied(ENOT_CREATOR), ); + borrow_global(token_address) } @@ -614,6 +615,7 @@ module aptos_token_objects::aptos_token { collection::creator(*collection) == signer::address_of(creator), error::permission_denied(ENOT_CREATOR), ); + borrow_global(collection_address) } diff --git a/aptos-move/framework/cached-packages/src/aptos_framework_sdk_builder.rs b/aptos-move/framework/cached-packages/src/aptos_framework_sdk_builder.rs index c7fe5a59010e1..b6f9d1d1d8c06 100644 --- a/aptos-move/framework/cached-packages/src/aptos_framework_sdk_builder.rs +++ b/aptos-move/framework/cached-packages/src/aptos_framework_sdk_builder.rs @@ -810,6 +810,15 @@ pub enum EntryFunctionCall { code: Vec>, }, + /// Revoke all storable permission handle of the signer immediately. + PermissionedSignerRevokeAllHandles {}, + + /// Revoke a specific storable permission handle immediately. This will disallow owner of + /// the storable permission handle to derive signer from it anymore. + PermissionedSignerRevokePermissionStorageAddress { + permissions_storage_addr: AccountAddress, + }, + /// Creates a new resource account and rotates the authentication key to either /// the optional auth key if it is non-empty (though auth keys are 32-bytes) /// or the source accounts current auth key. @@ -1593,6 +1602,10 @@ impl EntryFunctionCall { metadata_serialized, code, } => object_code_deployment_publish(metadata_serialized, code), + PermissionedSignerRevokeAllHandles {} => permissioned_signer_revoke_all_handles(), + PermissionedSignerRevokePermissionStorageAddress { + permissions_storage_addr, + } => permissioned_signer_revoke_permission_storage_address(permissions_storage_addr), ResourceAccountCreateResourceAccount { seed, optional_auth_key, @@ -3891,6 +3904,41 @@ pub fn object_code_deployment_publish( )) } +/// Revoke all storable permission handle of the signer immediately. +pub fn permissioned_signer_revoke_all_handles() -> TransactionPayload { + TransactionPayload::EntryFunction(EntryFunction::new( + ModuleId::new( + AccountAddress::new([ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 1, + ]), + ident_str!("permissioned_signer").to_owned(), + ), + ident_str!("revoke_all_handles").to_owned(), + vec![], + vec![], + )) +} + +/// Revoke a specific storable permission handle immediately. This will disallow owner of +/// the storable permission handle to derive signer from it anymore. +pub fn permissioned_signer_revoke_permission_storage_address( + permissions_storage_addr: AccountAddress, +) -> TransactionPayload { + TransactionPayload::EntryFunction(EntryFunction::new( + ModuleId::new( + AccountAddress::new([ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 1, + ]), + ident_str!("permissioned_signer").to_owned(), + ), + ident_str!("revoke_permission_storage_address").to_owned(), + vec![], + vec![bcs::to_bytes(&permissions_storage_addr).unwrap()], + )) +} + /// Creates a new resource account and rotates the authentication key to either /// the optional auth key if it is non-empty (though auth keys are 32-bytes) /// or the source accounts current auth key. @@ -6174,6 +6222,30 @@ mod decoder { } } + pub fn permissioned_signer_revoke_all_handles( + payload: &TransactionPayload, + ) -> Option { + if let TransactionPayload::EntryFunction(_script) = payload { + Some(EntryFunctionCall::PermissionedSignerRevokeAllHandles {}) + } else { + None + } + } + + pub fn permissioned_signer_revoke_permission_storage_address( + payload: &TransactionPayload, + ) -> Option { + if let TransactionPayload::EntryFunction(script) = payload { + Some( + EntryFunctionCall::PermissionedSignerRevokePermissionStorageAddress { + permissions_storage_addr: bcs::from_bytes(script.args().get(0)?).ok()?, + }, + ) + } else { + None + } + } + pub fn resource_account_create_resource_account( payload: &TransactionPayload, ) -> Option { @@ -7219,6 +7291,14 @@ static SCRIPT_FUNCTION_DECODER_MAP: once_cell::sync::Lazy + + + +
const PERMISSIONED_SIGNER: u64 = 82;
+
+ + + @@ -3330,6 +3341,52 @@ Deprecated feature + + + + +## Function `get_permissioned_signer_feature` + + + +
public fun get_permissioned_signer_feature(): u64
+
+ + + +
+Implementation + + +
public fun get_permissioned_signer_feature(): u64 { PERMISSIONED_SIGNER }
+
+ + + +
+ + + +## Function `is_permissioned_signer_enabled` + + + +
public fun is_permissioned_signer_enabled(): bool
+
+ + + +
+Implementation + + +
public fun is_permissioned_signer_enabled(): bool acquires Features {
+    is_enabled(PERMISSIONED_SIGNER)
+}
+
+ + +
diff --git a/aptos-move/framework/move-stdlib/doc/signer.md b/aptos-move/framework/move-stdlib/doc/signer.md index f6de8799b571c..6d75e235b353d 100644 --- a/aptos-move/framework/move-stdlib/doc/signer.md +++ b/aptos-move/framework/move-stdlib/doc/signer.md @@ -18,12 +18,21 @@ ## Function `borrow_address` -Borrows the address of the signer -Conceptually, you can think of the signer as being a struct wrapper around an -address -``` -struct signer has drop { addr: address } -``` +signer is a builtin move type that represents an address that has been verfied by the VM. + +VM Runtime representation is equivalent to following: +enum signer has drop { +Master { account: address }, +Permissioned { account: address, permissions_address: address }, +} + +for bcs serialization: +struct signer has drop { +account: address, +} +^ The discrepency is needed to maintain backwards compatibility of signer serialization +semantics. + borrow_address borrows this inner field diff --git a/aptos-move/framework/move-stdlib/sources/configs/features.move b/aptos-move/framework/move-stdlib/sources/configs/features.move index 2b3a5291c600d..62f60c569e424 100644 --- a/aptos-move/framework/move-stdlib/sources/configs/features.move +++ b/aptos-move/framework/move-stdlib/sources/configs/features.move @@ -615,6 +615,14 @@ module std::features { is_enabled(NATIVE_MEMORY_OPERATIONS) } + const PERMISSIONED_SIGNER: u64 = 83; + + public fun get_permissioned_signer_feature(): u64 { PERMISSIONED_SIGNER } + + public fun is_permissioned_signer_enabled(): bool acquires Features { + is_enabled(PERMISSIONED_SIGNER) + } + // ============================================================================================ // Feature Flag Implementation diff --git a/aptos-move/framework/move-stdlib/sources/signer.move b/aptos-move/framework/move-stdlib/sources/signer.move index c2e3ab3f559f8..f607e7d801000 100644 --- a/aptos-move/framework/move-stdlib/sources/signer.move +++ b/aptos-move/framework/move-stdlib/sources/signer.move @@ -1,10 +1,19 @@ module std::signer { - /// Borrows the address of the signer - /// Conceptually, you can think of the `signer` as being a struct wrapper around an - /// address - /// ``` - /// struct signer has drop { addr: address } - /// ``` + /// signer is a builtin move type that represents an address that has been verfied by the VM. + /// + /// VM Runtime representation is equivalent to following: + /// enum signer has drop { + /// Master { account: address }, + /// Permissioned { account: address, permissions_address: address }, + /// } + /// + /// for bcs serialization: + /// struct signer has drop { + /// account: address, + /// } + /// ^ The discrepency is needed to maintain backwards compatibility of signer serialization + /// semantics. + /// /// `borrow_address` borrows this inner field native public fun borrow_address(s: &signer): &address; diff --git a/aptos-move/framework/move-stdlib/src/natives/bcs.rs b/aptos-move/framework/move-stdlib/src/natives/bcs.rs index d7cd0e9d258c4..0028d48319cbb 100644 --- a/aptos-move/framework/move-stdlib/src/natives/bcs.rs +++ b/aptos-move/framework/move-stdlib/src/natives/bcs.rs @@ -70,6 +70,7 @@ fn native_to_bytes( let val = ref_to_val.read_ref()?; let serialized_value = match ValueSerDeContext::new() + .with_legacy_signer() .with_func_args_deserialization(context.function_value_extension()) .serialize(&val, &layout)? { @@ -136,6 +137,7 @@ fn serialized_size_impl( let ty_layout = context.type_to_type_layout(ty)?; ValueSerDeContext::new() + .with_legacy_signer() .with_func_args_deserialization(context.function_value_extension()) .with_delayed_fields_serde() .serialized_size(&value, &ty_layout) diff --git a/aptos-move/framework/src/natives/mod.rs b/aptos-move/framework/src/natives/mod.rs index dcfc80407f932..956c590b4afc8 100644 --- a/aptos-move/framework/src/natives/mod.rs +++ b/aptos-move/framework/src/natives/mod.rs @@ -15,6 +15,7 @@ pub mod function_info; pub mod hash; pub mod object; pub mod object_code_deployment; +pub mod permissioned_signer; pub mod randomness; pub mod state_storage; pub mod string_utils; @@ -91,6 +92,10 @@ pub fn all_natives( "dispatchable_fungible_asset", dispatchable_fungible_asset::make_all(builder) ); + add_natives_from_module!( + "permissioned_signer", + permissioned_signer::make_all(builder) + ); if inject_create_signer_for_gov_sim { add_natives_from_module!( diff --git a/aptos-move/framework/src/natives/permissioned_signer.rs b/aptos-move/framework/src/natives/permissioned_signer.rs new file mode 100644 index 0000000000000..cbebd63bd7f9f --- /dev/null +++ b/aptos-move/framework/src/natives/permissioned_signer.rs @@ -0,0 +1,109 @@ +// Copyright © Aptos Foundation +// SPDX-License-Identifier: Apache-2.0 +use aptos_gas_schedule::gas_params::natives::aptos_framework::{ + IS_PERMISSIONED_SIGNER_BASE, PERMISSION_ADDRESS_BASE, SIGNER_FROM_PERMISSIONED_HANDLE_BASE, +}; +use aptos_native_interface::{ + safely_pop_arg, RawSafeNative, SafeNativeBuilder, SafeNativeContext, SafeNativeError, + SafeNativeResult, +}; +use move_core_types::account_address::AccountAddress; +use move_vm_runtime::native_functions::NativeFunction; +use move_vm_types::{ + loaded_data::runtime_types::Type, + values::{SignerRef, Value}, +}; +use smallvec::{smallvec, SmallVec}; +use std::collections::VecDeque; + +/*************************************************************************************************** + * native fun is_permissioned_signer + * + * Returns true if the signer passed in is a permissioned signer + * gas cost: base_cost + * + **************************************************************************************************/ +fn native_is_permissioned_signer( + context: &mut SafeNativeContext, + _ty_args: Vec, + mut arguments: VecDeque, +) -> SafeNativeResult> { + debug_assert!(arguments.len() == 1); + + let signer = safely_pop_arg!(arguments, SignerRef); + + context.charge(IS_PERMISSIONED_SIGNER_BASE)?; + let result = signer.is_permissioned()?; + + Ok(smallvec![Value::bool(result)]) +} + +/*************************************************************************************************** + * native fun permission_address + * + * Returns the permission storage address if the signer passed in is a permissioned signer + * gas cost: base_cost + * + **************************************************************************************************/ +fn native_permission_address( + context: &mut SafeNativeContext, + _ty_args: Vec, + mut args: VecDeque, +) -> SafeNativeResult> { + debug_assert!(args.len() == 1); + + let signer = safely_pop_arg!(args, SignerRef); + + context.charge(PERMISSION_ADDRESS_BASE)?; + if !signer.is_permissioned()? { + return Err(SafeNativeError::Abort { abort_code: 3 }); + } + + Ok(smallvec![signer.permission_address()?]) +} + +/*************************************************************************************************** + * native fun signer_from_permissioned_handle_impl + * + * Returns the permission signer from a master signer. + * gas cost: base_cost + * + **************************************************************************************************/ +fn native_signer_from_permissioned( + context: &mut SafeNativeContext, + _ty_args: Vec, + mut arguments: VecDeque, +) -> SafeNativeResult> { + debug_assert!(arguments.len() == 2); + + let permission_addr = safely_pop_arg!(arguments, AccountAddress); + let master_addr = safely_pop_arg!(arguments, AccountAddress); + context.charge(SIGNER_FROM_PERMISSIONED_HANDLE_BASE)?; + + Ok(smallvec![Value::permissioned_signer( + master_addr, + permission_addr + )]) +} + +/*************************************************************************************************** + * module + * + **************************************************************************************************/ +pub fn make_all( + builder: &SafeNativeBuilder, +) -> impl Iterator + '_ { + let natives = [ + ( + "is_permissioned_signer", + native_is_permissioned_signer as RawSafeNative, + ), + ("permission_address", native_permission_address), + ( + "signer_from_permissioned_handle_impl", + native_signer_from_permissioned, + ), + ]; + + builder.make_named_natives(natives) +} diff --git a/aptos-move/framework/src/natives/string_utils.rs b/aptos-move/framework/src/natives/string_utils.rs index 0a4c7c71583f8..28e9fd50b103f 100644 --- a/aptos-move/framework/src/natives/string_utils.rs +++ b/aptos-move/framework/src/natives/string_utils.rs @@ -13,7 +13,7 @@ use move_core_types::{ account_address::AccountAddress, language_storage::TypeTag, u256, - value::{MoveFieldLayout, MoveStructLayout, MoveTypeLayout}, + value::{MoveFieldLayout, MoveStructLayout, MoveTypeLayout, MASTER_ADDRESS_FIELD_OFFSET}, }; use move_vm_runtime::native_functions::NativeFunction; use move_vm_types::{ @@ -188,8 +188,11 @@ fn native_format_impl( let addr = if fix_enabled { val.value_as::()? .unpack()? - .next() - .unwrap() + // The second field of a signer is always the master address regardless of which variants. + .nth(MASTER_ADDRESS_FIELD_OFFSET) + .ok_or_else(|| SafeNativeError::Abort { + abort_code: EINVALID_FORMAT, + })? .value_as::()? } else { val.value_as::()? diff --git a/aptos-move/framework/src/natives/util.rs b/aptos-move/framework/src/natives/util.rs index f39239491fae1..4ce9d70880bf1 100644 --- a/aptos-move/framework/src/natives/util.rs +++ b/aptos-move/framework/src/natives/util.rs @@ -43,6 +43,7 @@ fn native_from_bytes( UTIL_FROM_BYTES_BASE + UTIL_FROM_BYTES_PER_BYTE * NumBytes::new(bytes.len() as u64), )?; let val = match ValueSerDeContext::new() + .with_legacy_signer() .with_func_args_deserialization(context.function_value_extension()) .deserialize(&bytes, &layout) { diff --git a/crates/transaction-workloads-lib/src/args.rs b/crates/transaction-workloads-lib/src/args.rs index 402c4aeb3b54c..370bad4e72e18 100644 --- a/crates/transaction-workloads-lib/src/args.rs +++ b/crates/transaction-workloads-lib/src/args.rs @@ -78,6 +78,8 @@ pub enum TransactionTypeArg { SmartTablePicture1MWith1KChangeExceedsLimit, DeserializeU256, SimpleScript, + PermissionedTransfer, + APTTransfer, } impl TransactionTypeArg { @@ -351,6 +353,10 @@ impl TransactionTypeArg { }, TransactionTypeArg::DeserializeU256 => call_custom_module(EntryPoints::DeserializeU256), TransactionTypeArg::SimpleScript => call_custom_module(EntryPoints::SimpleScript), + TransactionTypeArg::PermissionedTransfer => { + call_custom_module(EntryPoints::APTPermissionedTransfer) + }, + TransactionTypeArg::APTTransfer => call_custom_module(EntryPoints::APTTransfer), } } diff --git a/crates/transaction-workloads-lib/src/move_workloads.rs b/crates/transaction-workloads-lib/src/move_workloads.rs index 8b5a191c55510..aa3167cb7fd3b 100644 --- a/crates/transaction-workloads-lib/src/move_workloads.rs +++ b/crates/transaction-workloads-lib/src/move_workloads.rs @@ -232,6 +232,8 @@ pub enum EntryPoints { /// there to slow down deserialization & verification, effectively making it more expensive to /// load it into code cache. SimpleScript, + APTPermissionedTransfer, + APTTransfer, } impl EntryPointTrait for EntryPoints { @@ -284,7 +286,9 @@ impl EntryPointTrait for EntryPoints { | EntryPoints::ResourceGroupsSenderWriteTag { .. } | EntryPoints::ResourceGroupsSenderMultiChange { .. } | EntryPoints::CoinInitAndMint - | EntryPoints::FungibleAssetMint => "framework_usecases", + | EntryPoints::FungibleAssetMint + | EntryPoints::APTPermissionedTransfer + | EntryPoints::APTTransfer => "framework_usecases", EntryPoints::TokenV2AmbassadorMint { .. } | EntryPoints::TokenV2AmbassadorBurn => { "ambassador_token" }, @@ -363,6 +367,9 @@ impl EntryPointTrait for EntryPoints { EntryPoints::IncGlobalMilestoneAggV2 { .. } | EntryPoints::CreateGlobalMilestoneAggV2 { .. } => "counter_with_milestone", EntryPoints::DeserializeU256 => "bcs_stream", + EntryPoints::APTPermissionedTransfer | EntryPoints::APTTransfer => { + "permissioned_transfer" + }, } } @@ -781,6 +788,20 @@ impl EntryPointTrait for EntryPoints { ], ) }, + EntryPoints::APTPermissionedTransfer => get_payload( + module_id, + ident_str!("transfer_permissioned").to_owned(), + vec![ + bcs::to_bytes(&other.expect("Must provide other")).unwrap(), + bcs::to_bytes(&1u64).unwrap(), + ], + ), + EntryPoints::APTTransfer => { + get_payload(module_id, ident_str!("transfer").to_owned(), vec![ + bcs::to_bytes(&other.expect("Must provide other")).unwrap(), + bcs::to_bytes(&1u64).unwrap(), + ]) + }, } } @@ -899,6 +920,9 @@ impl EntryPointTrait for EntryPoints { EntryPoints::DeserializeU256 => AutomaticArgs::None, EntryPoints::IncGlobalMilestoneAggV2 { .. } => AutomaticArgs::None, EntryPoints::CreateGlobalMilestoneAggV2 { .. } => AutomaticArgs::Signer, + EntryPoints::APTPermissionedTransfer | EntryPoints::APTTransfer => { + AutomaticArgs::Signer + }, } } } diff --git a/execution/executor/tests/internal_indexer_test.rs b/execution/executor/tests/internal_indexer_test.rs index 1f3b40739b900..cd55845261243 100644 --- a/execution/executor/tests/internal_indexer_test.rs +++ b/execution/executor/tests/internal_indexer_test.rs @@ -294,6 +294,7 @@ fn test_db_indexer_data() { ident_str!("aggregator_factory"), ident_str!("governance_proposal"), ident_str!("optional_aggregator"), + ident_str!("permissioned_signer"), ident_str!("transaction_context"), ident_str!("jwk_consensus_config"), ident_str!("ristretto255_elgamal"), diff --git a/testsuite/module-publish/src/packages/framework_usecases/sources/permissioned_transfer.move b/testsuite/module-publish/src/packages/framework_usecases/sources/permissioned_transfer.move new file mode 100644 index 0000000000000..a8fc045c0d64f --- /dev/null +++ b/testsuite/module-publish/src/packages/framework_usecases/sources/permissioned_transfer.move @@ -0,0 +1,24 @@ + +module 0xABCD::permissioned_transfer { + use aptos_framework::aptos_account; + use aptos_framework::permissioned_signer; + use aptos_framework::primary_fungible_store; + + public entry fun transfer_permissioned( + source: &signer, to: address, amount: u64 + ) { + let handle = permissioned_signer::create_permissioned_handle(source); + let permissioned_signer = permissioned_signer::signer_from_permissioned_handle(&handle); + + primary_fungible_store::grant_apt_permission(source, &permissioned_signer, amount); + aptos_account::transfer(&permissioned_signer, to, amount); + + permissioned_signer::destroy_permissioned_handle(handle); + } + + public entry fun transfer( + source: &signer, to: address, amount: u64 + ) { + aptos_account::transfer(source, to, amount); + } +} diff --git a/third_party/move/move-bytecode-verifier/transactional-tests/tests/script_signature/signer_double_signer.exp b/third_party/move/move-bytecode-verifier/transactional-tests/tests/script_signature/signer_double_signer.exp deleted file mode 100644 index 4b0549d55c296..0000000000000 --- a/third_party/move/move-bytecode-verifier/transactional-tests/tests/script_signature/signer_double_signer.exp +++ /dev/null @@ -1,19 +0,0 @@ -processed 3 tasks - -task 0 'run'. lines 1-6: -Error: Script execution failed with VMError: { - major_status: NUMBER_OF_ARGUMENTS_MISMATCH, - sub_status: None, - location: undefined, - indices: [], - offsets: [], -} - -task 1 'run'. lines 8-13: -Error: Script execution failed with VMError: { - major_status: NUMBER_OF_ARGUMENTS_MISMATCH, - sub_status: None, - location: undefined, - indices: [], - offsets: [], -} diff --git a/third_party/move/move-bytecode-verifier/transactional-tests/tests/script_signature/signer_double_signer.mvir b/third_party/move/move-bytecode-verifier/transactional-tests/tests/script_signature/signer_double_signer.mvir deleted file mode 100644 index 18b599d5fbd30..0000000000000 --- a/third_party/move/move-bytecode-verifier/transactional-tests/tests/script_signature/signer_double_signer.mvir +++ /dev/null @@ -1,20 +0,0 @@ -//# run --signers 0x1 -// missing signer -main(s: signer, s2: signer) { -label b0: - return; -} - -//# run --signers 0x1 --args 0 -// missing signer -main(s: signer, s2: signer, u: u64,) { -label b0: - return; -} - -//# run --args @0x1 0 @0x2 -// no longer an invalid signature, after V5 -main(s: signer, u: u64, s2: signer) { -label b0: - return; -} diff --git a/third_party/move/move-bytecode-verifier/transactional-tests/tests/script_signature/signer_double_signer.v2_exp b/third_party/move/move-bytecode-verifier/transactional-tests/tests/script_signature/signer_double_signer.v2_exp deleted file mode 100644 index 4b0549d55c296..0000000000000 --- a/third_party/move/move-bytecode-verifier/transactional-tests/tests/script_signature/signer_double_signer.v2_exp +++ /dev/null @@ -1,19 +0,0 @@ -processed 3 tasks - -task 0 'run'. lines 1-6: -Error: Script execution failed with VMError: { - major_status: NUMBER_OF_ARGUMENTS_MISMATCH, - sub_status: None, - location: undefined, - indices: [], - offsets: [], -} - -task 1 'run'. lines 8-13: -Error: Script execution failed with VMError: { - major_status: NUMBER_OF_ARGUMENTS_MISMATCH, - sub_status: None, - location: undefined, - indices: [], - offsets: [], -} diff --git a/third_party/move/move-bytecode-verifier/transactional-tests/tests/script_signature/signer_misplaced_signer_arg.exp b/third_party/move/move-bytecode-verifier/transactional-tests/tests/script_signature/signer_misplaced_signer_arg.exp deleted file mode 100644 index 5d92c423f3fd8..0000000000000 --- a/third_party/move/move-bytecode-verifier/transactional-tests/tests/script_signature/signer_misplaced_signer_arg.exp +++ /dev/null @@ -1 +0,0 @@ -processed 2 tasks diff --git a/third_party/move/move-bytecode-verifier/transactional-tests/tests/script_signature/signer_misplaced_signer_arg.mvir b/third_party/move/move-bytecode-verifier/transactional-tests/tests/script_signature/signer_misplaced_signer_arg.mvir deleted file mode 100644 index 7c0bf2513414f..0000000000000 --- a/third_party/move/move-bytecode-verifier/transactional-tests/tests/script_signature/signer_misplaced_signer_arg.mvir +++ /dev/null @@ -1,13 +0,0 @@ -//# run --args 0 @0x1 -// no longer an invalid signature, after V5 -main(u: u64, s: &signer) { -label b0: - return; -} - -//# run --args 0 @0x1 1 -// no longer an invalid signature, after V5 -main(u: u64, s: &signer, u2: u64) { -label b0: - return; -} diff --git a/third_party/move/move-compiler-v2/src/plan_builder.rs b/third_party/move/move-compiler-v2/src/plan_builder.rs index b03c91c712938..7502b6e6c9d2f 100644 --- a/third_party/move/move-compiler-v2/src/plan_builder.rs +++ b/third_party/move/move-compiler-v2/src/plan_builder.rs @@ -162,9 +162,16 @@ fn build_test_info( let mut arguments = Vec::new(); for param in function.get_parameters_ref() { - let Parameter(var, _ty, var_loc) = ¶m; + let Parameter(var, ty, var_loc) = ¶m; match test_annotation_params.get(var) { + Some(MoveValue::Address(addr)) => arguments.push(match ty { + Type::Primitive(PrimitiveType::Signer) => MoveValue::Signer(*addr), + Type::Reference(_, inner) if **inner == Type::Primitive(PrimitiveType::Signer) => { + MoveValue::Signer(*addr) + }, + _ => MoveValue::Address(*addr), + }), Some(value) => arguments.push(value.clone()), None => { let missing_param_msg = "Missing test parameter assignment in test. Expected a \ diff --git a/third_party/move/move-compiler/src/unit_test/plan_builder.rs b/third_party/move/move-compiler/src/unit_test/plan_builder.rs index c53926366cbd9..56b3f5140fd74 100644 --- a/third_party/move/move-compiler/src/unit_test/plan_builder.rs +++ b/third_party/move/move-compiler/src/unit_test/plan_builder.rs @@ -8,6 +8,7 @@ use crate::{ expansion::ast::{ self as E, Address, Attribute, AttributeValue, ModuleAccess_, ModuleIdent, ModuleIdent_, }, + hlir::ast::{BaseType_, SingleType_}, parser::ast::ConstantName, shared::{ known_attributes::{AttributeKind, KnownAttribute, TestingAttribute}, @@ -159,10 +160,19 @@ fn build_test_info<'func>( let test_annotation_params = parse_test_attribute(context, test_attribute, 0); let mut arguments = Vec::new(); - for (var, _) in &function.signature.parameters { + for (var, ty) in &function.signature.parameters { match test_annotation_params.get(&var.value()) { - Some(value) => arguments.push(value.clone()), - None => { + Some(MoveValue::Address(addr)) => match &ty.value { + SingleType_::Base(ty) => arguments.push( + if ty == &BaseType_::address(ty.loc) { + MoveValue::Address(*addr) + } else { + MoveValue::Signer(*addr) + }, + ), + SingleType_::Ref(_, _) => arguments.push(MoveValue::Signer(*addr)), + }, + _ => { let missing_param_msg = "Missing test parameter assignment in test. Expected a \ parameter to be assigned in this attribute"; context.env.add_diag(diag!( diff --git a/third_party/move/move-core/types/src/unit_tests/value_test.rs b/third_party/move/move-core/types/src/unit_tests/value_test.rs index 5c3bb08841503..1519fc7961c57 100644 --- a/third_party/move/move-core/types/src/unit_tests/value_test.rs +++ b/third_party/move/move-core/types/src/unit_tests/value_test.rs @@ -137,3 +137,13 @@ fn nested_typed_struct_deserialization() { }) ); } + +#[test] +fn signer_deserialization() { + let v = MoveValue::Signer(AccountAddress::ZERO); + let bytes = v.simple_serialize().unwrap(); + assert_eq!( + MoveValue::simple_deserialize(&bytes, &crate::value::MoveTypeLayout::Signer).unwrap(), + v + ); +} diff --git a/third_party/move/move-core/types/src/value.rs b/third_party/move/move-core/types/src/value.rs index de4ef817e4832..3fb94eb56e1b3 100644 --- a/third_party/move/move-core/types/src/value.rs +++ b/third_party/move/move-core/types/src/value.rs @@ -75,6 +75,19 @@ pub fn variant_name_placeholder(len: usize) -> &'static [&'static str] { } } +/// enum signer { +/// Master { account: address }, +/// Permissioned { account: address, permissions_address: address }, +/// } +/// enum variant tag for a master signer. +pub const MASTER_SIGNER_VARIANT: u16 = 0; +/// enum variant tag for a permissioned signer. +pub const PERMISSIONED_SIGNER_VARIANT: u16 = 1; +/// field offset of a master account address in a enum encoded signer. +pub const MASTER_ADDRESS_FIELD_OFFSET: usize = 1; +/// field offset of a permission storage address in a enum encoded permission signer. +pub const PERMISSION_ADDRESS_FIELD_OFFSET: usize = 2; + #[derive(Debug, PartialEq, Eq, Clone)] #[cfg_attr( any(test, feature = "fuzzing"), @@ -465,6 +478,13 @@ impl MoveStructLayout { }, } } + + pub fn signer() -> Self { + MoveStructLayout::RuntimeVariants(vec![vec![MoveTypeLayout::Address], vec![ + MoveTypeLayout::Address, + MoveTypeLayout::Address, + ]]) + } } impl<'d> serde::de::DeserializeSeed<'d> for &MoveTypeLayout { @@ -486,7 +506,26 @@ impl<'d> serde::de::DeserializeSeed<'d> for &MoveTypeLayout { AccountAddress::deserialize(deserializer).map(MoveValue::Address) }, MoveTypeLayout::Signer => { - AccountAddress::deserialize(deserializer).map(MoveValue::Signer) + let (variant, fields) = MoveStructLayout::signer() + .deserialize(deserializer)? + .into_optional_variant_and_fields(); + + if variant != Some(MASTER_SIGNER_VARIANT) { + return Err(D::Error::custom("cannot serialize permissioned signer")); + } + + // Runtime representation of signer looks following: + // enum signer { + // Master { account: address }, + // Permissioned { account: address, permissions_address: address }, + // } + // + // The first field always refers to the account address. + + Ok(MoveValue::Signer(match fields[0] { + MoveValue::Address(addr) => addr, + _ => return Err(D::Error::custom("signer deserialization error")), + })) }, MoveTypeLayout::Struct(ty) => Ok(MoveValue::Struct(ty.deserialize(deserializer)?)), MoveTypeLayout::Vector(layout) => Ok(MoveValue::Vector( @@ -691,7 +730,15 @@ impl serde::Serialize for MoveValue { MoveValue::U128(i) => serializer.serialize_u128(*i), MoveValue::U256(i) => i.serialize(serializer), MoveValue::Address(a) => a.serialize(serializer), - MoveValue::Signer(a) => a.serialize(serializer), + MoveValue::Signer(a) => { + // Runtime representation of signer looks following: + // enum signer { + // Master { account: address }, + // Permissioned { account: address, permissions_address: address }, + // } + MoveStruct::new_variant(MASTER_SIGNER_VARIANT, vec![MoveValue::Address(*a)]) + .serialize(serializer) + }, MoveValue::Vector(v) => { let mut t = serializer.serialize_seq(Some(v.len()))?; for val in v { diff --git a/third_party/move/move-prover/boogie-backend/src/prelude/prelude.bpl b/third_party/move/move-prover/boogie-backend/src/prelude/prelude.bpl index af262f11c9fbf..970ae252a2a53 100644 --- a/third_party/move/move-prover/boogie-backend/src/prelude/prelude.bpl +++ b/third_party/move/move-prover/boogie-backend/src/prelude/prelude.bpl @@ -1102,13 +1102,25 @@ procedure {:inline 1} $1_Account_create_signer( // Native Signer datatype $signer { - $signer($addr: int) + $signer($addr: int), + $permissioned_signer($addr: int, $permission_addr: int) } + function {:inline} $IsValid'signer'(s: $signer): bool { - $IsValid'address'(s->$addr) + if s is $signer then + $IsValid'address'(s->$addr) + else + $IsValid'address'(s->$addr) && + $IsValid'address'(s->$permission_addr) } + function {:inline} $IsEqual'signer'(s1: $signer, s2: $signer): bool { - s1 == s2 + if s1 is $signer && s2 is $signer then + s1 == s2 + else if s1 is $permissioned_signer && s2 is $permissioned_signer then + s1 == s2 + else + false } procedure {:inline 1} $1_signer_borrow_address(signer: $signer) returns (res: int) { diff --git a/third_party/move/move-prover/tests/sources/functional/bitwise_features.exp b/third_party/move/move-prover/tests/sources/functional/bitwise_features.exp new file mode 100644 index 0000000000000..c7a13354b635e --- /dev/null +++ b/third_party/move/move-prover/tests/sources/functional/bitwise_features.exp @@ -0,0 +1,12 @@ +Move prover returns: exiting with verification errors +error: verification out of resources/timeout (global timeout set to 80s) + ┌─ tests/sources/functional/bitwise_features.move:127:5 + │ +127 │ ╭ public fun disable_feature_flags(disable: vector) acquires Features { +128 │ │ let features = &mut borrow_global_mut(@std).features; +129 │ │ let i = 0; +130 │ │ let n = vector::length(&disable); + · │ +143 │ │ }; +144 │ │ } + │ ╰─────^ diff --git a/third_party/move/move-prover/tests/sources/functional/bitwise_features.v2_exp b/third_party/move/move-prover/tests/sources/functional/bitwise_features.v2_exp deleted file mode 100644 index 5910dbdcebcb8..0000000000000 --- a/third_party/move/move-prover/tests/sources/functional/bitwise_features.v2_exp +++ /dev/null @@ -1,12 +0,0 @@ -Move prover returns: exiting with verification errors -error: verification out of resources/timeout (global timeout set to 80s) - ┌─ tests/sources/functional/bitwise_features.move:154:5 - │ -154 │ ╭ public fun enable_feature_flags(enable: vector) acquires Features { -155 │ │ let features = &mut borrow_global_mut(@std).features; -156 │ │ let i = 0; -157 │ │ let n = vector::length(&enable); - · │ -170 │ │ }; -171 │ │ } - │ ╰─────^ diff --git a/third_party/move/move-prover/tests/sources/functional/loops_with_memory_ops.exp b/third_party/move/move-prover/tests/sources/functional/loops_with_memory_ops.exp index 32b6d76bc6da8..828edd8c598d7 100644 --- a/third_party/move/move-prover/tests/sources/functional/loops_with_memory_ops.exp +++ b/third_party/move/move-prover/tests/sources/functional/loops_with_memory_ops.exp @@ -45,8 +45,8 @@ error: induction case of the loop invariant does not hold = at tests/sources/functional/loops_with_memory_ops.move:80: nested_loop2 = at tests/sources/functional/loops_with_memory_ops.move:81: nested_loop2 = b = - = at tests/sources/functional/loops_with_memory_ops.move:81: nested_loop2 = a = + = at tests/sources/functional/loops_with_memory_ops.move:81: nested_loop2 = at tests/sources/functional/loops_with_memory_ops.move:85: nested_loop2 = i = = at tests/sources/functional/loops_with_memory_ops.move:86: nested_loop2 @@ -105,7 +105,6 @@ error: unknown assertion failed = at tests/sources/functional/loops_with_memory_ops.move:80: nested_loop2 = at tests/sources/functional/loops_with_memory_ops.move:81: nested_loop2 = b = - = at tests/sources/functional/loops_with_memory_ops.move:81: nested_loop2 = a = = at tests/sources/functional/loops_with_memory_ops.move:85: nested_loop2 = i = diff --git a/third_party/move/move-prover/tests/sources/functional/loops_with_memory_ops.v2_exp b/third_party/move/move-prover/tests/sources/functional/loops_with_memory_ops.v2_exp index c54e01557729d..5ad9d66da7ab5 100644 --- a/third_party/move/move-prover/tests/sources/functional/loops_with_memory_ops.v2_exp +++ b/third_party/move/move-prover/tests/sources/functional/loops_with_memory_ops.v2_exp @@ -41,7 +41,7 @@ error: induction case of the loop invariant does not hold = at tests/sources/functional/loops_with_memory_ops.move:80: nested_loop2 = at tests/sources/functional/loops_with_memory_ops.move:81: nested_loop2 = a = - = at tests/sources/functional/loops_with_memory_ops.move:81: nested_loop2 + = b = = at tests/sources/functional/loops_with_memory_ops.move:85: nested_loop2 = i = = at tests/sources/functional/loops_with_memory_ops.move:86: nested_loop2 @@ -97,7 +97,6 @@ error: unknown assertion failed = at tests/sources/functional/loops_with_memory_ops.move:80: nested_loop2 = at tests/sources/functional/loops_with_memory_ops.move:81: nested_loop2 = a = - = at tests/sources/functional/loops_with_memory_ops.move:81: nested_loop2 = b = = at tests/sources/functional/loops_with_memory_ops.move:85: nested_loop2 = i = diff --git a/third_party/move/move-prover/tests/sources/functional/trace.exp b/third_party/move/move-prover/tests/sources/functional/trace.exp index e24a41719d8a7..5f1a4a7b947ab 100644 --- a/third_party/move/move-prover/tests/sources/functional/trace.exp +++ b/third_party/move/move-prover/tests/sources/functional/trace.exp @@ -47,7 +47,7 @@ error: post-condition does not hold │ = Related Global Memory: = Resource name: TestTracing_R - = Values: {Address(18467): , Default: TestTracing.R{x = 4}} + = Values: {Address(6334): , Default: empty} = Related Bindings: = addr = = exists(addr) = @@ -74,7 +74,7 @@ error: global memory invariant does not hold │ = Related Global Memory: = Resource name: TestTracing_R - = Values: {Address(0): , Default: TestTracing.R{x = 5}} + = Values: {Address(0): , Default: empty} = at tests/sources/functional/trace.move:29: publish_invalid = at tests/sources/functional/trace.move:33: publish_invalid (spec) = `let addr = signer::address_of(s);` = diff --git a/third_party/move/move-prover/tests/sources/functional/trace.v2_exp b/third_party/move/move-prover/tests/sources/functional/trace.v2_exp index e24a41719d8a7..5f1a4a7b947ab 100644 --- a/third_party/move/move-prover/tests/sources/functional/trace.v2_exp +++ b/third_party/move/move-prover/tests/sources/functional/trace.v2_exp @@ -47,7 +47,7 @@ error: post-condition does not hold │ = Related Global Memory: = Resource name: TestTracing_R - = Values: {Address(18467): , Default: TestTracing.R{x = 4}} + = Values: {Address(6334): , Default: empty} = Related Bindings: = addr = = exists(addr) = @@ -74,7 +74,7 @@ error: global memory invariant does not hold │ = Related Global Memory: = Resource name: TestTracing_R - = Values: {Address(0): , Default: TestTracing.R{x = 5}} + = Values: {Address(0): , Default: empty} = at tests/sources/functional/trace.move:29: publish_invalid = at tests/sources/functional/trace.move:33: publish_invalid (spec) = `let addr = signer::address_of(s);` = diff --git a/third_party/move/move-stdlib/sources/signer.move b/third_party/move/move-stdlib/sources/signer.move index 1996b4331e987..f607e7d801000 100644 --- a/third_party/move/move-stdlib/sources/signer.move +++ b/third_party/move/move-stdlib/sources/signer.move @@ -1,11 +1,20 @@ module std::signer { - // Borrows the address of the signer - // Conceptually, you can think of the `signer` as being a struct wrapper arround an - // address - // ``` - // struct signer has drop { addr: address } - // ``` - // `borrow_address` borrows this inner field + /// signer is a builtin move type that represents an address that has been verfied by the VM. + /// + /// VM Runtime representation is equivalent to following: + /// enum signer has drop { + /// Master { account: address }, + /// Permissioned { account: address, permissions_address: address }, + /// } + /// + /// for bcs serialization: + /// struct signer has drop { + /// account: address, + /// } + /// ^ The discrepency is needed to maintain backwards compatibility of signer serialization + /// semantics. + /// + /// `borrow_address` borrows this inner field native public fun borrow_address(s: &signer): &address; // Copies the address of the signer diff --git a/third_party/move/move-stdlib/src/natives/bcs.rs b/third_party/move/move-stdlib/src/natives/bcs.rs index c1722c0165fa1..3e9743e2018da 100644 --- a/third_party/move/move-stdlib/src/natives/bcs.rs +++ b/third_party/move/move-stdlib/src/natives/bcs.rs @@ -64,6 +64,7 @@ fn native_to_bytes( // serialize value let val = ref_to_val.read_ref()?; let serialized_value = match ValueSerDeContext::new() + .with_legacy_signer() .with_func_args_deserialization(context.function_value_extension()) .serialize(&val, &layout)? { diff --git a/third_party/move/move-stdlib/src/natives/signer.rs b/third_party/move/move-stdlib/src/natives/signer.rs index 3a15343186a0c..90c8ccac06b48 100644 --- a/third_party/move/move-stdlib/src/natives/signer.rs +++ b/third_party/move/move-stdlib/src/natives/signer.rs @@ -37,10 +37,9 @@ fn native_borrow_address( debug_assert!(arguments.len() == 1); let signer_reference = pop_arg!(arguments, SignerRef); + let addr = signer_reference.borrow_signer()?; - Ok(NativeResult::ok(gas_params.base, smallvec![ - signer_reference.borrow_signer()? - ])) + Ok(NativeResult::ok(gas_params.base, smallvec![addr])) } pub fn make_native_borrow_address(gas_params: BorrowAddressGasParameters) -> NativeFunction { diff --git a/third_party/move/move-vm/runtime/src/interpreter.rs b/third_party/move/move-vm/runtime/src/interpreter.rs index 5950c9a08e5f6..03d6364edbb78 100644 --- a/third_party/move/move-vm/runtime/src/interpreter.rs +++ b/third_party/move/move-vm/runtime/src/interpreter.rs @@ -35,8 +35,8 @@ use move_vm_types::{ }, natives::function::NativeResult, values::{ - self, GlobalValue, IntegerValue, Locals, Reference, Struct, StructRef, VMValueCast, Value, - Vector, VectorRef, + self, GlobalValue, IntegerValue, Locals, Reference, SignerRef, Struct, StructRef, + VMValueCast, Value, Vector, VectorRef, }, views::TypeView, }; @@ -2163,9 +2163,9 @@ impl Frame { }, Bytecode::MoveTo(sd_idx) => { let resource = interpreter.operand_stack.pop()?; - let signer_reference = interpreter.operand_stack.pop_as::()?; + let signer_reference = interpreter.operand_stack.pop_as::()?; let addr = signer_reference - .borrow_field(0)? + .borrow_signer()? .value_as::()? .read_ref()? .value_as::()?; @@ -2175,9 +2175,9 @@ impl Frame { }, Bytecode::MoveToGeneric(si_idx) => { let resource = interpreter.operand_stack.pop()?; - let signer_reference = interpreter.operand_stack.pop_as::()?; + let signer_reference = interpreter.operand_stack.pop_as::()?; let addr = signer_reference - .borrow_field(0)? + .borrow_signer()? .value_as::()? .read_ref()? .value_as::()?; diff --git a/third_party/move/move-vm/transactional-tests/tests/builtins/gen_move_to.mvir b/third_party/move/move-vm/transactional-tests/tests/builtins/gen_move_to.mvir index a73419b95d424..2f6b7aca551ab 100644 --- a/third_party/move/move-vm/transactional-tests/tests/builtins/gen_move_to.mvir +++ b/third_party/move/move-vm/transactional-tests/tests/builtins/gen_move_to.mvir @@ -155,7 +155,7 @@ module 0x1.M { // // create, publish and exists -//# run --args @0x1 +//# run --signers 0x1 import 0x1.M; main(account: signer) { @@ -169,7 +169,7 @@ label b0: return; } -//# run --args @0x1 +//# run --signers 0x1 import 0x1.M; main(account: signer) { @@ -186,7 +186,7 @@ label b0: return; } -//# run --args @0x1 +//# run --signers 0x1 import 0x1.M; main(account: signer) { @@ -219,7 +219,7 @@ label b0: // // borrows and mutations -//# run --args @0x1 +//# run --signers 0x1 import 0x1.M; main(account: signer) { @@ -243,7 +243,7 @@ label b0: return; } -//# run --args @0x1 +//# run --signers 0x1 import 0x1.M; main(account: signer) { @@ -263,7 +263,7 @@ label b0: // // unpublish, destroy and exists -//# run --args @0x1 +//# run --signers 0x1 import 0x1.M; main(account: signer) { @@ -277,7 +277,7 @@ label b0: return; } -//# run --args @0x1 +//# run --signers 0x1 import 0x1.M; main(account: signer) { @@ -303,7 +303,7 @@ label b0: } -//# run --args @0x1 +//# run --signers 0x1 import 0x1.M; main(account: signer) { diff --git a/third_party/move/move-vm/transactional-tests/tests/entry_points/address_arg_is_not_signer.mvir b/third_party/move/move-vm/transactional-tests/tests/entry_points/address_arg_is_not_signer.mvir index dd04d0efb75cc..2c5ba78953a87 100644 --- a/third_party/move/move-vm/transactional-tests/tests/entry_points/address_arg_is_not_signer.mvir +++ b/third_party/move/move-vm/transactional-tests/tests/entry_points/address_arg_is_not_signer.mvir @@ -1,11 +1,11 @@ -//# run --args @0x1 +//# run --signers 0x1 main(s: signer) { label b0: return; } // DEPRECATED signers can now be passed in as addrs -//# run --args @0x0 @0x2 +//# run --signers 0x0 0x2 main(s: signer, s2: signer) { label b0: return; diff --git a/third_party/move/move-vm/transactional-tests/tests/entry_points/mixed_signer_inputs_function.exp b/third_party/move/move-vm/transactional-tests/tests/entry_points/mixed_signer_inputs_function.exp deleted file mode 100644 index 457ace9c4acb6..0000000000000 --- a/third_party/move/move-vm/transactional-tests/tests/entry_points/mixed_signer_inputs_function.exp +++ /dev/null @@ -1 +0,0 @@ -processed 4 tasks diff --git a/third_party/move/move-vm/transactional-tests/tests/entry_points/mixed_signer_inputs_function.mvir b/third_party/move/move-vm/transactional-tests/tests/entry_points/mixed_signer_inputs_function.mvir deleted file mode 100644 index a55e7a71e4c76..0000000000000 --- a/third_party/move/move-vm/transactional-tests/tests/entry_points/mixed_signer_inputs_function.mvir +++ /dev/null @@ -1,28 +0,0 @@ -//# publish - -// tests various mixed usage of signer in function arguments - -module 0x42.M { - -public main1(s1: signer, s2: &signer, s3: signer) { - label l0: - return; -} - -public main2(s1: &signer, u: u64, s2: signer, f: bool, s3: &signer) { - label l0: - return; -} - -public main3(u: u64, f: bool, a: address, s1: signer, s2: &signer) { - label l0: - return; -} - -} - -//# run 0x42::M::main1 --args @0x1 @0x1 @0x1 - -//# run 0x42::M::main2 --args @0x1 0 @0x1 false @0x1 - -//# run 0x42::M::main3 --args 0 false @0x1 @0x2 @0x3 diff --git a/third_party/move/move-vm/transactional-tests/tests/entry_points/mixed_signer_inputs_function.v2_exp b/third_party/move/move-vm/transactional-tests/tests/entry_points/mixed_signer_inputs_function.v2_exp deleted file mode 100644 index 457ace9c4acb6..0000000000000 --- a/third_party/move/move-vm/transactional-tests/tests/entry_points/mixed_signer_inputs_function.v2_exp +++ /dev/null @@ -1 +0,0 @@ -processed 4 tasks diff --git a/third_party/move/move-vm/transactional-tests/tests/entry_points/mixed_signer_inputs_scripts.exp b/third_party/move/move-vm/transactional-tests/tests/entry_points/mixed_signer_inputs_scripts.exp deleted file mode 100644 index fc5a4436b29d4..0000000000000 --- a/third_party/move/move-vm/transactional-tests/tests/entry_points/mixed_signer_inputs_scripts.exp +++ /dev/null @@ -1 +0,0 @@ -processed 3 tasks diff --git a/third_party/move/move-vm/transactional-tests/tests/entry_points/mixed_signer_inputs_scripts.mvir b/third_party/move/move-vm/transactional-tests/tests/entry_points/mixed_signer_inputs_scripts.mvir deleted file mode 100644 index 7892f86cc7a3a..0000000000000 --- a/third_party/move/move-vm/transactional-tests/tests/entry_points/mixed_signer_inputs_scripts.mvir +++ /dev/null @@ -1,21 +0,0 @@ -//# run --args @0x1 @0x1 @0x1 - -// tests various mixed usage of signer in script arguments - -main(s1: signer, s2: &signer, s3: signer) { - label l0: - return; -} - - -//# run --args @0x1 0 @0x1 false @0x1 -main(s1: &signer, u: u64, s2: signer, f: bool, s3: &signer) { - label l0: - return; -} - -//# run --args 0 false @0x1 @0x2 @0x3 -main(u: u64, f: bool, a: address, s1: signer, s2: &signer) { - label l0: - return; -} diff --git a/third_party/move/move-vm/transactional-tests/tests/entry_points/mixed_signer_inputs_scripts.v2_exp b/third_party/move/move-vm/transactional-tests/tests/entry_points/mixed_signer_inputs_scripts.v2_exp deleted file mode 100644 index fc5a4436b29d4..0000000000000 --- a/third_party/move/move-vm/transactional-tests/tests/entry_points/mixed_signer_inputs_scripts.v2_exp +++ /dev/null @@ -1 +0,0 @@ -processed 3 tasks diff --git a/third_party/move/move-vm/transactional-tests/tests/entry_points/return_values.exp b/third_party/move/move-vm/transactional-tests/tests/entry_points/return_values.exp index 13f10dcbdf982..207d694c94d44 100644 --- a/third_party/move/move-vm/transactional-tests/tests/entry_points/return_values.exp +++ b/third_party/move/move-vm/transactional-tests/tests/entry_points/return_values.exp @@ -2,4 +2,4 @@ processed 2 tasks task 1 'run'. lines 20-20: mutable inputs after call: local#4: { 4 } -return values: 0, { 0000000000000000000000000000000000000000000000000000000000000001 }, { 2 }, { 3 }, 3, 4 +return values: 0, { 0, 0000000000000000000000000000000000000000000000000000000000000001 }, { 2 }, { 3 }, 3, 4 diff --git a/third_party/move/move-vm/transactional-tests/tests/entry_points/return_values.mvir b/third_party/move/move-vm/transactional-tests/tests/entry_points/return_values.mvir index 875876e59f9df..1210f152defe0 100644 --- a/third_party/move/move-vm/transactional-tests/tests/entry_points/return_values.mvir +++ b/third_party/move/move-vm/transactional-tests/tests/entry_points/return_values.mvir @@ -6,8 +6,8 @@ module 0x42.M { struct S { f: u64 } public t( - x: u64, a: signer, + x: u64, s: Self.S, r1: &Self.S, r2: &mut Self.S @@ -17,4 +17,4 @@ module 0x42.M { } } -//# run 0x42::M::t --args 0 @0x1 2 3 4 +//# run 0x42::M::t --signers 0x1 --args 0 2 3 4 diff --git a/third_party/move/move-vm/transactional-tests/tests/entry_points/return_values.v2_exp b/third_party/move/move-vm/transactional-tests/tests/entry_points/return_values.v2_exp index 13f10dcbdf982..207d694c94d44 100644 --- a/third_party/move/move-vm/transactional-tests/tests/entry_points/return_values.v2_exp +++ b/third_party/move/move-vm/transactional-tests/tests/entry_points/return_values.v2_exp @@ -2,4 +2,4 @@ processed 2 tasks task 1 'run'. lines 20-20: mutable inputs after call: local#4: { 4 } -return values: 0, { 0000000000000000000000000000000000000000000000000000000000000001 }, { 2 }, { 3 }, 3, 4 +return values: 0, { 0, 0000000000000000000000000000000000000000000000000000000000000001 }, { 2 }, { 3 }, 3, 4 diff --git a/third_party/move/move-vm/transactional-tests/tests/entry_points/script_type_args_type_eq.mvir b/third_party/move/move-vm/transactional-tests/tests/entry_points/script_type_args_type_eq.mvir index 303be015fcb26..07f1320cb41ea 100644 --- a/third_party/move/move-vm/transactional-tests/tests/entry_points/script_type_args_type_eq.mvir +++ b/third_party/move/move-vm/transactional-tests/tests/entry_points/script_type_args_type_eq.mvir @@ -20,7 +20,7 @@ module 0x42.M { } // check: "Keep(EXECUTED)" -//# run --type-args u64 --args @0x1 +//# run --type-args u64 --signers 0x1 import 0x42.M; main(account: signer) { @@ -32,7 +32,7 @@ label b0: -//# run --type-args 0x42::M::Item --args @0x2 +//# run --type-args 0x42::M::Item --signers 0x2 import 0x42.M; main(account: signer) { @@ -43,7 +43,7 @@ label b0: } -//# run --type-args 0x42::M::Cup<0x42::M::Cup>> --args @0x3 +//# run --type-args 0x42::M::Cup<0x42::M::Cup>> --signers 0x3 import 0x42.M; main(account: signer) { diff --git a/third_party/move/move-vm/transactional-tests/tests/entry_points/serializer_deserializer.move b/third_party/move/move-vm/transactional-tests/tests/entry_points/serializer_deserializer.move index 85e996fad40ac..500f0c1f83633 100644 --- a/third_party/move/move-vm/transactional-tests/tests/entry_points/serializer_deserializer.move +++ b/third_party/move/move-vm/transactional-tests/tests/entry_points/serializer_deserializer.move @@ -37,7 +37,7 @@ module 0x42::t { } -//# run 0x42::t::add --args @0x42 +//# run 0x42::t::add --signers 0x42 //# view --address 0x42 --resource 0x42::t::Ints diff --git a/third_party/move/move-vm/transactional-tests/tests/recursion/runtime_layout_deeply_nested.mvir b/third_party/move/move-vm/transactional-tests/tests/recursion/runtime_layout_deeply_nested.mvir index 9044139ecd6f3..5a3ffa6b060f3 100644 --- a/third_party/move/move-vm/transactional-tests/tests/recursion/runtime_layout_deeply_nested.mvir +++ b/third_party/move/move-vm/transactional-tests/tests/recursion/runtime_layout_deeply_nested.mvir @@ -66,7 +66,7 @@ module 0x42.M { } -//# run --args @0x1 +//# run --signers 0x1 import 0x42.M; main(account: signer) { @@ -76,7 +76,7 @@ label b0: return; } -//# run --args @0x2 +//# run --signers 0x2 import 0x42.M; main(account: signer) { @@ -86,7 +86,7 @@ label b0: } -//# run --args @0x3 +//# run --signers 0x3 import 0x42.M; main(account: signer) { diff --git a/third_party/move/move-vm/types/src/value_serde.rs b/third_party/move/move-vm/types/src/value_serde.rs index c8f390096f9ac..dd9c22140ad26 100644 --- a/third_party/move/move-vm/types/src/value_serde.rs +++ b/third_party/move/move-vm/types/src/value_serde.rs @@ -62,6 +62,7 @@ pub struct ValueSerDeContext<'a> { #[allow(dead_code)] pub(crate) function_extension: Option<&'a dyn FunctionValueExtension>, pub(crate) delayed_fields_extension: Option>, + pub(crate) legacy_signer: bool, } impl<'a> ValueSerDeContext<'a> { @@ -71,9 +72,16 @@ impl<'a> ValueSerDeContext<'a> { Self { function_extension: None, delayed_fields_extension: None, + legacy_signer: false, } } + /// Serialize signer with legacy format to maintain backwards compatibility. + pub fn with_legacy_signer(mut self) -> Self { + self.legacy_signer = true; + self + } + /// Custom (de)serializer such that supports lookup of the argument types of a function during /// function value deserialization. pub fn with_func_args_deserialization( @@ -89,6 +97,7 @@ impl<'a> ValueSerDeContext<'a> { Self { function_extension: self.function_extension, delayed_fields_extension: None, + legacy_signer: self.legacy_signer, } } diff --git a/third_party/move/move-vm/types/src/values/serialization_tests.rs b/third_party/move/move-vm/types/src/values/serialization_tests.rs index e1c48c082fcbe..b4af6ed67ed00 100644 --- a/third_party/move/move-vm/types/src/values/serialization_tests.rs +++ b/third_party/move/move-vm/types/src/values/serialization_tests.rs @@ -261,3 +261,20 @@ fn test_serialized_size() { .serialized_size(&value, &layout)); } } + +#[test] +fn signer_round_trip_vm_value() { + let v = MoveValue::Signer(AccountAddress::ZERO); + let bytes = v.simple_serialize().unwrap(); + let vm_value = ValueSerDeContext::new() + .deserialize(&bytes, &MoveTypeLayout::Signer) + .unwrap(); + let vm_bytes = ValueSerDeContext::new() + .serialize(&vm_value, &MoveTypeLayout::Signer) + .unwrap() + .unwrap(); + assert_eq!( + v, + MoveValue::simple_deserialize(&vm_bytes, &MoveTypeLayout::Signer).unwrap() + ); +} diff --git a/third_party/move/move-vm/types/src/values/values_impl.rs b/third_party/move/move-vm/types/src/values/values_impl.rs index fae84d1694a85..a39dfdb16bf1b 100644 --- a/third_party/move/move-vm/types/src/values/values_impl.rs +++ b/third_party/move/move-vm/types/src/values/values_impl.rs @@ -19,8 +19,11 @@ use move_core_types::{ account_address::AccountAddress, effects::Op, gas_algebra::AbstractMemorySize, - u256, value, - value::{MoveStruct, MoveStructLayout, MoveTypeLayout, MoveValue}, + u256, + value::{ + self, MoveStruct, MoveStructLayout, MoveTypeLayout, MoveValue, MASTER_ADDRESS_FIELD_OFFSET, + MASTER_SIGNER_VARIANT, PERMISSIONED_SIGNER_VARIANT, PERMISSION_ADDRESS_FIELD_OFFSET, + }, vm_status::{sub_status::NFE_VECTOR_ERROR_BASE, StatusCode}, }; use serde::{ @@ -291,7 +294,10 @@ impl Container { } fn signer(x: AccountAddress) -> Self { - Container::Struct(Rc::new(RefCell::new(vec![ValueImpl::Address(x)]))) + Container::Struct(Rc::new(RefCell::new(vec![ + ValueImpl::U16(MASTER_SIGNER_VARIANT), + ValueImpl::Address(x), + ]))) } } @@ -1513,7 +1519,39 @@ impl Locals { impl SignerRef { pub fn borrow_signer(&self) -> PartialVMResult { - Ok(Value(self.0.borrow_elem(0)?)) + Ok(Value(self.0.borrow_elem(1)?)) + } + + pub fn is_permissioned(&self) -> PartialVMResult { + match &self.0 { + ContainerRef::Local(Container::Struct(s)) => { + Ok(*s.borrow()[0].as_value_ref::()? == PERMISSIONED_SIGNER_VARIANT) + }, + _ => Err( + PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR) + .with_message(format!("unexpected signer value: {:?}", self)), + ), + } + } + + /// Get the permission address associated with a signer. + /// Needs to make sure the signer passed in is a permissioned signer. + pub fn permission_address(&self) -> PartialVMResult { + match &self.0 { + ContainerRef::Local(Container::Struct(s)) => Ok(Value::address( + *s.borrow() + .get(PERMISSION_ADDRESS_FIELD_OFFSET) + .ok_or_else(|| { + PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR) + .with_message(format!("unexpected signer value: {:?}", self)) + })? + .as_value_ref::()?, + )), + _ => Err( + PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR) + .with_message(format!("unexpected signer value: {:?}", self)), + ), + } } } @@ -1676,6 +1714,13 @@ impl Value { Self(ValueImpl::Container(Container::signer(x))) } + pub fn permissioned_signer(x: AccountAddress, perm_storage_address: AccountAddress) -> Self { + Self::struct_(Struct::pack_variant(PERMISSIONED_SIGNER_VARIANT, vec![ + Value::address(x), + Value::address(perm_storage_address), + ])) + } + /// Create a "unowned" reference to a signer value (&signer) for populating the &signer in /// execute function pub fn signer_reference(x: AccountAddress) -> Self { @@ -3710,19 +3755,41 @@ impl<'c, 'l, 'v> serde::Serialize // Signer. (L::Signer, ValueImpl::Container(Container::Struct(r))) => { - let v = r.borrow(); - if v.len() != 1 { - return Err(invariant_violation::(format!( - "cannot serialize container as a signer -- expected 1 field got {}", - v.len() - ))); + if self.ctx.legacy_signer { + // Only allow serialization of master signer. + if *r.borrow()[0].as_value_ref::().map_err(|_| { + invariant_violation::(format!( + "First field of a signer needs to be an enum descriminator, got {:?}", + self.value + )) + })? != MASTER_SIGNER_VARIANT + { + return Err(S::Error::custom(PartialVMError::new(StatusCode::ABORTED))); + } + r.borrow() + .get(PERMISSION_ADDRESS_FIELD_OFFSET) + .ok_or_else(|| { + invariant_violation::(format!( + "cannot serialize container {:?} as {:?}", + self.value, self.layout + )) + })? + .as_value_ref::() + .map_err(|_| { + invariant_violation::(format!( + "cannot serialize container {:?} as {:?}", + self.value, self.layout + )) + })? + .serialize(serializer) + } else { + (SerializationReadyValue { + ctx: self.ctx, + layout: &MoveStructLayout::signer(), + value: &*r.borrow(), + }) + .serialize(serializer) } - (SerializationReadyValue { - ctx: self.ctx, - layout: &L::Address, - value: &v[0], - }) - .serialize(serializer) }, // Delayed values. For their serialization, we must have custom @@ -3874,7 +3941,17 @@ impl<'d, 'c> serde::de::DeserializeSeed<'d> for DeserializationSeed<'c, &MoveTyp L::U128 => u128::deserialize(deserializer).map(Value::u128), L::U256 => u256::U256::deserialize(deserializer).map(Value::u256), L::Address => AccountAddress::deserialize(deserializer).map(Value::address), - L::Signer => AccountAddress::deserialize(deserializer).map(Value::signer), + L::Signer => { + if self.ctx.legacy_signer { + AccountAddress::deserialize(deserializer).map(Value::signer) + } else { + let seed = DeserializationSeed { + ctx: self.ctx, + layout: &MoveStructLayout::signer(), + }; + Ok(Value::struct_(seed.deserialize(deserializer)?)) + } + }, // Structs. L::Struct(struct_layout) => { @@ -4660,10 +4737,7 @@ impl ValueImpl { (L::Signer, ValueImpl::Container(Container::Struct(r))) => { let v = r.borrow(); - if v.len() != 1 { - panic!("Unexpected signer layout: {:?}", v); - } - match &v[0] { + match &v[MASTER_ADDRESS_FIELD_OFFSET] { ValueImpl::Address(a) => MoveValue::Signer(*a), v => panic!("Unexpected non-address while converting signer: {:?}", v), } diff --git a/types/src/on_chain_config/aptos_features.rs b/types/src/on_chain_config/aptos_features.rs index 6b03fc343137d..2eb853194f837 100644 --- a/types/src/on_chain_config/aptos_features.rs +++ b/types/src/on_chain_config/aptos_features.rs @@ -104,6 +104,7 @@ pub enum FeatureFlag { /// that results in a new package created but without any code. With this feature, it is no /// longer possible and an explicit error is returned if publishing is attempted. DISALLOW_INIT_MODULE_TO_PUBLISH_MODULES = 82, + PERMISSIONED_SIGNER = 83, } impl FeatureFlag { @@ -186,6 +187,7 @@ impl FeatureFlag { FeatureFlag::COLLECTION_OWNER, FeatureFlag::ENABLE_LOADER_V2, FeatureFlag::DISALLOW_INIT_MODULE_TO_PUBLISH_MODULES, + FeatureFlag::PERMISSIONED_SIGNER, ] } }