delegation::delegation_service
This module provides a delegation service for staking on Aptos.
It:
- 1.
- 2.consumes the
owner_capability
of aStakePool
owner and uses it to delegate to the pool, - 3.makes the liquid staking protocol just one of many delegators,
- 4.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, - 5.lets the pool owner can retrieve the
owner_capability
of theirStakePool
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;
The struct which holds the
OwnerCapability
of the StakePool
and stores config parameters.struct ManagedStakePool has key
stake_pool_owner_cap:
stake::OwnerCapability
stores the OwnerCapability
of the StakePool owner.in_self_destruct: bool
stores 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
struct ManageCapability has store
The minimum stake that can be delegated to a managed stake pool.
const DEFAULT_MINIMUM_DELEGATION_AMOUNT: u64 = 10000000000000;
When trying to remove the stake pool owner's delegation during self-destruct.
const ECANNOT_FORCE_OWNER_TO_WITHDRAW: u64 = 12;
When the commission being set is higher than the
max_commission
.const ECOMMISSION_EXCEEDS_MAX: u64 = 2;
When the delegation amount is below the threshold.
const EDELEGATION_AMOUNT_TOO_SMALL: u64 = 10;
When the managed stake pool already exists at the address during initialization.
const EMANAGED_POOL_ALREADY_EXISTS: u64 = 5;
When the stake of the owner is below the minimum stake, and the pool is not in self-destruct.
const EMINIMUM_VIOLATION: u64 = 7;
When the the pool is not in self-destruct state.
const ENOT_SELF_DESTRUCTING: u64 = 8;
When the managed stake pool does not exist.
const EPOOL_DOES_NOT_EXIST: u64 = 11;
When the commission being charged for the protocol delegator is higher than the commission being charged to outside delegators.
const EPROTOCOL_COMMISSION_EXCEEDS_DEFAULT: u64 = 3;
When the pool is in the self-destruct state.
const ESELF_DESTRUCTING: u64 = 1;
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;
Assert the pool is in self-destruct mode
- If the managed stake pool is not in self-destruct state.
public fun assert_is_self_destructing(managed_pool_address: address)
Move the Managed Stake Pool into self-destruct mode. Cannot be reversed.
- If the pool does not exist.
public fun begin_self_destruct(manage_cap: &delegation_service::ManageCapability)
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;
}
Destroys the
ManageStakePool
and returns the StakePool's OwnerCapability
.- If the pool is not in self-destruct state.
public fun finish_self_destruct(manage_cap: delegation_service::ManageCapability): stake::OwnerCapability
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
}
Compound ManagedStakePool's commission. Returns the amount of commission paid to the owner in APT.
public fun pay_commission_to_owner(manage_cap: &delegation_service::ManageCapability): u64
Allows a
StakePool
owner managed_pool_owner
to start a ManagedStakePool
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
.Note: Commissions are specified in 6 decimal precision, following the value of
COMMISSION_NORMALIZER
in delegation_state
module (i.e. commission of 1000000
= 100%
).- To be associated with Tortuga protocol, this function should be called from
validator_router
module.
protocol_commission
must be lower thanmax_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
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,
}
}
Set minimum acceptable delegations from outside delegators. Does not affect pool owner and the protocol.
- Can only be called by the
pool_owner
.
public fun set_min_delegation_amount(pool_owner: &signer, value: u64)
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;
}
- If the delegation
amount
is less thanmin_delegation_amount
.
public fun delegate(delegator: &signer, managed_pool_address: address, amount: u64)
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
);
}
Change the default and protocol commissions.
- Only the
pool_owner
can call this function.
- If the
new_default_commission
is greater thanmax_commission
. - If the
new_protocol_commission
is greater thannew_default_commission
.
public fun change_commission(pool_owner: &signer, new_default_commission: u64, new_protocol_commission: u64)
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,
);
}
- Only the
pool_owner
can call this function.
public fun change_commission_recipient(pool_owner: &signer, new_commission_recipient_address: address)
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,
);
}
delegator
can call this function to withdraw num_shares
from the ManagedStakePool
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').- Only the
delegator
can call this function for themselves.
- If the withdrawal amount will leave less than
min_delegation_amount
in thedelegator
'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)
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
);
}
Disperses all APT if there is
inactive
stake in the StakePool
. Can be called permissionlessly.public fun trigger_payout_dispersal(managed_pool_address: address)
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
);
}
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
.- If the
delegator_address
ismanaged_pool_address
itself.
public fun remove_non_owner_delegator(managed_pool_address: address, delegator_address: address)
public entry fun remove_non_owner_delegator(
managed_pool_address: address,
delegator_address: address,
) acquires ManagedStakePool {
assert_pool_exists(managed_pool_address);
assert_is_self_destructing(managed_pool_address);
assert!(
delegator_address != managed_pool_address,
error::invalid_argument(ECANNOT_FORCE_OWNER_TO_WITHDRAW),
);
let managed_stake_pool = borrow_global<ManagedStakePool>(managed_pool_address);
delegation_state::pay_commission_to_owner(managed_pool_address);
delegation_state::disperse_all_payouts(
managed_pool_address,
&managed_stake_pool.stake_pool_owner_cap
);
let delegator_unreserved_shares =
delegation_state::get_unreserved_shares_with_delegator(
managed_pool_address,
delegator_address
);
delegation_state::reserve_shares_for_withdraw_internal(
delegator_address,
delegator_unreserved_shares,
managed_pool_address,
&managed_stake_pool.stake_pool_owner_cap,
);
}
Allow the
stake_pool
owner to set new_operator
as the operator of the pool. See 0x1::stake::set_operator_with_cap
for details.public fun set_operator(pool_owner: &signer, new_operator: address)
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
);
}
Allow the
stake_pool
owner to set new_voter
as the delegated voter of the pool. See 0x1::stake::set_delegated_voter_with_cap
for details.public fun set_delegated_voter(pool_owner: &signer, new_voter: address)
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
);
}
Assert the pool is not in self-destruct mode
- If the pool is in self-destruct mode.
fun assert_not_self_destructing(managed_pool_address: address)
fun assert_pool_exists(managed_pool_address: address)
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.- If the delegation
amount
is less thanmin_delegation_amount
.
fun certify_delegation(managed_pool_address: address, delegator_address: address, amount: u64)
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)
);
}
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.
- 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 theaptos_framework
after the withdrawal.
fun ensure_minimum_delegation_remaining(managed_pool_address: address, delegator_address: address, num_shares_to_withdraw: u64)
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)
);
}
Last modified 7mo ago