This module provides a delegation service for staking on Aptos.
It:
builds on top of module,
consumes the owner_capability of a StakePool owner and uses it to delegate to the pool,
makes the liquid staking protocol just one of many delegators,
requires the owner to join with the minimum stake as specified by the aptos_framework, and cannot go below that amount until they self-destruct, and,
lets the pool owner can retrieve the owner_capability of their StakePool after a successful self-destruct.
use 0x1::aptos_coin;
use 0x1::coin;
use 0x1::error;
use 0x1::signer;
use 0x1::stake;
use 0xc0ded0c0::delegation_state;
use 0xc0ded0c0::stake_pool_helpers;
Resource ManagedStakePool
The struct which holds the OwnerCapability of the StakePool and stores config parameters.
struct ManagedStakePool has key
Fields
Struct ManageCapability
struct ManageCapability has store
Fields
managed_pool_address: address
Constants
The minimum stake that can be delegated to a managed stake pool.
When the maximum number of delegators are already staked.
const ETOO_MANY_DELEGATIONS: u64 = 9;
The maximum number of delegators that can delegate to a managed pool.
const MAX_NUMBER_OF_DELEGATIONS: u64 = 100;
Function assert_is_self_destructing
Assert the pool is in self-destruct mode
Abort conditions
If the managed stake pool is not in self-destruct state.
public fun assert_is_self_destructing(managed_pool_address: address)
Implementation
public fun assert_is_self_destructing(
managed_pool_address: address
) acquires ManagedStakePool {
let managed_stake_pool = borrow_global<ManagedStakePool>(
managed_pool_address
);
assert!(
managed_stake_pool.in_self_destruct,
error::invalid_state(ENOT_SELF_DESTRUCTING)
);
}
Function begin_self_destruct
Move the Managed Stake Pool into self-destruct mode. Cannot be reversed.
Restrictions
Abort conditions
If the pool does not exist.
public fun begin_self_destruct(manage_cap: &delegation_service::ManageCapability)
Implementation
public fun begin_self_destruct(
manage_cap: &ManageCapability,
) acquires ManagedStakePool {
let managed_pool_address = manage_cap.managed_pool_address;
assert_pool_exists(managed_pool_address);
let managed_stake_pool = borrow_global_mut<ManagedStakePool>(
managed_pool_address
);
managed_stake_pool.in_self_destruct = true;
}
Function finish_self_destruct
Destroys the ManageStakePool and returns the StakePool's OwnerCapability.
Restrictions
Abort conditions
If the pool is not in self-destruct state.
public fun finish_self_destruct(manage_cap: delegation_service::ManageCapability): stake::OwnerCapability
Implementation
public fun finish_self_destruct(
manage_cap: ManageCapability,
): OwnerCapability acquires ManagedStakePool {
let managed_pool_address = manage_cap.managed_pool_address;
assert_is_self_destructing(managed_pool_address);
let ManagedStakePool {
stake_pool_owner_cap: owner_cap,
in_self_destruct: _b,
min_delegation_amount: _c,
max_commission: _m,
} = move_from<ManagedStakePool>(managed_pool_address);
delegation_state::pay_commission_to_owner(managed_pool_address);
delegation_state::disperse_all_payouts(
managed_pool_address,
&owner_cap
);
delegation_state::self_destruct_internal(managed_pool_address);
let ManageCapability {
managed_pool_address: _mpa,
} = manage_cap;
owner_cap
}
Function pay_commission_to_owner
Compound ManagedStakePool's commission. Returns the amount of commission paid to the owner in APT.
Restrictions
public fun pay_commission_to_owner(manage_cap: &delegation_service::ManageCapability): u64
Implementation
public fun pay_commission_to_owner(
manage_cap: &ManageCapability,
): u64 {
assert_pool_exists(manage_cap.managed_pool_address);
delegation_state::pay_commission_to_owner(
manage_cap.managed_pool_address
)
}
Function initialize
Restrictions
To be associated with Tortuga protocol, this function should be called from validator_router module.
Abort conditions
protocol_commission must be lower than max_commission.
public fun initialize(managed_pool_owner: &signer, max_commission: u64, commission_recipient_address: address, protocol_delegator_address: address, protocol_commission: u64): delegation_service::ManageCapability
Implementation
public fun initialize(
managed_pool_owner: &signer,
max_commission: u64,
commission_recipient_address: address,
protocol_delegator_address: address,
protocol_commission: u64,
): ManageCapability acquires ManagedStakePool {
let managed_pool_address = signer::address_of(managed_pool_owner);
assert!(
!exists<ManagedStakePool>(managed_pool_address),
error::invalid_argument(EMANAGED_POOL_ALREADY_EXISTS)
);
// extract `owner_capability` from the pool_owner
let stake_pool_owner_cap = stake::extract_owner_cap(managed_pool_owner);
let stake_pool_address = stake::get_owned_pool_address(
&stake_pool_owner_cap
);
// store the ManagedStakePool
let managed_stake_pool = ManagedStakePool {
stake_pool_owner_cap: stake_pool_owner_cap,
in_self_destruct: false,
min_delegation_amount: DEFAULT_MINIMUM_DELEGATION_AMOUNT,
max_commission: max_commission,
};
move_to(managed_pool_owner, managed_stake_pool);
delegation_state::initialize_internal(
managed_pool_owner,
protocol_delegator_address,
commission_recipient_address,
stake_pool_address
);
change_commission(
managed_pool_owner,
max_commission,
protocol_commission
);
ManageCapability {
managed_pool_address: managed_pool_address,
}
}
Function set_min_delegation_amount
Set minimum acceptable delegations from outside delegators. Does not affect pool owner and the protocol.
Restrictions
Can only be called by the pool_owner.
public fun set_min_delegation_amount(pool_owner: &signer, value: u64)
Implementation
public entry fun set_min_delegation_amount(
pool_owner: &signer,
value: u64
) acquires ManagedStakePool {
let managed_pool_address = signer::address_of(pool_owner);
let managed_stake_pool = borrow_global_mut<ManagedStakePool>(
managed_pool_address
);
managed_stake_pool.min_delegation_amount = value;
}
Function delegate
Abort conditions
If the delegation amount is less than min_delegation_amount.
public fun delegate(delegator: &signer, managed_pool_address: address, amount: u64)
Implementation
public entry fun delegate(
delegator: &signer,
managed_pool_address: address,
amount: u64,
) acquires ManagedStakePool {
assert_not_self_destructing(managed_pool_address);
let delegator_address = signer::address_of(delegator);
certify_delegation(managed_pool_address, delegator_address, amount);
let coins_to_delegate = coin::withdraw<AptosCoin>(delegator, amount);
let managed_stake_pool = borrow_global<ManagedStakePool>(
managed_pool_address
);
// make sure that newly minted shares for the delegator are not subject
// to any commissions by paying outstanding commissions first
delegation_state::pay_commission_to_owner(managed_pool_address);
delegation_state::disperse_all_payouts(
managed_pool_address,
&managed_stake_pool.stake_pool_owner_cap
);
delegation_state::delegate_internal(
delegator,
coins_to_delegate,
managed_pool_address,
&managed_stake_pool.stake_pool_owner_cap
);
}
Function change_commission
Change the default and protocol commissions.
Restrictions
Only the pool_owner can call this function.
Abort condition
If the new_default_commission is greater than max_commission.
If the new_protocol_commission is greater than new_default_commission.
public fun change_commission(pool_owner: &signer, new_default_commission: u64, new_protocol_commission: u64)
Implementation
public entry fun change_commission(
pool_owner: &signer,
new_default_commission: u64,
new_protocol_commission: u64,
) acquires ManagedStakePool {
let managed_pool_address = signer::address_of(pool_owner);
assert_pool_exists(managed_pool_address);
let managed_stake_pool = borrow_global_mut<ManagedStakePool>(
managed_pool_address
);
assert!(
new_default_commission <= managed_stake_pool.max_commission,
error::invalid_argument(ECOMMISSION_EXCEEDS_MAX)
);
assert!(
new_protocol_commission <= new_default_commission,
error::invalid_argument(EPROTOCOL_COMMISSION_EXCEEDS_DEFAULT)
);
// pay outstanding commission on original commission rate
delegation_state::pay_commission_to_owner(managed_pool_address);
delegation_state::change_commission_internal(
pool_owner,
new_default_commission,
new_protocol_commission,
);
}
Function change_commission_recipient
Restrictions
Only the pool_owner can call this function.
Abort conditions
public fun change_commission_recipient(pool_owner: &signer, new_commission_recipient_address: address)
Implementation
public entry fun change_commission_recipient(
pool_owner: &signer,
new_commission_recipient_address: address,
) {
let managed_pool_address = signer::address_of(pool_owner);
assert_pool_exists(managed_pool_address);
// pay outstanding commission to the old recipient
delegation_state::pay_commission_to_owner(managed_pool_address);
delegation_state::change_commission_recipient_internal(
managed_pool_address,
new_commission_recipient_address,
);
}
Function reserve_shares_for_withdraw
Restrictions
Only the delegator can call this function for themselves.
Abort conditions
If the withdrawal amount will leave less than min_delegation_amount in the delegator's account. However, it allows all the funds to be atomically withdrawn.
public fun reserve_shares_for_withdraw(delegator: &signer, managed_pool_address: address, num_shares: u64)
Implementation
public entry fun reserve_shares_for_withdraw(
delegator: &signer,
managed_pool_address: address,
num_shares: u64,
) acquires ManagedStakePool {
assert_pool_exists(managed_pool_address);
let delegator_address = signer::address_of(delegator);
// Check for min violation
ensure_minimum_delegation_remaining(
managed_pool_address,
delegator_address,
num_shares
);
let managed_stake_pool = borrow_global<ManagedStakePool>(
managed_pool_address
);
// make sure there is no outstanding commission in either pool before
// transferring
delegation_state::pay_commission_to_owner(managed_pool_address);
delegation_state::disperse_all_payouts(
managed_pool_address,
&managed_stake_pool.stake_pool_owner_cap
);
delegation_state::reserve_shares_for_withdraw_internal(
delegator_address,
num_shares,
managed_pool_address,
&managed_stake_pool.stake_pool_owner_cap
);
}
Function trigger_payout_dispersal
Disperses all APT if there is inactive stake in the StakePool. Can be called permissionlessly.
Abort conditions
public fun trigger_payout_dispersal(managed_pool_address: address)
Implementation
public entry fun trigger_payout_dispersal(
managed_pool_address: address
) acquires ManagedStakePool {
assert_pool_exists(managed_pool_address);
let stake_pool_address = delegation_state::get_stake_pool_address(
managed_pool_address
);
// nothing to disperse
if (
stake_pool_helpers::get_stake_pool_total_withdrawable_amount(
stake_pool_address
) == 0
) {
return
};
// first pay commission to the owner
delegation_state::pay_commission_to_owner(managed_pool_address);
let managed_stake_pool = borrow_global<ManagedStakePool>(
managed_pool_address
);
delegation_state::disperse_all_payouts(
managed_pool_address,
&managed_stake_pool.stake_pool_owner_cap
);
}
Function remove_non_owner_delegator
Reserve all shares for withdrawal for delegator_address. Can be called by anyone but only when pool is in self-destruct state. This ensures the owner can remove all delegators and retrieve their owner_capability.
Abort conditions
If the delegator_address is managed_pool_address itself.
public fun remove_non_owner_delegator(managed_pool_address: address, delegator_address: address)
public fun set_operator(pool_owner: &signer, new_operator: address)
Implementation
public entry fun set_operator(
pool_owner: &signer,
new_operator: address
) acquires ManagedStakePool {
let managed_pool_address = signer::address_of(pool_owner);
assert_pool_exists(managed_pool_address);
let managed_stake_pool = borrow_global<ManagedStakePool>(
managed_pool_address
);
stake::set_operator_with_cap(
&managed_stake_pool.stake_pool_owner_cap,
new_operator
);
}
Function set_delegated_voter
public fun set_delegated_voter(pool_owner: &signer, new_voter: address)
Implementation
public entry fun set_delegated_voter(
pool_owner: &signer,
new_voter: address
) acquires ManagedStakePool {
let managed_pool_address = signer::address_of(pool_owner);
assert_pool_exists(managed_pool_address);
let managed_stake_pool = borrow_global<ManagedStakePool>(
managed_pool_address
);
stake::set_delegated_voter_with_cap(
&managed_stake_pool.stake_pool_owner_cap,
new_voter
);
}
Function assert_not_self_destructing
Assert the pool is not in self-destruct mode
Abort conditions
If the pool is in self-destruct mode.
fun assert_not_self_destructing(managed_pool_address: address)
Implementation
fun assert_not_self_destructing(
managed_pool_address: address
) acquires ManagedStakePool {
let managed_stake_pool = borrow_global<ManagedStakePool>(
managed_pool_address
);
assert!(
!managed_stake_pool.in_self_destruct,
error::invalid_state(ESELF_DESTRUCTING)
);
}
Function assert_pool_exists
Abort conditions
fun assert_pool_exists(managed_pool_address: address)
Implementation
fun assert_pool_exists(managed_pool_address: address) {
assert!(
exists<ManagedStakePool>(managed_pool_address),
error::invalid_argument(EPOOL_DOES_NOT_EXIST)
);
}
Function certify_delegation
Certifies that a delegation with amount from delegator_address can be made. Aborts if criteria are not met. The protocol_delegator and the pool_owner, and existing delegators are not subject to these conditions.
Abort conditions
If the delegation amount is less than min_delegation_amount.
fun certify_delegation(managed_pool_address: address, delegator_address: address, amount: u64)
Implementation
fun certify_delegation(
managed_pool_address: address,
delegator_address: address,
amount: u64
) acquires ManagedStakePool {
if (delegator_address == managed_pool_address) {
return
};
if (
delegator_address ==
delegation_state::get_protocol_delegator_address(
managed_pool_address
)
) {
return
};
let managed_stake_pool = borrow_global<ManagedStakePool>(
managed_pool_address
);
let is_current_delegator = delegation_state::is_an_active_delegator(
managed_pool_address,
delegator_address
);
if (is_current_delegator) {
return
};
assert!(
amount >= managed_stake_pool.min_delegation_amount,
error::invalid_argument(EDELEGATION_AMOUNT_TOO_SMALL)
);
let current_length = delegation_state::get_total_length_of_share_maps(
managed_pool_address
);
assert!(
// this actually allows (MAX_NUMBER_OF_DELEGATIONS + 1) delegators,
// including the owner
current_length <= MAX_NUMBER_OF_DELEGATIONS,
error::out_of_range(ETOO_MANY_DELEGATIONS)
);
}
Function ensure_minimum_delegation_remaining
Ensures withdrawal does not change the delegator's stake below the minimum, unless a non-stake-pool-owner delegator is withdrawing all of their stake.
Abort conditions
If the non-owner delegator's stake would be non-zero but below the minimum after the withdrawal.
If the owner delegator's stake would be below the min_stake required by the aptos_framework after the withdrawal.
fun ensure_minimum_delegation_remaining(managed_pool_address: address, delegator_address: address, num_shares_to_withdraw: u64)
Implementation
fun ensure_minimum_delegation_remaining(
managed_pool_address: address,
delegator_address: address,
num_shares_to_withdraw: u64
) acquires ManagedStakePool {
// protocol is not subject to minimums
if (
delegator_address ==
delegation_state::get_protocol_delegator_address(
managed_pool_address
)
) {
return
};
// only called from `reserve_shares_to_withdraw`, pool existence is
// ensured
let managed_stake_pool = borrow_global<ManagedStakePool>(
managed_pool_address
);
let current_unreserved_shares =
delegation_state::get_unreserved_shares_with_delegator(
managed_pool_address,
delegator_address
);
if (
delegator_address != managed_pool_address &&
current_unreserved_shares == num_shares_to_withdraw
) {
// Non-owner delegator can always withdraw all of their stake
return
};
let remaining_balance_after_withdrawal =
delegation_state::get_shares_to_value_unreserved(
managed_pool_address,
current_unreserved_shares - num_shares_to_withdraw
);
let minimum = if (delegator_address == managed_pool_address) {
stake_pool_helpers::get_framework_min_stake()
} else {
managed_stake_pool.min_delegation_amount
};
assert!(
remaining_balance_after_withdrawal >= minimum ||
managed_stake_pool.in_self_destruct,
error::invalid_argument(EMINIMUM_VIOLATION)
);
}
stake_pool_owner_cap: stores the OwnerCapability of the StakePool owner.in_self_destruct: boolstores whether self-destruct has been called by the owner. The ManagedStakingPool does not accept new delegations in self-destruct mode.min_delegation_amount: u64max_commission: u64
Holders of can call permissioned functions. It is returned during initialization.
Can only be called using of the managed pool.
Can only be called using of the managed pool.
Only available to the holder of .
Allows a StakePool owner managed_pool_owner to start a that can accept delegations from outside delegators. The maximum commission that can be charged is given by max_commission. The address of the Tortuga protocol delegator is given by protocol_delegator_address and the commission charged to the protocol delegator is given by protocol_commission.
Returns the of the newly created .
Note: Commissions are specified in 6 decimal precision, following the value of COMMISSION_NORMALIZER in module (i.e. commission of 1000000 = 100%).
If the already exists on the given address.
Lets a delegator to delegate amount APT with a at address managed_pool_address.
If the is in self-destruct state.
If the pool already has delegations.
If the does not exist.
Change the commission recipient of the to new_commission_recipient_address.
If the does not exist.
delegator can call this function to withdraw num_shares from the at address managed_pool_address. This will move delegator shares from unreserved_pool to reserved_pool and move corresponding StakePool funds from 'active' to 'pending_inactive' state.. May have to call trigger_payout_dispersal when the stake unlocks (i.e., changes state from 'pending_inactive' to 'inactive').
If the does not exist.
If the does not exist.
If the is not in self_destruct state.
Allow the stake_pool owner to set new_operator as the operator of the pool. See for details.
Allow the stake_pool owner to set new_voter as the delegated voter of the pool. See for details.
Assert the has been initialized at the provided address.