Module 0x1::stake
Validator lifecycle:
- Prepare a validator node set up and call stake::initialize_validator
- Once ready to deposit stake (or have funds assigned by a staking service in exchange for ownership capability), call stake::add_stake (or *_with_cap versions if called from the staking service)
- Call stake::join_validator_set (or _with_cap version) to join the active validator set. Changes are effective in the next epoch.
- Validate and gain rewards. The stake will automatically be locked up for a fixed duration (set by governance) and automatically renewed at expiration.
- At any point, if the validator operator wants to update the consensus key or network/fullnode addresses, they can call stake::rotate_consensus_key and stake::update_network_and_fullnode_addresses. Similar to changes to stake, the changes to consensus key/network/fullnode addresses are only effective in the next epoch.
- Validator can request to unlock their stake at any time. However, their stake will only become withdrawable when their current lockup expires. This can be at most as long as the fixed lockup duration.
- After exiting, the validator can either explicitly leave the validator set by calling stake::leave_validator_set or if their stake drops below the min required, they would get removed at the end of the epoch.
- Validator can always rejoin the validator set by going through steps 2-3 again.
- An owner can always switch operators by calling stake::set_operator.
- An owner can always switch designated voter by calling stake::set_designated_voter.
- Resource
OwnerCapability - Resource
StakePool - Resource
ValidatorConfig - Struct
ValidatorInfo - Resource
ValidatorSet - Resource
PendingTransactionFee - Enum Resource
TransactionFeeConfig - Struct
DistributeTransactionFee - Resource
AptosCoinCapabilities - Struct
IndividualValidatorPerformance - Resource
ValidatorPerformance - Resource
PrecomputedValidatorSet - Struct
RegisterValidatorCandidateEvent - Struct
StakeManagementPermission - Struct
RegisterValidatorCandidate - Struct
SetOperatorEvent - Struct
SetOperator - Struct
AddStakeEvent - Struct
AddStake - Struct
ReactivateStakeEvent - Struct
ReactivateStake - Struct
RotateConsensusKeyEvent - Struct
RotateConsensusKey - Struct
UpdateNetworkAndFullnodeAddressesEvent - Struct
UpdateNetworkAndFullnodeAddresses - Struct
IncreaseLockupEvent - Struct
IncreaseLockup - Struct
JoinValidatorSetEvent - Struct
JoinValidatorSet - Struct
DistributeRewardsEvent - Struct
DistributeRewards - Struct
UnlockStakeEvent - Struct
UnlockStake - Struct
WithdrawStakeEvent - Struct
WithdrawStake - Struct
LeaveValidatorSetEvent - Struct
LeaveValidatorSet - Struct
ValidatorSetLivenessFallback - Resource
ValidatorFees - Resource
AllowedValidators - Resource
Ghost$ghost_valid_perf - Resource
Ghost$ghost_proposer_idx - Resource
Ghost$ghost_active_num - Resource
Ghost$ghost_pending_inactive_num - Constants
- Function
check_stake_permission - Function
grant_permission - Function
get_lockup_secs - Function
get_remaining_lockup_secs - Function
get_stake - Function
get_validator_state - Function
get_current_epoch_voting_power - Function
get_delegated_voter - Function
get_operator - Function
get_owned_pool_address - Function
owner_cap_exists - Function
get_pool_address_for_owner - Function
get_validator_index - Function
get_current_epoch_proposal_counts - Function
get_validator_config - Function
stake_pool_exists - Function
get_pending_transaction_fee - Function
initialize - Function
store_aptos_coin_mint_cap - Function
remove_validators - Function
initialize_pending_transaction_fee - Function
set_transaction_fee_limit_per_epoch_per_pool - Function
set_transaction_fee_config - Function
record_fee - Function
initialize_stake_owner - Function
initialize_validator - Function
initialize_owner - Function
extract_owner_cap - Function
deposit_owner_cap - Function
destroy_owner_cap - Function
set_operator - Function
set_operator_with_cap - Function
set_delegated_voter - Function
set_delegated_voter_with_cap - Function
add_stake - Function
add_stake_with_cap - Function
reactivate_stake - Function
reactivate_stake_with_cap - Function
rotate_consensus_key - Function
update_network_and_fullnode_addresses - Function
increase_lockup - Function
increase_lockup_with_cap - Function
join_validator_set - Function
join_validator_set_internal - Function
unlock - Function
unlock_with_cap - Function
withdraw - Function
withdraw_with_cap - Function
leave_validator_set - Function
is_current_epoch_validator - Function
update_performance_statistics - Function
on_new_epoch - Function
refresh_validator_set_in_place - Function
cur_validator_consensus_infos - Function
compute_simulated_validator_info - Function
next_validator_consensus_infos - Function
next_validator_consensus_infos_v2 - Function
validator_consensus_infos_from_validator_set - Function
addresses_from_validator_infos - Function
update_stake_pool - Function
get_reconfig_start_time_secs - Function
calculate_rewards_amount - Function
distribute_rewards - Function
append - Function
find_validator - Function
generate_validator_info - Function
get_voting_power - Function
update_voting_power_increase - Function
assert_stake_pool_exists - Function
configure_allowed_validators - Function
is_allowed - Function
assert_owner_cap_exists - Function
assert_reconfig_not_in_progress - Specification
- High-level Requirements
- Module-level Specification
- Resource
ValidatorSet - Function
get_validator_state - Function
get_pending_transaction_fee - Function
initialize - Function
remove_validators - Function
initialize_stake_owner - Function
initialize_validator - Function
extract_owner_cap - Function
deposit_owner_cap - Function
set_operator_with_cap - Function
set_delegated_voter_with_cap - Function
add_stake - Function
add_stake_with_cap - Function
reactivate_stake_with_cap - Function
rotate_consensus_key - Function
update_network_and_fullnode_addresses - Function
increase_lockup_with_cap - Function
join_validator_set - Function
unlock_with_cap - Function
withdraw - Function
leave_validator_set - Function
is_current_epoch_validator - Function
update_performance_statistics - Function
on_new_epoch - Function
compute_simulated_validator_info - Function
next_validator_consensus_infos_v2 - Function
validator_consensus_infos_from_validator_set - Function
update_stake_pool - Function
get_reconfig_start_time_secs - Function
calculate_rewards_amount - Function
distribute_rewards - Function
append - Function
find_validator - Function
update_voting_power_increase - Function
assert_stake_pool_exists - Function
configure_allowed_validators - Function
assert_owner_cap_exists
use 0x1::account;
use 0x1::aggregator_v2;
use 0x1::aptos_coin;
use 0x1::big_ordered_map;
use 0x1::bls12381;
use 0x1::chain_status;
use 0x1::coin;
use 0x1::create_signer;
use 0x1::error;
use 0x1::event;
use 0x1::features;
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;
use 0x1::system_addresses;
use 0x1::table;
use 0x1::timestamp;
use 0x1::validator_consensus_info;
use 0x1::vector;
Resource OwnerCapability
Capability that represents ownership and can be used to control the validator and the associated stake pool. Having this be separate from the signer for the account that the validator resources are hosted at allows modules to have control over a validator.
struct OwnerCapability has store, key
Fields
-
pool_address: address
Resource StakePool
Each validator has a separate StakePool resource and can provide a stake. Changes in stake for an active validator:
- If a validator calls add_stake, the newly added stake is moved to pending_active.
- If validator calls unlock, their stake is moved to pending_inactive.
- When the next epoch starts, any pending_inactive stake is moved to inactive and can be withdrawn. Any pending_active stake is moved to active and adds to the validator’s voting power.
Changes in stake for an inactive validator:
- If a validator calls add_stake, the newly added stake is moved directly to active.
- If validator calls unlock, their stake is moved directly to inactive.
- When the next epoch starts, the validator can be activated if their active stake is more than the minimum.
struct StakePool has key
Fields
-
active: coin::Coin<aptos_coin::AptosCoin> -
inactive: coin::Coin<aptos_coin::AptosCoin> -
pending_active: coin::Coin<aptos_coin::AptosCoin> -
pending_inactive: coin::Coin<aptos_coin::AptosCoin> -
locked_until_secs: u64 -
operator_address: address -
delegated_voter: address -
initialize_validator_events: event::EventHandle<stake::RegisterValidatorCandidateEvent> -
set_operator_events: event::EventHandle<stake::SetOperatorEvent> -
add_stake_events: event::EventHandle<stake::AddStakeEvent> -
reactivate_stake_events: event::EventHandle<stake::ReactivateStakeEvent> -
rotate_consensus_key_events: event::EventHandle<stake::RotateConsensusKeyEvent> -
update_network_and_fullnode_addresses_events: event::EventHandle<stake::UpdateNetworkAndFullnodeAddressesEvent> -
increase_lockup_events: event::EventHandle<stake::IncreaseLockupEvent> -
join_validator_set_events: event::EventHandle<stake::JoinValidatorSetEvent> -
distribute_rewards_events: event::EventHandle<stake::DistributeRewardsEvent> -
unlock_stake_events: event::EventHandle<stake::UnlockStakeEvent> -
withdraw_stake_events: event::EventHandle<stake::WithdrawStakeEvent> -
leave_validator_set_events: event::EventHandle<stake::LeaveValidatorSetEvent>
Resource ValidatorConfig
Validator info stored in validator address.
struct ValidatorConfig has copy, drop, store, key
Fields
Struct ValidatorInfo
Consensus information per validator, stored in ValidatorSet.
struct ValidatorInfo has copy, drop, store
Fields
-
addr: address -
voting_power: u64 -
config: stake::ValidatorConfig
Resource ValidatorSet
Full ValidatorSet, stored in @aptos_framework.
- join_validator_set adds to pending_active queue.
- leave_valdiator_set moves from active to pending_inactive queue.
- on_new_epoch processes two pending queues and refresh ValidatorInfo from the owner’s address.
struct ValidatorSet has copy, drop, store, key
Fields
-
consensus_scheme: u8 -
active_validators: vector<stake::ValidatorInfo> -
pending_inactive: vector<stake::ValidatorInfo> -
pending_active: vector<stake::ValidatorInfo> -
total_voting_power: u128 -
total_joining_power: u128
Resource PendingTransactionFee
Transaction fee that is collected in current epoch, indexed by validator_index.
struct PendingTransactionFee has store, key
Fields
-
pending_fee_by_validator: big_ordered_map::BigOrderedMap<u64, aggregator_v2::Aggregator<u64>>
Enum Resource TransactionFeeConfig
enum TransactionFeeConfig has drop, store, key
Variants
V0
Fields
-
max_fee_octa_allowed_per_epoch_per_pool: u64
Struct DistributeTransactionFee
#[event]
struct DistributeTransactionFee has drop, store
Fields
-
pool_address: address -
fee_amount: u64
Resource AptosCoinCapabilities
AptosCoin capabilities, set during genesis and stored in @CoreResource account. This allows the Stake module to mint rewards to stakers.
struct AptosCoinCapabilities has key
Fields
-
mint_cap: coin::MintCapability<aptos_coin::AptosCoin>
Struct IndividualValidatorPerformance
struct IndividualValidatorPerformance has drop, store
Fields
-
successful_proposals: u64 -
failed_proposals: u64
Resource ValidatorPerformance
struct ValidatorPerformance has key
Fields
-
validators: vector<stake::IndividualValidatorPerformance>
Resource PrecomputedValidatorSet
A system resource holding the pre-computed validator set for the next epoch.
Created by next_validator_consensus_infos_v2() when reconfig starts.
Consumed (destroyed) by on_new_epoch().
Resource existence is the “ready” flag — no Option wrapper needed.
struct PrecomputedValidatorSet has key
Fields
-
validator_set: stake::ValidatorSet -
is_liveness_fallback: bool
Struct RegisterValidatorCandidateEvent
struct RegisterValidatorCandidateEvent has drop, store
Fields
-
pool_address: address
Struct StakeManagementPermission
struct StakeManagementPermission has copy, drop, store
Fields
-
dummy_field: bool
Struct RegisterValidatorCandidate
#[event]
struct RegisterValidatorCandidate has drop, store
Fields
-
pool_address: address
Struct SetOperatorEvent
struct SetOperatorEvent has drop, store
Fields
-
pool_address: address -
old_operator: address -
new_operator: address
Struct SetOperator
#[event]
struct SetOperator has drop, store
Fields
-
pool_address: address -
old_operator: address -
new_operator: address
Struct AddStakeEvent
struct AddStakeEvent has drop, store
Fields
-
pool_address: address -
amount_added: u64
Struct AddStake
#[event]
struct AddStake has drop, store
Fields
-
pool_address: address -
amount_added: u64
Struct ReactivateStakeEvent
struct ReactivateStakeEvent has drop, store
Fields
-
pool_address: address -
amount: u64
Struct ReactivateStake
#[event]
struct ReactivateStake has drop, store
Fields
-
pool_address: address -
amount: u64
Struct RotateConsensusKeyEvent
struct RotateConsensusKeyEvent has drop, store
Struct RotateConsensusKey
#[event]
struct RotateConsensusKey has drop, store
Struct UpdateNetworkAndFullnodeAddressesEvent
struct UpdateNetworkAndFullnodeAddressesEvent has drop, store
Fields
Struct UpdateNetworkAndFullnodeAddresses
#[event]
struct UpdateNetworkAndFullnodeAddresses has drop, store
Fields
Struct IncreaseLockupEvent
struct IncreaseLockupEvent has drop, store
Fields
-
pool_address: address -
old_locked_until_secs: u64 -
new_locked_until_secs: u64
Struct IncreaseLockup
#[event]
struct IncreaseLockup has drop, store
Fields
-
pool_address: address -
old_locked_until_secs: u64 -
new_locked_until_secs: u64
Struct JoinValidatorSetEvent
struct JoinValidatorSetEvent has drop, store
Fields
-
pool_address: address
Struct JoinValidatorSet
#[event]
struct JoinValidatorSet has drop, store
Fields
-
pool_address: address
Struct DistributeRewardsEvent
struct DistributeRewardsEvent has drop, store
Fields
-
pool_address: address -
rewards_amount: u64
Struct DistributeRewards
The amount includes transaction fee and staking rewards.
#[event]
struct DistributeRewards has drop, store
Fields
-
pool_address: address -
rewards_amount: u64
Struct UnlockStakeEvent
struct UnlockStakeEvent has drop, store
Fields
-
pool_address: address -
amount_unlocked: u64
Struct UnlockStake
#[event]
struct UnlockStake has drop, store
Fields
-
pool_address: address -
amount_unlocked: u64
Struct WithdrawStakeEvent
struct WithdrawStakeEvent has drop, store
Fields
-
pool_address: address -
amount_withdrawn: u64
Struct WithdrawStake
#[event]
struct WithdrawStake has drop, store
Fields
-
pool_address: address -
amount_withdrawn: u64
Struct LeaveValidatorSetEvent
struct LeaveValidatorSetEvent has drop, store
Fields
-
pool_address: address
Struct LeaveValidatorSet
#[event]
struct LeaveValidatorSet has drop, store
Fields
-
pool_address: address
Struct ValidatorSetLivenessFallback
#[event]
struct ValidatorSetLivenessFallback has drop, store
Fields
-
minimum_stake: u64 -
emergency_validator_count: u64 -
total_emergency_voting_power: u128
Resource ValidatorFees
DEPRECATED
#[deprecated]
struct ValidatorFees has key
Fields
-
fees_table: table::Table<address, coin::Coin<aptos_coin::AptosCoin>>
Resource AllowedValidators
This provides an ACL for Testnet purposes. In testnet, everyone is a whale, a whale can be a validator. This allows a testnet to bring additional entities into the validator set without compromising the security of the testnet. This will NOT be enabled in Mainnet.
struct AllowedValidators has key
Fields
-
accounts: vector<address>
Resource Ghost$ghost_valid_perf
struct Ghost$ghost_valid_perf has copy, drop, store, key
Fields
Resource Ghost$ghost_proposer_idx
struct Ghost$ghost_proposer_idx has copy, drop, store, key
Fields
-
v: option::Option<u64>
Resource Ghost$ghost_active_num
struct Ghost$ghost_active_num has copy, drop, store, key
Fields
-
v: u64
Resource Ghost$ghost_pending_inactive_num
struct Ghost$ghost_pending_inactive_num has copy, drop, store, key
Fields
-
v: u64
Constants
const MAX_U64: u128 = 18446744073709551615;
Account is already registered as a validator candidate.
const EALREADY_REGISTERED: u64 = 8;
Limit the maximum value of rewards_rate in order to avoid any arithmetic overflow.
const MAX_REWARDS_RATE: u64 = 1000000;
Account is already a validator or pending validator.
const EALREADY_ACTIVE_VALIDATOR: u64 = 4;
Deprecated function call.
const EDEPRECATED: u64 = 30;
Table to store collected transaction fees for each validator already exists.
const EFEES_TABLE_ALREADY_EXISTS: u64 = 19;
Validator is not defined in the ACL of entities allowed to be validators
const EINELIGIBLE_VALIDATOR: u64 = 17;
Cannot update stake pool’s lockup to earlier than current lockup.
const EINVALID_LOCKUP: u64 = 18;
Invalid consensus public key
const EINVALID_PUBLIC_KEY: u64 = 11;
Can’t remove last validator.
const ELAST_VALIDATOR: u64 = 6;
Account does not have the right operator capability.
const ENOT_OPERATOR: u64 = 9;
Account is not a validator.
const ENOT_VALIDATOR: u64 = 5;
Validators cannot join or leave post genesis on this test network.
const ENO_POST_GENESIS_VALIDATOR_SET_CHANGE_ALLOWED: u64 = 10;
Signer does not have permission to perform stake logic.
const ENO_STAKE_PERMISSION: u64 = 28;
An account cannot own more than one owner capability.
const EOWNER_CAP_ALREADY_EXISTS: u64 = 16;
Owner capability does not exist at the provided account.
const EOWNER_CAP_NOT_FOUND: u64 = 15;
Validator set change temporarily disabled because of in-progress reconfiguration. Please retry after 1 minute.
const ERECONFIGURATION_IN_PROGRESS: u64 = 20;
Total stake exceeds maximum allowed.
const ESTAKE_EXCEEDS_MAX: u64 = 7;
Stake pool does not exist at the provided pool address.
const ESTAKE_POOL_DOES_NOT_EXIST: u64 = 14;
Too much stake to join validator set.
const ESTAKE_TOO_HIGH: u64 = 3;
Not enough stake to join validator set.
const ESTAKE_TOO_LOW: u64 = 2;
Transaction fee is not fully distributed at epoch ending.
const ETRANSACTION_FEE_NOT_FULLY_DISTRIBUTED: u64 = 29;
Validator Config not published.
const EVALIDATOR_CONFIG: u64 = 1;
Validator set exceeds the limit
const EVALIDATOR_SET_TOO_LARGE: u64 = 12;
Voting power increase has exceeded the limit for this current epoch.
const EVOTING_POWER_INCREASE_EXCEEDS_LIMIT: u64 = 13;
Limit the maximum size to u16::max, it’s the current limit of the bitvec https://github.com/aptos-labs/aptos-core/blob/main/crates/aptos-bitvec/src/lib.rs#L20
const MAX_VALIDATOR_SET_SIZE: u64 = 65536;
const VALIDATOR_STATUS_ACTIVE: u64 = 2;
const VALIDATOR_STATUS_INACTIVE: u64 = 4;
Validator status enum. We can switch to proper enum later once Move supports it.
const VALIDATOR_STATUS_PENDING_ACTIVE: u64 = 1;
const VALIDATOR_STATUS_PENDING_INACTIVE: u64 = 3;
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
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.
#[view]
public fun get_lockup_secs(pool_address: address): u64
Implementation
public fun get_lockup_secs(pool_address: address): u64 acquires StakePool {
assert_stake_pool_exists(pool_address);
borrow_global<StakePool>(pool_address).locked_until_secs
}
Function get_remaining_lockup_secs
Return the remaining lockup of the stake pool at pool_address.
This will throw an error if there’s no stake pool at pool_address.
#[view]
public fun get_remaining_lockup_secs(pool_address: address): u64
Implementation
public fun get_remaining_lockup_secs(pool_address: address): u64 acquires StakePool {
assert_stake_pool_exists(pool_address);
let lockup_time = borrow_global<StakePool>(pool_address).locked_until_secs;
if (lockup_time <= timestamp::now_seconds()) { 0 }
else {
lockup_time - timestamp::now_seconds()
}
}
Function get_stake
Return the different stake amounts for pool_address (whether the validator is active or not).
The returned amounts are for (active, inactive, pending_active, pending_inactive) stake respectively.
#[view]
public fun get_stake(pool_address: address): (u64, u64, u64, u64)
Implementation
public fun get_stake(pool_address: address): (u64, u64, u64, u64) acquires StakePool {
assert_stake_pool_exists(pool_address);
let stake_pool = borrow_global<StakePool>(pool_address);
(
coin::value(&stake_pool.active),
coin::value(&stake_pool.inactive),
coin::value(&stake_pool.pending_active),
coin::value(&stake_pool.pending_inactive)
)
}
Function get_validator_state
Returns the validator’s state.
#[view]
public fun get_validator_state(pool_address: address): u64
Implementation
public fun get_validator_state(pool_address: address): u64 acquires ValidatorSet {
let validator_set = borrow_global<ValidatorSet>(@aptos_framework);
if (find_validator(&validator_set.pending_active, pool_address).is_some()) {
VALIDATOR_STATUS_PENDING_ACTIVE
} else if (find_validator(&validator_set.active_validators, pool_address).is_some()) {
VALIDATOR_STATUS_ACTIVE
} else if (find_validator(&validator_set.pending_inactive, pool_address).is_some()) {
VALIDATOR_STATUS_PENDING_INACTIVE
} else {
VALIDATOR_STATUS_INACTIVE
}
}
Function get_current_epoch_voting_power
Return the voting power of the validator in the current epoch. This is the same as the validator’s total active and pending_inactive stake.
#[view]
public fun get_current_epoch_voting_power(pool_address: address): u64
Implementation
public fun get_current_epoch_voting_power(
pool_address: address
): u64 acquires StakePool, ValidatorSet {
assert_stake_pool_exists(pool_address);
let validator_state = get_validator_state(pool_address);
// Both active and pending inactive validators can still vote in the current epoch.
if (validator_state == VALIDATOR_STATUS_ACTIVE
|| validator_state == VALIDATOR_STATUS_PENDING_INACTIVE) {
let active_stake =
coin::value(&borrow_global<StakePool>(pool_address).active);
let pending_inactive_stake =
coin::value(&borrow_global<StakePool>(pool_address).pending_inactive);
active_stake + pending_inactive_stake
} else { 0 }
}
Function get_delegated_voter
Return the delegated voter of the validator at pool_address.
#[view]
public fun get_delegated_voter(pool_address: address): address
Implementation
public fun get_delegated_voter(pool_address: address): address acquires StakePool {
assert_stake_pool_exists(pool_address);
borrow_global<StakePool>(pool_address).delegated_voter
}
Function get_operator
Return the operator of the validator at pool_address.
#[view]
public fun get_operator(pool_address: address): address
Implementation
public fun get_operator(pool_address: address): address acquires StakePool {
assert_stake_pool_exists(pool_address);
borrow_global<StakePool>(pool_address).operator_address
}
Function get_owned_pool_address
Return the pool address in owner_cap.
public fun get_owned_pool_address(owner_cap: &stake::OwnerCapability): address
Implementation
public fun get_owned_pool_address(owner_cap: &OwnerCapability): address {
owner_cap.pool_address
}
Function owner_cap_exists
Return true if owner has an OwnerCapability resource.
#[view]
public fun owner_cap_exists(owner: address): bool
Implementation
public fun owner_cap_exists(owner: address): bool {
exists<OwnerCapability>(owner)
}
Function get_pool_address_for_owner
Return the pool address controlled by the OwnerCapability at owner.
#[view]
public fun get_pool_address_for_owner(owner: address): address
Implementation
public fun get_pool_address_for_owner(owner: address): address acquires OwnerCapability {
assert_owner_cap_exists(owner);
OwnerCapability[owner].pool_address
}
Function get_validator_index
Return the validator index for pool_address.
#[view]
public fun get_validator_index(pool_address: address): u64
Implementation
public fun get_validator_index(pool_address: address): u64 acquires ValidatorConfig {
assert_stake_pool_exists(pool_address);
borrow_global<ValidatorConfig>(pool_address).validator_index
}
Function get_current_epoch_proposal_counts
Return the number of successful and failed proposals for the proposal at the given validator index.
#[view]
public fun get_current_epoch_proposal_counts(validator_index: u64): (u64, u64)
Implementation
public fun get_current_epoch_proposal_counts(
validator_index: u64
): (u64, u64) acquires ValidatorPerformance {
let validator_performances =
&borrow_global<ValidatorPerformance>(@aptos_framework).validators;
let validator_performance = validator_performances.borrow(validator_index);
(
validator_performance.successful_proposals,
validator_performance.failed_proposals
)
}
Function get_validator_config
Return the validator’s config.
#[view]
public fun get_validator_config(pool_address: address): (vector<u8>, vector<u8>, vector<u8>)
Implementation
public fun get_validator_config(
pool_address: address
): (vector<u8>, vector<u8>, vector<u8>) acquires ValidatorConfig {
assert_stake_pool_exists(pool_address);
let validator_config = borrow_global<ValidatorConfig>(pool_address);
(
validator_config.consensus_pubkey,
validator_config.network_addresses,
validator_config.fullnode_addresses
)
}
Function stake_pool_exists
#[view]
public fun stake_pool_exists(addr: address): bool
Implementation
public fun stake_pool_exists(addr: address): bool {
exists<StakePool>(addr)
}
Function get_pending_transaction_fee
Returns the pending transaction fee that is accumulated in current epoch.
#[view]
public fun get_pending_transaction_fee(): vector<u64>
Implementation
public fun get_pending_transaction_fee(): vector<u64> acquires PendingTransactionFee {
let result = vector::empty();
let fee_table =
&borrow_global<PendingTransactionFee>(@aptos_framework).pending_fee_by_validator;
let num_validators = fee_table.compute_length();
let i = 0;
while (i < num_validators) {
result.push_back(fee_table.borrow(&i).read());
i += 1;
};
result
}
Function initialize
Initialize validator set to the core resource account.
public(friend) fun initialize(aptos_framework: &signer)
Implementation
public(friend) fun initialize(aptos_framework: &signer) {
system_addresses::assert_aptos_framework(aptos_framework);
move_to(
aptos_framework,
ValidatorSet {
consensus_scheme: 0,
active_validators: vector::empty(),
pending_active: vector::empty(),
pending_inactive: vector::empty(),
total_voting_power: 0,
total_joining_power: 0
}
);
move_to(aptos_framework, ValidatorPerformance { validators: vector::empty() });
}
Function store_aptos_coin_mint_cap
This is only called during Genesis, which is where MintCapability
public(friend) fun store_aptos_coin_mint_cap(aptos_framework: &signer, mint_cap: coin::MintCapability<aptos_coin::AptosCoin>)
Implementation
public(friend) fun store_aptos_coin_mint_cap(
aptos_framework: &signer, mint_cap: MintCapability<AptosCoin>
) {
system_addresses::assert_aptos_framework(aptos_framework);
move_to(aptos_framework, AptosCoinCapabilities { mint_cap })
}
Function remove_validators
Allow on chain governance to remove validators from the validator set.
public fun remove_validators(aptos_framework: &signer, validators: &vector<address>)
Implementation
public fun remove_validators(
aptos_framework: &signer, validators: &vector<address>
) acquires ValidatorSet {
assert_reconfig_not_in_progress();
system_addresses::assert_aptos_framework(aptos_framework);
let validator_set = borrow_global_mut<ValidatorSet>(@aptos_framework);
let active_validators = &mut validator_set.active_validators;
let pending_inactive = &mut validator_set.pending_inactive;
spec {
update ghost_active_num = len(active_validators);
update ghost_pending_inactive_num = len(pending_inactive);
};
let len_validators = validators.length();
let i = 0;
// Remove each validator from the validator set.
while ({
spec {
invariant i <= len_validators;
invariant spec_validators_are_initialized(active_validators);
invariant spec_validator_indices_are_valid(active_validators);
invariant spec_validators_are_initialized(pending_inactive);
invariant spec_validator_indices_are_valid(pending_inactive);
invariant ghost_active_num + ghost_pending_inactive_num
== len(active_validators) + len(pending_inactive);
};
i < len_validators
}) {
let validator = validators[i];
let validator_index = find_validator(active_validators, validator);
if (validator_index.is_some()) {
let validator_info =
active_validators.swap_remove(*validator_index.borrow());
pending_inactive.push_back(validator_info);
spec {
update ghost_active_num = ghost_active_num - 1;
update ghost_pending_inactive_num = ghost_pending_inactive_num + 1;
};
};
i += 1;
};
}
Function initialize_pending_transaction_fee
public fun initialize_pending_transaction_fee(framework: &signer)
Implementation
public fun initialize_pending_transaction_fee(framework: &signer) {
system_addresses::assert_aptos_framework(framework);
if (!exists<PendingTransactionFee>(@aptos_framework)) {
move_to(
framework,
PendingTransactionFee {
// The max leaf order is set to 10 because there is a existing limitation that a
// resource can only have 10 aggregators at max.
pending_fee_by_validator: big_ordered_map::new_with_config(
5, 10, true
)
}
);
}
}
Function set_transaction_fee_limit_per_epoch_per_pool
public fun set_transaction_fee_limit_per_epoch_per_pool(framework: &signer, limit_octa: u64)
Implementation
public fun set_transaction_fee_limit_per_epoch_per_pool(
framework: &signer, limit_octa: u64
) {
system_addresses::assert_aptos_framework(framework);
let config = TransactionFeeConfig::V0 {
max_fee_octa_allowed_per_epoch_per_pool: limit_octa
};
set_transaction_fee_config(framework, config);
}
Function set_transaction_fee_config
public fun set_transaction_fee_config(framework: &signer, config: stake::TransactionFeeConfig)
Implementation
public fun set_transaction_fee_config(
framework: &signer, config: TransactionFeeConfig
) acquires TransactionFeeConfig {
system_addresses::assert_aptos_framework(framework);
if (exists<TransactionFeeConfig>(@aptos_framework)) {
*borrow_global_mut<TransactionFeeConfig>(@aptos_framework) = config;
} else {
move_to(framework, config);
}
}
Function record_fee
public(friend) fun record_fee(vm: &signer, fee_distribution_validator_indices: vector<u64>, fee_amounts_octa: vector<u64>)
Implementation
public(friend) fun record_fee(
vm: &signer,
fee_distribution_validator_indices: vector<u64>,
fee_amounts_octa: vector<u64>
) acquires PendingTransactionFee {
// Operational constraint: can only be invoked by the VM.
system_addresses::assert_vm(vm);
assert!(
fee_distribution_validator_indices.length() == fee_amounts_octa.length()
);
let num_validators_to_distribute = fee_distribution_validator_indices.length();
let pending_fee = borrow_global_mut<PendingTransactionFee>(@aptos_framework);
let i = 0;
while (i < num_validators_to_distribute) {
let validator_index = fee_distribution_validator_indices[i];
let fee_octa = fee_amounts_octa[i];
pending_fee.pending_fee_by_validator.borrow_mut(&validator_index).add(
fee_octa
);
i += 1;
}
}
Function initialize_stake_owner
Initialize the validator account and give ownership to the signing account except it leaves the ValidatorConfig to be set by another entity. Note: this triggers setting the operator and owner, set it to the account’s address to set later.
public entry fun initialize_stake_owner(owner: &signer, initial_stake_amount: u64, operator: address, voter: address)
Implementation
public entry fun initialize_stake_owner(
owner: &signer,
initial_stake_amount: u64,
operator: address,
voter: address
) acquires AllowedValidators, OwnerCapability, StakePool, ValidatorSet {
check_stake_permission(owner);
initialize_owner(owner);
move_to(
owner,
ValidatorConfig {
consensus_pubkey: vector::empty(),
network_addresses: vector::empty(),
fullnode_addresses: vector::empty(),
validator_index: 0
}
);
if (initial_stake_amount > 0) {
add_stake(owner, initial_stake_amount);
};
let account_address = signer::address_of(owner);
if (account_address != operator) {
set_operator(owner, operator)
};
if (account_address != voter) {
set_delegated_voter(owner, voter)
};
}
Function initialize_validator
Initialize the validator account and give ownership to the signing account.
public entry fun initialize_validator(account: &signer, consensus_pubkey: vector<u8>, proof_of_possession: vector<u8>, network_addresses: vector<u8>, fullnode_addresses: vector<u8>)
Implementation
public entry fun initialize_validator(
account: &signer,
consensus_pubkey: vector<u8>,
proof_of_possession: vector<u8>,
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,
&proof_of_possession_from_bytes(proof_of_possession)
);
assert!(
pubkey_from_pop.is_some(), error::invalid_argument(EINVALID_PUBLIC_KEY)
);
initialize_owner(account);
move_to(
account,
ValidatorConfig {
consensus_pubkey,
network_addresses,
fullnode_addresses,
validator_index: 0
}
);
}
Function initialize_owner
fun initialize_owner(owner: &signer)
Implementation
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)
);
move_to(
owner,
StakePool {
active: coin::zero<AptosCoin>(),
pending_active: coin::zero<AptosCoin>(),
pending_inactive: coin::zero<AptosCoin>(),
inactive: coin::zero<AptosCoin>(),
locked_until_secs: 0,
operator_address: owner_address,
delegated_voter: owner_address,
// Events.
initialize_validator_events: account::new_event_handle<
RegisterValidatorCandidateEvent>(owner),
set_operator_events: account::new_event_handle<SetOperatorEvent>(owner),
add_stake_events: account::new_event_handle<AddStakeEvent>(owner),
reactivate_stake_events: account::new_event_handle<ReactivateStakeEvent>(
owner
),
rotate_consensus_key_events: account::new_event_handle<
RotateConsensusKeyEvent>(owner),
update_network_and_fullnode_addresses_events: account::new_event_handle<
UpdateNetworkAndFullnodeAddressesEvent>(owner),
increase_lockup_events: account::new_event_handle<IncreaseLockupEvent>(
owner
),
join_validator_set_events: account::new_event_handle<JoinValidatorSetEvent>(
owner
),
distribute_rewards_events: account::new_event_handle<DistributeRewardsEvent>(
owner
),
unlock_stake_events: account::new_event_handle<UnlockStakeEvent>(owner),
withdraw_stake_events: account::new_event_handle<WithdrawStakeEvent>(owner),
leave_validator_set_events: account::new_event_handle<
LeaveValidatorSetEvent>(owner)
}
);
move_to(owner, OwnerCapability { pool_address: owner_address });
}
Function extract_owner_cap
Extract and return owner capability from the signing account.
public fun extract_owner_cap(owner: &signer): stake::OwnerCapability
Implementation
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)
}
Function deposit_owner_cap
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: stake::OwnerCapability)
Implementation
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);
}
Function destroy_owner_cap
Destroy owner_cap.
public fun destroy_owner_cap(owner_cap: stake::OwnerCapability)
Implementation
public fun destroy_owner_cap(owner_cap: OwnerCapability) {
let OwnerCapability { pool_address: _ } = owner_cap;
}
Function set_operator
Allows an owner to change the operator of the stake pool.
public entry fun set_operator(owner: &signer, new_operator: address)
Implementation
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);
set_operator_with_cap(ownership_cap, new_operator);
}
Function set_operator_with_cap
Allows an account with ownership capability to change the operator of the stake pool.
public fun set_operator_with_cap(owner_cap: &stake::OwnerCapability, new_operator: address)
Implementation
public fun set_operator_with_cap(
owner_cap: &OwnerCapability, new_operator: address
) acquires StakePool {
let pool_address = owner_cap.pool_address;
assert_stake_pool_exists(pool_address);
let stake_pool = borrow_global_mut<StakePool>(pool_address);
let old_operator = stake_pool.operator_address;
stake_pool.operator_address = new_operator;
event::emit(SetOperator { pool_address, old_operator, new_operator });
}
Function set_delegated_voter
Allows an owner to change the delegated voter of the stake pool.
public entry fun set_delegated_voter(owner: &signer, new_voter: address)
Implementation
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);
set_delegated_voter_with_cap(ownership_cap, new_voter);
}
Function set_delegated_voter_with_cap
Allows an owner to change the delegated voter of the stake pool.
public fun set_delegated_voter_with_cap(owner_cap: &stake::OwnerCapability, new_voter: address)
Implementation
public fun set_delegated_voter_with_cap(
owner_cap: &OwnerCapability, new_voter: address
) acquires StakePool {
let pool_address = owner_cap.pool_address;
assert_stake_pool_exists(pool_address);
let stake_pool = borrow_global_mut<StakePool>(pool_address);
stake_pool.delegated_voter = new_voter;
}
Function add_stake
Add amount of coins from the account owning the StakePool.
public entry fun add_stake(owner: &signer, amount: u64)
Implementation
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);
add_stake_with_cap(ownership_cap, coin::withdraw<AptosCoin>(owner, amount));
}
Function add_stake_with_cap
Add coins into pool_address. this requires the corresponding owner_cap to be passed in.
public fun add_stake_with_cap(owner_cap: &stake::OwnerCapability, coins: coin::Coin<aptos_coin::AptosCoin>)
Implementation
public fun add_stake_with_cap(
owner_cap: &OwnerCapability, coins: Coin<AptosCoin>
) acquires StakePool, ValidatorSet {
assert_reconfig_not_in_progress();
let pool_address = owner_cap.pool_address;
assert_stake_pool_exists(pool_address);
let amount = coin::value(&coins);
if (amount == 0) {
coin::destroy_zero(coins);
return
};
// Only track and validate voting power increase for active and pending_active validator.
// Pending_inactive validator will be removed from the validator set in the next epoch.
// Inactive validator's total stake will be tracked when they join the validator set.
let validator_set = borrow_global<ValidatorSet>(@aptos_framework);
// Search directly rather using get_validator_state to save on unnecessary loops.
if (find_validator(&validator_set.active_validators, pool_address).is_some()
|| find_validator(&validator_set.pending_active, pool_address).is_some()) {
update_voting_power_increase(amount);
};
// Add to pending_active if it's a current validator because the stake is not counted until the next epoch.
// Otherwise, the delegation can be added to active directly as the validator is also activated in the epoch.
let stake_pool = borrow_global_mut<StakePool>(pool_address);
if (is_current_epoch_validator(pool_address)) {
coin::merge<AptosCoin>(&mut stake_pool.pending_active, coins);
} else {
coin::merge<AptosCoin>(&mut stake_pool.active, coins);
};
let (_, maximum_stake) =
staking_config::get_required_stake(&staking_config::get());
let voting_power = get_voting_power(stake_pool);
assert!(
voting_power <= maximum_stake, error::invalid_argument(ESTAKE_EXCEEDS_MAX)
);
event::emit(AddStake { pool_address, amount_added: amount });
}
Function reactivate_stake
Move amount of coins from pending_inactive to active.
public entry fun reactivate_stake(owner: &signer, amount: u64)
Implementation
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);
let ownership_cap = borrow_global<OwnerCapability>(owner_address);
reactivate_stake_with_cap(ownership_cap, amount);
}
Function reactivate_stake_with_cap
public fun reactivate_stake_with_cap(owner_cap: &stake::OwnerCapability, amount: u64)
Implementation
public fun reactivate_stake_with_cap(
owner_cap: &OwnerCapability, amount: u64
) acquires StakePool {
assert_reconfig_not_in_progress();
let pool_address = owner_cap.pool_address;
assert_stake_pool_exists(pool_address);
// Cap the amount to reactivate by the amount in pending_inactive.
let stake_pool = borrow_global_mut<StakePool>(pool_address);
let total_pending_inactive = coin::value(&stake_pool.pending_inactive);
amount = min(amount, total_pending_inactive);
// Since this does not count as a voting power change (pending inactive still counts as voting power in the
// current epoch), stake can be immediately moved from pending inactive to active.
// We also don't need to check voting power increase as there's none.
let reactivated_coins = coin::extract(&mut stake_pool.pending_inactive, amount);
coin::merge(&mut stake_pool.active, reactivated_coins);
event::emit(ReactivateStake { pool_address, amount });
}
Function rotate_consensus_key
Rotate the consensus key of the validator, it’ll take effect in next epoch.
public entry fun rotate_consensus_key(operator: &signer, pool_address: address, new_consensus_pubkey: vector<u8>, proof_of_possession: vector<u8>)
Implementation
public entry fun rotate_consensus_key(
operator: &signer,
pool_address: address,
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);
let stake_pool = borrow_global_mut<StakePool>(pool_address);
assert!(
signer::address_of(operator) == stake_pool.operator_address,
error::unauthenticated(ENOT_OPERATOR)
);
assert!(
exists<ValidatorConfig>(pool_address),
error::not_found(EVALIDATOR_CONFIG)
);
let validator_info = borrow_global_mut<ValidatorConfig>(pool_address);
let old_consensus_pubkey = validator_info.consensus_pubkey;
// 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(
new_consensus_pubkey,
&proof_of_possession_from_bytes(proof_of_possession)
);
assert!(
pubkey_from_pop.is_some(), error::invalid_argument(EINVALID_PUBLIC_KEY)
);
validator_info.consensus_pubkey = new_consensus_pubkey;
event::emit(
RotateConsensusKey { pool_address, old_consensus_pubkey, new_consensus_pubkey }
);
}
Function update_network_and_fullnode_addresses
Update the network and full node addresses of the validator. This only takes effect in the next epoch.
public entry fun update_network_and_fullnode_addresses(operator: &signer, pool_address: address, new_network_addresses: vector<u8>, new_fullnode_addresses: vector<u8>)
Implementation
public entry fun update_network_and_fullnode_addresses(
operator: &signer,
pool_address: address,
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);
assert!(
signer::address_of(operator) == stake_pool.operator_address,
error::unauthenticated(ENOT_OPERATOR)
);
assert!(
exists<ValidatorConfig>(pool_address),
error::not_found(EVALIDATOR_CONFIG)
);
let validator_info = borrow_global_mut<ValidatorConfig>(pool_address);
let old_network_addresses = validator_info.network_addresses;
validator_info.network_addresses = new_network_addresses;
let old_fullnode_addresses = validator_info.fullnode_addresses;
validator_info.fullnode_addresses = new_fullnode_addresses;
event::emit(
UpdateNetworkAndFullnodeAddresses {
pool_address,
old_network_addresses,
new_network_addresses,
old_fullnode_addresses,
new_fullnode_addresses
}
);
}
Function increase_lockup
Similar to increase_lockup_with_cap but will use ownership capability from the signing account.
public entry fun increase_lockup(owner: &signer)
Implementation
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);
increase_lockup_with_cap(ownership_cap);
}
Function increase_lockup_with_cap
Unlock from active delegation, it’s moved to pending_inactive if locked_until_secs < current_time or directly inactive if it’s not from an active validator.
public fun increase_lockup_with_cap(owner_cap: &stake::OwnerCapability)
Implementation
public fun increase_lockup_with_cap(owner_cap: &OwnerCapability) acquires StakePool {
let pool_address = owner_cap.pool_address;
assert_stake_pool_exists(pool_address);
let config = staking_config::get();
let stake_pool = borrow_global_mut<StakePool>(pool_address);
let old_locked_until_secs = stake_pool.locked_until_secs;
let new_locked_until_secs =
timestamp::now_seconds()
+ staking_config::get_recurring_lockup_duration(&config);
assert!(
old_locked_until_secs < new_locked_until_secs,
error::invalid_argument(EINVALID_LOCKUP)
);
stake_pool.locked_until_secs = new_locked_until_secs;
event::emit(
IncreaseLockup { pool_address, old_locked_until_secs, new_locked_until_secs }
);
}
Function join_validator_set
This can only called by the operator of the validator/staking pool.
public entry fun join_validator_set(operator: &signer, pool_address: address)
Implementation
public entry fun join_validator_set(
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)
);
join_validator_set_internal(operator, pool_address);
}
Function join_validator_set_internal
Request to have pool_address join the validator set. Can only be called after calling initialize_validator.
If the validator has the required stake (more than minimum and less than maximum allowed), they will be
added to the pending_active queue. All validators in this queue will be added to the active set when the next
epoch starts (eligibility will be rechecked).
This internal version can only be called by the Genesis module during Genesis.
public(friend) fun join_validator_set_internal(operator: &signer, pool_address: address)
Implementation
public(friend) fun join_validator_set_internal(
operator: &signer, pool_address: address
) acquires StakePool, ValidatorConfig, ValidatorSet {
assert_reconfig_not_in_progress();
assert_stake_pool_exists(pool_address);
let stake_pool = borrow_global_mut<StakePool>(pool_address);
assert!(
signer::address_of(operator) == stake_pool.operator_address,
error::unauthenticated(ENOT_OPERATOR)
);
assert!(
get_validator_state(pool_address) == VALIDATOR_STATUS_INACTIVE,
error::invalid_state(EALREADY_ACTIVE_VALIDATOR)
);
let config = staking_config::get();
let (minimum_stake, maximum_stake) = staking_config::get_required_stake(&config);
// Settle any pending_inactive whose lockup has already expired so it is not counted
// as voting power. An inactive validator's pending_inactive is never processed by
// update_stake_pool, so we must do it here before evaluating the minimum stake.
// Only settle when locked_until_secs > 0 (i.e., a lockup was ever explicitly set);
// a value of 0 means the pool was just created and the lockup has not been initialised yet.
if (stake_pool.locked_until_secs > 0
&& timestamp::now_seconds() >= stake_pool.locked_until_secs) {
coin::merge(
&mut stake_pool.inactive,
coin::extract_all(&mut stake_pool.pending_inactive)
);
};
let voting_power = get_voting_power(stake_pool);
assert!(voting_power >= minimum_stake, error::invalid_argument(ESTAKE_TOO_LOW));
assert!(voting_power <= maximum_stake, error::invalid_argument(ESTAKE_TOO_HIGH));
// Track and validate voting power increase.
update_voting_power_increase(voting_power);
// Add validator to pending_active, to be activated in the next epoch.
let validator_config = borrow_global<ValidatorConfig>(pool_address);
assert!(
!validator_config.consensus_pubkey.is_empty(),
error::invalid_argument(EINVALID_PUBLIC_KEY)
);
// Validate the current validator set size has not exceeded the limit.
let validator_set = borrow_global_mut<ValidatorSet>(@aptos_framework);
validator_set.pending_active.push_back(
generate_validator_info(pool_address, stake_pool, *validator_config)
);
let validator_set_size =
validator_set.active_validators.length()
+ validator_set.pending_active.length();
assert!(
validator_set_size <= MAX_VALIDATOR_SET_SIZE,
error::invalid_argument(EVALIDATOR_SET_TOO_LARGE)
);
event::emit(JoinValidatorSet { pool_address });
}
Function unlock
Similar to unlock_with_cap but will use ownership capability from the signing account.
public entry fun unlock(owner: &signer, amount: u64)
Implementation
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);
let ownership_cap = borrow_global<OwnerCapability>(owner_address);
unlock_with_cap(amount, ownership_cap);
}
Function unlock_with_cap
Unlock amount from the active stake. Only possible if the lockup has expired.
public fun unlock_with_cap(amount: u64, owner_cap: &stake::OwnerCapability)
Implementation
public fun unlock_with_cap(amount: u64, owner_cap: &OwnerCapability) acquires StakePool {
assert_reconfig_not_in_progress();
// Short-circuit if amount to unlock is 0 so we don't emit events.
if (amount == 0) { return };
// Unlocked coins are moved to pending_inactive. When the current lockup cycle expires, they will be moved into
// inactive in the earliest possible epoch transition.
let pool_address = owner_cap.pool_address;
assert_stake_pool_exists(pool_address);
let stake_pool = borrow_global_mut<StakePool>(pool_address);
// Cap amount to unlock by maximum active stake.
let amount = min(amount, coin::value(&stake_pool.active));
let unlocked_stake = coin::extract(&mut stake_pool.active, amount);
coin::merge<AptosCoin>(&mut stake_pool.pending_inactive, unlocked_stake);
event::emit(UnlockStake { pool_address, amount_unlocked: amount });
}
Function withdraw
Withdraw from account’s inactive stake.
public entry fun withdraw(owner: &signer, withdraw_amount: u64)
Implementation
public entry fun withdraw(
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);
let coins = withdraw_with_cap(ownership_cap, withdraw_amount);
coin::deposit<AptosCoin>(owner_address, coins);
}
Function withdraw_with_cap
Withdraw from pool_address’s inactive stake with the corresponding owner_cap.
public fun withdraw_with_cap(owner_cap: &stake::OwnerCapability, withdraw_amount: u64): coin::Coin<aptos_coin::AptosCoin>
Implementation
public fun withdraw_with_cap(
owner_cap: &OwnerCapability, withdraw_amount: u64
): Coin<AptosCoin> acquires StakePool, ValidatorSet {
assert_reconfig_not_in_progress();
let pool_address = owner_cap.pool_address;
assert_stake_pool_exists(pool_address);
let stake_pool = borrow_global_mut<StakePool>(pool_address);
// There's an edge case where a validator unlocks their stake and leaves the validator set before
// the stake is fully unlocked (the current lockup cycle has not expired yet).
// This can leave their stake stuck in pending_inactive even after the current lockup cycle expires.
if (get_validator_state(pool_address) == VALIDATOR_STATUS_INACTIVE
&& timestamp::now_seconds() >= stake_pool.locked_until_secs) {
let pending_inactive_stake =
coin::extract_all(&mut stake_pool.pending_inactive);
coin::merge(&mut stake_pool.inactive, pending_inactive_stake);
};
// Cap withdraw amount by total inactive coins.
withdraw_amount = min(withdraw_amount, coin::value(&stake_pool.inactive));
if (withdraw_amount == 0) return coin::zero<AptosCoin>();
event::emit(WithdrawStake { pool_address, amount_withdrawn: withdraw_amount });
coin::extract(&mut stake_pool.inactive, withdraw_amount)
}
Function leave_validator_set
Request to have pool_address leave the validator set. The validator is only actually removed from the set when
the next epoch starts.
The last validator in the set cannot leave. This is an edge case that should never happen as long as the network
is still operational.
Can only be called by the operator of the validator/staking pool.
public entry fun leave_validator_set(operator: &signer, pool_address: address)
Implementation
public entry fun leave_validator_set(
operator: &signer, pool_address: address
) acquires StakePool, ValidatorSet {
check_stake_permission(operator);
assert_reconfig_not_in_progress();
let config = staking_config::get();
assert!(
staking_config::get_allow_validator_set_change(&config),
error::invalid_argument(ENO_POST_GENESIS_VALIDATOR_SET_CHANGE_ALLOWED)
);
assert_stake_pool_exists(pool_address);
let stake_pool = borrow_global_mut<StakePool>(pool_address);
// Account has to be the operator.
assert!(
signer::address_of(operator) == stake_pool.operator_address,
error::unauthenticated(ENOT_OPERATOR)
);
let validator_set = borrow_global_mut<ValidatorSet>(@aptos_framework);
// If the validator is still pending_active, directly kick the validator out.
let maybe_pending_active_index =
find_validator(&validator_set.pending_active, pool_address);
if (maybe_pending_active_index.is_some()) {
validator_set.pending_active.swap_remove(maybe_pending_active_index.extract());
// Decrease the voting power increase as the pending validator's voting power was added when they requested
// to join. Now that they changed their mind, their voting power should not affect the joining limit of this
// epoch.
let validator_stake = (get_voting_power(stake_pool) as u128);
// total_joining_power should be larger than validator_stake but just in case there has been a small
// rounding error somewhere that can lead to an underflow, we still want to allow this transaction to
// succeed.
if (validator_set.total_joining_power > validator_stake) {
validator_set.total_joining_power -= validator_stake;
} else {
validator_set.total_joining_power = 0;
};
} else {
// Validate that the validator is already part of the validator set.
let maybe_active_index =
find_validator(&validator_set.active_validators, pool_address);
assert!(maybe_active_index.is_some(), error::invalid_state(ENOT_VALIDATOR));
let validator_info =
validator_set.active_validators.swap_remove(maybe_active_index.extract());
assert!(
validator_set.active_validators.length() > 0,
error::invalid_state(ELAST_VALIDATOR)
);
validator_set.pending_inactive.push_back(validator_info);
event::emit(LeaveValidatorSet { pool_address });
};
}
Function is_current_epoch_validator
Returns true if the current validator can still vote in the current epoch. This includes validators that requested to leave but are still in the pending_inactive queue and will be removed when the epoch starts.
public fun is_current_epoch_validator(pool_address: address): bool
Implementation
public fun is_current_epoch_validator(pool_address: address): bool acquires ValidatorSet {
assert_stake_pool_exists(pool_address);
let validator_state = get_validator_state(pool_address);
validator_state == VALIDATOR_STATUS_ACTIVE
|| validator_state == VALIDATOR_STATUS_PENDING_INACTIVE
}
Function update_performance_statistics
Update the validator performance (proposal statistics). This is only called by block::prologue(). This function cannot abort.
public(friend) fun update_performance_statistics(proposer_index: option::Option<u64>, failed_proposer_indices: vector<u64>)
Implementation
public(friend) fun update_performance_statistics(
proposer_index: Option<u64>, failed_proposer_indices: vector<u64>
) acquires ValidatorPerformance {
// Validator set cannot change until the end of the epoch, so the validator index in arguments should
// match with those of the validators in ValidatorPerformance resource.
let validator_perf = borrow_global_mut<ValidatorPerformance>(@aptos_framework);
let validator_len = validator_perf.validators.length();
spec {
update ghost_valid_perf = validator_perf;
update ghost_proposer_idx = proposer_index;
};
// proposer_index is an option because it can be missing (for NilBlocks)
if (proposer_index.is_some()) {
let cur_proposer_index = proposer_index.extract();
// Here, and in all other vector::borrow, skip any validator indices that are out of bounds,
// this ensures that this function doesn't abort if there are out of bounds errors.
if (cur_proposer_index < validator_len) {
let validator = validator_perf.validators.borrow_mut(cur_proposer_index);
spec {
assume validator.successful_proposals + 1 <= MAX_U64;
};
validator.successful_proposals += 1;
};
};
let f = 0;
let f_len = failed_proposer_indices.length();
while ({
spec {
invariant len(validator_perf.validators) == validator_len;
invariant (
option::is_some(ghost_proposer_idx)
&& option::borrow(ghost_proposer_idx) < validator_len
) ==>
(
validator_perf.validators[option::borrow(ghost_proposer_idx)].successful_proposals ==
ghost_valid_perf.validators[option::borrow(ghost_proposer_idx)].successful_proposals
+ 1
);
};
f < f_len
}) {
let validator_index = failed_proposer_indices[f];
if (validator_index < validator_len) {
let validator = validator_perf.validators.borrow_mut(validator_index);
spec {
assume validator.failed_proposals + 1 <= MAX_U64;
};
validator.failed_proposals += 1;
};
f += 1;
};
}
Function on_new_epoch
Triggered during a reconfiguration. This function shouldn’t abort.
- Distribute transaction fees and rewards to stake pools of active and pending inactive validators (requested to leave but not yet removed).
- Officially move pending active stake to active and move pending inactive stake to inactive. The staking pool’s voting power in this new epoch will be updated to the total active stake.
- Add pending active validators to the active set if they satisfy requirements so they can vote and remove pending inactive validators so they no longer can vote.
- The validator’s voting power in the validator set is updated to be the corresponding staking pool’s voting power.
public(friend) fun on_new_epoch()
Implementation
public(friend) fun on_new_epoch() acquires AptosCoinCapabilities, PendingTransactionFee, PrecomputedValidatorSet, StakePool, TransactionFeeConfig, ValidatorConfig, ValidatorPerformance, ValidatorSet {
let validator_set = borrow_global_mut<ValidatorSet>(@aptos_framework);
let config = staking_config::get();
let validator_perf = borrow_global_mut<ValidatorPerformance>(@aptos_framework);
// Process pending stake and distribute transaction fees and rewards for each currently active validator.
validator_set.active_validators.for_each_ref(|validator| {
let validator: &ValidatorInfo = validator;
update_stake_pool(validator_perf, validator.addr, &config);
});
// Process pending stake and distribute transaction fees and rewards for each currently pending_inactive validator
// (requested to leave but not removed yet).
validator_set.pending_inactive.for_each_ref(|validator| {
let validator: &ValidatorInfo = validator;
update_stake_pool(validator_perf, validator.addr, &config);
});
// Settle expired pending_inactive for pending_active validators before activating them.
// update_stake_pool is not called for pending_active validators, so without this step
// their expired pending_inactive would be incorrectly counted as voting power, causing
// a mismatch with the DKG validator set (which excludes expired pending_inactive) and
// an out-of-bounds panic in the epoch manager.
validator_set.pending_active.for_each_ref(|validator| {
let validator: &ValidatorInfo = validator;
let stake_pool = borrow_global_mut<StakePool>(validator.addr);
if (stake_pool.locked_until_secs > 0
&& get_reconfig_start_time_secs() >= stake_pool.locked_until_secs) {
coin::merge(
&mut stake_pool.inactive,
coin::extract_all(&mut stake_pool.pending_inactive)
);
};
});
// Activate currently pending_active validators.
append(&mut validator_set.active_validators, &mut validator_set.pending_active);
// Officially deactivate all pending_inactive validators. They will now no longer receive rewards.
validator_set.pending_inactive = vector::empty();
// Determine the next-epoch active set: either consume the cached value
// produced when reconfig started (async/DKG path), or compute it now from
// live state (sync/governance path). The two paths are mutually exclusive,
// so we avoid the O(n) per-validator recomputation when the cache exists.
let liveness_fallback_event = if (exists<PrecomputedValidatorSet>(@aptos_framework)) {
let PrecomputedValidatorSet { validator_set: precomputed, is_liveness_fallback } =
move_from<PrecomputedValidatorSet>(@aptos_framework);
*validator_set = precomputed;
if (is_liveness_fallback) {
let (minimum_stake, _) = staking_config::get_required_stake(&config);
option::some(ValidatorSetLivenessFallback {
minimum_stake,
emergency_validator_count: validator_set.active_validators.length(),
total_emergency_voting_power: validator_set.total_voting_power,
})
} else {
option::none()
}
} else {
refresh_validator_set_in_place(validator_set, &config)
};
validator_set.total_joining_power = 0;
if (liveness_fallback_event.is_some()) {
event::emit(liveness_fallback_event.extract());
};
// Update validator indices, reset performance scores, and renew lockups.
validator_perf.validators = vector::empty();
let recurring_lockup_duration_secs =
staking_config::get_recurring_lockup_duration(&config);
let vlen = validator_set.active_validators.length();
let validator_index = 0;
while ({
spec {
invariant spec_validators_are_initialized(validator_set.active_validators);
invariant len(validator_set.pending_active) == 0;
invariant len(validator_set.pending_inactive) == 0;
invariant 0 <= validator_index && validator_index <= vlen;
invariant vlen == len(validator_set.active_validators);
invariant forall i in 0..validator_index:
global<ValidatorConfig>(validator_set.active_validators[i].addr).validator_index
< validator_index;
invariant forall i in 0..validator_index:
validator_set.active_validators[i].config.validator_index
< validator_index;
invariant len(validator_perf.validators) == validator_index;
};
validator_index < vlen
}) {
let validator_info =
validator_set.active_validators.borrow_mut(validator_index);
validator_info.config.validator_index = validator_index;
let validator_config =
borrow_global_mut<ValidatorConfig>(validator_info.addr);
validator_config.validator_index = validator_index;
validator_perf.validators.push_back(
IndividualValidatorPerformance {
successful_proposals: 0,
failed_proposals: 0
}
);
// Automatically renew a validator's lockup for validators that will still be in the validator set in the
// next epoch.
let stake_pool = borrow_global_mut<StakePool>(validator_info.addr);
let now_secs = timestamp::now_seconds();
let reconfig_start_secs =
if (chain_status::is_operating()) {
get_reconfig_start_time_secs()
} else {
now_secs
};
if (stake_pool.locked_until_secs <= reconfig_start_secs) {
spec {
assume now_secs + recurring_lockup_duration_secs <= MAX_U64;
};
stake_pool.locked_until_secs = now_secs
+ recurring_lockup_duration_secs;
};
validator_index += 1;
};
if (exists<PendingTransactionFee>(@aptos_framework)) {
let pending_fee_by_validator =
&mut borrow_global_mut<PendingTransactionFee>(@aptos_framework).pending_fee_by_validator;
assert!(
pending_fee_by_validator.is_empty(),
error::internal(ETRANSACTION_FEE_NOT_FULLY_DISTRIBUTED)
);
validator_set.active_validators.for_each_ref(|v| pending_fee_by_validator.add(
v.config.validator_index, aggregator_v2::create_unbounded_aggregator<u64>()
));
};
if (features::periodical_reward_rate_decrease_enabled()) {
// Update rewards rate after reward distribution.
staking_config::calculate_and_save_latest_epoch_rewards_rate();
};
}
Function refresh_validator_set_in_place
Compute the next-epoch active validator set in place from live StakePool/ValidatorConfig state.
Used by on_new_epoch only on the sync (non-DKG) path; the async/DKG path consumes a
pre-computed PrecomputedValidatorSet instead.
Returns Some(ValidatorSetLivenessFallback) when the freshly-computed set was empty and we
fell back to retaining the previous active set.
fun refresh_validator_set_in_place(validator_set: &mut stake::ValidatorSet, config: &staking_config::StakingConfig): option::Option<stake::ValidatorSetLivenessFallback>
Implementation
fun refresh_validator_set_in_place(
validator_set: &mut ValidatorSet,
config: &staking_config::StakingConfig,
): Option<ValidatorSetLivenessFallback> acquires StakePool, ValidatorConfig {
// Update active validator set so that network address/public key change takes effect.
// Moreover, recalculate the total voting power, and deactivate the validator whose
// voting power is less than the minimum required stake.
let next_epoch_validators = vector::empty();
let (minimum_stake, _) = staking_config::get_required_stake(config);
let vlen = validator_set.active_validators.length();
let total_voting_power = 0;
let i = 0;
while ({
spec {
invariant spec_validators_are_initialized(next_epoch_validators);
invariant i <= vlen;
};
i < vlen
}) {
let old_validator_info = validator_set.active_validators.borrow_mut(i);
let pool_address = old_validator_info.addr;
let validator_config = borrow_global<ValidatorConfig>(pool_address);
let stake_pool = borrow_global<StakePool>(pool_address);
let new_validator_info =
generate_validator_info(pool_address, stake_pool, *validator_config);
// A validator needs at least the min stake required to join the validator set.
if (new_validator_info.voting_power >= minimum_stake) {
spec {
assume total_voting_power + new_validator_info.voting_power
<= MAX_U128;
};
total_voting_power +=(new_validator_info.voting_power as u128);
next_epoch_validators.push_back(new_validator_info);
};
i += 1;
};
// In the extreme case where the next epoch validator election produces an empty set (i.e., no staker satisfies the minimum stake or participation requirements), the system enters an emergency liveness preservation mode.
// Instead of transitioning to an empty validator set—which would render the network inoperable—the protocol retains the previous active validator set and recomputes the total voting power from it.
// A ValidatorSetLivenessFallback event is emitted to signal this critical governance and economic security failure.
if (!next_epoch_validators.is_empty()) {
validator_set.active_validators = next_epoch_validators;
validator_set.total_voting_power = total_voting_power;
option::none()
} else {
// We derive the next validator set from the previous epoch's active and pending-active stakers.
// If the resulting set is empty, it indicates that no staker is willing or qualified to participate
// in consensus anymore. In this case, the chain is considered effectively dead, and we must retain
// the previous active validator set as a last-resort liveness fallback.
// Recompute each validator's info from current stake (after update_stake_pool) so that
// voting_power and total_voting_power reflect rewards, fees, and merged stake—not stale values.
let refreshed_validators = vector::empty();
let emergency_total_voting_power = 0u128;
let fallback_vlen = validator_set.active_validators.length();
let fallback_i = 0;
while (fallback_i < fallback_vlen) {
let old_validator_info =
validator_set.active_validators.borrow(fallback_i);
let pool_address = old_validator_info.addr;
let validator_config = &ValidatorConfig[pool_address];
let stake_pool = &StakePool[pool_address];
let new_validator_info =
generate_validator_info(pool_address, stake_pool, *validator_config);
refreshed_validators.push_back(new_validator_info);
emergency_total_voting_power +=(new_validator_info.voting_power as u128);
fallback_i += 1;
};
validator_set.active_validators = refreshed_validators;
validator_set.total_voting_power = emergency_total_voting_power;
option::some(ValidatorSetLivenessFallback {
minimum_stake,
emergency_validator_count: validator_set.active_validators.length(),
total_emergency_voting_power: validator_set.total_voting_power
})
}
}
Function cur_validator_consensus_infos
Return the ValidatorConsensusInfo of each current validator, sorted by current validator index.
public fun cur_validator_consensus_infos(): vector<validator_consensus_info::ValidatorConsensusInfo>
Implementation
public fun cur_validator_consensus_infos(): vector<ValidatorConsensusInfo> acquires ValidatorSet {
let validator_set = borrow_global<ValidatorSet>(@aptos_framework);
validator_consensus_infos_from_validator_set(validator_set)
}
Function compute_simulated_validator_info
Compute simulated next-epoch voting power and ValidatorInfo for a candidate (no stake updates). If include_rewards, use validator_perf to add rewards for current validators; pending_active use false.
fun compute_simulated_validator_info(candidate: &stake::ValidatorInfo, validator_perf: &stake::ValidatorPerformance, rewards_rate: u64, rewards_rate_denominator: u64, validator_index: u64, include_rewards: bool): (u64, stake::ValidatorInfo)
Implementation
fun compute_simulated_validator_info(
candidate: &ValidatorInfo,
validator_perf: &ValidatorPerformance,
rewards_rate: u64,
rewards_rate_denominator: u64,
validator_index: u64,
include_rewards: bool
): (u64, ValidatorInfo) acquires StakePool, ValidatorConfig {
let stake_pool = borrow_global<StakePool>(candidate.addr);
let cur_active = coin::value(&stake_pool.active);
let cur_pending_active = coin::value(&stake_pool.pending_active);
let cur_pending_inactive = coin::value(&stake_pool.pending_inactive);
let cur_reward =
if (include_rewards && cur_active > 0) {
spec {
assert candidate.config.validator_index
< len(validator_perf.validators);
};
let cur_perf =
validator_perf.validators.borrow(candidate.config.validator_index);
spec {
assume cur_perf.successful_proposals + cur_perf.failed_proposals
<= MAX_U64;
};
calculate_rewards_amount(
cur_active,
cur_perf.successful_proposals,
cur_perf.successful_proposals + cur_perf.failed_proposals,
rewards_rate,
rewards_rate_denominator
)
} else { 0 };
let lockup_expired = get_reconfig_start_time_secs()
>= stake_pool.locked_until_secs;
spec {
assume cur_active + cur_pending_active + cur_reward <= MAX_U64;
assume cur_active + cur_pending_inactive + cur_pending_active + cur_reward
<= MAX_U64;
};
let new_voting_power =
cur_active
+ if (lockup_expired) { 0 }
else {
cur_pending_inactive
} + cur_pending_active + cur_reward;
let config = *borrow_global<ValidatorConfig>(candidate.addr);
config.validator_index = validator_index;
(
new_voting_power,
ValidatorInfo { addr: candidate.addr, voting_power: new_voting_power, config }
)
}
Function next_validator_consensus_infos
Deprecated. Use next_validator_consensus_infos_v2() instead.
public fun next_validator_consensus_infos(): vector<validator_consensus_info::ValidatorConsensusInfo>
Implementation
public fun next_validator_consensus_infos(): vector<ValidatorConsensusInfo> {
abort(error::unavailable(EDEPRECATED))
}
Function next_validator_consensus_infos_v2
Pre-compute the next validator set and store the result. Should only be called when reconfiguration starts.
public(friend) fun next_validator_consensus_infos_v2(): stake::ValidatorSet
Implementation
public(friend) fun next_validator_consensus_infos_v2(): ValidatorSet acquires PrecomputedValidatorSet, ValidatorSet, ValidatorPerformance, StakePool, ValidatorConfig {
if (exists<PrecomputedValidatorSet>(@aptos_framework)) {
// Cache hit: already computed for this reconfig, return stored result.
return borrow_global<PrecomputedValidatorSet>(@aptos_framework).validator_set;
};
let cur_validator_set = borrow_global<ValidatorSet>(@aptos_framework);
let staking_config = staking_config::get();
let validator_perf = borrow_global<ValidatorPerformance>(@aptos_framework);
let (minimum_stake, _) = staking_config::get_required_stake(&staking_config);
let (rewards_rate, rewards_rate_denominator) =
staking_config::get_reward_rate(&staking_config);
let new_active_validators = vector[];
let num_new_actives = 0;
let candidate_idx = 0;
let new_total_power = 0;
let num_cur_actives = cur_validator_set.active_validators.length();
let num_cur_pending_actives = cur_validator_set.pending_active.length();
spec {
assume num_cur_actives + num_cur_pending_actives <= MAX_U64;
};
let num_candidates = num_cur_actives + num_cur_pending_actives;
while ({
spec {
invariant candidate_idx <= num_candidates;
invariant spec_validators_are_initialized(new_active_validators);
invariant len(new_active_validators) == num_new_actives;
invariant forall i in 0..len(new_active_validators):
new_active_validators[i].config.validator_index == i;
invariant num_new_actives <= candidate_idx;
invariant spec_validators_are_initialized(new_active_validators);
};
candidate_idx < num_candidates
}) {
let candidate_in_current = candidate_idx < num_cur_actives;
// Order matches on_new_epoch's append(): active then pending_active (append uses pop_back → reverse).
let candidate =
if (candidate_in_current) {
cur_validator_set.active_validators.borrow(candidate_idx)
} else {
cur_validator_set.pending_active.borrow(
num_candidates - 1 - candidate_idx
)
};
let (new_voting_power, new_validator_info) =
compute_simulated_validator_info(
candidate,
validator_perf,
rewards_rate,
rewards_rate_denominator,
num_new_actives,
candidate_in_current
);
if (new_voting_power >= minimum_stake) {
spec {
assume new_total_power + new_voting_power <= MAX_U128;
};
new_total_power +=(new_voting_power as u128);
new_active_validators.push_back(new_validator_info);
num_new_actives += 1;
};
candidate_idx += 1;
};
// Mirror on_new_epoch's empty-validator-set fallback: active then pending_active.
// append() uses pop_back so pending_active ends up in reverse order; match that here.
let is_liveness_fallback = false;
if (new_active_validators.is_empty()
&& (num_cur_actives > 0 || num_cur_pending_actives > 0)) {
let num_fallback = num_cur_actives + num_cur_pending_actives;
for (fallback_idx in 0..num_fallback) {
let in_active = fallback_idx < num_cur_actives;
let candidate =
if (in_active) {
cur_validator_set.active_validators.borrow(fallback_idx)
} else {
cur_validator_set.pending_active.borrow(
num_fallback - 1 - fallback_idx
)
};
let (new_voting_power, new_validator_info) =
compute_simulated_validator_info(
candidate,
validator_perf,
rewards_rate,
rewards_rate_denominator,
new_active_validators.length(),
in_active
);
new_active_validators.push_back(new_validator_info);
new_total_power +=(new_voting_power as u128);
is_liveness_fallback = true;
};
};
let new_validator_set = ValidatorSet {
consensus_scheme: cur_validator_set.consensus_scheme,
active_validators: new_active_validators,
pending_inactive: vector[],
pending_active: vector[],
total_voting_power: new_total_power,
total_joining_power: 0
};
// Save the result by creating the resource.
move_to(
&create_signer::create_signer(@aptos_framework),
PrecomputedValidatorSet { validator_set: new_validator_set, is_liveness_fallback },
);
new_validator_set
}
Function validator_consensus_infos_from_validator_set
public(friend) fun validator_consensus_infos_from_validator_set(validator_set: &stake::ValidatorSet): vector<validator_consensus_info::ValidatorConsensusInfo>
Implementation
friend fun validator_consensus_infos_from_validator_set(
validator_set: &ValidatorSet
): vector<ValidatorConsensusInfo> {
let validator_consensus_infos = vector[];
let num_active = validator_set.active_validators.length();
let num_pending_inactive = validator_set.pending_inactive.length();
spec {
assume num_active + num_pending_inactive <= MAX_U64;
};
let total = num_active + num_pending_inactive;
// Pre-fill the return value with dummy values.
let idx = 0;
while ({
spec {
invariant idx
<= len(validator_set.active_validators)
+ len(validator_set.pending_inactive);
invariant len(validator_consensus_infos) == idx;
invariant len(validator_consensus_infos)
<= len(validator_set.active_validators)
+ len(validator_set.pending_inactive);
};
idx < total
}) {
validator_consensus_infos.push_back(validator_consensus_info::default());
idx += 1;
};
spec {
assert len(validator_consensus_infos)
== len(validator_set.active_validators)
+ len(validator_set.pending_inactive);
assert spec_validator_indices_are_valid_config(
validator_set.active_validators,
len(validator_set.active_validators)
+ len(validator_set.pending_inactive)
);
};
validator_set.active_validators.for_each_ref(
|obj| {
let vi: &ValidatorInfo = obj;
spec {
assume len(validator_consensus_infos)
== len(validator_set.active_validators)
+ len(validator_set.pending_inactive);
assert vi.config.validator_index < len(validator_consensus_infos);
};
let vci = vector::borrow_mut(
&mut validator_consensus_infos, vi.config.validator_index
);
*vci = validator_consensus_info::new(
vi.addr, vi.config.consensus_pubkey, vi.voting_power
);
spec {
assert len(validator_consensus_infos)
== len(validator_set.active_validators)
+ len(validator_set.pending_inactive);
};
}
);
validator_set.pending_inactive.for_each_ref(
|obj| {
let vi: &ValidatorInfo = obj;
spec {
assume len(validator_consensus_infos)
== len(validator_set.active_validators)
+ len(validator_set.pending_inactive);
assert vi.config.validator_index < len(validator_consensus_infos);
};
let vci = vector::borrow_mut(
&mut validator_consensus_infos, vi.config.validator_index
);
*vci = validator_consensus_info::new(
vi.addr, vi.config.consensus_pubkey, vi.voting_power
);
spec {
assert len(validator_consensus_infos)
== len(validator_set.active_validators)
+ len(validator_set.pending_inactive);
};
}
);
validator_consensus_infos
}
Function addresses_from_validator_infos
fun addresses_from_validator_infos(infos: &vector<stake::ValidatorInfo>): vector<address>
Implementation
fun addresses_from_validator_infos(infos: &vector<ValidatorInfo>): vector<address> {
infos.map_ref(|obj| {
let info: &ValidatorInfo = obj;
info.addr
})
}
Function update_stake_pool
Calculate the stake amount of a stake pool for the next epoch.
Update individual validator’s stake pool if commit == true.
- distribute transaction fees to active/pending_inactive delegations
- distribute rewards to active/pending_inactive delegations
- process pending_active, pending_inactive correspondingly This function shouldn’t abort.
fun update_stake_pool(validator_perf: &stake::ValidatorPerformance, pool_address: address, staking_config: &staking_config::StakingConfig)
Implementation
fun update_stake_pool(
validator_perf: &ValidatorPerformance,
pool_address: address,
staking_config: &StakingConfig
) acquires AptosCoinCapabilities, PendingTransactionFee, StakePool, TransactionFeeConfig, ValidatorConfig {
let stake_pool = borrow_global_mut<StakePool>(pool_address);
let validator_config = borrow_global<ValidatorConfig>(pool_address);
let validator_index = validator_config.validator_index;
let cur_validator_perf = validator_perf.validators.borrow(validator_index);
let num_successful_proposals = cur_validator_perf.successful_proposals;
let fee_pending_inactive = 0;
let fee_active = 0;
let fee_limit =
if (exists<TransactionFeeConfig>(@aptos_framework)) {
let TransactionFeeConfig::V0 { max_fee_octa_allowed_per_epoch_per_pool } =
borrow_global<TransactionFeeConfig>(@aptos_framework);
*max_fee_octa_allowed_per_epoch_per_pool
} else {
MAX_U64 as u64
};
if (exists<PendingTransactionFee>(@aptos_framework)) {
let pending_fee_by_validator =
&mut borrow_global_mut<PendingTransactionFee>(@aptos_framework).pending_fee_by_validator;
if (pending_fee_by_validator.contains(&validator_index)) {
let fee_octa = pending_fee_by_validator.remove(&validator_index).read();
if (fee_octa > fee_limit) {
fee_octa = fee_limit;
};
let stake_active = (coin::value(&stake_pool.active) as u128);
let stake_pending_inactive =
(coin::value(&stake_pool.pending_inactive) as u128);
fee_pending_inactive =
(
((fee_octa as u128) * stake_pending_inactive
/ (stake_active + stake_pending_inactive)) as u64
);
fee_active = fee_octa - fee_pending_inactive;
}
};
spec {
// The following addition should not overflow because `num_total_proposals` cannot be larger than 86400,
// the maximum number of proposals in a day (1 proposal per second).
assume cur_validator_perf.successful_proposals
+ cur_validator_perf.failed_proposals <= MAX_U64;
};
let num_total_proposals =
cur_validator_perf.successful_proposals
+ cur_validator_perf.failed_proposals;
let (rewards_rate, rewards_rate_denominator) =
staking_config::get_reward_rate(staking_config);
let rewards_active =
distribute_rewards(
&mut stake_pool.active,
num_successful_proposals,
num_total_proposals,
rewards_rate,
rewards_rate_denominator
);
let rewards_pending_inactive =
distribute_rewards(
&mut stake_pool.pending_inactive,
num_successful_proposals,
num_total_proposals,
rewards_rate,
rewards_rate_denominator
);
spec {
assume rewards_active + rewards_pending_inactive <= MAX_U64;
};
if (std::features::is_distribute_transaction_fee_enabled()) {
let mint_cap =
&borrow_global<AptosCoinCapabilities>(@aptos_framework).mint_cap;
if (fee_active > 0) {
coin::merge(&mut stake_pool.active, coin::mint(fee_active, mint_cap));
};
if (fee_pending_inactive > 0) {
coin::merge(
&mut stake_pool.pending_inactive,
coin::mint(fee_pending_inactive, mint_cap)
);
};
let fee_amount = fee_active + fee_pending_inactive;
if (fee_amount > 0) {
event::emit(DistributeTransactionFee { pool_address, fee_amount });
};
};
let rewards_amount = rewards_active + rewards_pending_inactive;
// Pending active stake can now be active.
coin::merge(
&mut stake_pool.active, coin::extract_all(&mut stake_pool.pending_active)
);
// Pending inactive stake is only fully unlocked and moved into inactive if the current lockup cycle has expired
let current_lockup_expiration = stake_pool.locked_until_secs;
if (get_reconfig_start_time_secs() >= current_lockup_expiration) {
coin::merge(
&mut stake_pool.inactive,
coin::extract_all(&mut stake_pool.pending_inactive)
);
};
event::emit(DistributeRewards { pool_address, rewards_amount });
}
Function get_reconfig_start_time_secs
Assuming we are in a middle of a reconfiguration (no matter it is immediate or async), get its start time.
fun get_reconfig_start_time_secs(): u64
Implementation
fun get_reconfig_start_time_secs(): u64 {
if (reconfiguration_state::is_in_progress()) {
reconfiguration_state::start_time_secs()
} else {
timestamp::now_seconds()
}
}
Function calculate_rewards_amount
Calculate the rewards amount.
fun calculate_rewards_amount(stake_amount: u64, num_successful_proposals: u64, num_total_proposals: u64, rewards_rate: u64, rewards_rate_denominator: u64): u64
Implementation
fun calculate_rewards_amount(
stake_amount: u64,
num_successful_proposals: u64,
num_total_proposals: u64,
rewards_rate: u64,
rewards_rate_denominator: u64
): u64 {
spec {
// The following condition must hold because
// (1) num_successful_proposals <= num_total_proposals, and
// (2) `num_total_proposals` cannot be larger than 86400, the maximum number of proposals
// in a day (1 proposal per second), and `num_total_proposals` is reset to 0 every epoch.
assume num_successful_proposals * MAX_REWARDS_RATE <= MAX_U64;
};
// The rewards amount is equal to (stake amount * rewards rate * performance multiplier).
// We do multiplication in u128 before division to avoid the overflow and minimize the rounding error.
let rewards_numerator =
(stake_amount as u128) * (rewards_rate as u128)
* (num_successful_proposals as u128);
let rewards_denominator =
(rewards_rate_denominator as u128) * (num_total_proposals as u128);
if (rewards_denominator > 0) {
((rewards_numerator / rewards_denominator) as u64)
} else { 0 }
}
Function distribute_rewards
Mint rewards corresponding to current epoch’s stake and num_successful_votes.
fun distribute_rewards(stake: &mut coin::Coin<aptos_coin::AptosCoin>, num_successful_proposals: u64, num_total_proposals: u64, rewards_rate: u64, rewards_rate_denominator: u64): u64
Implementation
fun distribute_rewards(
stake: &mut Coin<AptosCoin>,
num_successful_proposals: u64,
num_total_proposals: u64,
rewards_rate: u64,
rewards_rate_denominator: u64
): u64 acquires AptosCoinCapabilities {
let stake_amount = coin::value(stake);
let rewards_amount =
if (stake_amount > 0) {
calculate_rewards_amount(
stake_amount,
num_successful_proposals,
num_total_proposals,
rewards_rate,
rewards_rate_denominator
)
} else { 0 };
if (rewards_amount > 0) {
let mint_cap =
&borrow_global<AptosCoinCapabilities>(@aptos_framework).mint_cap;
let rewards = coin::mint(rewards_amount, mint_cap);
coin::merge(stake, rewards);
};
rewards_amount
}
Function append
fun append<T>(v1: &mut vector<T>, v2: &mut vector<T>)
Implementation
fun append<T>(v1: &mut vector<T>, v2: &mut vector<T>) {
while (!v2.is_empty()) {
v1.push_back(v2.pop_back());
}
}
Function find_validator
fun find_validator(v: &vector<stake::ValidatorInfo>, addr: address): option::Option<u64>
Implementation
fun find_validator(v: &vector<ValidatorInfo>, addr: address): Option<u64> {
let i = 0;
let len = v.length();
while ({
spec {
invariant !(exists j in 0..i: v[j].addr == addr);
};
i < len
}) {
if (v.borrow(i).addr == addr) {
return option::some(i)
};
i += 1;
};
option::none()
}
Function generate_validator_info
fun generate_validator_info(addr: address, stake_pool: &stake::StakePool, config: stake::ValidatorConfig): stake::ValidatorInfo
Implementation
fun generate_validator_info(
addr: address, stake_pool: &StakePool, config: ValidatorConfig
): ValidatorInfo {
let voting_power = get_voting_power(stake_pool);
ValidatorInfo { addr, voting_power, config }
}
Function get_voting_power
Returns the sum of all non-inactive stake (active + pending_active + pending_inactive). This equals the actual voting power for the next epoch only when called after expired pending_inactive has been settled to inactive (e.g. in on_new_epoch or join_validator_set).
fun get_voting_power(stake_pool: &stake::StakePool): u64
Implementation
fun get_voting_power(stake_pool: &StakePool): u64 {
let value_pending_active = coin::value(&stake_pool.pending_active);
let value_active = coin::value(&stake_pool.active);
let value_pending_inactive = coin::value(&stake_pool.pending_inactive);
spec {
assume value_pending_active + value_active + value_pending_inactive
<= MAX_U64;
};
value_pending_active + value_active + value_pending_inactive
}
Function update_voting_power_increase
fun update_voting_power_increase(increase_amount: u64)
Implementation
fun update_voting_power_increase(increase_amount: u64) acquires ValidatorSet {
let validator_set = borrow_global_mut<ValidatorSet>(@aptos_framework);
let voting_power_increase_limit =
(
staking_config::get_voting_power_increase_limit(&staking_config::get()) as u128
);
validator_set.total_joining_power +=(increase_amount as u128);
// Only validator voting power increase if the current validator set's voting power > 0.
if (validator_set.total_voting_power > 0) {
assert!(
validator_set.total_joining_power
<= validator_set.total_voting_power * voting_power_increase_limit
/ 100,
error::invalid_argument(EVOTING_POWER_INCREASE_EXCEEDS_LIMIT)
);
}
}
Function assert_stake_pool_exists
fun assert_stake_pool_exists(pool_address: address)
Implementation
fun assert_stake_pool_exists(pool_address: address) {
assert!(
stake_pool_exists(pool_address),
error::invalid_argument(ESTAKE_POOL_DOES_NOT_EXIST)
);
}
Function configure_allowed_validators
public fun configure_allowed_validators(aptos_framework: &signer, accounts: vector<address>)
Implementation
public fun configure_allowed_validators(
aptos_framework: &signer, accounts: vector<address>
) acquires AllowedValidators {
let aptos_framework_address = signer::address_of(aptos_framework);
system_addresses::assert_aptos_framework(aptos_framework);
if (!exists<AllowedValidators>(aptos_framework_address)) {
move_to(aptos_framework, AllowedValidators { accounts });
} else {
let allowed = borrow_global_mut<AllowedValidators>(aptos_framework_address);
allowed.accounts = accounts;
}
}
Function is_allowed
fun is_allowed(account: address): bool
Implementation
fun is_allowed(account: address): bool acquires AllowedValidators {
if (!exists<AllowedValidators>(@aptos_framework)) { true }
else {
let allowed = borrow_global<AllowedValidators>(@aptos_framework);
allowed.accounts.contains(&account)
}
}
Function assert_owner_cap_exists
fun assert_owner_cap_exists(owner: address)
Implementation
fun assert_owner_cap_exists(owner: address) {
assert!(
exists<OwnerCapability>(owner),
error::not_found(EOWNER_CAP_NOT_FOUND)
);
}
Function assert_reconfig_not_in_progress
fun assert_reconfig_not_in_progress()
Implementation
fun assert_reconfig_not_in_progress() {
assert!(
!reconfiguration_state::is_in_progress(),
error::invalid_state(ERECONFIGURATION_IN_PROGRESS)
);
}
Specification
High-level Requirements
| No. | Requirement | Criticality | Implementation | Enforcement |
|---|---|---|---|---|
| 1 | The validator set resource stores consensus information for each validator. The consensus scheme remains consistent across all validators within the set. | Low | The consensus_scheme attribute within ValidatorSet initializes with the value zero during the module's initialization and its value remains unchanged afterward. | Formally verified by the data invariant of ValidatorSet. |
| 2 | The owner of a validator is immutable. | Low | During the initialization of a validator, the owner attribute becomes the signer's address. This assignment establishes the signer as the owner and controller of the validator entity. Subsequently, the owner attribute remains unchanged throughout the validator's lifespan, maintaining its assigned value without any modifications. | Formally verified in the schema ValidatorOwnerNoChange. |
| 3 | The total staked value in the stake pool should remain constant, excluding operations related to adding and withdrawing. | Low | The total staked value (AptosCoin) of a stake pool is grouped by: active, inactive, pending_active, and pending_inactive. The stake value remains constant except during the execution of the add_stake_with_cap or withdraw_with_cap functions or on_new_epoch (which distributes the reward). | Formally specified in the schema StakedValueNoChange. |
| 4 | During each epoch, the following operations should be consistently performed without aborting: rewards distribution, validator activation/deactivation, updates to validator sets and voting power, and renewal of lockups. | Low | The on_new_epoch function is triggered at each epoch boundary to perform distribution of the transaction fee, updates to active/inactive stakes, updates to pending active/inactive validators and adjusts voting power of the validators without aborting. | Formally verified via on_new_epoch. This also requires a manual review to verify the state updates of the stake pool. |
Module-level Specification
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);
invariant [suspendable] chain_status::is_operating() ==>
exists<ValidatorSet>(@aptos_framework);
apply ValidatorOwnerNoChange to *;
apply ValidatorNotChangeDuringReconfig to * except on_new_epoch;
apply StakePoolNotChangeDuringReconfig to * except on_new_epoch, update_stake_pool;
global ghost_valid_perf: ValidatorPerformance;
global ghost_proposer_idx: Option<u64>;
global ghost_active_num: u64;
global ghost_pending_inactive_num: u64;
Resource ValidatorSet
struct ValidatorSet has copy, drop, store, key
-
consensus_scheme: u8 -
active_validators: vector<stake::ValidatorInfo> -
pending_inactive: vector<stake::ValidatorInfo> -
pending_active: vector<stake::ValidatorInfo> -
total_voting_power: u128 -
total_joining_power: u128
// This enforces high-level requirement 1:
invariant consensus_scheme == 0;
schema ValidatorNotChangeDuringReconfig {
ensures (
reconfiguration_state::spec_is_in_progress()
&& old(exists<ValidatorSet>(@aptos_framework))
) ==>
old(global<ValidatorSet>(@aptos_framework))
== global<ValidatorSet>(@aptos_framework);
}
schema StakePoolNotChangeDuringReconfig {
ensures forall a: address where old(exists<StakePool>(a)):
reconfiguration_state::spec_is_in_progress() ==>
(
old(global<StakePool>(a).pending_inactive)
== global<StakePool>(a).pending_inactive
&& old(global<StakePool>(a).pending_active)
== global<StakePool>(a).pending_active
&& old(global<StakePool>(a).inactive)
== global<StakePool>(a).inactive
&& old(global<StakePool>(a).active)
== global<StakePool>(a).active
);
}
schema ValidatorOwnerNoChange {
// This enforces high-level requirement 2:
ensures forall addr: address where old(exists<OwnerCapability>(addr)):
old(global<OwnerCapability>(addr)).pool_address
== global<OwnerCapability>(addr).pool_address;
}
schema StakedValueNochange {
pool_address: address;
let stake_pool = global<StakePool>(pool_address);
let post post_stake_pool = global<StakePool>(pool_address);
// This enforces high-level requirement 3:
ensures stake_pool.active.value + stake_pool.inactive.value
+ stake_pool.pending_active.value + stake_pool.pending_inactive.value
== post_stake_pool.active.value + post_stake_pool.inactive.value
+ post_stake_pool.pending_active.value
+ post_stake_pool.pending_inactive.value;
}
fun validator_set_is_valid(): bool {
let validator_set = global<ValidatorSet>(@aptos_framework);
validator_set_is_valid_impl(validator_set)
}
fun validator_set_is_valid_impl(validator_set: ValidatorSet): bool {
spec_validators_are_initialized(validator_set.active_validators)
&& spec_validators_are_initialized(validator_set.pending_inactive)
&& spec_validators_are_initialized(validator_set.pending_active)
&& spec_validator_indices_are_valid(validator_set.active_validators)
&& spec_validator_indices_are_valid(validator_set.pending_inactive)
&& spec_validator_indices_active_pending_inactive(validator_set)
}
Function get_validator_state
#[view]
public fun get_validator_state(pool_address: address): u64
aborts_if !exists<ValidatorSet>(@aptos_framework);
let validator_set = global<ValidatorSet>(@aptos_framework);
ensures result == VALIDATOR_STATUS_PENDING_ACTIVE ==>
spec_contains(validator_set.pending_active, pool_address);
ensures result == VALIDATOR_STATUS_ACTIVE ==>
spec_contains(validator_set.active_validators, pool_address);
ensures result == VALIDATOR_STATUS_PENDING_INACTIVE ==>
spec_contains(validator_set.pending_inactive, pool_address);
ensures result == VALIDATOR_STATUS_INACTIVE ==>
(
!spec_contains(validator_set.pending_active, pool_address)
&& !spec_contains(validator_set.active_validators, pool_address)
&& !spec_contains(validator_set.pending_inactive, pool_address)
);
Function get_pending_transaction_fee
#[view]
public fun get_pending_transaction_fee(): vector<u64>
pragma verify = false;
Function initialize
public(friend) fun initialize(aptos_framework: &signer)
pragma disable_invariants_in_body;
let aptos_addr = signer::address_of(aptos_framework);
aborts_if !system_addresses::is_aptos_framework_address(aptos_addr);
aborts_if exists<ValidatorSet>(aptos_addr);
aborts_if exists<ValidatorPerformance>(aptos_addr);
ensures exists<ValidatorSet>(aptos_addr);
ensures global<ValidatorSet>(aptos_addr).consensus_scheme == 0;
ensures exists<ValidatorPerformance>(aptos_addr);
Function remove_validators
public fun remove_validators(aptos_framework: &signer, validators: &vector<address>)
requires chain_status::is_operating();
let validator_set = global<ValidatorSet>(@aptos_framework);
let post post_validator_set = global<ValidatorSet>(@aptos_framework);
let active_validators = validator_set.active_validators;
let post post_active_validators = post_validator_set.active_validators;
let pending_inactive_validators = validator_set.pending_inactive;
let post post_pending_inactive_validators = post_validator_set.pending_inactive;
invariant len(active_validators) > 0;
ensures len(active_validators) + len(pending_inactive_validators)
== len(post_active_validators) + len(post_pending_inactive_validators);
Function initialize_stake_owner
public entry fun initialize_stake_owner(owner: &signer, initial_stake_amount: u64, operator: address, voter: address)
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 {
consensus_pubkey: vector::empty(),
network_addresses: vector::empty(),
fullnode_addresses: vector::empty(),
validator_index: 0
};
ensures global<OwnerCapability>(addr) == OwnerCapability { pool_address: addr };
let post stakepool = global<StakePool>(addr);
let post active = stakepool.active.value;
let post pending_active = stakepool.pending_active.value;
ensures spec_is_current_epoch_validator(addr) ==>
pending_active == initial_stake_amount;
ensures !spec_is_current_epoch_validator(addr) ==>
active == initial_stake_amount;
Function initialize_validator
public entry fun initialize_validator(account: &signer, consensus_pubkey: vector<u8>, proof_of_possession: vector<u8>, network_addresses: vector<u8>, fullnode_addresses: vector<u8>)
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)
);
aborts_if !option::is_some(pubkey_from_pop);
let addr = signer::address_of(account);
let post_addr = signer::address_of(account);
let allowed = global<AllowedValidators>(@aptos_framework);
aborts_if exists<ValidatorConfig>(addr);
aborts_if exists<AllowedValidators>(@aptos_framework)
&& !vector::spec_contains(allowed.accounts, addr);
aborts_if stake_pool_exists(addr);
aborts_if exists<OwnerCapability>(addr);
aborts_if !exists<account::Account>(addr);
aborts_if global<account::Account>(addr).guid_creation_num + 12 > MAX_U64;
aborts_if global<account::Account>(addr).guid_creation_num + 12
>= account::MAX_GUID_CREATION_NUM;
ensures exists<StakePool>(post_addr);
ensures global<OwnerCapability>(post_addr)
== OwnerCapability { pool_address: post_addr };
ensures global<ValidatorConfig>(post_addr)
== ValidatorConfig {
consensus_pubkey,
network_addresses,
fullnode_addresses,
validator_index: 0
};
Function extract_owner_cap
public fun extract_owner_cap(owner: &signer): stake::OwnerCapability
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);
Function deposit_owner_cap
public fun deposit_owner_cap(owner: &signer, owner_cap: stake::OwnerCapability)
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;
Function set_operator_with_cap
public fun set_operator_with_cap(owner_cap: &stake::OwnerCapability, new_operator: address)
let pool_address = owner_cap.pool_address;
let post post_stake_pool = global<StakePool>(pool_address);
modifies global<StakePool>(pool_address);
include StakedValueNochange;
ensures post_stake_pool.operator_address == new_operator;
Function set_delegated_voter_with_cap
public fun set_delegated_voter_with_cap(owner_cap: &stake::OwnerCapability, new_voter: address)
let pool_address = owner_cap.pool_address;
let post post_stake_pool = global<StakePool>(pool_address);
include StakedValueNochange;
aborts_if !exists<StakePool>(pool_address);
modifies global<StakePool>(pool_address);
ensures post_stake_pool.delegated_voter == new_voter;
Function add_stake
public entry fun add_stake(owner: &signer, amount: u64)
pragma verify = false;
pragma aborts_if_is_partial;
include AbortsIfSignerPermissionStake { s: owner };
aborts_if reconfiguration_state::spec_is_in_progress();
include ResourceRequirement;
include AddStakeAbortsIfAndEnsures;
Function add_stake_with_cap
public fun add_stake_with_cap(owner_cap: &stake::OwnerCapability, coins: coin::Coin<aptos_coin::AptosCoin>)
pragma disable_invariants_in_body;
pragma verify = false;
include ResourceRequirement;
let amount = coins.value;
aborts_if reconfiguration_state::spec_is_in_progress();
include AddStakeWithCapAbortsIfAndEnsures { amount };
Function reactivate_stake_with_cap
public fun reactivate_stake_with_cap(owner_cap: &stake::OwnerCapability, amount: u64)
let pool_address = owner_cap.pool_address;
include StakedValueNochange;
aborts_if reconfiguration_state::spec_is_in_progress();
aborts_if !stake_pool_exists(pool_address);
let pre_stake_pool = global<StakePool>(pool_address);
let post stake_pool = global<StakePool>(pool_address);
modifies global<StakePool>(pool_address);
let min_amount = aptos_std::math64::min(
amount, pre_stake_pool.pending_inactive.value
);
ensures stake_pool.pending_inactive.value
== pre_stake_pool.pending_inactive.value - min_amount;
ensures stake_pool.active.value == pre_stake_pool.active.value + min_amount;
Function rotate_consensus_key
public entry fun rotate_consensus_key(operator: &signer, pool_address: address, new_consensus_pubkey: vector<u8>, proof_of_possession: vector<u8>)
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);
aborts_if signer::address_of(operator) != pre_stake_pool.operator_address;
aborts_if !exists<ValidatorConfig>(pool_address);
let pubkey_from_pop = bls12381::spec_public_key_from_bytes_with_pop(
new_consensus_pubkey,
proof_of_possession_from_bytes(proof_of_possession)
);
aborts_if !option::is_some(pubkey_from_pop);
modifies global<ValidatorConfig>(pool_address);
include StakedValueNochange;
ensures validator_info.consensus_pubkey == new_consensus_pubkey;
Function update_network_and_fullnode_addresses
public entry fun update_network_and_fullnode_addresses(operator: &signer, pool_address: address, new_network_addresses: vector<u8>, new_fullnode_addresses: vector<u8>)
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;
aborts_if reconfiguration_state::spec_is_in_progress();
aborts_if !exists<StakePool>(pool_address);
aborts_if !exists<ValidatorConfig>(pool_address);
aborts_if signer::address_of(operator) != pre_stake_pool.operator_address;
ensures validator_info.network_addresses == new_network_addresses;
ensures validator_info.fullnode_addresses == new_fullnode_addresses;
Function increase_lockup_with_cap
public fun increase_lockup_with_cap(owner_cap: &stake::OwnerCapability)
let config = global<staking_config::StakingConfig>(@aptos_framework);
let pool_address = owner_cap.pool_address;
let pre_stake_pool = global<StakePool>(pool_address);
let post stake_pool = global<StakePool>(pool_address);
let now_seconds = timestamp::spec_now_seconds();
let lockup = config.recurring_lockup_duration_secs;
modifies global<StakePool>(pool_address);
include StakedValueNochange;
aborts_if !exists<StakePool>(pool_address);
aborts_if pre_stake_pool.locked_until_secs >= lockup + now_seconds;
aborts_if lockup + now_seconds > MAX_U64;
aborts_if !exists<timestamp::CurrentTimeMicroseconds>(@aptos_framework);
aborts_if !exists<staking_config::StakingConfig>(@aptos_framework);
ensures stake_pool.locked_until_secs == lockup + now_seconds;
Function join_validator_set
public entry fun join_validator_set(operator: &signer, pool_address: address)
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);
aborts_if !exists<StakingConfig>(@aptos_framework);
aborts_if !exists<ValidatorSet>(@aptos_framework);
aborts_if reconfiguration_state::spec_is_in_progress();
let stake_pool = global<StakePool>(pool_address);
let validator_set = global<ValidatorSet>(@aptos_framework);
let post p_validator_set = global<ValidatorSet>(@aptos_framework);
aborts_if signer::address_of(operator) != stake_pool.operator_address;
aborts_if option::is_some(
spec_find_validator(validator_set.active_validators, pool_address)
)
|| option::is_some(
spec_find_validator(validator_set.pending_inactive, pool_address)
)
|| option::is_some(
spec_find_validator(validator_set.pending_active, pool_address)
);
let config = staking_config::get();
let voting_power = get_voting_power(stake_pool);
let minimum_stake = config.minimum_stake;
let maximum_stake = config.maximum_stake;
aborts_if voting_power < minimum_stake;
aborts_if voting_power > maximum_stake;
let validator_config = global<ValidatorConfig>(pool_address);
aborts_if vector::is_empty(validator_config.consensus_pubkey);
let validator_set_size = vector::length(validator_set.active_validators)
+ vector::length(validator_set.pending_active) + 1;
aborts_if validator_set_size > MAX_VALIDATOR_SET_SIZE;
let voting_power_increase_limit = (
staking_config::get_voting_power_increase_limit(config) as u128
);
aborts_if (validator_set.total_joining_power + (voting_power as u128)) > MAX_U128;
aborts_if validator_set.total_voting_power * voting_power_increase_limit
> MAX_U128;
aborts_if validator_set.total_voting_power > 0
&& (validator_set.total_joining_power + (voting_power as u128)) * 100
> validator_set.total_voting_power * voting_power_increase_limit;
let post p_validator_info = ValidatorInfo {
addr: pool_address,
voting_power,
config: validator_config
};
ensures validator_set.total_joining_power + voting_power
== p_validator_set.total_joining_power;
ensures vector::spec_contains(p_validator_set.pending_active, p_validator_info);
Function unlock_with_cap
public fun unlock_with_cap(amount: u64, owner_cap: &stake::OwnerCapability)
pragma verify_duration_estimate = 300;
let pool_address = owner_cap.pool_address;
let pre_stake_pool = global<StakePool>(pool_address);
let post stake_pool = global<StakePool>(pool_address);
aborts_if reconfiguration_state::spec_is_in_progress();
aborts_if amount != 0 && !exists<StakePool>(pool_address);
modifies global<StakePool>(pool_address);
include StakedValueNochange;
let min_amount = aptos_std::math64::min(amount, pre_stake_pool.active.value);
ensures stake_pool.active.value == pre_stake_pool.active.value - min_amount;
ensures stake_pool.pending_inactive.value
== pre_stake_pool.pending_inactive.value + min_amount;
Function withdraw
public entry fun withdraw(owner: &signer, withdraw_amount: u64)
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);
let pool_address = ownership_cap.pool_address;
let stake_pool = global<StakePool>(pool_address);
aborts_if !exists<OwnerCapability>(addr);
aborts_if !exists<StakePool>(pool_address);
aborts_if !exists<ValidatorSet>(@aptos_framework);
let validator_set = global<ValidatorSet>(@aptos_framework);
let bool_find_validator = !option::is_some(
spec_find_validator(validator_set.active_validators, pool_address)
)
&& !option::is_some(
spec_find_validator(validator_set.pending_inactive, pool_address)
)
&& !option::is_some(
spec_find_validator(validator_set.pending_active, pool_address)
);
aborts_if bool_find_validator
&& !exists<timestamp::CurrentTimeMicroseconds>(@aptos_framework);
let new_withdraw_amount_1 = min(
withdraw_amount,
stake_pool.inactive.value + stake_pool.pending_inactive.value
);
let new_withdraw_amount_2 = min(withdraw_amount, stake_pool.inactive.value);
aborts_if bool_find_validator
&& timestamp::now_seconds() > stake_pool.locked_until_secs
&& new_withdraw_amount_1 > 0
&& stake_pool.inactive.value + stake_pool.pending_inactive.value
< new_withdraw_amount_1;
aborts_if !(
bool_find_validator
&& exists<timestamp::CurrentTimeMicroseconds>(@aptos_framework)
)
&& new_withdraw_amount_2 > 0
&& stake_pool.inactive.value < new_withdraw_amount_2;
aborts_if !exists<coin::CoinStore<AptosCoin>>(addr);
include coin::DepositAbortsIf<AptosCoin> { account_addr: addr };
let coin_store = global<coin::CoinStore<AptosCoin>>(addr);
let post p_coin_store = global<coin::CoinStore<AptosCoin>>(addr);
ensures bool_find_validator
&& timestamp::now_seconds() > stake_pool.locked_until_secs
&& exists<account::Account>(addr)
&& exists<coin::CoinStore<AptosCoin>>(addr) ==>
coin_store.coin.value + new_withdraw_amount_1 == p_coin_store.coin.value;
ensures !(
bool_find_validator
&& exists<timestamp::CurrentTimeMicroseconds>(@aptos_framework)
)
&& exists<account::Account>(addr)
&& exists<coin::CoinStore<AptosCoin>>(addr) ==>
coin_store.coin.value + new_withdraw_amount_2 == p_coin_store.coin.value;
Function leave_validator_set
public entry fun leave_validator_set(operator: &signer, pool_address: address)
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);
aborts_if !exists<StakePool>(pool_address);
aborts_if !exists<ValidatorSet>(@aptos_framework);
aborts_if !exists<staking_config::StakingConfig>(@aptos_framework);
let stake_pool = global<StakePool>(pool_address);
aborts_if signer::address_of(operator) != stake_pool.operator_address;
let validator_set = global<ValidatorSet>(@aptos_framework);
let validator_find_bool = option::is_some(
spec_find_validator(validator_set.pending_active, pool_address)
);
let active_validators = validator_set.active_validators;
let pending_active = validator_set.pending_active;
let post post_validator_set = global<ValidatorSet>(@aptos_framework);
let post post_active_validators = post_validator_set.active_validators;
let pending_inactive_validators = validator_set.pending_inactive;
let post post_pending_inactive_validators = post_validator_set.pending_inactive;
ensures len(active_validators) + len(pending_inactive_validators)
== len(post_active_validators) + len(post_pending_inactive_validators);
aborts_if !validator_find_bool
&& !option::is_some(spec_find_validator(active_validators, pool_address));
aborts_if !validator_find_bool
&& vector::length(validator_set.active_validators)
<= option::borrow(spec_find_validator(active_validators, pool_address));
aborts_if !validator_find_bool
&& vector::length(validator_set.active_validators) < 2;
aborts_if validator_find_bool
&& vector::length(validator_set.pending_active)
<= option::borrow(spec_find_validator(pending_active, pool_address));
let post p_validator_set = global<ValidatorSet>(@aptos_framework);
let validator_stake = (get_voting_power(stake_pool) as u128);
ensures validator_find_bool
&& validator_set.total_joining_power > validator_stake ==>
p_validator_set.total_joining_power
== validator_set.total_joining_power - validator_stake;
ensures !validator_find_bool ==>
!option::is_some(
spec_find_validator(p_validator_set.pending_active, pool_address)
);
Function is_current_epoch_validator
public fun is_current_epoch_validator(pool_address: address): bool
include ResourceRequirement;
aborts_if !spec_has_stake_pool(pool_address);
ensures result == spec_is_current_epoch_validator(pool_address);
Function update_performance_statistics
public(friend) fun update_performance_statistics(proposer_index: option::Option<u64>, failed_proposer_indices: vector<u64>)
requires chain_status::is_operating();
aborts_if false;
let validator_perf = global<ValidatorPerformance>(@aptos_framework);
let post post_validator_perf = global<ValidatorPerformance>(@aptos_framework);
let validator_len = len(validator_perf.validators);
ensures (
option::is_some(ghost_proposer_idx)
&& option::borrow(ghost_proposer_idx) < validator_len
) ==>
(
post_validator_perf.validators[option::borrow(ghost_proposer_idx)].successful_proposals ==
validator_perf.validators[option::borrow(ghost_proposer_idx)].successful_proposals
+ 1
);
Function on_new_epoch
public(friend) fun on_new_epoch()
pragma verify = false;
pragma disable_invariants_in_body;
include ResourceRequirement;
include GetReconfigStartTimeRequirement;
include staking_config::StakingRewardsConfigRequirement;
include aptos_framework::aptos_coin::ExistsAptosCoin;
// This enforces high-level requirement 4:
aborts_if false;
ensures !exists<PrecomputedValidatorSet>(@aptos_framework);
ensures old(exists<PrecomputedValidatorSet>(@aptos_framework)) ==>
global<ValidatorSet>(@aptos_framework).active_validators
== old(global<PrecomputedValidatorSet>(@aptos_framework)).validator_set.active_validators;
Function compute_simulated_validator_info
fun compute_simulated_validator_info(candidate: &stake::ValidatorInfo, validator_perf: &stake::ValidatorPerformance, rewards_rate: u64, rewards_rate_denominator: u64, validator_index: u64, include_rewards: bool): (u64, stake::ValidatorInfo)
include GetReconfigStartTimeRequirement;
requires rewards_rate <= staking_config::MAX_REWARDS_RATE;
requires rewards_rate_denominator > 0;
requires rewards_rate <= rewards_rate_denominator;
requires include_rewards ==>
candidate.config.validator_index < len(validator_perf.validators);
aborts_if false;
ensures result_2.addr == candidate.addr;
ensures result_2.config.validator_index == validator_index;
ensures result_2.voting_power == result_1;
Function next_validator_consensus_infos_v2
public(friend) fun next_validator_consensus_infos_v2(): stake::ValidatorSet
pragma verify_duration_estimate = 300;
aborts_if false;
include ResourceRequirement;
include GetReconfigStartTimeRequirement;
include features::spec_periodical_reward_rate_decrease_enabled() ==>
staking_config::StakingRewardsConfigEnabledRequirement;
ensures exists<PrecomputedValidatorSet>(@aptos_framework);
Function validator_consensus_infos_from_validator_set
public(friend) fun validator_consensus_infos_from_validator_set(validator_set: &stake::ValidatorSet): vector<validator_consensus_info::ValidatorConsensusInfo>
aborts_if false;
invariant spec_validator_indices_are_valid_config(
validator_set.active_validators,
len(validator_set.active_validators) + len(validator_set.pending_inactive)
);
invariant len(validator_set.pending_inactive) == 0
|| spec_validator_indices_are_valid_config(
validator_set.pending_inactive,
len(validator_set.active_validators)
+ len(validator_set.pending_inactive)
);
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()
}
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
}
}
Function update_stake_pool
fun update_stake_pool(validator_perf: &stake::ValidatorPerformance, pool_address: address, staking_config: &staking_config::StakingConfig)
pragma verify_duration_estimate = 300;
include ResourceRequirement;
include GetReconfigStartTimeRequirement;
include staking_config::StakingRewardsConfigRequirement;
include UpdateStakePoolAbortsIf;
let stake_pool = global<StakePool>(pool_address);
let validator_config = global<ValidatorConfig>(pool_address);
let cur_validator_perf = validator_perf.validators[validator_config.validator_index];
let num_successful_proposals = cur_validator_perf.successful_proposals;
let num_total_proposals = cur_validator_perf.successful_proposals
+ cur_validator_perf.failed_proposals;
let rewards_rate = spec_get_reward_rate_1(staking_config);
let rewards_rate_denominator = spec_get_reward_rate_2(staking_config);
let rewards_amount_1 = if (stake_pool.active.value > 0) {
spec_rewards_amount(
stake_pool.active.value,
num_successful_proposals,
num_total_proposals,
rewards_rate,
rewards_rate_denominator
)
} else { 0 };
let rewards_amount_2 = if (stake_pool.pending_inactive.value > 0) {
spec_rewards_amount(
stake_pool.pending_inactive.value,
num_successful_proposals,
num_total_proposals,
rewards_rate,
rewards_rate_denominator
)
} else { 0 };
let post post_stake_pool = global<StakePool>(pool_address);
let post post_active_value = post_stake_pool.active.value;
let post post_pending_inactive_value = post_stake_pool.pending_inactive.value;
let post post_inactive_value = post_stake_pool.inactive.value;
ensures post_stake_pool.pending_active.value == 0;
ensures post_active_value
== stake_pool.active.value + rewards_amount_1
+ stake_pool.pending_active.value;
ensures if (spec_get_reconfig_start_time_secs() >= stake_pool.locked_until_secs) {
post_pending_inactive_value == 0
&& post_inactive_value
== stake_pool.inactive.value + stake_pool.pending_inactive.value
+ rewards_amount_2
} else {
post_pending_inactive_value
== stake_pool.pending_inactive.value + rewards_amount_2
};
schema AbortsIfSignerPermissionStake {
s: signer;
let perm = StakeManagementPermission {};
aborts_if !permissioned_signer::spec_check_permission_exists(s, perm);
}
schema UpdateStakePoolAbortsIf {
pool_address: address;
validator_perf: ValidatorPerformance;
aborts_if !exists<StakePool>(pool_address);
aborts_if !exists<ValidatorConfig>(pool_address);
aborts_if global<ValidatorConfig>(pool_address).validator_index
>= len(validator_perf.validators);
let aptos_addr = type_info::type_of<AptosCoin>().account_address;
let stake_pool = global<StakePool>(pool_address);
include DistributeRewardsAbortsIf { stake: stake_pool.active };
include DistributeRewardsAbortsIf { stake: stake_pool.pending_inactive };
}
Function get_reconfig_start_time_secs
fun get_reconfig_start_time_secs(): u64
include GetReconfigStartTimeRequirement;
schema GetReconfigStartTimeRequirement {
requires exists<timestamp::CurrentTimeMicroseconds>(@aptos_framework);
include reconfiguration_state::StartTimeSecsRequirement;
}
fun spec_get_reconfig_start_time_secs(): u64 {
if (exists<reconfiguration_state::State>(@aptos_framework)) {
reconfiguration_state::spec_start_time_secs()
} else {
timestamp::spec_now_seconds()
}
}
fun spec_get_lockup_secs(pool_address: address): u64 {
global<StakePool>(pool_address).locked_until_secs
}
Function calculate_rewards_amount
fun calculate_rewards_amount(stake_amount: u64, num_successful_proposals: u64, num_total_proposals: u64, rewards_rate: u64, rewards_rate_denominator: u64): u64
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;
requires num_successful_proposals <= num_total_proposals;
ensures [concrete](rewards_rate_denominator * num_total_proposals == 0) ==> result ==
0;
ensures [concrete](rewards_rate_denominator * num_total_proposals > 0) ==>
{
let amount =
((stake_amount * rewards_rate * num_successful_proposals)
/ (rewards_rate_denominator * num_total_proposals));
result == amount
};
aborts_if false;
ensures [abstract] result
== spec_rewards_amount(
stake_amount,
num_successful_proposals,
num_total_proposals,
rewards_rate,
rewards_rate_denominator
);
Function distribute_rewards
fun distribute_rewards(stake: &mut coin::Coin<aptos_coin::AptosCoin>, num_successful_proposals: u64, num_total_proposals: u64, rewards_rate: u64, rewards_rate_denominator: u64): u64
pragma aborts_if_is_partial;
include ResourceRequirement;
requires rewards_rate <= MAX_REWARDS_RATE;
requires rewards_rate_denominator > 0;
requires rewards_rate <= rewards_rate_denominator;
requires num_successful_proposals <= num_total_proposals;
include DistributeRewardsAbortsIf;
ensures old(stake.value) > 0 ==>
result
== spec_rewards_amount(
old(stake.value),
num_successful_proposals,
num_total_proposals,
rewards_rate,
rewards_rate_denominator
);
ensures old(stake.value) > 0 ==>
stake.value
== old(stake.value)
+ spec_rewards_amount(
old(stake.value),
num_successful_proposals,
num_total_proposals,
rewards_rate,
rewards_rate_denominator
);
ensures old(stake.value) == 0 ==> result == 0;
ensures old(stake.value) == 0 ==>
stake.value == old(stake.value);
schema DistributeRewardsAbortsIf {
stake: Coin<AptosCoin>;
num_successful_proposals: num;
num_total_proposals: num;
rewards_rate: num;
rewards_rate_denominator: num;
let stake_amount = coin::value(stake);
let rewards_amount = if (stake_amount > 0) {
spec_rewards_amount(
stake_amount,
num_successful_proposals,
num_total_proposals,
rewards_rate,
rewards_rate_denominator
)
} else { 0 };
let amount = rewards_amount;
let addr = type_info::type_of<AptosCoin>().account_address;
aborts_if (rewards_amount > 0) && !exists<coin::CoinInfo<AptosCoin>>(addr);
modifies global<coin::CoinInfo<AptosCoin>>(addr);
include (rewards_amount > 0) ==>
coin::CoinAddAbortsIf<AptosCoin> { amount: amount };
}
Function append
fun append<T>(v1: &mut vector<T>, v2: &mut vector<T>)
pragma opaque, verify = false;
aborts_if false;
ensures len(v1) == old(len(v1) + len(v2));
ensures len(v2) == 0;
ensures (forall i in 0..old(len(v1)): v1[i] == old(v1[i]));
ensures (
forall i in old(len(v1))..len(v1):
v1[i] == old(v2[len(v2) - (i - len(v1)) - 1])
);
Function find_validator
fun find_validator(v: &vector<stake::ValidatorInfo>, addr: address): option::Option<u64>
pragma opaque;
aborts_if false;
ensures option::is_none(result) ==>
(forall i in 0..len(v): v[i].addr != addr);
ensures option::is_some(result) ==>
v[option::borrow(result)].addr == addr;
ensures option::is_some(result) ==>
spec_contains(v, addr);
ensures [abstract] result == spec_find_validator(v, addr);
Function update_voting_power_increase
fun update_voting_power_increase(increase_amount: u64)
requires !reconfiguration_state::spec_is_in_progress();
aborts_if !exists<ValidatorSet>(@aptos_framework);
aborts_if !exists<staking_config::StakingConfig>(@aptos_framework);
let aptos = @aptos_framework;
let pre_validator_set = global<ValidatorSet>(aptos);
let post validator_set = global<ValidatorSet>(aptos);
let staking_config = global<staking_config::StakingConfig>(aptos);
let voting_power_increase_limit = staking_config.voting_power_increase_limit;
aborts_if pre_validator_set.total_joining_power + increase_amount > MAX_U128;
aborts_if pre_validator_set.total_voting_power > 0
&& pre_validator_set.total_voting_power * voting_power_increase_limit
> MAX_U128;
aborts_if pre_validator_set.total_voting_power > 0
&& pre_validator_set.total_joining_power + increase_amount
> pre_validator_set.total_voting_power * voting_power_increase_limit
/ 100;
ensures validator_set.total_voting_power > 0 ==>
validator_set.total_joining_power
<= validator_set.total_voting_power * voting_power_increase_limit / 100;
ensures validator_set.total_joining_power
== pre_validator_set.total_joining_power + increase_amount;
Function assert_stake_pool_exists
fun assert_stake_pool_exists(pool_address: address)
aborts_if !stake_pool_exists(pool_address);
Function configure_allowed_validators
public fun configure_allowed_validators(aptos_framework: &signer, accounts: vector<address>)
let aptos_framework_address = signer::address_of(aptos_framework);
aborts_if !system_addresses::is_aptos_framework_address(aptos_framework_address);
let post allowed = global<AllowedValidators>(aptos_framework_address);
ensures allowed.accounts == accounts;
Function assert_owner_cap_exists
fun assert_owner_cap_exists(owner: address)
aborts_if !exists<OwnerCapability>(owner);