From 2c9dece4b0d82a1110554d6f38d5b4a94af413eb Mon Sep 17 00:00:00 2001 From: Junkil Park Date: Thu, 19 Dec 2024 16:11:43 -0800 Subject: [PATCH] Refine Voting Error Codes (#15590) This commit refines voting error codes by introducing more specific classifications: ENO_VOTING_POWER, EINSUFFICIENT_STAKE_LOCKUP, and EPROPOSAL_EXPIRED. --- .../aptos-framework/doc/aptos_governance.md | 81 ++++- .../aptos-framework/doc/delegation_pool.md | 1 + .../framework/aptos-framework/doc/stake.md | 305 +----------------- .../sources/aptos_governance.move | 84 ++++- .../sources/aptos_governance.spec.move | 10 + .../sources/delegation_pool.move | 121 ++++++- .../aptos-framework/sources/stake.spec.move | 4 + 7 files changed, 289 insertions(+), 317 deletions(-) diff --git a/aptos-move/framework/aptos-framework/doc/aptos_governance.md b/aptos-move/framework/aptos-framework/doc/aptos_governance.md index ef3a011b5ed74..c7c97bebe112c 100644 --- a/aptos-move/framework/aptos-framework/doc/aptos_governance.md +++ b/aptos-move/framework/aptos-framework/doc/aptos_governance.md @@ -39,6 +39,7 @@ on a proposal multiple times as long as the total voting power of these votes do - [Function `get_required_proposer_stake`](#0x1_aptos_governance_get_required_proposer_stake) - [Function `has_entirely_voted`](#0x1_aptos_governance_has_entirely_voted) - [Function `get_remaining_voting_power`](#0x1_aptos_governance_get_remaining_voting_power) +- [Function `assert_proposal_expiration`](#0x1_aptos_governance_assert_proposal_expiration) - [Function `create_proposal`](#0x1_aptos_governance_create_proposal) - [Function `create_proposal_v2`](#0x1_aptos_governance_create_proposal_v2) - [Function `create_proposal_v2_impl`](#0x1_aptos_governance_create_proposal_v2_impl) @@ -74,6 +75,7 @@ on a proposal multiple times as long as the total voting power of these votes do - [Function `get_required_proposer_stake`](#@Specification_1_get_required_proposer_stake) - [Function `has_entirely_voted`](#@Specification_1_has_entirely_voted) - [Function `get_remaining_voting_power`](#@Specification_1_get_remaining_voting_power) + - [Function `assert_proposal_expiration`](#@Specification_1_assert_proposal_expiration) - [Function `create_proposal`](#@Specification_1_create_proposal) - [Function `create_proposal_v2`](#@Specification_1_create_proposal_v2) - [Function `create_proposal_v2_impl`](#@Specification_1_create_proposal_v2_impl) @@ -758,6 +760,16 @@ Partial voting feature hasn't been properly initialized. + + +The proposal has expired. + + +
const EPROPOSAL_EXPIRED: u64 = 15;
+
+ + + Proposal is not ready to be resolved. Waiting on time or votes @@ -1160,6 +1172,43 @@ Note: a stake pool's voting power on a proposal could increase over time(e.g. re + + + + +## Function `assert_proposal_expiration` + + + +
public fun assert_proposal_expiration(stake_pool: address, proposal_id: u64)
+
+ + + +
+Implementation + + +
public fun assert_proposal_expiration(stake_pool: address, proposal_id: u64) {
+    assert_voting_initialization();
+    let proposal_expiration = voting::get_proposal_expiration_secs<GovernanceProposal>(
+        @aptos_framework,
+        proposal_id
+    );
+    // The voter's stake needs to be locked up at least as long as the proposal's expiration.
+    assert!(
+        proposal_expiration <= stake::get_lockup_secs(stake_pool),
+        error::invalid_argument(EINSUFFICIENT_STAKE_LOCKUP),
+    );
+    assert!(
+        timestamp::now_seconds() <= proposal_expiration,
+        error::invalid_argument(EPROPOSAL_EXPIRED),
+    );
+}
+
+ + +
@@ -1498,15 +1547,7 @@ cannot vote on the proposal even after partial governance voting is enabled. let voter_address = signer::address_of(voter); assert!(stake::get_delegated_voter(stake_pool) == voter_address, error::invalid_argument(ENOT_DELEGATED_VOTER)); - // The voter's stake needs to be locked up at least as long as the proposal's expiration. - let proposal_expiration = voting::get_proposal_expiration_secs<GovernanceProposal>( - @aptos_framework, - proposal_id - ); - assert!( - stake::get_lockup_secs(stake_pool) >= proposal_expiration, - error::invalid_argument(EINSUFFICIENT_STAKE_LOCKUP), - ); + assert_proposal_expiration(stake_pool, proposal_id); // If a stake pool has already voted on a proposal before partial governance voting is enabled, // `get_remaining_voting_power` returns 0. @@ -2435,6 +2476,28 @@ Abort if structs have already been created. + + +### Function `assert_proposal_expiration` + + +
public fun assert_proposal_expiration(stake_pool: address, proposal_id: u64)
+
+ + + + +
include VotingInitializationAbortIfs;
+include voting::AbortsIfNotContainProposalID<GovernanceProposal>{voting_forum_address: @aptos_framework};
+let proposal_expiration = voting::spec_get_proposal_expiration_secs<GovernanceProposal>(@aptos_framework, proposal_id);
+aborts_if !stake::stake_pool_exists(stake_pool);
+aborts_if proposal_expiration > stake::spec_get_lockup_secs(stake_pool);
+aborts_if !exists<timestamp::CurrentTimeMicroseconds>(@aptos_framework);
+aborts_if timestamp::now_seconds() > proposal_expiration;
+
+ + + ### Function `create_proposal` diff --git a/aptos-move/framework/aptos-framework/doc/delegation_pool.md b/aptos-move/framework/aptos-framework/doc/delegation_pool.md index 268d0618fb722..f9c83df1ff80f 100644 --- a/aptos-move/framework/aptos-framework/doc/delegation_pool.md +++ b/aptos-move/framework/aptos-framework/doc/delegation_pool.md @@ -2955,6 +2955,7 @@ Vote on a proposal with a voter's voting power. To successfully vote, the follow if (voting_power > remaining_voting_power) { voting_power = remaining_voting_power; }; + aptos_governance::assert_proposal_expiration(pool_address, proposal_id); assert!(voting_power > 0, error::invalid_argument(ENO_VOTING_POWER)); let governance_records = borrow_global_mut<GovernanceRecords>(pool_address); diff --git a/aptos-move/framework/aptos-framework/doc/stake.md b/aptos-move/framework/aptos-framework/doc/stake.md index 53d815ae1ed8a..dedfe15027641 100644 --- a/aptos-move/framework/aptos-framework/doc/stake.md +++ b/aptos-move/framework/aptos-framework/doc/stake.md @@ -4565,148 +4565,6 @@ Returns validator's next epoch voting power, including pending_active, active, a - - - - -
fun spec_validator_index_upper_bound(): u64 {
-   len(global<ValidatorPerformance>(@aptos_framework).validators)
-}
-
- - - - - - - -
fun spec_has_stake_pool(a: address): bool {
-   exists<StakePool>(a)
-}
-
- - - - - - - -
fun spec_has_validator_config(a: address): bool {
-   exists<ValidatorConfig>(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` @@ -5492,158 +5350,6 @@ Returns validator's next epoch voting power, including pending_active, active, a - - - - -
schema AddStakeWithCapAbortsIfAndEnsures {
-    owner_cap: OwnerCapability;
-    amount: u64;
-    let pool_address = owner_cap.pool_address;
-    aborts_if !exists<StakePool>(pool_address);
-    let config = global<staking_config::StakingConfig>(@aptos_framework);
-    let validator_set = global<ValidatorSet>(@aptos_framework);
-    let voting_power_increase_limit = config.voting_power_increase_limit;
-    let post post_validator_set = global<ValidatorSet>(@aptos_framework);
-    let update_voting_power_increase = amount != 0 && (spec_contains(validator_set.active_validators, pool_address)
-                                                       || spec_contains(validator_set.pending_active, pool_address));
-    aborts_if update_voting_power_increase && validator_set.total_joining_power + amount > MAX_U128;
-    ensures update_voting_power_increase ==> post_validator_set.total_joining_power == validator_set.total_joining_power + amount;
-    aborts_if update_voting_power_increase && validator_set.total_voting_power > 0
-            && validator_set.total_voting_power * voting_power_increase_limit > MAX_U128;
-    aborts_if update_voting_power_increase && validator_set.total_voting_power > 0
-            && validator_set.total_joining_power + amount > validator_set.total_voting_power * voting_power_increase_limit / 100;
-    let stake_pool = global<StakePool>(pool_address);
-    let post post_stake_pool = global<StakePool>(pool_address);
-    let value_pending_active = stake_pool.pending_active.value;
-    let value_active = stake_pool.active.value;
-    ensures amount != 0 && spec_is_current_epoch_validator(pool_address) ==> post_stake_pool.pending_active.value == value_pending_active + amount;
-    ensures amount != 0 && !spec_is_current_epoch_validator(pool_address) ==> post_stake_pool.active.value == value_active + amount;
-    let maximum_stake = config.maximum_stake;
-    let value_pending_inactive = stake_pool.pending_inactive.value;
-    let next_epoch_voting_power = value_pending_active + value_active + value_pending_inactive;
-    let voting_power = next_epoch_voting_power + amount;
-    aborts_if amount != 0 && voting_power > MAX_U64;
-    aborts_if amount != 0 && voting_power > maximum_stake;
-}
-
- - - - - - - -
schema AddStakeAbortsIfAndEnsures {
-    owner: signer;
-    amount: u64;
-    let owner_address = signer::address_of(owner);
-    aborts_if !exists<OwnerCapability>(owner_address);
-    let owner_cap = global<OwnerCapability>(owner_address);
-    include AddStakeWithCapAbortsIfAndEnsures { owner_cap };
-}
-
- - - - - - - -
fun spec_is_allowed(account: address): bool {
-   if (!exists<AllowedValidators>(@aptos_framework)) {
-       true
-   } else {
-       let allowed = global<AllowedValidators>(@aptos_framework);
-       contains(allowed.accounts, account)
-   }
-}
-
- - - - - - - -
fun spec_find_validator(v: vector<ValidatorInfo>, addr: address): Option<u64>;
-
- - - - - - - -
fun spec_validators_are_initialized(validators: vector<ValidatorInfo>): bool {
-   forall i in 0..len(validators):
-       spec_has_stake_pool(validators[i].addr) &&
-           spec_has_validator_config(validators[i].addr)
-}
-
- - - - - - - -
fun spec_validators_are_initialized_addrs(addrs: vector<address>): bool {
-   forall i in 0..len(addrs):
-       spec_has_stake_pool(addrs[i]) &&
-           spec_has_validator_config(addrs[i])
-}
-
- - - - - - - -
fun spec_validator_indices_are_valid(validators: vector<ValidatorInfo>): bool {
-   spec_validator_indices_are_valid_addr(validators, spec_validator_index_upper_bound()) &&
-       spec_validator_indices_are_valid_config(validators, spec_validator_index_upper_bound())
-}
-
- - - - - - - -
fun spec_validator_indices_are_valid_addr(validators: vector<ValidatorInfo>, upper_bound: u64): bool {
-   forall i in 0..len(validators):
-       global<ValidatorConfig>(validators[i].addr).validator_index < upper_bound
-}
-
- - - - - - - -
fun spec_validator_indices_are_valid_config(validators: vector<ValidatorInfo>, upper_bound: u64): bool {
-   forall i in 0..len(validators):
-       validators[i].config.validator_index < upper_bound
-}
-
- - - - - - - -
fun spec_validator_indices_active_pending_inactive(validator_set: ValidatorSet): bool {
-   len(validator_set.pending_inactive) + len(validator_set.active_validators) == spec_validator_index_upper_bound()
-}
-
- - - ### Function `update_stake_pool` @@ -5755,6 +5461,17 @@ Returns validator's next epoch voting power, including pending_active, active, a + + + + +
fun spec_get_lockup_secs(pool_address: address): u64 {
+   global<StakePool>(pool_address).locked_until_secs
+}
+
+ + + ### Function `calculate_rewards_amount` diff --git a/aptos-move/framework/aptos-framework/sources/aptos_governance.move b/aptos-move/framework/aptos-framework/sources/aptos_governance.move index fb1469bc7e684..f5de768d05864 100644 --- a/aptos-move/framework/aptos-framework/sources/aptos_governance.move +++ b/aptos-move/framework/aptos-framework/sources/aptos_governance.move @@ -62,6 +62,8 @@ module aptos_framework::aptos_governance { const EPARTIAL_VOTING_NOT_INITIALIZED: u64 = 13; /// The proposal in the argument is not a partial voting proposal. const ENOT_PARTIAL_VOTING_PROPOSAL: u64 = 14; + /// The proposal has expired. + const EPROPOSAL_EXPIRED: u64 = 15; /// 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; @@ -331,6 +333,23 @@ module aptos_framework::aptos_governance { get_voting_power(stake_pool) - used_voting_power } + public fun assert_proposal_expiration(stake_pool: address, proposal_id: u64) { + assert_voting_initialization(); + let proposal_expiration = voting::get_proposal_expiration_secs( + @aptos_framework, + proposal_id + ); + // The voter's stake needs to be locked up at least as long as the proposal's expiration. + assert!( + proposal_expiration <= stake::get_lockup_secs(stake_pool), + error::invalid_argument(EINSUFFICIENT_STAKE_LOCKUP), + ); + assert!( + timestamp::now_seconds() <= proposal_expiration, + error::invalid_argument(EPROPOSAL_EXPIRED), + ); + } + /// Create a single-step proposal with the backing `stake_pool`. /// @param execution_hash Required. This is the hash of the resolution script. When the proposal is resolved, /// only the exact script with matching hash can be successfully executed. @@ -512,15 +531,7 @@ module aptos_framework::aptos_governance { let voter_address = signer::address_of(voter); assert!(stake::get_delegated_voter(stake_pool) == voter_address, error::invalid_argument(ENOT_DELEGATED_VOTER)); - // The voter's stake needs to be locked up at least as long as the proposal's expiration. - let proposal_expiration = voting::get_proposal_expiration_secs( - @aptos_framework, - proposal_id - ); - assert!( - stake::get_lockup_secs(stake_pool) >= proposal_expiration, - error::invalid_argument(EINSUFFICIENT_STAKE_LOCKUP), - ); + assert_proposal_expiration(stake_pool, proposal_id); // If a stake pool has already voted on a proposal before partial governance voting is enabled, // `get_remaining_voting_power` returns 0. @@ -963,7 +974,7 @@ module aptos_framework::aptos_governance { } #[test(aptos_framework = @aptos_framework, proposer = @0x123, voter_1 = @0x234, voter_2 = @345)] - #[expected_failure(abort_code = 0x10004, location = aptos_framework::voting)] + #[expected_failure(abort_code = 65541, location = aptos_framework::aptos_governance)] public entry fun test_cannot_double_vote( aptos_framework: signer, proposer: signer, @@ -975,7 +986,7 @@ module aptos_framework::aptos_governance { create_proposal( &proposer, signer::address_of(&proposer), - b"", + b"0", b"", b"", ); @@ -986,7 +997,54 @@ module aptos_framework::aptos_governance { } #[test(aptos_framework = @aptos_framework, proposer = @0x123, voter_1 = @0x234, voter_2 = @345)] - #[expected_failure(abort_code = 0x10004, location = aptos_framework::voting)] + #[expected_failure(abort_code = 65551, location = aptos_framework::aptos_governance)] + public entry fun test_cannot_vote_for_expired_proposal( + aptos_framework: signer, + proposer: signer, + voter_1: signer, + voter_2: signer, + ) acquires ApprovedExecutionHashes, GovernanceConfig, GovernanceResponsbility, VotingRecords, VotingRecordsV2, GovernanceEvents { + setup_partial_voting_with_initialized_stake(&aptos_framework, &proposer, &voter_1, &voter_2); + + create_proposal( + &proposer, + signer::address_of(&proposer), + b"0", + b"", + b"", + ); + + timestamp::fast_forward_seconds(2000); + stake::end_epoch(); + + // Should abort because the proposal has expired. + vote(&voter_1, signer::address_of(&voter_1), 0, true); + } + + #[test(aptos_framework = @aptos_framework, proposer = @0x123, voter_1 = @0x234, voter_2 = @0x345)] + #[expected_failure(abort_code = 65539, location = aptos_framework::aptos_governance)] + public entry fun test_cannot_vote_due_to_insufficient_stake_lockup( + aptos_framework: signer, + proposer: signer, + voter_1: signer, + voter_2: signer, + ) acquires ApprovedExecutionHashes, GovernanceConfig, GovernanceResponsbility, VotingRecords, VotingRecordsV2, GovernanceEvents { + setup_partial_voting_with_initialized_stake(&aptos_framework, &proposer, &voter_1, &voter_2); + + create_proposal( + &proposer, + signer::address_of(&proposer), + b"0", + b"", + b"", + ); + + // Should abort due to insufficient stake lockup. + vote(&voter_1, signer::address_of(&voter_1), 0, true); + } + + #[test(aptos_framework = @aptos_framework, proposer = @0x123, voter_1 = @0x234, voter_2 = @345)] + #[expected_failure(abort_code = 65541, location = aptos_framework::aptos_governance)] public entry fun test_cannot_double_vote_with_different_voter_addresses( aptos_framework: signer, proposer: signer, @@ -998,7 +1056,7 @@ module aptos_framework::aptos_governance { create_proposal( &proposer, signer::address_of(&proposer), - b"", + b"0", b"", b"", ); 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 c3d18d593533c..3ea2572dd8fea 100644 --- a/aptos-move/framework/aptos-framework/sources/aptos_governance.spec.move +++ b/aptos-move/framework/aptos-framework/sources/aptos_governance.spec.move @@ -832,6 +832,16 @@ spec aptos_framework::aptos_governance { include VotingInitializationAbortIfs; } + spec assert_proposal_expiration(stake_pool: address, proposal_id: u64) { + include VotingInitializationAbortIfs; + include voting::AbortsIfNotContainProposalID{voting_forum_address: @aptos_framework}; + let proposal_expiration = voting::spec_get_proposal_expiration_secs(@aptos_framework, proposal_id); + aborts_if !stake::stake_pool_exists(stake_pool); + aborts_if proposal_expiration > stake::spec_get_lockup_secs(stake_pool); + aborts_if !exists(@aptos_framework); + aborts_if timestamp::now_seconds() > proposal_expiration; + } + spec force_end_epoch(aptos_framework: &signer) { use aptos_framework::reconfiguration_with_dkg; use std::signer; diff --git a/aptos-move/framework/aptos-framework/sources/delegation_pool.move b/aptos-move/framework/aptos-framework/sources/delegation_pool.move index 0e21967dcb53b..3b5cc7600f881 100644 --- a/aptos-move/framework/aptos-framework/sources/delegation_pool.move +++ b/aptos-move/framework/aptos-framework/sources/delegation_pool.move @@ -954,6 +954,7 @@ module aptos_framework::delegation_pool { if (voting_power > remaining_voting_power) { voting_power = remaining_voting_power; }; + aptos_governance::assert_proposal_expiration(pool_address, proposal_id); assert!(voting_power > 0, error::invalid_argument(ENO_VOTING_POWER)); let governance_records = borrow_global_mut(pool_address); @@ -4816,7 +4817,7 @@ module aptos_framework::delegation_pool { validator: &signer, delegator1: &signer, ) acquires DelegationPoolOwnership, DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage, DelegationPoolAllowlisting { - // This test case checks the scenario where delegation_pool_partial_governance_voting is disabled. + // This test case begins with the delegation_pool_partial_governance_voting feature disabled. features::change_feature_flags_for_testing( aptos_framework, vector[], @@ -4834,6 +4835,124 @@ module aptos_framework::delegation_pool { vote(delegator1, pool_address, proposal1_id, 10 * ONE_APT, true); } + #[test(aptos_framework = @aptos_framework, validator1 = @0x123, validator2 = @0x234, delegator1 = @0x010, delegator2 = @0x020)] + #[expected_failure(abort_code = 65539, location = aptos_framework::aptos_governance)] + public entry fun test_vote_should_failed_due_to_insufficient_stake_lockup ( + aptos_framework: &signer, + validator1: &signer, + validator2: &signer, + delegator1: &signer, + delegator2: &signer, + ) acquires DelegationPoolOwnership, DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage, DelegationPoolAllowlisting { + // This test case begins with the delegation_pool_partial_governance_voting feature disabled. + features::change_feature_flags_for_testing( + aptos_framework, + vector[], + vector[features::get_delegation_pool_partial_governance_voting()] + ); + + let _ = setup_vote(aptos_framework, validator1, false); + + let validator1_address = signer::address_of(validator1); + let pool1_address = get_owned_pool_address(validator1_address); + let delegator1_address = signer::address_of(delegator1); + account::create_account_for_test(delegator1_address); + stake::mint(delegator1, 110 * ONE_APT); + add_stake(delegator1, pool1_address, 10 * ONE_APT); + + timestamp::fast_forward_seconds(LOCKUP_CYCLE_SECONDS - 500); + end_aptos_epoch(); + + initialize_test_validator(validator2, 100 * ONE_APT, true, true); + let validator2_address = signer::address_of(validator2); + let pool2_address = get_owned_pool_address(validator2_address); + let delegator2_address = signer::address_of(delegator2); + account::create_account_for_test(delegator2_address); + stake::mint(delegator2, 110 * ONE_APT); + add_stake(delegator2, pool2_address, 10 * ONE_APT); + + let proposal_id = aptos_governance::create_proposal_v2_impl( + validator2, + pool2_address, + b"0", + b"", + b"", + true, + ); + + features::change_feature_flags_for_testing( + aptos_framework, + vector[features::get_delegation_pool_partial_governance_voting()], + vector[] + ); + enable_partial_governance_voting(pool1_address); + enable_partial_governance_voting(pool2_address); + + vote(delegator2, pool2_address, proposal_id, 10 * ONE_APT, true); + // Should abort with the error EINSUFFICIENT_STAKE_LOCKUP. + vote(delegator1, pool1_address, proposal_id, 10 * ONE_APT, true); + } + + #[test(aptos_framework = @aptos_framework, validator = @0x123, delegator1 = @0x010)] + #[expected_failure(abort_code = 65551, location = aptos_framework::aptos_governance)] + public entry fun test_vote_should_failed_due_to_proposal_expired( + aptos_framework: &signer, + validator: &signer, + delegator1: &signer, + ) acquires DelegationPoolOwnership, DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage, DelegationPoolAllowlisting { + // This test case begins with the delegation_pool_partial_governance_voting feature disabled. + features::change_feature_flags_for_testing( + aptos_framework, + vector[], + vector[features::get_delegation_pool_partial_governance_voting()] + ); + // partial voing hasn't been enabled yet. A proposal has been created by the validator. + let proposal1_id = setup_vote(aptos_framework, validator, true); + + let validator_address = signer::address_of(validator); + let pool_address = get_owned_pool_address(validator_address); + let delegator1_address = signer::address_of(delegator1); + account::create_account_for_test(delegator1_address); + + stake::mint(delegator1, 110 * ONE_APT); + add_stake(delegator1, pool_address, 10 * ONE_APT); + + timestamp::fast_forward_seconds(2000); + end_aptos_epoch(); + + // Should abort with the error EPROPOSAL_EXPIRED. + vote(delegator1, pool_address, proposal1_id, 10 * ONE_APT, true); + } + + #[test(aptos_framework = @aptos_framework, validator = @0x123, delegator1 = @0x010)] + #[expected_failure(abort_code = 0x10010, location = Self)] + public entry fun test_vote_should_failed_due_to_insufficient_voting_power( + aptos_framework: &signer, + validator: &signer, + delegator1: &signer, + ) acquires DelegationPoolOwnership, DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage, DelegationPoolAllowlisting { + // This test case checks the scenario where delegation_pool_partial_governance_voting is disabled. + features::change_feature_flags_for_testing( + aptos_framework, + vector[], + vector[features::get_delegation_pool_partial_governance_voting()] + ); + // partial voing hasn't been enabled yet. A proposal has been created by the validator. + let proposal1_id = setup_vote(aptos_framework, validator, true); + + let validator_address = signer::address_of(validator); + let pool_address = get_owned_pool_address(validator_address); + let delegator1_address = signer::address_of(delegator1); + account::create_account_for_test(delegator1_address); + + stake::mint(delegator1, 110 * ONE_APT); + add_stake(delegator1, pool_address, 10 * ONE_APT); + end_aptos_epoch(); + vote(delegator1, pool_address, proposal1_id, 10 * ONE_APT, true); + // Should abort with the error ENO_VOTING_POWER. + vote(delegator1, pool_address, proposal1_id, 10 * ONE_APT, true); + } + #[test(aptos_framework = @aptos_framework, validator = @0x123, delegator1 = @0x010, voter1 = @0x030)] public entry fun test_delegate_voting_power_should_pass_even_if_no_stake( aptos_framework: &signer, diff --git a/aptos-move/framework/aptos-framework/sources/stake.spec.move b/aptos-move/framework/aptos-framework/sources/stake.spec.move index 975f77bd14b1f..f922821e8ac83 100644 --- a/aptos-move/framework/aptos-framework/sources/stake.spec.move +++ b/aptos-move/framework/aptos-framework/sources/stake.spec.move @@ -586,6 +586,10 @@ spec aptos_framework::stake { } } + spec fun spec_get_lockup_secs(pool_address: address): u64 { + global(pool_address).locked_until_secs + } + spec calculate_rewards_amount { pragma opaque; // TODO: set because of timeout (property proved)