Module 0x1::delegation_pool
Allow multiple delegators to participate in the same stake pool in order to collect the minimum stake required to join the validator set. Delegators are rewarded out of the validator rewards proportionally to their stake and provided the same stake-management API as the stake pool owner.
The main accounting logic in the delegation pool contract handles the following:
- Tracks how much stake each delegator owns, privately deposited as well as earned.
Accounting individual delegator stakes is achieved through the shares-based pool defined at
aptos_std::pool_u64, hence delegators own shares rather than absolute stakes into the delegation pool. - Tracks rewards earned by the stake pool, implicitly by the delegation one, in the meantime and distribute them accordingly.
- Tracks lockup cycles on the stake pool in order to separate inactive stake (not earning rewards) from pending_inactive stake (earning rewards) and allow its delegators to withdraw the former.
- Tracks how much commission fee has to be paid to the operator out of incoming rewards before distributing them to the internal pool_u64 pools.
In order to distinguish between stakes in different states and route rewards accordingly, separate pool_u64 pools are used for individual stake states:
- one of
active+pending_activestake - one of
inactivestake FOR each past observed lockup cycle (OLC) on the stake pool - one of
pending_inactivestake scheduled during this ongoing OLC
As stake-state transitions and rewards are computed only at the stake pool level, the delegation pool gets outdated. To mitigate this, at any interaction with the delegation pool, a process of synchronization to the underlying stake pool is executed before the requested operation itself.
At synchronization:
- stake deviations between the two pools are actually the rewards produced in the meantime.
- the commission fee is extracted from the rewards, the remaining stake is distributed to the internal pool_u64 pools and then the commission stake used to buy shares for operator.
- if detecting that the lockup expired on the stake pool, the delegation pool will isolate its pending_inactive stake (now inactive) and create a new pool_u64 to host future pending_inactive stake scheduled this newly started lockup. Detecting a lockup expiration on the stake pool resumes to detecting new inactive stake.
Accounting main invariants:
- each stake-management operation (add/unlock/reactivate/withdraw) and operator change triggers the synchronization process before executing its own function.
- each OLC maps to one or more real lockups on the stake pool, but not the opposite. Actually, only a real lockup with ‘activity’ (which inactivated some unlocking stake) triggers the creation of a new OLC.
- unlocking and/or unlocked stake originating from different real lockups are never mixed together into the same pool_u64. This invalidates the accounting of which rewards belong to whom.
- no delegator can have unlocking and/or unlocked stake (pending withdrawals) in different OLCs. This ensures delegators do not have to keep track of the OLCs when they unlocked. When creating a new pending withdrawal, the existing one is executed (withdrawn) if is already inactive.
add_stakefees are always refunded, but only after the epoch when they have been charged ends.- withdrawing pending_inactive stake (when validator had gone inactive before its lockup expired) does not inactivate any stake additional to the requested one to ensure OLC would not advance indefinitely.
- the pending withdrawal exists at an OLC iff delegator owns some shares within the shares pool of that OLC.
Example flow:
- A node operator creates a delegation pool by calling
initialize_delegation_pooland sets its commission fee to 0% (for simplicity). A stake pool is created with no initial stake and owned by a resource account controlled by the delegation pool. - Delegator A adds 100 stake which is converted to 100 shares into the active pool_u64
- Operator joins the validator set as the stake pool has now the minimum stake
- The stake pool earned rewards and now has 200 active stake. A's active shares are worth 200 coins as the commission fee is 0%.
- A requests
unlockfor 100 stake - Synchronization detects 200 - 100 active rewards which are entirely (0% commission) added to the active pool.
- 100 coins = (100 * 100) / 200 = 50 shares are redeemed from the active pool and exchanged for 100 shares into the pending_inactive one on A's behalf
- Delegator B adds 200 stake which is converted to (200 * 50) / 100 = 100 shares into the active pool
- The stake pool earned rewards and now has 600 active and 200 pending_inactive stake.
- A requests
reactivate_stakefor 100 stake - Synchronization detects 600 - 300 active and 200 - 100 pending_inactive rewards which are both entirely distributed to their corresponding pools
- 100 coins = (100 * 100) / 200 = 50 shares are redeemed from the pending_inactive pool and exchanged for (100 * 150) / 600 = 25 shares into the active one on A's behalf
- The lockup expires on the stake pool, inactivating the entire pending_inactive stake
- B requests
unlockfor 100 stake - Synchronization detects no active or pending_inactive rewards, but 0 -> 100 inactive stake on the stake pool, so it advances the observed lockup cycle and creates a pool_u64 for the new lockup, hence allowing previous pending_inactive shares to be redeemed
- 100 coins = (100 * 175) / 700 = 25 shares are redeemed from the active pool and exchanged for 100 shares into the new pending_inactive one on B's behalf
- The stake pool earned rewards and now has some pending_inactive rewards.
- A requests
withdrawfor its entire inactive stake - Synchronization detects no new inactive stake, but some pending_inactive rewards which are distributed to the (2nd) pending_inactive pool
- A's 50 shares = (50 * 100) / 50 = 100 coins are redeemed from the (1st) inactive pool and 100 stake is transferred to A
- Resource
DelegationPoolOwnership - Struct
ObservedLockupCycle - Resource
DelegationPool - Struct
VotingRecordKey - Struct
VoteDelegation - Struct
DelegatedVotes - Resource
GovernanceRecords - Resource
BeneficiaryForOperator - Resource
NextCommissionPercentage - Resource
DelegationPoolAllowlisting - Enum
DelegationPermission - Struct
AddStake - Struct
AddStakeEvent - Struct
ReactivateStake - Struct
ReactivateStakeEvent - Struct
UnlockStake - Struct
UnlockStakeEvent - Struct
WithdrawStake - Struct
WithdrawStakeEvent - Struct
DistributeCommissionEvent - Struct
DistributeCommission - Struct
Vote - Struct
VoteEvent - Struct
CreateProposal - Struct
CreateProposalEvent - Struct
DelegateVotingPower - Struct
DelegateVotingPowerEvent - Struct
SetBeneficiaryForOperator - Struct
CommissionPercentageChange - Struct
EnableDelegatorsAllowlisting - Struct
DisableDelegatorsAllowlisting - Struct
AllowlistDelegator - Struct
RemoveDelegatorFromAllowlist - Struct
EvictDelegator - Constants
- Function
owner_cap_exists - Function
get_owned_pool_address - Function
delegation_pool_exists - Function
partial_governance_voting_enabled - Function
observed_lockup_cycle - Function
is_next_commission_percentage_effective - Function
operator_commission_percentage - Function
operator_commission_percentage_next_lockup_cycle - Function
shareholders_count_active_pool - Function
get_delegation_pool_stake - Function
get_pending_withdrawal - Function
get_stake - Function
get_add_stake_fee - Function
can_withdraw_pending_inactive - Function
calculate_and_update_voter_total_voting_power - Function
calculate_and_update_remaining_voting_power - Function
calculate_and_update_delegator_voter - Function
calculate_and_update_voting_delegation - Function
get_expected_stake_pool_address - Function
min_remaining_secs_for_commission_change - Function
allowlisting_enabled - Function
delegator_allowlisted - Function
get_delegators_allowlist - Function
check_delegation_pool_management_permission - Function
grant_delegation_pool_management_permission - Function
check_stake_management_permission - Function
grant_stake_management_permission - Function
initialize_delegation_pool - Function
beneficiary_for_operator - Function
enable_partial_governance_voting - Function
vote - Function
create_proposal - Function
assert_owner_cap_exists - Function
assert_delegation_pool_exists - Function
assert_min_active_balance - Function
assert_min_pending_inactive_balance - Function
assert_partial_governance_voting_enabled - Function
assert_allowlisting_enabled - Function
assert_delegator_allowlisted - Function
coins_to_redeem_to_ensure_min_stake - Function
coins_to_transfer_to_ensure_min_stake - Function
retrieve_stake_pool_owner - Function
get_pool_address - Function
get_delegator_active_shares - Function
get_delegator_pending_inactive_shares - Function
get_used_voting_power - Function
create_resource_account_seed - Function
borrow_mut_used_voting_power - Function
update_and_borrow_mut_delegator_vote_delegation - Function
update_and_borrow_mut_delegated_votes - Function
olc_with_index - Function
calculate_total_voting_power - Function
calculate_and_update_delegator_voter_internal - Function
calculate_and_update_delegated_votes - Function
borrow_mut_delegators_allowlist - Function
set_operator - Function
set_beneficiary_for_operator - Function
update_commission_percentage - Function
set_delegated_voter - Function
delegate_voting_power - Function
enable_delegators_allowlisting - Function
disable_delegators_allowlisting - Function
allowlist_delegator - Function
remove_delegator_from_allowlist - Function
evict_delegator - Function
add_stake - Function
unlock - Function
unlock_internal - Function
reactivate_stake - Function
withdraw - Function
withdraw_internal - Function
pending_withdrawal_exists - Function
pending_inactive_shares_pool_mut - Function
pending_inactive_shares_pool - Function
execute_pending_withdrawal - Function
buy_in_active_shares - Function
buy_in_pending_inactive_shares - Function
amount_to_shares_to_redeem - Function
redeem_active_shares - Function
redeem_inactive_shares - Function
calculate_stake_pool_drift - Function
synchronize_delegation_pool - Function
assert_and_update_proposal_used_voting_power - Function
update_governance_records_for_buy_in_active_shares - Function
update_governance_records_for_buy_in_pending_inactive_shares - Function
update_governanace_records_for_redeem_active_shares - Function
update_governanace_records_for_redeem_pending_inactive_shares - Function
multiply_then_divide - Specification
use 0x1::account;
use 0x1::aptos_account;
use 0x1::aptos_coin;
use 0x1::aptos_governance;
use 0x1::coin;
use 0x1::error;
use 0x1::event;
use 0x1::features;
use 0x1::permissioned_signer;
use 0x1::pool_u64_unbound;
use 0x1::signer;
use 0x1::smart_table;
use 0x1::stake;
use 0x1::staking_config;
use 0x1::table;
use 0x1::table_with_length;
use 0x1::timestamp;
use 0x1::vector;
Resource DelegationPoolOwnership
Capability that represents ownership over privileged operations on the underlying stake pool.
struct DelegationPoolOwnership has store, key
Fields
-
pool_address: address - equal to address of the resource account owning the stake pool
Struct ObservedLockupCycle
struct ObservedLockupCycle has copy, drop, store
Fields
-
index: u64
Resource DelegationPool
struct DelegationPool has key
Fields
-
active_shares: pool_u64_unbound::Pool -
observed_lockup_cycle: delegation_pool::ObservedLockupCycle -
inactive_shares: table::Table<delegation_pool::ObservedLockupCycle, pool_u64_unbound::Pool> -
pending_withdrawals: table::Table<address, delegation_pool::ObservedLockupCycle> -
stake_pool_signer_cap: account::SignerCapability -
total_coins_inactive: u64 -
operator_commission_percentage: u64 -
add_stake_events: event::EventHandle<delegation_pool::AddStakeEvent> -
reactivate_stake_events: event::EventHandle<delegation_pool::ReactivateStakeEvent> -
unlock_stake_events: event::EventHandle<delegation_pool::UnlockStakeEvent> -
withdraw_stake_events: event::EventHandle<delegation_pool::WithdrawStakeEvent> -
distribute_commission_events: event::EventHandle<delegation_pool::DistributeCommissionEvent>
Struct VotingRecordKey
struct VotingRecordKey has copy, drop, store
Fields
-
voter: address -
proposal_id: u64
Struct VoteDelegation
Track delegated voter of each delegator.
struct VoteDelegation has copy, drop, store
Fields
-
voter: address -
pending_voter: address -
last_locked_until_secs: u64
Struct DelegatedVotes
Track total voting power of each voter.
struct DelegatedVotes has copy, drop, store
Fields
-
active_shares: u128 -
pending_inactive_shares: u128 -
active_shares_next_lockup: u128 -
last_locked_until_secs: u64
Resource GovernanceRecords
Track governance information of a delegation(e.g. voter delegation/voting power calculation). This struct should be stored in the delegation pool resource account.
struct GovernanceRecords has key
Fields
-
votes: smart_table::SmartTable<delegation_pool::VotingRecordKey, u64> -
votes_per_proposal: smart_table::SmartTable<u64, u64> -
vote_delegation: smart_table::SmartTable<address, delegation_pool::VoteDelegation> -
delegated_votes: smart_table::SmartTable<address, delegation_pool::DelegatedVotes> -
vote_events: event::EventHandle<delegation_pool::VoteEvent> -
create_proposal_events: event::EventHandle<delegation_pool::CreateProposalEvent> -
delegate_voting_power_events: event::EventHandle<delegation_pool::DelegateVotingPowerEvent>
Resource BeneficiaryForOperator
struct BeneficiaryForOperator has key
Fields
-
beneficiary_for_operator: address
Resource NextCommissionPercentage
struct NextCommissionPercentage has key
Fields
-
commission_percentage_next_lockup_cycle: u64 -
effective_after_secs: u64
Resource DelegationPoolAllowlisting
Tracks a delegation pool’s allowlist of delegators. If allowlisting is enabled, existing delegators are not implicitly allowlisted and they can be individually evicted later by the pool owner.
struct DelegationPoolAllowlisting has key
Fields
-
allowlist: smart_table::SmartTable<address, bool>
Enum DelegationPermission
enum DelegationPermission has copy, drop, store
Variants
DelegationPoolManagementPermission
Fields
StakeManagementPermission
Fields
Struct AddStake
#[event]
struct AddStake has drop, store
Fields
-
pool_address: address -
delegator_address: address -
amount_added: u64 -
add_stake_fee: u64
Struct AddStakeEvent
struct AddStakeEvent has drop, store
Fields
-
pool_address: address -
delegator_address: address -
amount_added: u64 -
add_stake_fee: u64
Struct ReactivateStake
#[event]
struct ReactivateStake has drop, store
Fields
-
pool_address: address -
delegator_address: address -
amount_reactivated: u64
Struct ReactivateStakeEvent
struct ReactivateStakeEvent has drop, store
Fields
-
pool_address: address -
delegator_address: address -
amount_reactivated: u64
Struct UnlockStake
#[event]
struct UnlockStake has drop, store
Fields
-
pool_address: address -
delegator_address: address -
amount_unlocked: u64
Struct UnlockStakeEvent
struct UnlockStakeEvent has drop, store
Fields
-
pool_address: address -
delegator_address: address -
amount_unlocked: u64
Struct WithdrawStake
#[event]
struct WithdrawStake has drop, store
Fields
-
pool_address: address -
delegator_address: address -
amount_withdrawn: u64
Struct WithdrawStakeEvent
struct WithdrawStakeEvent has drop, store
Fields
-
pool_address: address -
delegator_address: address -
amount_withdrawn: u64
Struct DistributeCommissionEvent
#[event]
struct DistributeCommissionEvent has drop, store
Fields
-
pool_address: address -
operator: address -
commission_active: u64 -
commission_pending_inactive: u64
Struct DistributeCommission
#[event]
struct DistributeCommission has drop, store
Fields
-
pool_address: address -
operator: address -
beneficiary: address -
commission_active: u64 -
commission_pending_inactive: u64
Struct Vote
#[event]
struct Vote has drop, store
Fields
-
voter: address -
proposal_id: u64 -
delegation_pool: address -
num_votes: u64 -
should_pass: bool
Struct VoteEvent
struct VoteEvent has drop, store
Fields
-
voter: address -
proposal_id: u64 -
delegation_pool: address -
num_votes: u64 -
should_pass: bool
Struct CreateProposal
#[event]
struct CreateProposal has drop, store
Fields
-
proposal_id: u64 -
voter: address -
delegation_pool: address
Struct CreateProposalEvent
struct CreateProposalEvent has drop, store
Fields
-
proposal_id: u64 -
voter: address -
delegation_pool: address
Struct DelegateVotingPower
#[event]
struct DelegateVotingPower has drop, store
Fields
-
pool_address: address -
delegator: address -
voter: address
Struct DelegateVotingPowerEvent
struct DelegateVotingPowerEvent has drop, store
Fields
-
pool_address: address -
delegator: address -
voter: address
Struct SetBeneficiaryForOperator
#[event]
struct SetBeneficiaryForOperator has drop, store
Fields
-
operator: address -
old_beneficiary: address -
new_beneficiary: address
Struct CommissionPercentageChange
#[event]
struct CommissionPercentageChange has drop, store
Fields
-
pool_address: address -
owner: address -
commission_percentage_next_lockup_cycle: u64
Struct EnableDelegatorsAllowlisting
#[event]
struct EnableDelegatorsAllowlisting has drop, store
Fields
-
pool_address: address
Struct DisableDelegatorsAllowlisting
#[event]
struct DisableDelegatorsAllowlisting has drop, store
Fields
-
pool_address: address
Struct AllowlistDelegator
#[event]
struct AllowlistDelegator has drop, store
Fields
-
pool_address: address -
delegator_address: address
Struct RemoveDelegatorFromAllowlist
#[event]
struct RemoveDelegatorFromAllowlist has drop, store
Fields
-
pool_address: address -
delegator_address: address
Struct EvictDelegator
#[event]
struct EvictDelegator has drop, store
Fields
-
pool_address: address -
delegator_address: address
Constants
const MAX_U64: u64 = 18446744073709551615;
Function is deprecated.
const EDEPRECATED_FUNCTION: u64 = 12;
The function is disabled or hasn’t been enabled.
const EDISABLED_FUNCTION: u64 = 13;
The account is not the operator of the stake pool.
const ENOT_OPERATOR: u64 = 18;
Account is already owning a delegation pool.
const EOWNER_CAP_ALREADY_EXISTS: u64 = 2;
Delegation pool owner capability does not exist at the provided account.
const EOWNER_CAP_NOT_FOUND: u64 = 1;
const VALIDATOR_STATUS_INACTIVE: u64 = 4;
The voter does not have sufficient stake to create a proposal.
const EINSUFFICIENT_PROPOSER_STAKE: u64 = 15;
The voter does not have any voting power on this proposal.
const ENO_VOTING_POWER: u64 = 16;
The stake pool has already voted on the proposal before enabling partial governance voting on this delegation pool.
const EALREADY_VOTED_BEFORE_ENABLE_PARTIAL_VOTING: u64 = 17;
Cannot evict an allowlisted delegator, should remove them from the allowlist first.
const ECANNOT_EVICT_ALLOWLISTED_DELEGATOR: u64 = 26;
Cannot unlock the accumulated active stake of NULL_SHAREHOLDER(0x0).
const ECANNOT_UNLOCK_NULL_SHAREHOLDER: u64 = 27;
Use delegator voting flow instead. Delegation pools can no longer specify a single delegated voter.
const ECAN_NO_LONGER_SET_DELEGATED_VOTER: u64 = 29;
Changing operator commission rate in delegation pool is not supported.
const ECOMMISSION_RATE_CHANGE_NOT_SUPPORTED: u64 = 22;
Creating delegation pools is not enabled yet.
const EDELEGATION_POOLS_DISABLED: u64 = 10;
Delegation pool does not exist at the provided pool address.
const EDELEGATION_POOL_DOES_NOT_EXIST: u64 = 3;
Delegators allowlisting should be enabled to perform this operation.
const EDELEGATORS_ALLOWLISTING_NOT_ENABLED: u64 = 24;
Delegators allowlisting is not supported.
const EDELEGATORS_ALLOWLISTING_NOT_SUPPORTED: u64 = 23;
Delegator’s active balance cannot be less than MIN_COINS_ON_SHARES_POOL.
const EDELEGATOR_ACTIVE_BALANCE_TOO_LOW: u64 = 8;
Cannot add/reactivate stake unless being allowlisted by the pool owner.
const EDELEGATOR_NOT_ALLOWLISTED: u64 = 25;
Delegator’s pending_inactive balance cannot be less than MIN_COINS_ON_SHARES_POOL.
const EDELEGATOR_PENDING_INACTIVE_BALANCE_TOO_LOW: u64 = 9;
Commission percentage has to be between 0 and MAX_FEE - 100%.
const EINVALID_COMMISSION_PERCENTAGE: u64 = 5;
There is not enough active stake on the stake pool to unlock.
const ENOT_ENOUGH_ACTIVE_STAKE_TO_UNLOCK: u64 = 6;
Signer does not have permission to perform delegation logic.
const ENO_DELEGATION_PERMISSION: u64 = 28;
Changing beneficiaries for operators is not supported.
const EOPERATOR_BENEFICIARY_CHANGE_NOT_SUPPORTED: u64 = 19;
Partial governance voting hasn’t been enabled on this delegation pool.
const EPARTIAL_GOVERNANCE_VOTING_NOT_ENABLED: u64 = 14;
There is a pending withdrawal to be executed before unlocking any new stake.
const EPENDING_WITHDRAWAL_EXISTS: u64 = 4;
Slashing (if implemented) should not be applied to already inactive stake.
Not only it invalidates the accounting of past observed lockup cycles (OLC),
but is also unfair to delegators whose stake has been inactive before validator started misbehaving.
Additionally, the inactive stake does not count on the voting power of validator.
const ESLASHED_INACTIVE_STAKE_ON_PAST_OLC: u64 = 7;
Commission percentage increase is too large.
const ETOO_LARGE_COMMISSION_INCREASE: u64 = 20;
Commission percentage change is too late in this lockup period, and should be done at least a quarter (1/4) of the lockup duration before the lockup cycle ends.
const ETOO_LATE_COMMISSION_CHANGE: u64 = 21;
Cannot request to withdraw zero stake.
const EWITHDRAW_ZERO_STAKE: u64 = 11;
Maximum commission percentage increase per lockup cycle. 10% is represented as 1000.
const MAX_COMMISSION_INCREASE: u64 = 1000;
Maximum operator percentage fee(of double digit precision): 22.85% is represented as 2285
const MAX_FEE: u64 = 10000;
Minimum coins to exist on a shares pool at all times. Enforced per delegator for both active and pending_inactive pools. This constraint ensures the share price cannot overly increase and lead to substantial losses when buying shares (can lose at most 1 share which may be worth a lot if current share price is high). This constraint is not enforced on inactive pools as they only allow redeems (can lose at most 1 coin regardless of current share price).
const MIN_COINS_ON_SHARES_POOL: u64 = 1000000000;
const MODULE_SALT: vector<u8> = [97, 112, 116, 111, 115, 95, 102, 114, 97, 109, 101, 119, 111, 114, 107, 58, 58, 100, 101, 108, 101, 103, 97, 116, 105, 111, 110, 95, 112, 111, 111, 108];
Special shareholder temporarily owning the add_stake fees charged during this epoch.
On each add_stake operation any resulted fee is used to buy active shares for this shareholder.
First synchronization after this epoch ends will distribute accumulated fees to the rest of the pool as refunds.
const NULL_SHAREHOLDER: address = 0x0;
Scaling factor of shares pools used within the delegation pool
const SHARES_SCALING_FACTOR: u64 = 10000000000000000;
Function owner_cap_exists
Return whether supplied address addr is owner of a delegation pool.
#[view]
public fun owner_cap_exists(addr: address): bool
Implementation
public fun owner_cap_exists(addr: address): bool {
exists<DelegationPoolOwnership>(addr)
}
Function get_owned_pool_address
Return address of the delegation pool owned by owner or fail if there is none.
#[view]
public fun get_owned_pool_address(owner: address): address
Implementation
public fun get_owned_pool_address(owner: address): address acquires DelegationPoolOwnership {
assert_owner_cap_exists(owner);
borrow_global<DelegationPoolOwnership>(owner).pool_address
}
Function delegation_pool_exists
Return whether a delegation pool exists at supplied address addr.
#[view]
public fun delegation_pool_exists(addr: address): bool
Implementation
public fun delegation_pool_exists(addr: address): bool {
exists<DelegationPool>(addr)
}
Function partial_governance_voting_enabled
Return whether a delegation pool has already enabled partial governance voting.
#[view]
public fun partial_governance_voting_enabled(pool_address: address): bool
Implementation
public fun partial_governance_voting_enabled(pool_address: address): bool {
exists<GovernanceRecords>(pool_address) && stake::get_delegated_voter(pool_address) == pool_address
}
Function observed_lockup_cycle
Return the index of current observed lockup cycle on delegation pool pool_address.
#[view]
public fun observed_lockup_cycle(pool_address: address): u64
Implementation
public fun observed_lockup_cycle(pool_address: address): u64 acquires DelegationPool {
assert_delegation_pool_exists(pool_address);
borrow_global<DelegationPool>(pool_address).observed_lockup_cycle.index
}
Function is_next_commission_percentage_effective
Return whether the commission percentage for the next lockup cycle is effective.
#[view]
public fun is_next_commission_percentage_effective(pool_address: address): bool
Implementation
public fun is_next_commission_percentage_effective(pool_address: address): bool acquires NextCommissionPercentage {
exists<NextCommissionPercentage>(pool_address) &&
timestamp::now_seconds() >= borrow_global<NextCommissionPercentage>(pool_address).effective_after_secs
}
Function operator_commission_percentage
Return the operator commission percentage set on the delegation pool pool_address.
#[view]
public fun operator_commission_percentage(pool_address: address): u64
Implementation
public fun operator_commission_percentage(
pool_address: address
): u64 acquires DelegationPool, NextCommissionPercentage {
assert_delegation_pool_exists(pool_address);
if (is_next_commission_percentage_effective(pool_address)) {
operator_commission_percentage_next_lockup_cycle(pool_address)
} else {
borrow_global<DelegationPool>(pool_address).operator_commission_percentage
}
}
Function operator_commission_percentage_next_lockup_cycle
Return the operator commission percentage for the next lockup cycle.
#[view]
public fun operator_commission_percentage_next_lockup_cycle(pool_address: address): u64
Implementation
public fun operator_commission_percentage_next_lockup_cycle(
pool_address: address
): u64 acquires DelegationPool, NextCommissionPercentage {
assert_delegation_pool_exists(pool_address);
if (exists<NextCommissionPercentage>(pool_address)) {
borrow_global<NextCommissionPercentage>(pool_address).commission_percentage_next_lockup_cycle
} else {
borrow_global<DelegationPool>(pool_address).operator_commission_percentage
}
}
Function shareholders_count_active_pool
Return the number of delegators owning active stake within pool_address.
#[view]
public fun shareholders_count_active_pool(pool_address: address): u64
Implementation
public fun shareholders_count_active_pool(pool_address: address): u64 acquires DelegationPool {
assert_delegation_pool_exists(pool_address);
borrow_global<DelegationPool>(pool_address).active_shares.shareholders_count()
}
Function get_delegation_pool_stake
Return the stake amounts on pool_address in the different states:
(active,inactive,pending_active,pending_inactive)
#[view]
public fun get_delegation_pool_stake(pool_address: address): (u64, u64, u64, u64)
Implementation
public fun get_delegation_pool_stake(pool_address: address): (u64, u64, u64, u64) {
assert_delegation_pool_exists(pool_address);
stake::get_stake(pool_address)
}
Function get_pending_withdrawal
Return whether the given delegator has any withdrawable stake. If they recently requested to unlock some stake and the stake pool’s lockup cycle has not ended, their coins are not withdrawable yet.
#[view]
public fun get_pending_withdrawal(pool_address: address, delegator_address: address): (bool, u64)
Implementation
public fun get_pending_withdrawal(
pool_address: address,
delegator_address: address
): (bool, u64) acquires DelegationPool {
assert_delegation_pool_exists(pool_address);
let pool = borrow_global<DelegationPool>(pool_address);
let (
lockup_cycle_ended,
_,
pending_inactive,
_,
commission_pending_inactive
) = calculate_stake_pool_drift(pool);
let (withdrawal_exists, withdrawal_olc) = pending_withdrawal_exists(pool, delegator_address);
if (!withdrawal_exists) {
// if no pending withdrawal, there is neither inactive nor pending_inactive stake
(false, 0)
} else {
// delegator has either inactive or pending_inactive stake due to automatic withdrawals
let inactive_shares = pool.inactive_shares.borrow(withdrawal_olc);
if (withdrawal_olc.index < pool.observed_lockup_cycle.index) {
// if withdrawal's lockup cycle ended on delegation pool then it is inactive
(true, inactive_shares.balance(delegator_address))
} else {
pending_inactive = inactive_shares.shares_to_amount_with_total_coins(inactive_shares.shares(delegator_address), pending_inactive - commission_pending_inactive);
// if withdrawal's lockup cycle ended ONLY on stake pool then it is also inactive
(lockup_cycle_ended, pending_inactive)
}
}
}
Function get_stake
Return total stake owned by delegator_address within delegation pool pool_address
in each of its individual states: (active,inactive,pending_inactive)
#[view]
public fun get_stake(pool_address: address, delegator_address: address): (u64, u64, u64)
Implementation
public fun get_stake(
pool_address: address,
delegator_address: address
): (u64, u64, u64) acquires DelegationPool, BeneficiaryForOperator {
assert_delegation_pool_exists(pool_address);
let pool = borrow_global<DelegationPool>(pool_address);
let (
lockup_cycle_ended,
active,
_,
commission_active,
commission_pending_inactive
) = calculate_stake_pool_drift(pool);
let total_active_shares = pool.active_shares.total_shares();
let delegator_active_shares = pool.active_shares.shares(delegator_address);
let (_, _, pending_active, _) = stake::get_stake(pool_address);
if (pending_active == 0) {
// zero `pending_active` stake indicates that either there are no `add_stake` fees or
// previous epoch has ended and should identify shares owning these fees as released
total_active_shares -= pool.active_shares.shares(NULL_SHAREHOLDER);
if (delegator_address == NULL_SHAREHOLDER) {
delegator_active_shares = 0
}
};
active = pool.active_shares.shares_to_amount_with_total_stats(delegator_active_shares, active - commission_active, total_active_shares);
// get state and stake (0 if there is none) of the pending withdrawal
let (withdrawal_inactive, withdrawal_stake) = get_pending_withdrawal(pool_address, delegator_address);
// report non-active stakes accordingly to the state of the pending withdrawal
let (inactive, pending_inactive) = if (withdrawal_inactive) (withdrawal_stake, 0) else (0, withdrawal_stake);
// should also include commission rewards in case of the operator account
// operator rewards are actually used to buy shares which is introducing
// some imprecision (received stake would be slightly less)
// but adding rewards onto the existing stake is still a good approximation
if (delegator_address == beneficiary_for_operator(get_operator(pool_address))) {
active += commission_active;
// in-flight pending_inactive commission can coexist with already inactive withdrawal
if (lockup_cycle_ended) {
inactive += commission_pending_inactive
} else {
pending_inactive += commission_pending_inactive
}
};
(active, inactive, pending_inactive)
}
Function get_add_stake_fee
Return refundable stake to be extracted from added amount at add_stake operation on pool pool_address.
If the validator produces rewards this epoch, added stake goes directly to pending_active and
does not earn rewards. However, all shares within a pool appreciate uniformly and when this epoch ends:
- either added shares are still
pending_activeand steal from rewards of existingactivestake - or have moved to
pending_inactiveand get full rewards (they displacedactivestake atunlock) To mitigate this, some of the added stake is extracted and fed back into the pool as placeholder for the rewards the remaining stake would have earned if active: extracted-fee = (amount - extracted-fee) * reward-rate% * (100% - operator-commission%)
#[view]
public fun get_add_stake_fee(pool_address: address, amount: u64): u64
Implementation
public fun get_add_stake_fee(
pool_address: address,
amount: u64
): u64 acquires DelegationPool, NextCommissionPercentage {
if (stake::is_current_epoch_validator(pool_address)) {
let (rewards_rate, rewards_rate_denominator) = staking_config::get_reward_rate(&staking_config::get());
if (rewards_rate_denominator > 0) {
assert_delegation_pool_exists(pool_address);
rewards_rate *= (MAX_FEE - operator_commission_percentage(pool_address));
rewards_rate_denominator *= MAX_FEE;
((((amount as u128) * (rewards_rate as u128)) / ((rewards_rate as u128) + (rewards_rate_denominator as u128))) as u64)
} else { 0 }
} else { 0 }
}
Function can_withdraw_pending_inactive
Return whether pending_inactive stake can be directly withdrawn from
the delegation pool, implicitly its stake pool, in the special case
the validator had gone inactive before its lockup expired.
#[view]
public fun can_withdraw_pending_inactive(pool_address: address): bool
Implementation
public fun can_withdraw_pending_inactive(pool_address: address): bool {
stake::get_validator_state(pool_address) == VALIDATOR_STATUS_INACTIVE &&
timestamp::now_seconds() >= stake::get_lockup_secs(pool_address)
}
Function calculate_and_update_voter_total_voting_power
Return the total voting power of a delegator in a delegation pool. This function syncs DelegationPool to the latest state.
#[view]
public fun calculate_and_update_voter_total_voting_power(pool_address: address, voter: address): u64
Implementation
public fun calculate_and_update_voter_total_voting_power(
pool_address: address,
voter: address
): u64 acquires DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage {
assert_partial_governance_voting_enabled(pool_address);
// Delegation pool need to be synced to explain rewards(which could change the coin amount) and
// commission(which could cause share transfer).
synchronize_delegation_pool(pool_address);
let pool = borrow_global<DelegationPool>(pool_address);
let governance_records = borrow_global_mut<GovernanceRecords>(pool_address);
let latest_delegated_votes = update_and_borrow_mut_delegated_votes(pool, governance_records, voter);
calculate_total_voting_power(pool, latest_delegated_votes)
}
Function calculate_and_update_remaining_voting_power
Return the remaining voting power of a delegator in a delegation pool on a proposal. This function syncs DelegationPool to the latest state.
#[view]
public fun calculate_and_update_remaining_voting_power(pool_address: address, voter_address: address, proposal_id: u64): u64
Implementation
public fun calculate_and_update_remaining_voting_power(
pool_address: address,
voter_address: address,
proposal_id: u64
): u64 acquires DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage {
assert_partial_governance_voting_enabled(pool_address);
// If the whole stake pool has no voting power(e.g. it has already voted before partial
// governance voting flag is enabled), the delegator also has no voting power.
if (aptos_governance::get_remaining_voting_power(pool_address, proposal_id) == 0) {
return 0
};
let total_voting_power = calculate_and_update_voter_total_voting_power(pool_address, voter_address);
let governance_records = borrow_global<GovernanceRecords>(pool_address);
total_voting_power - get_used_voting_power(governance_records, voter_address, proposal_id)
}
Function calculate_and_update_delegator_voter
Return the latest delegated voter of a delegator in a delegation pool. This function syncs DelegationPool to the latest state.
#[view]
public fun calculate_and_update_delegator_voter(pool_address: address, delegator_address: address): address
Implementation
public fun calculate_and_update_delegator_voter(
pool_address: address,
delegator_address: address
): address acquires DelegationPool, GovernanceRecords {
assert_partial_governance_voting_enabled(pool_address);
calculate_and_update_delegator_voter_internal(
borrow_global<DelegationPool>(pool_address),
borrow_global_mut<GovernanceRecords>(pool_address),
delegator_address
)
}
Function calculate_and_update_voting_delegation
Return the current state of a voting delegation of a delegator in a delegation pool.
#[view]
public fun calculate_and_update_voting_delegation(pool_address: address, delegator_address: address): (address, address, u64)
Implementation
public fun calculate_and_update_voting_delegation(
pool_address: address,
delegator_address: address
): (address, address, u64) acquires DelegationPool, GovernanceRecords {
assert_partial_governance_voting_enabled(pool_address);
let vote_delegation = update_and_borrow_mut_delegator_vote_delegation(
borrow_global<DelegationPool>(pool_address),
borrow_global_mut<GovernanceRecords>(pool_address),
delegator_address
);
(vote_delegation.voter, vote_delegation.pending_voter, vote_delegation.last_locked_until_secs)
}
Function get_expected_stake_pool_address
Return the address of the stake pool to be created with the provided owner, and seed.
#[view]
public fun get_expected_stake_pool_address(owner: address, delegation_pool_creation_seed: vector<u8>): address
Implementation
public fun get_expected_stake_pool_address(owner: address, delegation_pool_creation_seed: vector<u8>
): address {
let seed = create_resource_account_seed(delegation_pool_creation_seed);
account::create_resource_address(&owner, seed)
}
Function min_remaining_secs_for_commission_change
Return the minimum remaining time in seconds for commission change, which is one fourth of the lockup duration.
#[view]
public fun min_remaining_secs_for_commission_change(): u64
Implementation
public fun min_remaining_secs_for_commission_change(): u64 {
let config = staking_config::get();
staking_config::get_recurring_lockup_duration(&config) / 4
}
Function allowlisting_enabled
Return whether allowlisting is enabled for the provided delegation pool.
#[view]
public fun allowlisting_enabled(pool_address: address): bool
Implementation
public fun allowlisting_enabled(pool_address: address): bool {
assert_delegation_pool_exists(pool_address);
exists<DelegationPoolAllowlisting>(pool_address)
}
Function delegator_allowlisted
Return whether the provided delegator is allowlisted. A delegator is allowlisted if:
- allowlisting is disabled on the pool
- delegator is part of the allowlist
#[view]
public fun delegator_allowlisted(pool_address: address, delegator_address: address): bool
Implementation
public fun delegator_allowlisted(
pool_address: address,
delegator_address: address,
): bool acquires DelegationPoolAllowlisting {
if (!allowlisting_enabled(pool_address)) { return true };
freeze(borrow_mut_delegators_allowlist(pool_address)).contains(delegator_address)
}
Function get_delegators_allowlist
Return allowlist or revert if allowlisting is not enabled for the provided delegation pool.
#[view]
public fun get_delegators_allowlist(pool_address: address): vector<address>
Implementation
public fun get_delegators_allowlist(
pool_address: address,
): vector<address> acquires DelegationPoolAllowlisting {
assert_allowlisting_enabled(pool_address);
let allowlist = vector[];
freeze(borrow_mut_delegators_allowlist(pool_address)).for_each_ref(|delegator, _v| {
vector::push_back(&mut allowlist, *delegator);
});
allowlist
}
Function check_delegation_pool_management_permission
Permissions
fun check_delegation_pool_management_permission(s: &signer)
Implementation
inline fun check_delegation_pool_management_permission(s: &signer) {
assert!(
permissioned_signer::check_permission_exists(s, DelegationPermission::DelegationPoolManagementPermission {}),
error::permission_denied(ENO_DELEGATION_PERMISSION),
);
}
Function grant_delegation_pool_management_permission
public fun grant_delegation_pool_management_permission(master: &signer, permissioned_signer: &signer)
Implementation
public fun grant_delegation_pool_management_permission(master: &signer, permissioned_signer: &signer) {
permissioned_signer::authorize_unlimited(master, permissioned_signer, DelegationPermission::DelegationPoolManagementPermission {})
}
Function check_stake_management_permission
fun check_stake_management_permission(s: &signer)
Implementation
inline fun check_stake_management_permission(s: &signer) {
assert!(
permissioned_signer::check_permission_exists(s, DelegationPermission::StakeManagementPermission {}),
error::permission_denied(ENO_DELEGATION_PERMISSION),
);
}
Function grant_stake_management_permission
public fun grant_stake_management_permission(master: &signer, permissioned_signer: &signer)
Implementation
public fun grant_stake_management_permission(master: &signer, permissioned_signer: &signer) {
permissioned_signer::authorize_unlimited(master, permissioned_signer, DelegationPermission::StakeManagementPermission {})
}
Function initialize_delegation_pool
Initialize a delegation pool of custom fixed operator_commission_percentage.
A resource account is created from owner signer and its supplied delegation_pool_creation_seed
to host the delegation pool resource and own the underlying stake pool.
Ownership over setting the operator/voter is granted to owner who has both roles initially.
public entry fun initialize_delegation_pool(owner: &signer, operator_commission_percentage: u64, delegation_pool_creation_seed: vector<u8>)
Implementation
public entry fun initialize_delegation_pool(
owner: &signer,
operator_commission_percentage: u64,
delegation_pool_creation_seed: vector<u8>,
) acquires DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage {
check_delegation_pool_management_permission(owner);
let owner_address = signer::address_of(owner);
assert!(!owner_cap_exists(owner_address), error::already_exists(EOWNER_CAP_ALREADY_EXISTS));
assert!(operator_commission_percentage <= MAX_FEE, error::invalid_argument(EINVALID_COMMISSION_PERCENTAGE));
// generate a seed to be used to create the resource account hosting the delegation pool
let seed = create_resource_account_seed(delegation_pool_creation_seed);
let (stake_pool_signer, stake_pool_signer_cap) = account::create_resource_account(owner, seed);
coin::register<AptosCoin>(&stake_pool_signer);
// stake_pool_signer will be owner of the stake pool and have its `stake::OwnerCapability`
let pool_address = signer::address_of(&stake_pool_signer);
stake::initialize_stake_owner(&stake_pool_signer, 0, owner_address, owner_address);
let inactive_shares = table::new<ObservedLockupCycle, pool_u64::Pool>();
inactive_shares.add(olc_with_index(0), pool_u64::create_with_scaling_factor(SHARES_SCALING_FACTOR));
move_to(&stake_pool_signer, DelegationPool {
active_shares: pool_u64::create_with_scaling_factor(SHARES_SCALING_FACTOR),
observed_lockup_cycle: olc_with_index(0),
inactive_shares,
pending_withdrawals: table::new<address, ObservedLockupCycle>(),
stake_pool_signer_cap,
total_coins_inactive: 0,
operator_commission_percentage,
add_stake_events: account::new_event_handle<AddStakeEvent>(&stake_pool_signer),
reactivate_stake_events: account::new_event_handle<ReactivateStakeEvent>(&stake_pool_signer),
unlock_stake_events: account::new_event_handle<UnlockStakeEvent>(&stake_pool_signer),
withdraw_stake_events: account::new_event_handle<WithdrawStakeEvent>(&stake_pool_signer),
distribute_commission_events: account::new_event_handle<DistributeCommissionEvent>(&stake_pool_signer),
});
// save delegation pool ownership and resource account address (inner stake pool address) on `owner`
move_to(owner, DelegationPoolOwnership { pool_address });
// All delegation pool enable partial governance voting by default once the feature flag is enabled.
enable_partial_governance_voting(pool_address);
}
Function beneficiary_for_operator
Return the beneficiary address of the operator.
#[view]
public fun beneficiary_for_operator(operator: address): address
Implementation
public fun beneficiary_for_operator(operator: address): address acquires BeneficiaryForOperator {
if (exists<BeneficiaryForOperator>(operator)) {
return borrow_global<BeneficiaryForOperator>(operator).beneficiary_for_operator
} else {
operator
}
}
Function enable_partial_governance_voting
Enable partial governance voting on a stake pool. The voter of this stake pool will be managed by this module. The existing voter will be replaced. The function is permissionless.
public entry fun enable_partial_governance_voting(pool_address: address)
Implementation
public entry fun enable_partial_governance_voting(
pool_address: address,
) acquires DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage {
assert_delegation_pool_exists(pool_address);
// synchronize delegation and stake pools before any user operation.
synchronize_delegation_pool(pool_address);
let delegation_pool = borrow_global<DelegationPool>(pool_address);
let stake_pool_signer = retrieve_stake_pool_owner(delegation_pool);
// delegated_voter is managed by the stake pool itself, which signer capability is managed by DelegationPool.
// So voting power of this stake pool can only be used through this module.
stake::set_delegated_voter(&stake_pool_signer, signer::address_of(&stake_pool_signer));
move_to(&stake_pool_signer, GovernanceRecords {
votes: smart_table::new(),
votes_per_proposal: smart_table::new(),
vote_delegation: smart_table::new(),
delegated_votes: smart_table::new(),
vote_events: account::new_event_handle<VoteEvent>(&stake_pool_signer),
create_proposal_events: account::new_event_handle<CreateProposalEvent>(&stake_pool_signer),
delegate_voting_power_events: account::new_event_handle<DelegateVotingPowerEvent>(&stake_pool_signer),
});
}
Function vote
Vote on a proposal with a voter’s voting power. To successfully vote, the following conditions must be met:
- The voting period of the proposal hasn’t ended.
- The delegation pool’s lockup period ends after the voting period of the proposal.
- The voter still has spare voting power on this proposal.
- The delegation pool never votes on the proposal before enabling partial governance voting.
public entry fun vote(voter: &signer, pool_address: address, proposal_id: u64, voting_power: u64, should_pass: bool)
Implementation
public entry fun vote(
voter: &signer,
pool_address: address,
proposal_id: u64,
voting_power: u64,
should_pass: bool
) acquires DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage {
check_stake_management_permission(voter);
assert_partial_governance_voting_enabled(pool_address);
// synchronize delegation and stake pools before any user operation.
synchronize_delegation_pool(pool_address);
let voter_address = signer::address_of(voter);
let remaining_voting_power = calculate_and_update_remaining_voting_power(
pool_address,
voter_address,
proposal_id
);
if (voting_power > remaining_voting_power) {
voting_power = remaining_voting_power;
};
aptos_governance::assert_proposal_expiration(pool_address, proposal_id);
assert!(voting_power > 0, error::invalid_argument(ENO_VOTING_POWER));
let governance_records = borrow_global_mut<GovernanceRecords>(pool_address);
// Check a edge case during the transient period of enabling partial governance voting.
assert_and_update_proposal_used_voting_power(governance_records, pool_address, proposal_id, voting_power);
let used_voting_power = borrow_mut_used_voting_power(governance_records, voter_address, proposal_id);
*used_voting_power += voting_power;
let pool_signer = retrieve_stake_pool_owner(borrow_global<DelegationPool>(pool_address));
aptos_governance::partial_vote(&pool_signer, pool_address, proposal_id, voting_power, should_pass);
event::emit(
Vote {
voter: voter_address,
proposal_id,
delegation_pool: pool_address,
num_votes: voting_power,
should_pass,
}
);
}
Function create_proposal
A voter could create a governance proposal by this function. To successfully create a proposal, the voter’s
voting power in THIS delegation pool must be not less than the minimum required voting power specified in
aptos_governance.move.
public entry fun create_proposal(voter: &signer, pool_address: address, execution_hash: vector<u8>, metadata_location: vector<u8>, metadata_hash: vector<u8>, is_multi_step_proposal: bool)
Implementation
public entry fun create_proposal(
voter: &signer,
pool_address: address,
execution_hash: vector<u8>,
metadata_location: vector<u8>,
metadata_hash: vector<u8>,
is_multi_step_proposal: bool,
) acquires DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage {
check_stake_management_permission(voter);
assert_partial_governance_voting_enabled(pool_address);
// synchronize delegation and stake pools before any user operation
synchronize_delegation_pool(pool_address);
let voter_addr = signer::address_of(voter);
let pool = borrow_global<DelegationPool>(pool_address);
let governance_records = borrow_global_mut<GovernanceRecords>(pool_address);
let total_voting_power = calculate_and_update_delegated_votes(pool, governance_records, voter_addr);
assert!(
total_voting_power >= aptos_governance::get_required_proposer_stake(),
error::invalid_argument(EINSUFFICIENT_PROPOSER_STAKE));
let pool_signer = retrieve_stake_pool_owner(borrow_global<DelegationPool>(pool_address));
let proposal_id = aptos_governance::create_proposal_v2_impl(
&pool_signer,
pool_address,
execution_hash,
metadata_location,
metadata_hash,
is_multi_step_proposal,
);
event::emit(
CreateProposal {
proposal_id,
voter: voter_addr,
delegation_pool: pool_address,
}
);
}
Function assert_owner_cap_exists
fun assert_owner_cap_exists(owner: address)
Implementation
fun assert_owner_cap_exists(owner: address) {
assert!(owner_cap_exists(owner), error::not_found(EOWNER_CAP_NOT_FOUND));
}
Function assert_delegation_pool_exists
fun assert_delegation_pool_exists(pool_address: address)
Implementation
fun assert_delegation_pool_exists(pool_address: address) {
assert!(delegation_pool_exists(pool_address), error::invalid_argument(EDELEGATION_POOL_DOES_NOT_EXIST));
}
Function assert_min_active_balance
fun assert_min_active_balance(pool: &delegation_pool::DelegationPool, delegator_address: address)
Implementation
fun assert_min_active_balance(pool: &DelegationPool, delegator_address: address) {
let balance = pool.active_shares.balance(delegator_address);
assert!(balance >= MIN_COINS_ON_SHARES_POOL, error::invalid_argument(EDELEGATOR_ACTIVE_BALANCE_TOO_LOW));
}
Function assert_min_pending_inactive_balance
fun assert_min_pending_inactive_balance(pool: &delegation_pool::DelegationPool, delegator_address: address)
Implementation
fun assert_min_pending_inactive_balance(pool: &DelegationPool, delegator_address: address) {
let balance = pending_inactive_shares_pool(pool).balance(delegator_address);
assert!(
balance >= MIN_COINS_ON_SHARES_POOL,
error::invalid_argument(EDELEGATOR_PENDING_INACTIVE_BALANCE_TOO_LOW)
);
}
Function assert_partial_governance_voting_enabled
fun assert_partial_governance_voting_enabled(pool_address: address)
Implementation
fun assert_partial_governance_voting_enabled(pool_address: address) {
assert_delegation_pool_exists(pool_address);
assert!(
partial_governance_voting_enabled(pool_address),
error::invalid_state(EPARTIAL_GOVERNANCE_VOTING_NOT_ENABLED)
);
}
Function assert_allowlisting_enabled
fun assert_allowlisting_enabled(pool_address: address)
Implementation
fun assert_allowlisting_enabled(pool_address: address) {
assert!(allowlisting_enabled(pool_address), error::invalid_state(EDELEGATORS_ALLOWLISTING_NOT_ENABLED));
}
Function assert_delegator_allowlisted
fun assert_delegator_allowlisted(pool_address: address, delegator_address: address)
Implementation
fun assert_delegator_allowlisted(
pool_address: address,
delegator_address: address,
) acquires DelegationPoolAllowlisting {
assert!(
delegator_allowlisted(pool_address, delegator_address),
error::permission_denied(EDELEGATOR_NOT_ALLOWLISTED)
);
}
Function coins_to_redeem_to_ensure_min_stake
fun coins_to_redeem_to_ensure_min_stake(src_shares_pool: &pool_u64_unbound::Pool, shareholder: address, amount: u64): u64
Implementation
fun coins_to_redeem_to_ensure_min_stake(
src_shares_pool: &pool_u64::Pool,
shareholder: address,
amount: u64,
): u64 {
// find how many coins would be redeemed if supplying `amount`
let redeemed_coins = src_shares_pool.shares_to_amount(amount_to_shares_to_redeem(src_shares_pool, shareholder, amount));
// if balance drops under threshold then redeem it entirely
let src_balance = src_shares_pool.balance(shareholder);
if (src_balance - redeemed_coins < MIN_COINS_ON_SHARES_POOL) {
amount = src_balance;
};
amount
}
Function coins_to_transfer_to_ensure_min_stake
fun coins_to_transfer_to_ensure_min_stake(src_shares_pool: &pool_u64_unbound::Pool, dst_shares_pool: &pool_u64_unbound::Pool, shareholder: address, amount: u64): u64
Implementation
fun coins_to_transfer_to_ensure_min_stake(
src_shares_pool: &pool_u64::Pool,
dst_shares_pool: &pool_u64::Pool,
shareholder: address,
amount: u64,
): u64 {
// find how many coins would be redeemed from source if supplying `amount`
let redeemed_coins = src_shares_pool.shares_to_amount(amount_to_shares_to_redeem(src_shares_pool, shareholder, amount));
// if balance on destination would be less than threshold then redeem difference to threshold
let dst_balance = dst_shares_pool.balance(shareholder);
if (dst_balance + redeemed_coins < MIN_COINS_ON_SHARES_POOL) {
// `redeemed_coins` >= `amount` - 1 as redeem can lose at most 1 coin
amount = MIN_COINS_ON_SHARES_POOL - dst_balance + 1;
};
// check if new `amount` drops balance on source under threshold and adjust
coins_to_redeem_to_ensure_min_stake(src_shares_pool, shareholder, amount)
}
Function retrieve_stake_pool_owner
Retrieves the shared resource account owning the stake pool in order to forward a stake-management operation to this underlying pool.
fun retrieve_stake_pool_owner(pool: &delegation_pool::DelegationPool): signer
Implementation
fun retrieve_stake_pool_owner(pool: &DelegationPool): signer {
account::create_signer_with_capability(&pool.stake_pool_signer_cap)
}
Function get_pool_address
Get the address of delegation pool reference pool.
fun get_pool_address(pool: &delegation_pool::DelegationPool): address
Implementation
fun get_pool_address(pool: &DelegationPool): address {
account::get_signer_capability_address(&pool.stake_pool_signer_cap)
}
Function get_delegator_active_shares
Get the active share amount of the delegator.
fun get_delegator_active_shares(pool: &delegation_pool::DelegationPool, delegator: address): u128
Implementation
fun get_delegator_active_shares(pool: &DelegationPool, delegator: address): u128 {
pool.active_shares.shares(delegator)
}
Function get_delegator_pending_inactive_shares
Get the pending inactive share amount of the delegator.
fun get_delegator_pending_inactive_shares(pool: &delegation_pool::DelegationPool, delegator: address): u128
Implementation
fun get_delegator_pending_inactive_shares(pool: &DelegationPool, delegator: address): u128 {
pending_inactive_shares_pool(pool).shares(delegator)
}
Function get_used_voting_power
Get the used voting power of a voter on a proposal.
fun get_used_voting_power(governance_records: &delegation_pool::GovernanceRecords, voter: address, proposal_id: u64): u64
Implementation
fun get_used_voting_power(governance_records: &GovernanceRecords, voter: address, proposal_id: u64): u64 {
let votes = &governance_records.votes;
let key = VotingRecordKey {
voter,
proposal_id,
};
*votes.borrow_with_default(key, &0)
}
Function create_resource_account_seed
Create the seed to derive the resource account address.
fun create_resource_account_seed(delegation_pool_creation_seed: vector<u8>): vector<u8>
Implementation
fun create_resource_account_seed(
delegation_pool_creation_seed: vector<u8>,
): vector<u8> {
let seed = vector::empty<u8>();
// include module salt (before any subseeds) to avoid conflicts with other modules creating resource accounts
seed.append(MODULE_SALT);
// include an additional salt in case the same resource account has already been created
seed.append(delegation_pool_creation_seed);
seed
}
Function borrow_mut_used_voting_power
Borrow the mutable used voting power of a voter on a proposal.
fun borrow_mut_used_voting_power(governance_records: &mut delegation_pool::GovernanceRecords, voter: address, proposal_id: u64): &mut u64
Implementation
inline fun borrow_mut_used_voting_power(
governance_records: &mut GovernanceRecords,
voter: address,
proposal_id: u64
): &mut u64 {
let votes = &mut governance_records.votes;
let key = VotingRecordKey {
proposal_id,
voter,
};
votes.borrow_mut_with_default(key, 0)
}
Function update_and_borrow_mut_delegator_vote_delegation
Update VoteDelegation of a delegator to up-to-date then borrow_mut it.
fun update_and_borrow_mut_delegator_vote_delegation(pool: &delegation_pool::DelegationPool, governance_records: &mut delegation_pool::GovernanceRecords, delegator: address): &mut delegation_pool::VoteDelegation
Implementation
fun update_and_borrow_mut_delegator_vote_delegation(
pool: &DelegationPool,
governance_records: &mut GovernanceRecords,
delegator: address
): &mut VoteDelegation {
let pool_address = get_pool_address(pool);
let locked_until_secs = stake::get_lockup_secs(pool_address);
let vote_delegation_table = &mut governance_records.vote_delegation;
// By default, a delegator's delegated voter is itself.
// TODO: recycle storage when VoteDelegation equals to default value.
if (!vote_delegation_table.contains(delegator)) {
return vote_delegation_table.borrow_mut_with_default(delegator, VoteDelegation {
voter: delegator,
last_locked_until_secs: locked_until_secs,
pending_voter: delegator,
})
};
let vote_delegation = vote_delegation_table.borrow_mut(delegator);
// A lockup period has passed since last time `vote_delegation` was updated. Pending voter takes effect.
if (vote_delegation.last_locked_until_secs < locked_until_secs) {
vote_delegation.voter = vote_delegation.pending_voter;
vote_delegation.last_locked_until_secs = locked_until_secs;
};
vote_delegation
}
Function update_and_borrow_mut_delegated_votes
Update DelegatedVotes of a voter to up-to-date then borrow_mut it.
fun update_and_borrow_mut_delegated_votes(pool: &delegation_pool::DelegationPool, governance_records: &mut delegation_pool::GovernanceRecords, voter: address): &mut delegation_pool::DelegatedVotes
Implementation
fun update_and_borrow_mut_delegated_votes(
pool: &DelegationPool,
governance_records: &mut GovernanceRecords,
voter: address
): &mut DelegatedVotes {
let pool_address = get_pool_address(pool);
let locked_until_secs = stake::get_lockup_secs(pool_address);
let delegated_votes_per_voter = &mut governance_records.delegated_votes;
// By default, a delegator's voter is itself.
// TODO: recycle storage when DelegatedVotes equals to default value.
if (!delegated_votes_per_voter.contains(voter)) {
let active_shares = get_delegator_active_shares(pool, voter);
let inactive_shares = get_delegator_pending_inactive_shares(pool, voter);
return delegated_votes_per_voter.borrow_mut_with_default(voter, DelegatedVotes {
active_shares,
pending_inactive_shares: inactive_shares,
active_shares_next_lockup: active_shares,
last_locked_until_secs: locked_until_secs,
})
};
let delegated_votes = delegated_votes_per_voter.borrow_mut(voter);
// A lockup period has passed since last time `delegated_votes` was updated. Pending voter takes effect.
if (delegated_votes.last_locked_until_secs < locked_until_secs) {
delegated_votes.active_shares = delegated_votes.active_shares_next_lockup;
delegated_votes.pending_inactive_shares = 0;
delegated_votes.last_locked_until_secs = locked_until_secs;
};
delegated_votes
}
Function olc_with_index
fun olc_with_index(index: u64): delegation_pool::ObservedLockupCycle
Implementation
fun olc_with_index(index: u64): ObservedLockupCycle {
ObservedLockupCycle { index }
}
Function calculate_total_voting_power
Given the amounts of shares in active_shares pool and inactive_shares pool, calculate the total voting
power, which equals to the sum of the coin amounts.
fun calculate_total_voting_power(delegation_pool: &delegation_pool::DelegationPool, latest_delegated_votes: &delegation_pool::DelegatedVotes): u64
Implementation
fun calculate_total_voting_power(delegation_pool: &DelegationPool, latest_delegated_votes: &DelegatedVotes): u64 {
let active_amount = delegation_pool.active_shares.shares_to_amount(latest_delegated_votes.active_shares);
let pending_inactive_amount = pending_inactive_shares_pool(delegation_pool).shares_to_amount(latest_delegated_votes.pending_inactive_shares);
active_amount + pending_inactive_amount
}
Function calculate_and_update_delegator_voter_internal
Update VoteDelegation of a delegator to up-to-date then return the latest voter.
fun calculate_and_update_delegator_voter_internal(pool: &delegation_pool::DelegationPool, governance_records: &mut delegation_pool::GovernanceRecords, delegator: address): address
Implementation
fun calculate_and_update_delegator_voter_internal(
pool: &DelegationPool,
governance_records: &mut GovernanceRecords,
delegator: address
): address {
let vote_delegation = update_and_borrow_mut_delegator_vote_delegation(pool, governance_records, delegator);
vote_delegation.voter
}
Function calculate_and_update_delegated_votes
Update DelegatedVotes of a voter to up-to-date then return the total voting power of this voter.
fun calculate_and_update_delegated_votes(pool: &delegation_pool::DelegationPool, governance_records: &mut delegation_pool::GovernanceRecords, voter: address): u64
Implementation
fun calculate_and_update_delegated_votes(
pool: &DelegationPool,
governance_records: &mut GovernanceRecords,
voter: address
): u64 {
let delegated_votes = update_and_borrow_mut_delegated_votes(pool, governance_records, voter);
calculate_total_voting_power(pool, delegated_votes)
}
Function borrow_mut_delegators_allowlist
fun borrow_mut_delegators_allowlist(pool_address: address): &mut smart_table::SmartTable<address, bool>
Implementation
inline fun borrow_mut_delegators_allowlist(pool_address: address): &mut SmartTable<address, bool> {
&mut borrow_global_mut<DelegationPoolAllowlisting>(pool_address).allowlist
}
Function set_operator
Allows an owner to change the operator of the underlying stake pool.
public entry fun set_operator(owner: &signer, new_operator: address)
Implementation
public entry fun set_operator(
owner: &signer,
new_operator: address
) acquires DelegationPoolOwnership, DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage {
check_delegation_pool_management_permission(owner);
let pool_address = get_owned_pool_address(signer::address_of(owner));
// synchronize delegation and stake pools before any user operation
// ensure the old operator is paid its uncommitted commission rewards
synchronize_delegation_pool(pool_address);
stake::set_operator(&retrieve_stake_pool_owner(borrow_global<DelegationPool>(pool_address)), new_operator);
}
Function set_beneficiary_for_operator
Allows an operator to change its beneficiary. Any existing unpaid commission rewards will be paid to the new
beneficiary. To ensure payment to the current beneficiary, one should first call synchronize_delegation_pool
before switching the beneficiary. An operator can set one beneficiary for delegation pools, not a separate
one for each pool.
public entry fun set_beneficiary_for_operator(operator: &signer, new_beneficiary: address)
Implementation
public entry fun set_beneficiary_for_operator(
operator: &signer,
new_beneficiary: address
) acquires BeneficiaryForOperator {
check_stake_management_permission(operator);
// The beneficiay address of an operator is stored under the operator's address.
// So, the operator does not need to be validated with respect to a staking pool.
let operator_addr = signer::address_of(operator);
let old_beneficiary = beneficiary_for_operator(operator_addr);
if (exists<BeneficiaryForOperator>(operator_addr)) {
borrow_global_mut<BeneficiaryForOperator>(operator_addr).beneficiary_for_operator = new_beneficiary;
} else {
move_to(operator, BeneficiaryForOperator { beneficiary_for_operator: new_beneficiary });
};
emit(SetBeneficiaryForOperator {
operator: operator_addr,
old_beneficiary,
new_beneficiary,
});
}
Function update_commission_percentage
Allows an owner to update the commission percentage for the operator of the underlying stake pool.
public entry fun update_commission_percentage(owner: &signer, new_commission_percentage: u64)
Implementation
public entry fun update_commission_percentage(
owner: &signer,
new_commission_percentage: u64
) acquires DelegationPoolOwnership, DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage {
check_delegation_pool_management_permission(owner);
assert!(new_commission_percentage <= MAX_FEE, error::invalid_argument(EINVALID_COMMISSION_PERCENTAGE));
let owner_address = signer::address_of(owner);
let pool_address = get_owned_pool_address(owner_address);
assert!(
operator_commission_percentage(pool_address) + MAX_COMMISSION_INCREASE >= new_commission_percentage,
error::invalid_argument(ETOO_LARGE_COMMISSION_INCREASE)
);
assert!(
stake::get_remaining_lockup_secs(pool_address) >= min_remaining_secs_for_commission_change(),
error::invalid_state(ETOO_LATE_COMMISSION_CHANGE)
);
// synchronize delegation and stake pools before any user operation. this ensures:
// (1) the operator is paid its uncommitted commission rewards with the old commission percentage, and
// (2) any pending commission percentage change is applied before the new commission percentage is set.
synchronize_delegation_pool(pool_address);
if (exists<NextCommissionPercentage>(pool_address)) {
let commission_percentage = borrow_global_mut<NextCommissionPercentage>(pool_address);
commission_percentage.commission_percentage_next_lockup_cycle = new_commission_percentage;
commission_percentage.effective_after_secs = stake::get_lockup_secs(pool_address);
} else {
let delegation_pool = borrow_global<DelegationPool>(pool_address);
let pool_signer = account::create_signer_with_capability(&delegation_pool.stake_pool_signer_cap);
move_to(&pool_signer, NextCommissionPercentage {
commission_percentage_next_lockup_cycle: new_commission_percentage,
effective_after_secs: stake::get_lockup_secs(pool_address),
});
};
event::emit(CommissionPercentageChange {
pool_address,
owner: owner_address,
commission_percentage_next_lockup_cycle: new_commission_percentage,
});
}
Function set_delegated_voter
Deprecated. Use the partial governance voting flow instead.
#[deprecated]
public entry fun set_delegated_voter(_owner: &signer, _new_voter: address)
Implementation
public entry fun set_delegated_voter(
_owner: &signer,
_new_voter: address
) {
abort ECAN_NO_LONGER_SET_DELEGATED_VOTER
}
Function delegate_voting_power
Allows a delegator to delegate its voting power to a voter. If this delegator already has a delegated voter, this change won’t take effects until the next lockup period.
public entry fun delegate_voting_power(delegator: &signer, pool_address: address, new_voter: address)
Implementation
public entry fun delegate_voting_power(
delegator: &signer,
pool_address: address,
new_voter: address
) acquires DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage {
check_stake_management_permission(delegator);
assert_partial_governance_voting_enabled(pool_address);
// synchronize delegation and stake pools before any user operation
synchronize_delegation_pool(pool_address);
let delegator_address = signer::address_of(delegator);
let delegation_pool = borrow_global<DelegationPool>(pool_address);
let governance_records = borrow_global_mut<GovernanceRecords>(pool_address);
let delegator_vote_delegation = update_and_borrow_mut_delegator_vote_delegation(
delegation_pool,
governance_records,
delegator_address
);
let pending_voter: address = delegator_vote_delegation.pending_voter;
// No need to update if the voter doesn't really change.
if (pending_voter != new_voter) {
delegator_vote_delegation.pending_voter = new_voter;
let active_shares = get_delegator_active_shares(delegation_pool, delegator_address);
// <active shares> of <pending voter of shareholder> -= <active_shares>
// <active shares> of <new voter of shareholder> += <active_shares>
let pending_delegated_votes = update_and_borrow_mut_delegated_votes(
delegation_pool,
governance_records,
pending_voter
);
pending_delegated_votes.active_shares_next_lockup -= active_shares;
let new_delegated_votes = update_and_borrow_mut_delegated_votes(
delegation_pool,
governance_records,
new_voter
);
new_delegated_votes.active_shares_next_lockup += active_shares;
};
event::emit(DelegateVotingPower {
pool_address,
delegator: delegator_address,
voter: new_voter,
});
}
Function enable_delegators_allowlisting
Enable delegators allowlisting as the pool owner.
public entry fun enable_delegators_allowlisting(owner: &signer)
Implementation
public entry fun enable_delegators_allowlisting(
owner: &signer,
) acquires DelegationPoolOwnership, DelegationPool {
check_delegation_pool_management_permission(owner);
assert!(
features::delegation_pool_allowlisting_enabled(),
error::invalid_state(EDELEGATORS_ALLOWLISTING_NOT_SUPPORTED)
);
let pool_address = get_owned_pool_address(signer::address_of(owner));
if (allowlisting_enabled(pool_address)) { return };
let pool_signer = retrieve_stake_pool_owner(borrow_global<DelegationPool>(pool_address));
move_to(&pool_signer, DelegationPoolAllowlisting { allowlist: smart_table::new<address, bool>() });
event::emit(EnableDelegatorsAllowlisting { pool_address });
}
Function disable_delegators_allowlisting
Disable delegators allowlisting as the pool owner. The existing allowlist will be emptied.
public entry fun disable_delegators_allowlisting(owner: &signer)
Implementation
public entry fun disable_delegators_allowlisting(
owner: &signer,
) acquires DelegationPoolOwnership, DelegationPoolAllowlisting {
check_delegation_pool_management_permission(owner);
let pool_address = get_owned_pool_address(signer::address_of(owner));
assert_allowlisting_enabled(pool_address);
let DelegationPoolAllowlisting { allowlist } = move_from<DelegationPoolAllowlisting>(pool_address);
// if the allowlist becomes too large, the owner can always remove some delegators
allowlist.destroy();
event::emit(DisableDelegatorsAllowlisting { pool_address });
}
Function allowlist_delegator
Allowlist a delegator as the pool owner.
public entry fun allowlist_delegator(owner: &signer, delegator_address: address)
Implementation
public entry fun allowlist_delegator(
owner: &signer,
delegator_address: address,
) acquires DelegationPoolOwnership, DelegationPoolAllowlisting {
check_delegation_pool_management_permission(owner);
let pool_address = get_owned_pool_address(signer::address_of(owner));
assert_allowlisting_enabled(pool_address);
if (delegator_allowlisted(pool_address, delegator_address)) { return };
borrow_mut_delegators_allowlist(pool_address).add(delegator_address, true);
event::emit(AllowlistDelegator { pool_address, delegator_address });
}
Function remove_delegator_from_allowlist
Remove a delegator from the allowlist as the pool owner, but do not unlock their stake.
public entry fun remove_delegator_from_allowlist(owner: &signer, delegator_address: address)
Implementation
public entry fun remove_delegator_from_allowlist(
owner: &signer,
delegator_address: address,
) acquires DelegationPoolOwnership, DelegationPoolAllowlisting {
check_delegation_pool_management_permission(owner);
let pool_address = get_owned_pool_address(signer::address_of(owner));
assert_allowlisting_enabled(pool_address);
if (!delegator_allowlisted(pool_address, delegator_address)) { return };
borrow_mut_delegators_allowlist(pool_address).remove(delegator_address);
event::emit(RemoveDelegatorFromAllowlist { pool_address, delegator_address });
}
Function evict_delegator
Evict a delegator that is not allowlisted by unlocking their entire stake.
public entry fun evict_delegator(owner: &signer, delegator_address: address)
Implementation
public entry fun evict_delegator(
owner: &signer,
delegator_address: address,
) acquires DelegationPoolOwnership, DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage, DelegationPoolAllowlisting {
check_delegation_pool_management_permission(owner);
let pool_address = get_owned_pool_address(signer::address_of(owner));
assert_allowlisting_enabled(pool_address);
assert!(
!delegator_allowlisted(pool_address, delegator_address),
error::invalid_state(ECANNOT_EVICT_ALLOWLISTED_DELEGATOR)
);
// synchronize pool in order to query latest balance of delegator
synchronize_delegation_pool(pool_address);
let pool = borrow_global<DelegationPool>(pool_address);
if (get_delegator_active_shares(pool, delegator_address) == 0) { return };
unlock_internal(delegator_address, pool_address, pool.active_shares.balance(delegator_address));
event::emit(EvictDelegator { pool_address, delegator_address });
}
Function add_stake
Add amount of coins to the delegation pool pool_address.
public entry fun add_stake(delegator: &signer, pool_address: address, amount: u64)
Implementation
public entry fun add_stake(
delegator: &signer,
pool_address: address,
amount: u64
) acquires DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage, DelegationPoolAllowlisting {
check_stake_management_permission(delegator);
// short-circuit if amount to add is 0 so no event is emitted
if (amount == 0) { return };
let delegator_address = signer::address_of(delegator);
assert_delegator_allowlisted(pool_address, delegator_address);
// synchronize delegation and stake pools before any user operation
synchronize_delegation_pool(pool_address);
// fee to be charged for adding `amount` stake on this delegation pool at this epoch
let add_stake_fee = get_add_stake_fee(pool_address, amount);
let pool = borrow_global_mut<DelegationPool>(pool_address);
// stake the entire amount to the stake pool
aptos_account::transfer(delegator, pool_address, amount);
stake::add_stake(&retrieve_stake_pool_owner(pool), amount);
// but buy shares for delegator just for the remaining amount after fee
buy_in_active_shares(pool, delegator_address, amount - add_stake_fee);
assert_min_active_balance(pool, delegator_address);
// grant temporary ownership over `add_stake` fees to a separate shareholder in order to:
// - not mistake them for rewards to pay the operator from
// - distribute them together with the `active` rewards when this epoch ends
// in order to appreciate all shares on the active pool atomically
buy_in_active_shares(pool, NULL_SHAREHOLDER, add_stake_fee);
event::emit(
AddStake {
pool_address,
delegator_address,
amount_added: amount,
add_stake_fee,
},
);
}
Function unlock
Unlock amount from the active + pending_active stake of delegator or
at most how much active stake there is on the stake pool.
public entry fun unlock(delegator: &signer, pool_address: address, amount: u64)
Implementation
public entry fun unlock(
delegator: &signer,
pool_address: address,
amount: u64
) acquires DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage {
check_stake_management_permission(delegator);
// short-circuit if amount to unlock is 0 so no event is emitted
if (amount == 0) { return };
// synchronize delegation and stake pools before any user operation
synchronize_delegation_pool(pool_address);
let delegator_address = signer::address_of(delegator);
unlock_internal(delegator_address, pool_address, amount);
}
Function unlock_internal
fun unlock_internal(delegator_address: address, pool_address: address, amount: u64)
Implementation
fun unlock_internal(
delegator_address: address,
pool_address: address,
amount: u64
) acquires DelegationPool, GovernanceRecords {
assert!(delegator_address != NULL_SHAREHOLDER, error::invalid_argument(ECANNOT_UNLOCK_NULL_SHAREHOLDER));
// fail unlock of more stake than `active` on the stake pool
let (active, _, _, _) = stake::get_stake(pool_address);
assert!(amount <= active, error::invalid_argument(ENOT_ENOUGH_ACTIVE_STAKE_TO_UNLOCK));
let pool = borrow_global_mut<DelegationPool>(pool_address);
amount = coins_to_transfer_to_ensure_min_stake(
&pool.active_shares,
pending_inactive_shares_pool(pool),
delegator_address,
amount,
);
amount = redeem_active_shares(pool, delegator_address, amount);
stake::unlock(&retrieve_stake_pool_owner(pool), amount);
buy_in_pending_inactive_shares(pool, delegator_address, amount);
assert_min_pending_inactive_balance(pool, delegator_address);
event::emit(
UnlockStake {
pool_address,
delegator_address,
amount_unlocked: amount,
},
);
}
Function reactivate_stake
Move amount of coins from pending_inactive to active.
public entry fun reactivate_stake(delegator: &signer, pool_address: address, amount: u64)
Implementation
public entry fun reactivate_stake(
delegator: &signer,
pool_address: address,
amount: u64
) acquires DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage, DelegationPoolAllowlisting {
check_stake_management_permission(delegator);
// short-circuit if amount to reactivate is 0 so no event is emitted
if (amount == 0) { return };
let delegator_address = signer::address_of(delegator);
assert_delegator_allowlisted(pool_address, delegator_address);
// synchronize delegation and stake pools before any user operation
synchronize_delegation_pool(pool_address);
let pool = borrow_global_mut<DelegationPool>(pool_address);
amount = coins_to_transfer_to_ensure_min_stake(
pending_inactive_shares_pool(pool),
&pool.active_shares,
delegator_address,
amount,
);
let observed_lockup_cycle = pool.observed_lockup_cycle;
amount = redeem_inactive_shares(pool, delegator_address, amount, observed_lockup_cycle);
stake::reactivate_stake(&retrieve_stake_pool_owner(pool), amount);
buy_in_active_shares(pool, delegator_address, amount);
assert_min_active_balance(pool, delegator_address);
event::emit(
ReactivateStake {
pool_address,
delegator_address,
amount_reactivated: amount,
},
);
}
Function withdraw
Withdraw amount of owned inactive stake from the delegation pool at pool_address.
public entry fun withdraw(delegator: &signer, pool_address: address, amount: u64)
Implementation
public entry fun withdraw(
delegator: &signer,
pool_address: address,
amount: u64
) acquires DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage {
check_stake_management_permission(delegator);
assert!(amount > 0, error::invalid_argument(EWITHDRAW_ZERO_STAKE));
// synchronize delegation and stake pools before any user operation
synchronize_delegation_pool(pool_address);
withdraw_internal(borrow_global_mut<DelegationPool>(pool_address), signer::address_of(delegator), amount);
}
Function withdraw_internal
fun withdraw_internal(pool: &mut delegation_pool::DelegationPool, delegator_address: address, amount: u64)
Implementation
fun withdraw_internal(
pool: &mut DelegationPool,
delegator_address: address,
amount: u64
) acquires GovernanceRecords {
// TODO: recycle storage when a delegator fully exits the delegation pool.
// short-circuit if amount to withdraw is 0 so no event is emitted
if (amount == 0) { return };
let pool_address = get_pool_address(pool);
let (withdrawal_exists, withdrawal_olc) = pending_withdrawal_exists(pool, delegator_address);
// exit if no withdrawal or (it is pending and cannot withdraw pending_inactive stake from stake pool)
if (!(
withdrawal_exists &&
(withdrawal_olc.index < pool.observed_lockup_cycle.index || can_withdraw_pending_inactive(pool_address))
)) { return };
if (withdrawal_olc.index == pool.observed_lockup_cycle.index) {
amount = coins_to_redeem_to_ensure_min_stake(
pending_inactive_shares_pool(pool),
delegator_address,
amount,
)
};
amount = redeem_inactive_shares(pool, delegator_address, amount, withdrawal_olc);
let stake_pool_owner = &retrieve_stake_pool_owner(pool);
// stake pool will inactivate entire pending_inactive stake at `stake::withdraw` to make it withdrawable
// however, bypassing the inactivation of excess stake (inactivated but not withdrawn) ensures
// the OLC is not advanced indefinitely on `unlock`-`withdraw` paired calls
if (can_withdraw_pending_inactive(pool_address)) {
// get excess stake before being entirely inactivated
let (_, _, _, pending_inactive) = stake::get_stake(pool_address);
if (withdrawal_olc.index == pool.observed_lockup_cycle.index) {
// `amount` less excess if withdrawing pending_inactive stake
pending_inactive -= amount
};
// escape excess stake from inactivation
stake::reactivate_stake(stake_pool_owner, pending_inactive);
stake::withdraw(stake_pool_owner, amount);
// restore excess stake to the pending_inactive state
stake::unlock(stake_pool_owner, pending_inactive);
} else {
// no excess stake if `stake::withdraw` does not inactivate at all
stake::withdraw(stake_pool_owner, amount);
};
aptos_account::transfer(stake_pool_owner, delegator_address, amount);
// commit withdrawal of possibly inactive stake to the `total_coins_inactive`
// known by the delegation pool in order to not mistake it for slashing at next synchronization
let (_, inactive, _, _) = stake::get_stake(pool_address);
pool.total_coins_inactive = inactive;
event::emit(
WithdrawStake {
pool_address,
delegator_address,
amount_withdrawn: amount,
},
);
}
Function pending_withdrawal_exists
Return the unique observed lockup cycle where delegator delegator_address may have
unlocking (or already unlocked) stake to be withdrawn from delegation pool pool.
A bool is returned to signal if a pending withdrawal exists at all.
fun pending_withdrawal_exists(pool: &delegation_pool::DelegationPool, delegator_address: address): (bool, delegation_pool::ObservedLockupCycle)
Implementation
fun pending_withdrawal_exists(pool: &DelegationPool, delegator_address: address): (bool, ObservedLockupCycle) {
if (pool.pending_withdrawals.contains(delegator_address)) {
(true, *pool.pending_withdrawals.borrow(delegator_address))
} else {
(false, olc_with_index(0))
}
}
Function pending_inactive_shares_pool_mut
Return a mutable reference to the shares pool of pending_inactive stake on the
delegation pool, always the last item in inactive_shares.
fun pending_inactive_shares_pool_mut(pool: &mut delegation_pool::DelegationPool): &mut pool_u64_unbound::Pool
Implementation
fun pending_inactive_shares_pool_mut(pool: &mut DelegationPool): &mut pool_u64::Pool {
let observed_lockup_cycle = pool.observed_lockup_cycle;
pool.inactive_shares.borrow_mut(observed_lockup_cycle)
}
Function pending_inactive_shares_pool
fun pending_inactive_shares_pool(pool: &delegation_pool::DelegationPool): &pool_u64_unbound::Pool
Implementation
fun pending_inactive_shares_pool(pool: &DelegationPool): &pool_u64::Pool {
pool.inactive_shares.borrow(pool.observed_lockup_cycle)
}
Function execute_pending_withdrawal
Execute the pending withdrawal of delegator_address on delegation pool pool
if existing and already inactive to allow the creation of a new one.
pending_inactive stake would be left untouched even if withdrawable and should
be explicitly withdrawn by delegator
fun execute_pending_withdrawal(pool: &mut delegation_pool::DelegationPool, delegator_address: address)
Implementation
fun execute_pending_withdrawal(pool: &mut DelegationPool, delegator_address: address) acquires GovernanceRecords {
let (withdrawal_exists, withdrawal_olc) = pending_withdrawal_exists(pool, delegator_address);
if (withdrawal_exists && withdrawal_olc.index < pool.observed_lockup_cycle.index) {
withdraw_internal(pool, delegator_address, MAX_U64);
}
}
Function buy_in_active_shares
Buy shares into the active pool on behalf of delegator shareholder who
deposited coins_amount. This function doesn’t make any coin transfer.
fun buy_in_active_shares(pool: &mut delegation_pool::DelegationPool, shareholder: address, coins_amount: u64): u128
Implementation
fun buy_in_active_shares(
pool: &mut DelegationPool,
shareholder: address,
coins_amount: u64,
): u128 acquires GovernanceRecords {
let new_shares = pool.active_shares.amount_to_shares(coins_amount);
// No need to buy 0 shares.
if (new_shares == 0) { return 0 };
// Always update governance records before any change to the shares pool.
let pool_address = get_pool_address(pool);
if (partial_governance_voting_enabled(pool_address)) {
update_governance_records_for_buy_in_active_shares(pool, pool_address, new_shares, shareholder);
};
pool.active_shares.buy_in(shareholder, coins_amount);
new_shares
}
Function buy_in_pending_inactive_shares
Buy shares into the pending_inactive pool on behalf of delegator shareholder who
redeemed coins_amount from the active pool to schedule it for unlocking.
If delegator’s pending withdrawal exists and has been inactivated, execute it firstly
to ensure there is always only one withdrawal request.
fun buy_in_pending_inactive_shares(pool: &mut delegation_pool::DelegationPool, shareholder: address, coins_amount: u64): u128
Implementation
fun buy_in_pending_inactive_shares(
pool: &mut DelegationPool,
shareholder: address,
coins_amount: u64,
): u128 acquires GovernanceRecords {
let new_shares = pending_inactive_shares_pool(pool).amount_to_shares(coins_amount);
// never create a new pending withdrawal unless delegator owns some pending_inactive shares
if (new_shares == 0) { return 0 };
// Always update governance records before any change to the shares pool.
let pool_address = get_pool_address(pool);
if (partial_governance_voting_enabled(pool_address)) {
update_governance_records_for_buy_in_pending_inactive_shares(pool, pool_address, new_shares, shareholder);
};
// cannot buy inactive shares, only pending_inactive at current lockup cycle
pending_inactive_shares_pool_mut(pool).buy_in(shareholder, coins_amount);
// execute the pending withdrawal if exists and is inactive before creating a new one
execute_pending_withdrawal(pool, shareholder);
// save observed lockup cycle for the new pending withdrawal
let observed_lockup_cycle = pool.observed_lockup_cycle;
assert!(*pool.pending_withdrawals.borrow_mut_with_default(shareholder, observed_lockup_cycle) == observed_lockup_cycle,
error::invalid_state(EPENDING_WITHDRAWAL_EXISTS)
);
new_shares
}
Function amount_to_shares_to_redeem
Convert coins_amount of coins to be redeemed from shares pool shares_pool
to the exact number of shares to redeem in order to achieve this.
fun amount_to_shares_to_redeem(shares_pool: &pool_u64_unbound::Pool, shareholder: address, coins_amount: u64): u128
Implementation
fun amount_to_shares_to_redeem(
shares_pool: &pool_u64::Pool,
shareholder: address,
coins_amount: u64,
): u128 {
if (coins_amount >= shares_pool.balance(shareholder)) {
// cap result at total shares of shareholder to pass `EINSUFFICIENT_SHARES` on subsequent redeem
shares_pool.shares(shareholder)
} else {
shares_pool.amount_to_shares(coins_amount)
}
}
Function redeem_active_shares
Redeem shares from the active pool on behalf of delegator shareholder who
wants to unlock coins_amount of its active stake.
Extracted coins will be used to buy shares into the pending_inactive pool and
be available for withdrawal when current OLC ends.
fun redeem_active_shares(pool: &mut delegation_pool::DelegationPool, shareholder: address, coins_amount: u64): u64
Implementation
fun redeem_active_shares(
pool: &mut DelegationPool,
shareholder: address,
coins_amount: u64,
): u64 acquires GovernanceRecords {
let shares_to_redeem = amount_to_shares_to_redeem(&pool.active_shares, shareholder, coins_amount);
// silently exit if not a shareholder otherwise redeem would fail with `ESHAREHOLDER_NOT_FOUND`
if (shares_to_redeem == 0) return 0;
// Always update governance records before any change to the shares pool.
let pool_address = get_pool_address(pool);
if (partial_governance_voting_enabled(pool_address)) {
update_governanace_records_for_redeem_active_shares(pool, pool_address, shares_to_redeem, shareholder);
};
pool.active_shares.redeem_shares(shareholder, shares_to_redeem)
}
Function redeem_inactive_shares
Redeem shares from the inactive pool at lockup_cycle < current OLC on behalf of
delegator shareholder who wants to withdraw coins_amount of its unlocked stake.
Redeem shares from the pending_inactive pool at lockup_cycle == current OLC on behalf of
delegator shareholder who wants to reactivate coins_amount of its unlocking stake.
For latter case, extracted coins will be used to buy shares into the active pool and
escape inactivation when current lockup ends.
fun redeem_inactive_shares(pool: &mut delegation_pool::DelegationPool, shareholder: address, coins_amount: u64, lockup_cycle: delegation_pool::ObservedLockupCycle): u64
Implementation
fun redeem_inactive_shares(
pool: &mut DelegationPool,
shareholder: address,
coins_amount: u64,
lockup_cycle: ObservedLockupCycle,
): u64 acquires GovernanceRecords {
let shares_to_redeem = amount_to_shares_to_redeem(
pool.inactive_shares.borrow(lockup_cycle),
shareholder,
coins_amount);
// silently exit if not a shareholder otherwise redeem would fail with `ESHAREHOLDER_NOT_FOUND`
if (shares_to_redeem == 0) return 0;
// Always update governance records before any change to the shares pool.
let pool_address = get_pool_address(pool);
// Only redeem shares from the pending_inactive pool at `lockup_cycle` == current OLC.
if (partial_governance_voting_enabled(pool_address) && lockup_cycle.index == pool.observed_lockup_cycle.index) {
update_governanace_records_for_redeem_pending_inactive_shares(
pool,
pool_address,
shares_to_redeem,
shareholder
);
};
let inactive_shares = pool.inactive_shares.borrow_mut(lockup_cycle);
// 1. reaching here means delegator owns inactive/pending_inactive shares at OLC `lockup_cycle`
let redeemed_coins = inactive_shares.redeem_shares(shareholder, shares_to_redeem);
// if entirely reactivated pending_inactive stake or withdrawn inactive one,
// re-enable unlocking for delegator by deleting this pending withdrawal
if (inactive_shares.shares(shareholder) == 0) {
// 2. a delegator owns inactive/pending_inactive shares only at the OLC of its pending withdrawal
// 1 & 2: the pending withdrawal itself has been emptied of shares and can be safely deleted
pool.pending_withdrawals.remove(shareholder);
};
// destroy inactive shares pool of past OLC if all its stake has been withdrawn
if (lockup_cycle.index < pool.observed_lockup_cycle.index && inactive_shares.total_coins() == 0) {
pool.inactive_shares.remove(lockup_cycle).destroy_empty();
};
redeemed_coins
}
Function calculate_stake_pool_drift
Calculate stake deviations between the delegation and stake pools in order to capture the rewards earned in the meantime, resulted operator commission and whether the lockup expired on the stake pool.
fun calculate_stake_pool_drift(pool: &delegation_pool::DelegationPool): (bool, u64, u64, u64, u64)
Implementation
fun calculate_stake_pool_drift(pool: &DelegationPool): (bool, u64, u64, u64, u64) {
let (active, inactive, pending_active, pending_inactive) = stake::get_stake(get_pool_address(pool));
assert!(
inactive >= pool.total_coins_inactive,
error::invalid_state(ESLASHED_INACTIVE_STAKE_ON_PAST_OLC)
);
// determine whether a new lockup cycle has been ended on the stake pool and
// inactivated SOME `pending_inactive` stake which should stop earning rewards now,
// thus requiring separation of the `pending_inactive` stake on current observed lockup
// and the future one on the newly started lockup
let lockup_cycle_ended = inactive > pool.total_coins_inactive;
// actual coins on stake pool belonging to the active shares pool
active += pending_active;
// actual coins on stake pool belonging to the shares pool hosting `pending_inactive` stake
// at current observed lockup cycle, either pending: `pending_inactive` or already inactivated:
if (lockup_cycle_ended) {
// `inactive` on stake pool = any previous `inactive` stake +
// any previous `pending_inactive` stake and its rewards (both inactivated)
pending_inactive = inactive - pool.total_coins_inactive
};
// on stake-management operations, total coins on the internal shares pools and individual
// stakes on the stake pool are updated simultaneously, thus the only stakes becoming
// unsynced are rewards and slashes routed exclusively to/out the stake pool
// operator `active` rewards not persisted yet to the active shares pool
let pool_active = pool.active_shares.total_coins();
let commission_active = if (active > pool_active) {
math64::mul_div(active - pool_active, pool.operator_commission_percentage, MAX_FEE)
} else {
// handle any slashing applied to `active` stake
0
};
// operator `pending_inactive` rewards not persisted yet to the pending_inactive shares pool
let pool_pending_inactive = pending_inactive_shares_pool(pool).total_coins();
let commission_pending_inactive = if (pending_inactive > pool_pending_inactive) {
math64::mul_div(
pending_inactive - pool_pending_inactive,
pool.operator_commission_percentage,
MAX_FEE
)
} else {
// handle any slashing applied to `pending_inactive` stake
0
};
(lockup_cycle_ended, active, pending_inactive, commission_active, commission_pending_inactive)
}
Function synchronize_delegation_pool
Synchronize delegation and stake pools: distribute yet-undetected rewards to the corresponding internal shares pools, assign commission to operator and eventually prepare delegation pool for a new lockup cycle.
public entry fun synchronize_delegation_pool(pool_address: address)
Implementation
public entry fun synchronize_delegation_pool(
pool_address: address
) acquires DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage {
assert_delegation_pool_exists(pool_address);
let pool = borrow_global_mut<DelegationPool>(pool_address);
let (
lockup_cycle_ended,
active,
pending_inactive,
commission_active,
commission_pending_inactive
) = calculate_stake_pool_drift(pool);
// zero `pending_active` stake indicates that either there are no `add_stake` fees or
// previous epoch has ended and should release the shares owning the existing fees
let (_, _, pending_active, _) = stake::get_stake(pool_address);
if (pending_active == 0) {
// renounce ownership over the `add_stake` fees by redeeming all shares of
// the special shareholder, implicitly their equivalent coins, out of the active shares pool
redeem_active_shares(pool, NULL_SHAREHOLDER, MAX_U64);
};
// distribute rewards remaining after commission, to delegators (to already existing shares)
// before buying shares for the operator for its entire commission fee
// otherwise, operator's new shares would additionally appreciate from rewards it does not own
// update total coins accumulated by `active` + `pending_active` shares
// redeemed `add_stake` fees are restored and distributed to the rest of the pool as rewards
pool.active_shares.update_total_coins(active - commission_active);
// update total coins accumulated by `pending_inactive` shares at current observed lockup cycle
pending_inactive_shares_pool_mut(pool).update_total_coins(pending_inactive - commission_pending_inactive);
// reward operator its commission out of uncommitted active rewards (`add_stake` fees already excluded)
buy_in_active_shares(pool, beneficiary_for_operator(stake::get_operator(pool_address)), commission_active);
// reward operator its commission out of uncommitted pending_inactive rewards
buy_in_pending_inactive_shares(
pool,
beneficiary_for_operator(stake::get_operator(pool_address)),
commission_pending_inactive
);
event::emit_event(
&mut pool.distribute_commission_events,
DistributeCommissionEvent {
pool_address,
operator: stake::get_operator(pool_address),
commission_active,
commission_pending_inactive,
},
);
emit(DistributeCommission {
pool_address,
operator: stake::get_operator(pool_address),
beneficiary: beneficiary_for_operator(stake::get_operator(pool_address)),
commission_active,
commission_pending_inactive,
});
// advance lockup cycle on delegation pool if already ended on stake pool (AND stake explicitly inactivated)
if (lockup_cycle_ended) {
// capture inactive coins over all ended lockup cycles (including this ending one)
let (_, inactive, _, _) = stake::get_stake(pool_address);
pool.total_coins_inactive = inactive;
// advance lockup cycle on the delegation pool
pool.observed_lockup_cycle.index += 1;
// start new lockup cycle with a fresh shares pool for `pending_inactive` stake
pool.inactive_shares.add(pool.observed_lockup_cycle, pool_u64::create_with_scaling_factor(SHARES_SCALING_FACTOR));
};
if (is_next_commission_percentage_effective(pool_address)) {
pool.operator_commission_percentage = borrow_global<NextCommissionPercentage>(
pool_address
).commission_percentage_next_lockup_cycle;
}
}
Function assert_and_update_proposal_used_voting_power
fun assert_and_update_proposal_used_voting_power(governance_records: &mut delegation_pool::GovernanceRecords, pool_address: address, proposal_id: u64, voting_power: u64)
Implementation
inline fun assert_and_update_proposal_used_voting_power(
governance_records: &mut GovernanceRecords, pool_address: address, proposal_id: u64, voting_power: u64
) {
let stake_pool_remaining_voting_power = aptos_governance::get_remaining_voting_power(pool_address, proposal_id);
let stake_pool_used_voting_power = aptos_governance::get_voting_power(
pool_address
) - stake_pool_remaining_voting_power;
let proposal_used_voting_power = governance_records.votes_per_proposal.borrow_mut_with_default(proposal_id, 0);
// A edge case: Before enabling partial governance voting on a delegation pool, the delegation pool has
// a voter which can vote with all voting power of this delegation pool. If the voter votes on a proposal after
// partial governance voting flag is enabled, the delegation pool doesn't have enough voting power on this
// proposal for all the delegators. To be fair, no one can vote on this proposal through this delegation pool.
// To detect this case, check if the stake pool had used voting power not through delegation_pool module.
assert!(
stake_pool_used_voting_power == *proposal_used_voting_power,
error::invalid_argument(EALREADY_VOTED_BEFORE_ENABLE_PARTIAL_VOTING)
);
*proposal_used_voting_power += voting_power;
}
Function update_governance_records_for_buy_in_active_shares
fun update_governance_records_for_buy_in_active_shares(pool: &delegation_pool::DelegationPool, pool_address: address, new_shares: u128, shareholder: address)
Implementation
fun update_governance_records_for_buy_in_active_shares(
pool: &DelegationPool, pool_address: address, new_shares: u128, shareholder: address
) acquires GovernanceRecords {
// <active shares> of <shareholder> += <new_shares> ---->
// <active shares> of <current voter of shareholder> += <new_shares>
// <active shares> of <next voter of shareholder> += <new_shares>
let governance_records = borrow_global_mut<GovernanceRecords>(pool_address);
let vote_delegation = update_and_borrow_mut_delegator_vote_delegation(pool, governance_records, shareholder);
let current_voter = vote_delegation.voter;
let pending_voter = vote_delegation.pending_voter;
let current_delegated_votes =
update_and_borrow_mut_delegated_votes(pool, governance_records, current_voter);
current_delegated_votes.active_shares += new_shares;
if (pending_voter == current_voter) {
current_delegated_votes.active_shares_next_lockup += new_shares;
} else {
let pending_delegated_votes =
update_and_borrow_mut_delegated_votes(pool, governance_records, pending_voter);
pending_delegated_votes.active_shares_next_lockup += new_shares;
};
}
Function update_governance_records_for_buy_in_pending_inactive_shares
fun update_governance_records_for_buy_in_pending_inactive_shares(pool: &delegation_pool::DelegationPool, pool_address: address, new_shares: u128, shareholder: address)
Implementation
fun update_governance_records_for_buy_in_pending_inactive_shares(
pool: &DelegationPool, pool_address: address, new_shares: u128, shareholder: address
) acquires GovernanceRecords {
// <pending inactive shares> of <shareholder> += <new_shares> ---->
// <pending inactive shares> of <current voter of shareholder> += <new_shares>
// no impact on <pending inactive shares> of <next voter of shareholder>
let governance_records = borrow_global_mut<GovernanceRecords>(pool_address);
let current_voter = calculate_and_update_delegator_voter_internal(pool, governance_records, shareholder);
let current_delegated_votes = update_and_borrow_mut_delegated_votes(pool, governance_records, current_voter);
current_delegated_votes.pending_inactive_shares += new_shares;
}
Function update_governanace_records_for_redeem_active_shares
fun update_governanace_records_for_redeem_active_shares(pool: &delegation_pool::DelegationPool, pool_address: address, shares_to_redeem: u128, shareholder: address)
Implementation
fun update_governanace_records_for_redeem_active_shares(
pool: &DelegationPool, pool_address: address, shares_to_redeem: u128, shareholder: address
) acquires GovernanceRecords {
// <active shares> of <shareholder> -= <shares_to_redeem> ---->
// <active shares> of <current voter of shareholder> -= <shares_to_redeem>
// <active shares> of <next voter of shareholder> -= <shares_to_redeem>
let governance_records = borrow_global_mut<GovernanceRecords>(pool_address);
let vote_delegation = update_and_borrow_mut_delegator_vote_delegation(
pool,
governance_records,
shareholder
);
let current_voter = vote_delegation.voter;
let pending_voter = vote_delegation.pending_voter;
let current_delegated_votes = update_and_borrow_mut_delegated_votes(pool, governance_records, current_voter);
current_delegated_votes.active_shares -= shares_to_redeem;
if (current_voter == pending_voter) {
current_delegated_votes.active_shares_next_lockup -= shares_to_redeem;
} else {
let pending_delegated_votes =
update_and_borrow_mut_delegated_votes(pool, governance_records, pending_voter);
pending_delegated_votes.active_shares_next_lockup -= shares_to_redeem;
};
}
Function update_governanace_records_for_redeem_pending_inactive_shares
fun update_governanace_records_for_redeem_pending_inactive_shares(pool: &delegation_pool::DelegationPool, pool_address: address, shares_to_redeem: u128, shareholder: address)
Implementation
fun update_governanace_records_for_redeem_pending_inactive_shares(
pool: &DelegationPool, pool_address: address, shares_to_redeem: u128, shareholder: address
) acquires GovernanceRecords {
// <pending inactive shares> of <shareholder> -= <shares_to_redeem> ---->
// <pending inactive shares> of <current voter of shareholder> -= <shares_to_redeem>
// no impact on <pending inactive shares> of <next voter of shareholder>
let governance_records = borrow_global_mut<GovernanceRecords>(pool_address);
let current_voter = calculate_and_update_delegator_voter_internal(pool, governance_records, shareholder);
let current_delegated_votes = update_and_borrow_mut_delegated_votes(pool, governance_records, current_voter);
current_delegated_votes.pending_inactive_shares -= shares_to_redeem;
}
Function multiply_then_divide
Deprecated, prefer math64::mul_div
#[deprecated]
public fun multiply_then_divide(x: u64, y: u64, z: u64): u64
Implementation
public fun multiply_then_divide(x: u64, y: u64, z: u64): u64 {
math64::mul_div(x, y, z)
}
Specification
High-level Requirements
| No. | Requirement | Criticality | Implementation | Enforcement |
|---|---|---|---|---|
| 1 | Every DelegationPool has only one corresponding StakePool stored at the same address. | Critical | Upon calling the initialize_delegation_pool function, a resource account is created from the "owner" signer to host the delegation pool resource and own the underlying stake pool. | Audited that the address of StakePool equals address of DelegationPool and the data invariant on the DelegationPool. |
| 2 | The signer capability within the delegation pool has an address equal to the address of the delegation pool. | Critical | The initialize_delegation_pool function moves the DelegationPool resource to the address associated with stake_pool_signer, which also possesses the signer capability. | Audited that the address of signer cap equals address of DelegationPool. |
| 3 | A delegator holds shares exclusively in one inactive shares pool, which could either be an already inactive pool or the pending_inactive pool. | High | The get_stake function returns the inactive stake owned by a delegator and checks which state the shares are in via the get_pending_withdrawal function. | Audited that either inactive or pending_inactive stake after invoking the get_stake function is zero and both are never non-zero. |
| 4 | The specific pool in which the delegator possesses inactive shares becomes designated as the pending withdrawal pool for that delegator. | Medium | The get_pending_withdrawal function checks if any pending withdrawal exists for a delegate address and if there is neither inactive nor pending_inactive stake, the pending_withdrawal_exists returns false. | This has been audited. |
| 5 | The existence of a pending withdrawal implies that it is associated with a pool where the delegator possesses inactive shares. | Medium | In the get_pending_withdrawal function, if withdrawal_exists is true, the function returns true and a non-zero amount | get_pending_withdrawal has been audited. |
| 6 | An inactive shares pool should have coins allocated to it; otherwise, it should become deleted. | Medium | The redeem_inactive_shares function has a check that destroys the inactive shares pool, given that it is empty. | shares pools have been audited. |
| 7 | The index of the pending withdrawal will not exceed the current OLC on DelegationPool. | High | The get_pending_withdrawal function has a check which ensures that withdrawal_olc.index < pool.observed_lockup_cycle.index. | This has been audited. |
| 8 | Slashing is not possible for inactive stakes. | Critical | The number of inactive staked coins must be greater than or equal to the total_coins_inactive of the pool. | This has been audited. |
| 9 | The delegator's active or pending inactive stake will always meet or exceed the minimum allowed value. | Medium | The add_stake, unlock and reactivate_stake functions ensure the active_shares or pending_inactive_shares balance for the delegator is greater than or equal to the MIN_COINS_ON_SHARES_POOL value. | Audited the comparison of active_shares or inactive_shares balance for the delegator with the MIN_COINS_ON_SHARES_POOL value. |
| 10 | The delegation pool exists at a given address. | Low | Functions that operate on the DelegationPool abort if there is no DelegationPool struct under the given pool_address. | Audited that there is no DelegationPool structure assigned to the pool_address given as a parameter. |
| 11 | The initialization of the delegation pool is contingent upon enabling the delegation pools feature. | Critical | The initialize_delegation_pool function should proceed if the DELEGATION_POOLS feature is enabled. | This has been audited. |
Module-level Specification
pragma verify=false;