diff --git a/aptos-move/framework/aptos-framework/doc/aptos_governance.md b/aptos-move/framework/aptos-framework/doc/aptos_governance.md index a1af79916d8..72d8499f0ab 100644 --- a/aptos-move/framework/aptos-framework/doc/aptos_governance.md +++ b/aptos-move/framework/aptos-framework/doc/aptos_governance.md @@ -37,6 +37,8 @@ on a proposal multiple times as long as the total voting power of these votes do - [Function `initialize`](#0x1_aptos_governance_initialize) - [Function `update_governance_config`](#0x1_aptos_governance_update_governance_config) - [Function `initialize_partial_voting`](#0x1_aptos_governance_initialize_partial_voting) +- [Function `partial_voting_initialized`](#0x1_aptos_governance_partial_voting_initialized) +- [Function `initialize_partial_voting_if_needed`](#0x1_aptos_governance_initialize_partial_voting_if_needed) - [Function `get_voting_duration_secs`](#0x1_aptos_governance_get_voting_duration_secs) - [Function `get_min_voting_threshold`](#0x1_aptos_governance_get_min_voting_threshold) - [Function `get_required_proposer_stake`](#0x1_aptos_governance_get_required_proposer_stake) @@ -72,6 +74,8 @@ on a proposal multiple times as long as the total voting power of these votes do - [Function `initialize`](#@Specification_1_initialize) - [Function `update_governance_config`](#@Specification_1_update_governance_config) - [Function `initialize_partial_voting`](#@Specification_1_initialize_partial_voting) + - [Function `partial_voting_initialized`](#@Specification_1_partial_voting_initialized) + - [Function `initialize_partial_voting_if_needed`](#@Specification_1_initialize_partial_voting_if_needed) - [Function `get_voting_duration_secs`](#@Specification_1_get_voting_duration_secs) - [Function `get_min_voting_threshold`](#@Specification_1_get_min_voting_threshold) - [Function `get_required_proposer_stake`](#@Specification_1_get_required_proposer_stake) @@ -1099,6 +1103,65 @@ proposals with a signer for the aptos_framework (0x1) account. + + + + +## Function `partial_voting_initialized` + + + +
#[view]
+public fun partial_voting_initialized(): bool
+
+ + + +
+Implementation + + +
public fun partial_voting_initialized(): bool {
+    exists<VotingRecordsV2>(@aptos_framework)
+}
+
+ + + +
+ + + +## Function `initialize_partial_voting_if_needed` + +Initializes the state for Aptos Governance partial voting if it has not already been initialized. +This can only be called with a signer for the aptos_framework (0x1) account. + + +
public fun initialize_partial_voting_if_needed(aptos_framework: &signer)
+
+ + + +
+Implementation + + +
public fun initialize_partial_voting_if_needed(
+    aptos_framework: &signer,
+) {
+    system_addresses::assert_aptos_framework(aptos_framework);
+
+    if (!partial_voting_initialized()) {
+        move_to(aptos_framework, VotingRecordsV2 {
+            votes: smart_table::new(),
+        });
+    }
+}
+
+ + +
@@ -2319,6 +2382,44 @@ Abort if structs have already been created. + + +### Function `partial_voting_initialized` + + +
#[view]
+public fun partial_voting_initialized(): bool
+
+ + + + +
pragma opaque;
+aborts_if false;
+ensures result == exists<VotingRecordsV2>(@aptos_framework);
+
+ + + + + +### Function `initialize_partial_voting_if_needed` + + +
public fun initialize_partial_voting_if_needed(aptos_framework: &signer)
+
+ + +Signer address must be @aptos_framework. + + +
let addr = signer::address_of(aptos_framework);
+aborts_if addr != @aptos_framework;
+ensures exists<VotingRecordsV2>(@aptos_framework);
+
+ + + diff --git a/aptos-move/framework/aptos-framework/doc/delegation_pool.md b/aptos-move/framework/aptos-framework/doc/delegation_pool.md index 1b91662621b..065149909a1 100644 --- a/aptos-move/framework/aptos-framework/doc/delegation_pool.md +++ b/aptos-move/framework/aptos-framework/doc/delegation_pool.md @@ -152,6 +152,7 @@ transferred to A - [Function `owner_cap_exists`](#0x1_delegation_pool_owner_cap_exists) - [Function `get_owned_pool_address`](#0x1_delegation_pool_get_owned_pool_address) - [Function `delegation_pool_exists`](#0x1_delegation_pool_delegation_pool_exists) +- [Function `governance_records_initialized`](#0x1_delegation_pool_governance_records_initialized) - [Function `partial_governance_voting_enabled`](#0x1_delegation_pool_partial_governance_voting_enabled) - [Function `observed_lockup_cycle`](#0x1_delegation_pool_observed_lockup_cycle) - [Function `is_next_commission_percentage_effective`](#0x1_delegation_pool_is_next_commission_percentage_effective) @@ -179,6 +180,7 @@ transferred to A - [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) +- [Function `enable_partial_governance_voting_if_needed`](#0x1_delegation_pool_enable_partial_governance_voting_if_needed) - [Function `vote`](#0x1_delegation_pool_vote) - [Function `create_proposal`](#0x1_delegation_pool_create_proposal) - [Function `assert_owner_cap_exists`](#0x1_delegation_pool_assert_owner_cap_exists) @@ -2120,6 +2122,32 @@ Return whether a delegation pool exists at supplied address addr. + + + + +## Function `governance_records_initialized` + +Return whether a delegation pool has governance records initialized. + + +
#[view]
+public fun governance_records_initialized(pool_address: address): bool
+
+ + + +
+Implementation + + +
public fun governance_records_initialized(pool_address: address): bool {
+    exists<GovernanceRecords>(pool_address)
+}
+
+ + +
@@ -2140,7 +2168,7 @@ Return whether a delegation pool has already enabled partial governance voting.
public fun partial_governance_voting_enabled(pool_address: address): bool {
-    exists<GovernanceRecords>(pool_address) && stake::get_delegated_voter(pool_address) == pool_address
+    governance_records_initialized(pool_address) && stake::get_delegated_voter(pool_address) == pool_address
 }
 
@@ -3083,6 +3111,37 @@ The existing voter will be replaced. The function is permissionless. + + + + +## Function `enable_partial_governance_voting_if_needed` + +Enable partial governance voting on a delegation pool if it has not already been initialized. +This is intended for idempotent migration scripts over existing delegation pools. + + +
public entry fun enable_partial_governance_voting_if_needed(pool_address: address)
+
+ + + +
+Implementation + + +
public entry fun enable_partial_governance_voting_if_needed(
+    pool_address: address,
+) acquires DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage {
+    assert_delegation_pool_exists(pool_address);
+    if (!governance_records_initialized(pool_address)) {
+        enable_partial_governance_voting(pool_address);
+    }
+}
+
+ + +
diff --git a/aptos-move/framework/aptos-framework/doc/staking_contract.md b/aptos-move/framework/aptos-framework/doc/staking_contract.md index 85a4ffa6b75..4df53db1a5f 100644 --- a/aptos-move/framework/aptos-framework/doc/staking_contract.md +++ b/aptos-move/framework/aptos-framework/doc/staking_contract.md @@ -1319,6 +1319,16 @@ Store amount must be at least the min stake required for a stake pool to join th + + +Beneficiary cannot be a reserved address that cannot receive coin distributions. + + +
const EINVALID_BENEFICIARY_ADDRESS: u64 = 10;
+
+ + + Caller must be either the staker, operator, or beneficiary. @@ -2283,6 +2293,12 @@ the beneficiary. An operator can set one beneficiary for staking contract pools, assert!(features::operator_beneficiary_change_enabled(), std::error::invalid_state( EOPERATOR_BENEFICIARY_CHANGE_NOT_SUPPORTED )); + // @vm_reserved can never have an account created for it, so it can't receive coin distributions. + // Allowing it as a beneficiary would permanently brick distribution for the staking contract. + assert!( + new_beneficiary != @vm_reserved, + error::invalid_argument(EINVALID_BENEFICIARY_ADDRESS), + ); // The beneficiay address of an operator is stored under the operator's address. // So, the operator does not need to be validated with respect to a staking pool. let operator_addr = signer::address_of(operator); diff --git a/aptos-move/framework/cached-packages/src/aptos_framework_sdk_builder.rs b/aptos-move/framework/cached-packages/src/aptos_framework_sdk_builder.rs index 2cff97836bb..7cf74286cc3 100644 --- a/aptos-move/framework/cached-packages/src/aptos_framework_sdk_builder.rs +++ b/aptos-move/framework/cached-packages/src/aptos_framework_sdk_builder.rs @@ -525,6 +525,12 @@ pub enum EntryFunctionCall { pool_address: AccountAddress, }, + /// Enable partial governance voting on a delegation pool if it has not already been initialized. + /// This is intended for idempotent migration scripts over existing delegation pools. + DelegationPoolEnablePartialGovernanceVotingIfNeeded { + pool_address: AccountAddress, + }, + /// Evict a delegator that is not allowlisted by unlocking their entire stake. DelegationPoolEvictDelegator { delegator_address: AccountAddress, @@ -1657,6 +1663,9 @@ impl EntryFunctionCall { DelegationPoolEnablePartialGovernanceVoting { pool_address } => { delegation_pool_enable_partial_governance_voting(pool_address) }, + DelegationPoolEnablePartialGovernanceVotingIfNeeded { pool_address } => { + delegation_pool_enable_partial_governance_voting_if_needed(pool_address) + }, DelegationPoolEvictDelegator { delegator_address } => { delegation_pool_evict_delegator(delegator_address) }, @@ -3498,6 +3507,25 @@ pub fn delegation_pool_enable_partial_governance_voting( )) } +/// Enable partial governance voting on a delegation pool if it has not already been initialized. +/// This is intended for idempotent migration scripts over existing delegation pools. +pub fn delegation_pool_enable_partial_governance_voting_if_needed( + pool_address: AccountAddress, +) -> TransactionPayload { + TransactionPayload::EntryFunction(EntryFunction::new( + ModuleId::new( + AccountAddress::new([ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 1, + ]), + ident_str!("delegation_pool").to_owned(), + ), + ident_str!("enable_partial_governance_voting_if_needed").to_owned(), + vec![], + vec![bcs::to_bytes(&pool_address).unwrap()], + )) +} + /// Evict a delegator that is not allowlisted by unlocking their entire stake. pub fn delegation_pool_evict_delegator(delegator_address: AccountAddress) -> TransactionPayload { TransactionPayload::EntryFunction(EntryFunction::new( @@ -6722,6 +6750,20 @@ mod decoder { } } + pub fn delegation_pool_enable_partial_governance_voting_if_needed( + payload: &TransactionPayload, + ) -> Option { + if let TransactionPayload::EntryFunction(script) = payload { + Some( + EntryFunctionCall::DelegationPoolEnablePartialGovernanceVotingIfNeeded { + pool_address: bcs::from_bytes(script.args().get(0)?).ok()?, + }, + ) + } else { + None + } + } + pub fn delegation_pool_evict_delegator( payload: &TransactionPayload, ) -> Option { @@ -8390,6 +8432,10 @@ static SCRIPT_FUNCTION_DECODER_MAP: once_cell::sync::LazyALLOW_SERIALIZED_SCRIPT_ARGS as feature flag 72
const NATIVE_BRIDGE: u64 = 72;