delegation_pool - [mainnet]
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_active
stake - one of
inactive
stake FOR each past observed lockup cycle (OLC) on the stake pool - one of
pending_inactive
stake 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_stake
fees 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_pool
and 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
unlock
for 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_stake
for 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
unlock
for 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
withdraw
for 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
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;
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 unlock
ing 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;
Structs
ObservedLockupCycle
struct ObservedLockupCycle has copy, drop, store
Fields
-
index: u64
VotingRecordKey
struct VotingRecordKey has copy, drop, store
Fields
-
voter: address
-
proposal_id: u64
VoteDelegation
Track delegated voter of each delegator.
struct VoteDelegation has copy, drop, store
Fields
-
voter: address
-
pending_voter: address
-
last_locked_until_secs: u64
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
AddStake
#[event]struct AddStake has drop, store
Fields
-
pool_address: address
-
delegator_address: address
-
amount_added: u64
-
add_stake_fee: u64
AddStakeEvent
struct AddStakeEvent has drop, store
Fields
-
pool_address: address
-
delegator_address: address
-
amount_added: u64
-
add_stake_fee: u64
ReactivateStake
#[event]struct ReactivateStake has drop, store
Fields
-
pool_address: address
-
delegator_address: address
-
amount_reactivated: u64
ReactivateStakeEvent
struct ReactivateStakeEvent has drop, store
Fields
-
pool_address: address
-
delegator_address: address
-
amount_reactivated: u64
UnlockStake
#[event]struct UnlockStake has drop, store
Fields
-
pool_address: address
-
delegator_address: address
-
amount_unlocked: u64
UnlockStakeEvent
struct UnlockStakeEvent has drop, store
Fields
-
pool_address: address
-
delegator_address: address
-
amount_unlocked: u64
WithdrawStake
#[event]struct WithdrawStake has drop, store
Fields
-
pool_address: address
-
delegator_address: address
-
amount_withdrawn: u64
WithdrawStakeEvent
struct WithdrawStakeEvent has drop, store
Fields
-
pool_address: address
-
delegator_address: address
-
amount_withdrawn: u64
DistributeCommissionEvent
#[event]struct DistributeCommissionEvent has drop, store
Fields
-
pool_address: address
-
operator: address
-
commission_active: u64
-
commission_pending_inactive: u64
DistributeCommission
#[event]struct DistributeCommission has drop, store
Fields
-
pool_address: address
-
operator: address
-
beneficiary: address
-
commission_active: u64
-
commission_pending_inactive: u64
Vote
#[event]struct Vote has drop, store
Fields
-
voter: address
-
proposal_id: u64
-
delegation_pool: address
-
num_votes: u64
-
should_pass: bool
VoteEvent
struct VoteEvent has drop, store
Fields
-
voter: address
-
proposal_id: u64
-
delegation_pool: address
-
num_votes: u64
-
should_pass: bool
CreateProposal
#[event]struct CreateProposal has drop, store
Fields
-
proposal_id: u64
-
voter: address
-
delegation_pool: address
CreateProposalEvent
struct CreateProposalEvent has drop, store
Fields
-
proposal_id: u64
-
voter: address
-
delegation_pool: address
DelegateVotingPower
#[event]struct DelegateVotingPower has drop, store
Fields
-
pool_address: address
-
delegator: address
-
voter: address
DelegateVotingPowerEvent
struct DelegateVotingPowerEvent has drop, store
Fields
-
pool_address: address
-
delegator: address
-
voter: address
SetBeneficiaryForOperator
#[event]struct SetBeneficiaryForOperator has drop, store
Fields
-
operator: address
-
old_beneficiary: address
-
new_beneficiary: address
CommissionPercentageChange
#[event]struct CommissionPercentageChange has drop, store
Fields
-
pool_address: address
-
owner: address
-
commission_percentage_next_lockup_cycle: u64
EnableDelegatorsAllowlisting
#[event]struct EnableDelegatorsAllowlisting has drop, store
Fields
-
pool_address: address
DisableDelegatorsAllowlisting
#[event]struct DisableDelegatorsAllowlisting has drop, store
Fields
-
pool_address: address
AllowlistDelegator
#[event]struct AllowlistDelegator has drop, store
Fields
-
pool_address: address
-
delegator_address: address
RemoveDelegatorFromAllowlist
#[event]struct RemoveDelegatorFromAllowlist has drop, store
Fields
-
pool_address: address
-
delegator_address: address
EvictDelegator
#[event]struct EvictDelegator has drop, store
Fields
-
pool_address: address
-
delegator_address: address
Resources
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
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>
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>
BeneficiaryForOperator
struct BeneficiaryForOperator has key
Fields
-
beneficiary_for_operator: address
NextCommissionPercentage
struct NextCommissionPercentage has key
Fields
-
commission_percentage_next_lockup_cycle: u64
-
effective_after_secs: u64
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>
Functions
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)}
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}
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)}
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}
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}
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}
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 }}
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 }}
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); pool_u64::shareholders_count(&borrow_global<DelegationPool>(pool_address).active_shares)}
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)}
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 = table::borrow(&pool.inactive_shares, 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, pool_u64::balance(inactive_shares, delegator_address)) } else { pending_inactive = pool_u64::shares_to_amount_with_total_coins( inactive_shares, pool_u64::shares(inactive_shares, delegator_address), // exclude operator pending_inactive rewards not converted to shares yet 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) } }}
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_u64::total_shares(&pool.active_shares); let delegator_active_shares = pool_u64::shares(&pool.active_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 = total_active_shares - pool_u64::shares(&pool.active_shares, NULL_SHAREHOLDER); if (delegator_address == NULL_SHAREHOLDER) { delegator_active_shares = 0 } }; active = pool_u64::shares_to_amount_with_total_stats( &pool.active_shares, delegator_active_shares, // exclude operator active rewards not converted to shares yet 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 = active + commission_active; // in-flight pending_inactive commission can coexist with already inactive withdrawal if (lockup_cycle_ended) { inactive = inactive + commission_pending_inactive } else { pending_inactive = pending_inactive + commission_pending_inactive } };
(active, inactive, pending_inactive)}
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_active
and steal from rewards of existingactive
stake - or have moved to
pending_inactive
and get full rewards (they displacedactive
stake 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 = rewards_rate * (MAX_FEE - operator_commission_percentage(pool_address)); rewards_rate_denominator = 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 }}
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)}
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)}
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)}
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 )}
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)}
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)}
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}
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)}
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 }; smart_table::contains(freeze(borrow_mut_delegators_allowlist(pool_address)), delegator_address)}
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[]; smart_table::for_each_ref(freeze(borrow_mut_delegators_allowlist(pool_address)), |delegator, _v| { vector::push_back(&mut allowlist, *delegator); }); allowlist}
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), );}
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 {})}
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), );}
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 {})}
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>(); table::add( &mut inactive_shares, 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);}
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 }}
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), });}
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 = *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);
if (features::module_event_migration_enabled()) { event::emit( Vote { voter: voter_address, proposal_id, delegation_pool: pool_address, num_votes: voting_power, should_pass, } ); } else { event::emit_event( &mut governance_records.vote_events, VoteEvent { voter: voter_address, proposal_id, delegation_pool: pool_address, num_votes: voting_power, should_pass, } ); };}
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, );
let governance_records = borrow_global_mut<GovernanceRecords>(pool_address);
if (features::module_event_migration_enabled()) { event::emit( CreateProposal { proposal_id, voter: voter_addr, delegation_pool: pool_address, } ); } else { event::emit_event( &mut governance_records.create_proposal_events, CreateProposalEvent { proposal_id, voter: voter_addr, delegation_pool: pool_address, } ); };}
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));}
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));}
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_u64::balance(&pool.active_shares, delegator_address); assert!(balance >= MIN_COINS_ON_SHARES_POOL, error::invalid_argument(EDELEGATOR_ACTIVE_BALANCE_TOO_LOW));}
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 = pool_u64::balance(pending_inactive_shares_pool(pool), delegator_address); assert!( balance >= MIN_COINS_ON_SHARES_POOL, error::invalid_argument(EDELEGATOR_PENDING_INACTIVE_BALANCE_TOO_LOW) );}
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) );}
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));}
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) );}
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 = pool_u64::shares_to_amount( src_shares_pool, amount_to_shares_to_redeem(src_shares_pool, shareholder, amount) ); // if balance drops under threshold then redeem it entirely let src_balance = pool_u64::balance(src_shares_pool, shareholder); if (src_balance - redeemed_coins < MIN_COINS_ON_SHARES_POOL) { amount = src_balance; }; amount}
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 = pool_u64::shares_to_amount( src_shares_pool, 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 = pool_u64::balance(dst_shares_pool, 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)}
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)}
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)}
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_u64::shares(&pool.active_shares, delegator)}
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 { pool_u64::shares(pending_inactive_shares_pool(pool), delegator)}
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, }; *smart_table::borrow_with_default(votes, key, &0)}
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 vector::append(&mut seed, MODULE_SALT); // include an additional salt in case the same resource account has already been created vector::append(&mut seed, delegation_pool_creation_seed); seed}
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, }; smart_table::borrow_mut_with_default(votes, key, 0)}
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 (!smart_table::contains(vote_delegation_table, delegator)) { return smart_table::borrow_mut_with_default(vote_delegation_table, delegator, VoteDelegation { voter: delegator, last_locked_until_secs: locked_until_secs, pending_voter: delegator, }) };
let vote_delegation = smart_table::borrow_mut(vote_delegation_table, 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}
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 (!smart_table::contains(delegated_votes_per_voter, voter)) { let active_shares = get_delegator_active_shares(pool, voter); let inactive_shares = get_delegator_pending_inactive_shares(pool, voter); return smart_table::borrow_mut_with_default(delegated_votes_per_voter, 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 = smart_table::borrow_mut(delegated_votes_per_voter, 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}
olc_with_index
fun olc_with_index(index: u64): delegation_pool::ObservedLockupCycle
Implementation
fun olc_with_index(index: u64): ObservedLockupCycle { ObservedLockupCycle { index }}
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 = pool_u64::shares_to_amount( &delegation_pool.active_shares, latest_delegated_votes.active_shares); let pending_inactive_amount = pool_u64::shares_to_amount( pending_inactive_shares_pool(delegation_pool), latest_delegated_votes.pending_inactive_shares); active_amount + pending_inactive_amount}
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}
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)}
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> acquires DelegationPoolAllowlisting { &mut borrow_global_mut<DelegationPoolAllowlisting>(pool_address).allowlist}
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);}
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, });}
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, });}
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}
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 = 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 = new_delegated_votes.active_shares_next_lockup + active_shares; };
if (features::module_event_migration_enabled()) { event::emit(DelegateVotingPower { pool_address, delegator: delegator_address, voter: new_voter, }) } else { event::emit_event(&mut governance_records.delegate_voting_power_events, DelegateVotingPowerEvent { pool_address, delegator: delegator_address, voter: new_voter, }); };}
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 });}
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 smart_table::destroy(allowlist);
event::emit(DisableDelegatorsAllowlisting { pool_address });}
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 };
smart_table::add(borrow_mut_delegators_allowlist(pool_address), delegator_address, true);
event::emit(AllowlistDelegator { pool_address, delegator_address });}
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 };
smart_table::remove(borrow_mut_delegators_allowlist(pool_address), delegator_address);
event::emit(RemoveDelegatorFromAllowlist { pool_address, delegator_address });}
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_u64::balance(&pool.active_shares, delegator_address));
event::emit(EvictDelegator { pool_address, delegator_address });}
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);
if (features::module_event_migration_enabled()) { event::emit( AddStake { pool_address, delegator_address, amount_added: amount, add_stake_fee, }, ); } else { event::emit_event( &mut pool.add_stake_events, AddStakeEvent { pool_address, delegator_address, amount_added: amount, add_stake_fee, }, ); };}
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);}
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);
if (features::module_event_migration_enabled()) { event::emit( UnlockStake { pool_address, delegator_address, amount_unlocked: amount, }, ); } else { event::emit_event( &mut pool.unlock_stake_events, UnlockStakeEvent { pool_address, delegator_address, amount_unlocked: amount, }, ); };}
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);
if (features::module_event_migration_enabled()) { event::emit( ReactivateStake { pool_address, delegator_address, amount_reactivated: amount, }, ); } else { event::emit_event( &mut pool.reactivate_stake_events, ReactivateStakeEvent { pool_address, delegator_address, amount_reactivated: amount, }, ); };}
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);}
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 = 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;
if (features::module_event_migration_enabled()) { event::emit( WithdrawStake { pool_address, delegator_address, amount_withdrawn: amount, }, ); } else { event::emit_event( &mut pool.withdraw_stake_events, WithdrawStakeEvent { pool_address, delegator_address, amount_withdrawn: amount, }, ); };}
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 (table::contains(&pool.pending_withdrawals, delegator_address)) { (true, *table::borrow(&pool.pending_withdrawals, delegator_address)) } else { (false, olc_with_index(0)) }}
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; table::borrow_mut(&mut pool.inactive_shares, observed_lockup_cycle)}
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 { table::borrow(&pool.inactive_shares, pool.observed_lockup_cycle)}
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); }}
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_u64::amount_to_shares(&pool.active_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_u64::buy_in(&mut pool.active_shares, shareholder, coins_amount); new_shares}
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 = pool_u64::amount_to_shares(pending_inactive_shares_pool(pool), 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 pool_u64::buy_in(pending_inactive_shares_pool_mut(pool), 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!(*table::borrow_mut_with_default( &mut pool.pending_withdrawals, shareholder, observed_lockup_cycle ) == observed_lockup_cycle, error::invalid_state(EPENDING_WITHDRAWAL_EXISTS) );
new_shares}
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 >= pool_u64::balance(shares_pool, shareholder)) { // cap result at total shares of shareholder to pass `EINSUFFICIENT_SHARES` on subsequent redeem pool_u64::shares(shares_pool, shareholder) } else { pool_u64::amount_to_shares(shares_pool, coins_amount) }}
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_u64::redeem_shares(&mut pool.active_shares, shareholder, shares_to_redeem)}
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( table::borrow(&pool.inactive_shares, 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 = table::borrow_mut(&mut pool.inactive_shares, lockup_cycle); // 1. reaching here means delegator owns inactive/pending_inactive shares at OLC `lockup_cycle` let redeemed_coins = pool_u64::redeem_shares(inactive_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 (pool_u64::shares(inactive_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 table::remove(&mut pool.pending_withdrawals, shareholder); }; // destroy inactive shares pool of past OLC if all its stake has been withdrawn if (lockup_cycle.index < pool.observed_lockup_cycle.index && total_coins(inactive_shares) == 0) { pool_u64::destroy_empty(table::remove(&mut pool.inactive_shares, lockup_cycle)); };
redeemed_coins}
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 = 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 = total_coins(&pool.active_shares); 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 = total_coins(pending_inactive_shares_pool(pool)); 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)}
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_u64::update_total_coins(&mut pool.active_shares, active - commission_active); // update total coins accumulated by `pending_inactive` shares at current observed lockup cycle pool_u64::update_total_coins( pending_inactive_shares_pool_mut(pool), 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 = pool.observed_lockup_cycle.index + 1; // start new lockup cycle with a fresh shares pool for `pending_inactive` stake table::add( &mut pool.inactive_shares, 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; }}
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 = smart_table::borrow_mut_with_default( &mut governance_records.votes_per_proposal, 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 = *proposal_used_voting_power + voting_power;}
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 = current_delegated_votes.active_shares + new_shares; if (pending_voter == current_voter) { current_delegated_votes.active_shares_next_lockup = 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 = pending_delegated_votes.active_shares_next_lockup + new_shares; };}
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 = current_delegated_votes.pending_inactive_shares + new_shares;}
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 = current_delegated_votes.active_shares - shares_to_redeem; if (current_voter == pending_voter) { current_delegated_votes.active_shares_next_lockup = 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 = pending_delegated_votes.active_shares_next_lockup - shares_to_redeem; };}
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 = current_delegated_votes.pending_inactive_shares - shares_to_redeem;}
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;
Enum DelegationPermission
enum DelegationPermission has copy, drop, store