diff --git a/aptos-move/framework/aptos-framework/doc/aptos_governance.md b/aptos-move/framework/aptos-framework/doc/aptos_governance.md index 0edd157f955d2..1caea3d905f98 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_governance_permission`](#0x1_aptos_governance_check_governance_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 = 16;
+
+ + + The specified stake pool must be part of the validator set @@ -827,6 +868,59 @@ Proposal metadata attribute keys. + + +## Function `check_governance_permission` + +Permissions + + +
fun check_governance_permission(s: &signer)
+
+ + + +
+Implementation + + +
inline fun check_governance_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_governance_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)); @@ -2118,7 +2214,20 @@ Return a signer for making changes to 0x1 as part of on-chain governance proposa
pragma verify = true;
-pragma aborts_if_is_strict;
+pragma aborts_if_is_partial;
+
+ + + + + + + +
schema AbortsIfPermissionedSigner {
+    s: signer;
+    let perm = GovernancePermission {};
+    aborts_if !permissioned_signer::spec_check_permission_exists(s, perm);
+}
 
diff --git a/aptos-move/framework/aptos-framework/doc/code.md b/aptos-move/framework/aptos-framework/doc/code.md index ce18af9e16fbc..fe773e27c0f99 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 `CodePublishingPermission`](#0x1_code_CodePublishingPermission) - [Struct `AllowedDep`](#0x1_code_AllowedDep) - [Constants](#@Constants_0) +- [Function `check_code_publishing_permission`](#0x1_code_check_code_publishing_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 `CodePublishingPermission` + + + +
struct CodePublishingPermission 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_code_publishing_permission` + +Permissions + + +
public(friend) fun check_code_publishing_permission(s: &signer)
+
+ + + +
+Implementation + + +
public(friend) fun check_code_publishing_permission(s: &signer) {
+    assert!(
+        permissioned_signer::check_permission_exists(s, CodePublishingPermission {}),
+        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, CodePublishingPermission {})
+}
+
+ + + +
+ ## Function `upgrade_policy_arbitrary` @@ -598,6 +692,7 @@ package.
public fun publish_package(owner: &signer, pack: PackageMetadata, code: vector<vector<u8>>) 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,
@@ -679,6 +774,7 @@ package.
 
 
 
public fun freeze_code_object(publisher: &signer, code_object: Object<PackageRegistry>) acquires PackageRegistry {
+    check_code_publishing_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!(
@@ -1073,7 +1169,7 @@ Native function to initiate module loading, including a list of allowed dependen
 
 
 
pragma verify = true;
-pragma aborts_if_is_strict;
+pragma aborts_if_is_partial;
 
@@ -1253,4 +1349,17 @@ Native function to initiate module loading, including a list of allowed dependen
+ + + + + +
schema AbortsIfPermissionedSigner {
+    s: signer;
+    let perm = CodePublishingPermission {};
+    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/create_signer.md b/aptos-move/framework/aptos-framework/doc/create_signer.md index a66df38b9c601..5c15eb6bec51f 100644 --- a/aptos-move/framework/aptos-framework/doc/create_signer.md +++ b/aptos-move/framework/aptos-framework/doc/create_signer.md @@ -127,6 +127,16 @@ Convert address to singer and return.
pragma opaque;
 aborts_if [abstract] false;
 ensures [abstract] signer::address_of(result) == addr;
+ensures [abstract] result == spec_create_signer(addr);
+
+ + + + + + + +
fun spec_create_signer(addr: address): signer;
 
diff --git a/aptos-move/framework/aptos-framework/doc/delegation_pool.md b/aptos-move/framework/aptos-framework/doc/delegation_pool.md index c17ab31779be0..99ed5b67a0d25 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) +- [Enum `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,10 @@ 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_delegation_pool_management_permission`](#0x1_delegation_pool_check_delegation_pool_management_permission) +- [Function `grant_delegation_pool_management_permission`](#0x1_delegation_pool_grant_delegation_pool_management_permission) +- [Function `check_stake_management_permission`](#0x1_delegation_pool_check_stake_management_permission) +- [Function `grant_stake_management_permission`](#0x1_delegation_pool_grant_stake_management_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 +250,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 +684,55 @@ evicted later by the pool owner. + + + + +## Enum `DelegationPermission` + + + +
enum DelegationPermission has copy, drop, store
+
+ + + +
+Variants + + +
+DelegationPoolManagementPermission + + +
+Fields + + +
+
+ + +
+ +
+ +
+StakeManagementPermission + + +
+Fields + + +
+
+ + +
+ +
+
@@ -1828,6 +1883,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 +2821,109 @@ Return allowlist or revert if allowlisting is not enabled for the provided deleg + + + + +## Function `check_delegation_pool_management_permission` + +Permissions + + +
fun check_delegation_pool_management_permission(s: &signer)
+
+ + + +
+Implementation + + +
inline fun check_delegation_pool_management_permission(s: &signer) {
+    assert!(
+        permissioned_signer::check_permission_exists(s, DelegationPermission::DelegationPoolManagementPermission {}),
+        error::permission_denied(ENO_DELEGATION_PERMISSION),
+    );
+}
+
+ + + +
+ + + +## Function `grant_delegation_pool_management_permission` + + + +
public fun grant_delegation_pool_management_permission(master: &signer, permissioned_signer: &signer)
+
+ + + +
+Implementation + + +
public fun grant_delegation_pool_management_permission(master: &signer, permissioned_signer: &signer) {
+    permissioned_signer::authorize_unlimited(master, permissioned_signer, DelegationPermission::DelegationPoolManagementPermission {})
+}
+
+ + + +
+ + + +## Function `check_stake_management_permission` + + + +
fun check_stake_management_permission(s: &signer)
+
+ + + +
+Implementation + + +
inline fun check_stake_management_permission(s: &signer) {
+    assert!(
+        permissioned_signer::check_permission_exists(s, DelegationPermission::StakeManagementPermission {}),
+        error::permission_denied(ENO_DELEGATION_PERMISSION),
+    );
+}
+
+ + + +
+ + + +## Function `grant_stake_management_permission` + + + +
public fun grant_stake_management_permission(master: &signer, permissioned_signer: &signer)
+
+ + + +
+Implementation + + +
public fun grant_stake_management_permission(master: &signer, permissioned_signer: &signer) {
+    permissioned_signer::authorize_unlimited(master, permissioned_signer, DelegationPermission::StakeManagementPermission {})
+}
+
+ + +
@@ -2782,6 +2950,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_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)); @@ -2942,6 +3111,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_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); @@ -3022,6 +3192,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_stake_management_permission(voter); assert_partial_governance_voting_enabled(pool_address); // synchronize delegation and stake pools before any user operation @@ -3794,6 +3965,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_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 @@ -3829,6 +4001,7 @@ one for each 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 )); @@ -3874,6 +4047,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_delegation_pool_management_permission(owner); assert!(features::commission_change_delegation_pool_enabled(), error::invalid_state( ECOMMISSION_RATE_CHANGE_NOT_SUPPORTED )); @@ -3939,6 +4113,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_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(), @@ -3977,6 +4152,7 @@ this change won't take effects until the next lockup period. 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 @@ -4054,6 +4230,7 @@ Enable delegators allowlisting as the pool owner.
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)
@@ -4092,6 +4269,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_delegation_pool_management_permission(owner);
     let pool_address = get_owned_pool_address(signer::address_of(owner));
     assert_allowlisting_enabled(pool_address);
 
@@ -4127,6 +4305,7 @@ Allowlist a delegator as the pool owner.
     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);
 
@@ -4162,6 +4341,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_delegation_pool_management_permission(owner);
     let pool_address = get_owned_pool_address(signer::address_of(owner));
     assert_allowlisting_enabled(pool_address);
 
@@ -4197,6 +4377,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_delegation_pool_management_permission(owner);
     let pool_address = get_owned_pool_address(signer::address_of(owner));
     assert_allowlisting_enabled(pool_address);
     assert!(
@@ -4241,6 +4422,7 @@ Add amount of coins to the delegation pool pool_addressaddress,
     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 };
 
@@ -4318,6 +4500,7 @@ at most how much active stake there is on the stake 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 };
 
@@ -4419,6 +4602,7 @@ Move amount of coins from pending_inactive to active.
     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 };
 
@@ -4489,6 +4673,7 @@ Withdraw amount of owned inactive stake from the delegation pool at
     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/doc/object_code_deployment.md b/aptos-move/framework/aptos-framework/doc/object_code_deployment.md
index e4caf56a77c74..db2ac6c296522 100644
--- a/aptos-move/framework/aptos-framework/doc/object_code_deployment.md
+++ b/aptos-move/framework/aptos-framework/doc/object_code_deployment.md
@@ -190,6 +190,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 @@ -243,6 +253,7 @@ the code to be published via code. T metadata_serialized: vector<u8>, code: vector<vector<u8>>, ) { + code::check_code_publishing_permission(publisher); assert!( features::is_object_code_deployment_enabled(), error::unavailable(EOBJECT_CODE_DEPLOYMENT_NOT_SUPPORTED), @@ -319,6 +330,7 @@ Requires the publisher to be the owner of the code_object. code: vector<vector<u8>>, code_object: Object<PackageRegistry>, ) 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/doc/resource_account.md b/aptos-move/framework/aptos-framework/doc/resource_account.md index 45d7d6ebc4914..95e3d0ec197ab 100644 --- a/aptos-move/framework/aptos-framework/doc/resource_account.md +++ b/aptos-move/framework/aptos-framework/doc/resource_account.md @@ -486,6 +486,7 @@ 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);
 include RotateAccountAuthenticationKeyAndStoreCapabilityAbortsIfWithoutAccountLimit;
 
diff --git a/aptos-move/framework/aptos-framework/doc/stake.md b/aptos-move/framework/aptos-framework/doc/stake.md index 825dfe567c8f8..564db5d3f1434 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 `StakeManagementPermission`](#0x1_stake_StakeManagementPermission) - [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_stake_permission`](#0x1_stake_check_stake_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 `StakeManagementPermission` + + + +
struct StakeManagementPermission 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_stake_permission` + +Permissions + + +
fun check_stake_permission(s: &signer)
+
+ + + +
+Implementation + + +
inline fun check_stake_permission(s: &signer) {
+    assert!(
+        permissioned_signer::check_permission_exists(s, StakeManagementPermission {}),
+        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, StakeManagementPermission {})
+}
+
+ + + +
+ ## Function `get_lockup_secs` @@ -2385,6 +2479,7 @@ to set later. operator: address, voter: address, ) acquires AllowedValidators, OwnerCapability, StakePool, ValidatorSet { + check_stake_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_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, @@ -2471,6 +2567,7 @@ Initialize the validator account and give ownership to the signing account.
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));
@@ -2525,6 +2622,7 @@ 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<OwnerCapability>(owner_address)
@@ -2553,6 +2651,7 @@ staking pool.
 
 
 
public fun deposit_owner_cap(owner: &signer, owner_cap: OwnerCapability) {
+    check_stake_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_stake_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_stake_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_stake_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_stake_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_stake_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_stake_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_stake_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_stake_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_stake_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_stake_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_stake_permission(operator);
     assert_reconfig_not_in_progress();
     let config = staking_config::get();
     assert!(
@@ -4546,6 +4656,7 @@ Returns validator's next epoch voting power, including pending_active, active, a
 
 
 
pragma verify = true;
+pragma aborts_if_is_partial;
 invariant [suspendable] exists<ValidatorSet>(@aptos_framework) ==> validator_set_is_valid();
 invariant [suspendable] chain_status::is_operating() ==> exists<AptosCoinCapabilities>(@aptos_framework);
 invariant [suspendable] chain_status::is_operating() ==> exists<ValidatorPerformance>(@aptos_framework);
@@ -4565,6 +4676,115 @@ Returns validator's next epoch voting power, including pending_active, active, a
 
 
 
+
+
+
+
+
fun spec_rewards_amount(
+   stake_amount: u64,
+   num_successful_proposals: u64,
+   num_total_proposals: u64,
+   rewards_rate: u64,
+   rewards_rate_denominator: u64,
+): u64;
+
+ + + + + + + +
fun spec_contains(validators: vector<ValidatorInfo>, addr: address): bool {
+   exists i in 0..len(validators): validators[i].addr == addr
+}
+
+ + + + + + + +
fun spec_is_current_epoch_validator(pool_address: address): bool {
+   let validator_set = global<ValidatorSet>(@aptos_framework);
+   !spec_contains(validator_set.pending_active, pool_address)
+       && (spec_contains(validator_set.active_validators, pool_address)
+       || spec_contains(validator_set.pending_inactive, pool_address))
+}
+
+ + + + + + + +
schema ResourceRequirement {
+    requires exists<AptosCoinCapabilities>(@aptos_framework);
+    requires exists<ValidatorPerformance>(@aptos_framework);
+    requires exists<ValidatorSet>(@aptos_framework);
+    requires exists<StakingConfig>(@aptos_framework);
+    requires exists<StakingRewardsConfig>(@aptos_framework) || !features::spec_periodical_reward_rate_decrease_enabled();
+    requires exists<timestamp::CurrentTimeMicroseconds>(@aptos_framework);
+}
+
+ + + + + + + +
fun spec_get_reward_rate_1(config: StakingConfig): num {
+   if (features::spec_periodical_reward_rate_decrease_enabled()) {
+       let epoch_rewards_rate = global<staking_config::StakingRewardsConfig>(@aptos_framework).rewards_rate;
+       if (epoch_rewards_rate.value == 0) {
+           0
+       } else {
+           let denominator_0 = aptos_std::fixed_point64::spec_divide_u128(staking_config::MAX_REWARDS_RATE, epoch_rewards_rate);
+           let denominator = if (denominator_0 > MAX_U64) {
+               MAX_U64
+           } else {
+               denominator_0
+           };
+           let nominator = aptos_std::fixed_point64::spec_multiply_u128(denominator, epoch_rewards_rate);
+           nominator
+       }
+   } else {
+           config.rewards_rate
+   }
+}
+
+ + + + + + + +
fun spec_get_reward_rate_2(config: StakingConfig): num {
+   if (features::spec_periodical_reward_rate_decrease_enabled()) {
+       let epoch_rewards_rate = global<staking_config::StakingRewardsConfig>(@aptos_framework).rewards_rate;
+       if (epoch_rewards_rate.value == 0) {
+           1
+       } else {
+           let denominator_0 = aptos_std::fixed_point64::spec_divide_u128(staking_config::MAX_REWARDS_RATE, epoch_rewards_rate);
+           let denominator = if (denominator_0 > MAX_U64) {
+               MAX_U64
+           } else {
+               denominator_0
+           };
+           denominator
+       }
+   } else {
+           config.rewards_rate_denominator
+   }
+}
+
+ + + ### Resource `ValidatorSet` @@ -4792,6 +5012,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 +5048,11 @@ Returns validator's next epoch voting power, including pending_active, active, a
 
 
 
-
let pubkey_from_pop = bls12381::spec_public_key_from_bytes_with_pop(
+
pragma verify = false;
+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 +5091,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 +5112,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 +5175,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 +5199,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 +5244,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 +5276,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 +5335,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 +5415,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 +5464,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);
@@ -5400,6 +5653,19 @@ Returns validator's next epoch voting power, including pending_active, active, a
 
 
 
+
+
+
+
schema AbortsIfSignerPermissionStake {
+    s: signer;
+    let perm = StakeManagementPermission {};
+    aborts_if !permissioned_signer::spec_check_permission_exists(s, perm);
+}
+
+ + + + @@ -5485,6 +5751,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..2a2f4577aa04d 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_stake_proxy_permission`](#0x1_staking_proxy_check_stake_proxy_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_stake_proxy_permission` + +Permissions + + +
fun check_stake_proxy_permission(s: &signer)
+
+ + + +
+Implementation + + +
inline fun check_stake_proxy_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_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| {
@@ -134,6 +237,7 @@
 
 
 
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);
@@ -162,6 +266,7 @@
 
 
 
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);
@@ -189,6 +294,7 @@
 
 
 
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| {
@@ -220,6 +326,7 @@
 
 
 
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);
@@ -247,6 +354,7 @@
 
 
 
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);
     };
@@ -257,7 +365,7 @@
 
 
 
-
+
 
 ## Specification
 
@@ -324,12 +432,31 @@
 
 
 
pragma verify = true;
-pragma aborts_if_is_strict;
+pragma aborts_if_is_partial;
 
- + + +### 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` @@ -362,13 +489,14 @@ Aborts if conditions of SetStackingContractVoter and SetStackPoolVoterAbortsIf a
pragma aborts_if_is_partial;
+pragma verify_duration_estimate = 120;
 include SetStakingContractVoter;
 include SetStakePoolVoterAbortsIf;
 
- + ### Function `set_vesting_contract_operator` @@ -384,7 +512,7 @@ Aborts if conditions of SetStackingContractVoter and SetStackPoolVoterAbortsIf a - + ### Function `set_staking_contract_operator` @@ -438,7 +566,7 @@ Aborts if conditions of SetStackingContractVoter and SetStackPoolVoterAbortsIf a - + ### Function `set_stake_pool_operator` @@ -452,6 +580,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 +597,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 +610,7 @@ One of them are not exists
 
 
 
-
+
 
 ### Function `set_vesting_contract_voter`
 
@@ -489,7 +626,7 @@ One of them are not exists
 
 
 
-
+
 
 ### Function `set_staking_contract_voter`
 
@@ -501,6 +638,9 @@ One of them are not exists
 
 
 
include SetStakingContractVoter;
+include AbortsIfSignerPermissionStakeProxy {
+    s: owner
+};
 
@@ -531,7 +671,7 @@ Then abort if the resource is not exist - + ### Function `set_stake_pool_voter` @@ -543,6 +683,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 +700,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 +712,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/vesting.md b/aptos-move/framework/aptos-framework/doc/vesting.md index 95b80f3be991a..9d7cf203786c9 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_vest_permission`](#0x1_vesting_check_vest_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,34 @@ withdrawable, admin can call admin_withdraw to withdraw all funds to the vesting + + + + +## Struct `VestPermission` + +Permissions to mutate the vesting config for a given account. + + +
struct VestPermission has copy, drop, store
+
+ + + +
+Fields + + +
+
+dummy_field: bool +
+
+ +
+
+ +
@@ -1470,6 +1502,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 +1682,59 @@ Vesting contract has been terminated and all funds have been released back to th + + +## Function `check_vest_permission` + +Permissions + + +
fun check_vest_permission(s: &signer)
+
+ + + +
+Implementation + + +
inline fun check_vest_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` @@ -2162,6 +2257,7 @@ Create a vesting contract with a given configurations. // Optional seed used when creating the staking contract account. contract_creation_seed: vector<u8>, ): address acquires AdminStore { + check_vest_permission(admin); assert!( !system_addresses::is_reserved_address(withdrawal_address), error::invalid_argument(EINVALID_WITHDRAWAL_ADDRESS), @@ -2980,6 +3076,7 @@ account. contract_address: address, shareholder: address, ) acquires VestingAccountManagement, VestingContract { + check_vest_permission(account); let vesting_contract = borrow_global_mut<VestingContract>(contract_address); let addr = signer::address_of(account); assert!( @@ -3199,6 +3296,7 @@ This address should be deterministic for the same admin and vesting contract cre admin: &signer, contract_creation_seed: vector<u8>, ): (signer, SignerCapability) acquires AdminStore { + check_vest_permission(admin); let admin_store = borrow_global_mut<AdminStore>(signer::address_of(admin)); let seed = bcs::to_bytes(&signer::address_of(admin)); vector::append(&mut seed, bcs::to_bytes(&admin_store.nonce)); @@ -3238,6 +3336,7 @@ This address should be deterministic for the same admin and vesting contract cre
fun verify_admin(admin: &signer, vesting_contract: &VestingContract) {
+    check_vest_permission(admin);
     assert!(signer::address_of(admin) == vesting_contract.admin, error::unauthenticated(ENOT_ADMIN));
 }
 
@@ -3492,7 +3591,7 @@ This address should be deterministic for the same admin and vesting contract cre
pragma verify = true;
-pragma aborts_if_is_strict;
+pragma aborts_if_is_partial;
 // This enforces high-level requirement 2:
 invariant forall a: address where exists<VestingContract>(a):
     global<VestingContract>(a).grant_pool.shareholders_limit <= MAXIMUM_SHAREHOLDERS;
@@ -3500,6 +3599,19 @@ This address should be deterministic for the same admin and vesting contract cre
 
 
 
+
+
+
+
+
schema AbortsIfPermissionedSigner {
+    s: signer;
+    let perm = VestPermission {};
+    aborts_if !permissioned_signer::spec_check_permission_exists(s, perm);
+}
+
+ + + ### Function `stake_pool_address` @@ -4247,7 +4359,8 @@ This address should be deterministic for the same admin and vesting contract cre -
include VerifyAdminAbortsIf;
+
pragma verify_duration_estimate = 120;
+include VerifyAdminAbortsIf;
 
@@ -4327,7 +4440,9 @@ This address should be deterministic for the same admin and vesting contract cre -
// This enforces high-level requirement 9:
+
pragma verify_duration_estimate = 120;
+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 +4623,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..6d9187b10d064 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_vote_permission`](#0x1_voting_check_vote_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_vote_permission` + +Permissions + + +
fun check_vote_permission(s: &signer)
+
+ + + +
+Implementation + + +
inline fun check_vote_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_vote_permission(account);
     let addr = signer::address_of(account);
     assert!(!exists<VotingForum<ProposalType>>(addr), error::already_exists(EVOTING_FORUM_ALREADY_REGISTERED));
 
@@ -1910,7 +2005,20 @@ Return true if the voting period of the given proposal has already ended.
 
 
 
pragma verify = true;
-pragma aborts_if_is_strict;
+pragma aborts_if_is_partial;
+
+ + + + + + + +
schema AbortsIfPermissionedSigner {
+    s: signer;
+    let perm = VotePermission {};
+    aborts_if !permissioned_signer::spec_check_permission_exists(s, perm);
+}
 
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/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/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/resource_account.spec.move b/aptos-move/framework/aptos-framework/sources/resource_account.spec.move index e5cce243cd84b..2bd5dd294f2e0 100644 --- a/aptos-move/framework/aptos-framework/sources/resource_account.spec.move +++ b/aptos-move/framework/aptos-framework/sources/resource_account.spec.move @@ -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; } 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 3b7a01b8710a6..29eea04f434ce 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,11 @@ spec aptos_framework::stake { network_addresses: vector, fullnode_addresses: vector, ){ + pragma verify = false; + + 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 +176,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 +232,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 +274,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 +312,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 +372,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 +421,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 +529,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; @@ -595,6 +629,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; @@ -672,7 +707,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(); @@ -680,10 +715,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; @@ -697,7 +735,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_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/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),