This module handles interaction between the liquid staking protocol and the validators. It is responsible for:
delegations and withdrawals from validators,
validator signup and removal.
The delegators to the protocol will interact usually with tortuga::stake_router module.
This module uses oracle::validator_states module, which keeps track of changes in the states of the validators and calculates their performance.
It provides an endpoint for validators to be able to charge commissions. Charging commission often will compound the commissions, and will also keep update the state of this contract.
It also manages the AptosCoinReserve for the protocol, which holds the APT that the delegators have requested for withdrawal (but not claimed yet) or deposited to the protocol.
Validators will receive delegations from the protocol after they call validator_signup. They also will be able to receive outside delegations.
This module only manages the stake that the protocol has with the validators.
use 0x1::account;
use 0x1::aptos_coin;
use 0x1::bcs;
use 0x1::coin;
use 0x1::error;
use 0x1::event;
use 0x1::option;
use 0x1::signer;
use 0x1::simple_map;
use 0x1::stake;
use 0x1::timestamp;
use 0xc0ded0c0::delegation_service;
use 0xc0ded0c0::delegation_state;
use 0xc0ded0c0::iterable_table_custom;
use 0xc0ded0c0::stake_pool_helpers;
use 0xc0ded0c0::validator_states;
use 0xc0ded0c1::permissions;
use 0xc0ded0c2::tortuga_governance;
Resource ValidatorContract
This struct is used to allow permissioned validator signup.
Withdrawal to distribute claims should be from earliest validator to maximize the liquidity of the protocol. This struct helps with that mechanism as follows:
protocol sets a withdraw signature which is valid as long as timestamp::now_seconds() < unlocking_at and managed_pool_address is still a validator in our set.
In case of a deficit, anyone can then request a withdraw for any validator as long as that validator's unlocking_at is not later than the one in withdraw signature.
If the withdraw signature has expired (i.e. the managed_pool_address has either left the validator set of the liquid staking contract or has unlocked already), then a withdrawal can be requested from any validator.
This strikes a balance between keeping the protocol liquid and not allowing early withdraw their stake before they are supposed to.
struct WithdrawSignature has drop, key
Fields
managed_pool_address: addressAddress where the ManagedStakePool for the validator is stored.unlocking_at: u64The timestamp when the validator's stake will be unlocked.
Resource AptosCoinReserve
This reserve store coins which can be either delegated to validators, or withdrawn from them to clear Tickets in stake_router module.
Delegation account for a validator, stores the address and capabilities.
struct DelegationAccount has store
Fields
managed_pool_address: addressAddress where the ManagedStakePool for the validator is stored.signer_cap: account::SignerCapabilityThe SignerCapability for the resource account that delegates to the validator.delegation_service_manage_cap: delegation_service::ManageCapabilityThe ManageCapability for the validator's ManagedStakePool.
Struct ValidatorSignupEvent
Event generated when a validator successfully signs up.
struct ValidatorSignupEvent has drop, store
Fields
managed_pool_address: addressAddress where the ManagedStakePool for the validator is stored.
Struct ValidatorOffboardEvent
Event generated when a validator is off-boarded.
struct ValidatorOffboardEvent has drop, store
Fields
managed_pool_address: addressAddress where the ManagedStakePool for the validator is stored.
Struct PayCommissionEvent
Event generated when a validator is paid commission.
struct PayCommissionEvent has drop, store
Fields
managed_pool_address: addressAddress where the ManagedStakePool for the validator is stored.commission: u64Commission paid in APT.
Struct WithdrawToReserveEvent
Event generated when a validator withdraws to the reserve.
struct WithdrawToReserveEvent has drop, store
Fields
managed_pool_address: addressAddress where the ManagedStakePool for the validator is stored.amount: u64Amount withdrawn in APT.
managed_pool_address: addressAddress of the validator unlocking at unlocking_at.unlocking_at: u64The timestamp when the validator's stake will be unlocked.
Constants
Validator state active.
const VALIDATOR_STATUS_ACTIVE: u64 = 2;
Validator state inactive.
const VALIDATOR_STATUS_INACTIVE: u64 = 4;
The following constants must match those of stake.move Validator state pending_active.
const VALIDATOR_STATUS_PENDING_ACTIVE: u64 = 1;
Validator state pending_inactive.
const VALIDATOR_STATUS_PENDING_INACTIVE: u64 = 3;
When an unauthorized action is performed.
const EPERMISSION_DENIED: u64 = 3;
The maximum minimum transaction amount
const DEFAULT_MAX_MAX_COMMISSION: u64 = 1000000;
When a validator is already associated with the protocol.
const EALREADY_AN_ASSOCIATED_VALIDATOR: u64 = 10;
When a validator tries to rejoin without claiming the owner capability first.
When the protocol has a withdrawable amount with a validator.
const ENONZERO_WITHDRAWABLE_AMOUNT: u64 = 9;
When the module is not initialized.
const ENOT_INITIALIZED: u64 = 11;
When there is no OwnerCapability to claim with the protocol.
const ENO_OWNER_CAP_TO_CLAIM: u64 = 12;
When self-signup is not allowed.
const ESELF_SIGNUP_NOT_ALLOWED: u64 = 15;
When more than max_number_of_validators validators are already associated with the protocol.
const ETOO_MANY_VALIDATORS: u64 = 8;
When the validator is not associated with the protocol.
const EVALIDATOR_NOT_FOUND: u64 = 2;
When an unlock is requested from a validator but is not allowed by the current WithdrawSignature.
const EVALIDATOR_UNLOCK_IS_TOO_FAR: u64 = 14;
Function initialize
Initializes the module with a given max_number_of_validators. It is initialized in the module tortuga::stake_router.
public(friend) fun initialize(tortuga_governance_deployer: &signer, max_number_of_validators: u64)
Implementation
public(friend) fun initialize(
tortuga_governance_deployer: &signer,
max_number_of_validators: u64,
) {
move_to(tortuga_governance_deployer, AptosCoinReserve { coin: coin::zero<AptosCoin>() });
let validator_signup_events =
account::new_event_handle<ValidatorSignupEvent>(tortuga_governance_deployer);
let validator_offboard_events =
account::new_event_handle<ValidatorOffboardEvent>(tortuga_governance_deployer);
let withdraw_signature_set_events =
account::new_event_handle<WithdrawSignatureSetEvent>(tortuga_governance_deployer);
let cap = validator_states::initialize_validator_states(tortuga_governance_deployer);
move_to(tortuga_governance_deployer, Status {
vetted_validators: simple_map::create<address, ValidatorContract>(),
allow_self_signup: false,
max_number_of_validators: max_number_of_validators,
max_max_commission: DEFAULT_MAX_MAX_COMMISSION,
unclaimed_stake_pool_owner_caps:
iterable_table_custom::new<address, OwnerCapability>(),
validator_signup_events,
validator_offboard_events,
withdraw_signature_set_events,
validate_states_update_cap: cap,
});
move_to(tortuga_governance_deployer, DelegationAccounts {
accounts: iterable_table_custom::new<address, DelegationAccount>(),
pay_commission_events:
account::new_event_handle<PayCommissionEvent>(tortuga_governance_deployer),
withdraw_to_reserve_events:
account::new_event_handle<WithdrawToReserveEvent>(tortuga_governance_deployer),
});
// Set a dummy withdraw signature to start with
move_to(tortuga_governance_deployer, WithdrawSignature {
managed_pool_address: @aptos_framework,
unlocking_at: 0
});
}
Function assert_no_withdrawable_amount
Ensures that the protocol does not have any currently withdrawable amount in the validator at managed_pool_address.
Abort conditions
If is_withdrawable_amount_nonzero() returns true.
public fun assert_no_withdrawable_amount(managed_pool_address: address)
Implementation
public fun assert_no_withdrawable_amount(
managed_pool_address: address
) acquires DelegationAccounts {
assert!(
!is_withdrawable_amount_positive(managed_pool_address),
error::invalid_state(ENONZERO_WITHDRAWABLE_AMOUNT)
);
}
Function get_total_balance
Returns the total balance in APT staked with the validators and in the AptosCoinReserve. This may will only be 100% accurate if the oracle was updated in the current epoch.
public fun get_total_balance(): u64
Implementation
public fun get_total_balance(): u64 acquires AptosCoinReserve {
get_reserve_balance() +
validator_states::get_total_balance_with_validators(
@tortuga_governance
)
}
public fun set_allow_self_signup(tortuga_governance: &signer, value: bool)
Implementation
public entry fun set_allow_self_signup(
tortuga_governance: &signer,
value: bool
) acquires Status {
permissions::assert_authority<TortugaGovernance>(tortuga_governance);
let signup_status = borrow_global_mut<Status>(@tortuga_governance);
signup_status.allow_self_signup = value;
}
Function set_withdraw_signature
Set the WithdrawSigner for the protocol to the validator managed_pool_address such that it expires when managed_pool_address unlocks or if managed_pool_address is unassociated with the protocol.
It allows permissionless unstaking while still maintaining liquidity.
Function set_validator_state_permissioned_score_for_validator
Set the score for validator at managed_pool_address to value. It would not affect target_delegations for the validator unless oracle::validator_states::StatsConfig.allow_permissionless_scoring is set to false.
See oracle::validator_states module for details of the scoring mechanisms.
public fun vet_a_validator(tortuga_governance: &signer, potential_managed_pool_owner: address)
Implementation
public entry fun vet_a_validator(
tortuga_governance: &signer,
potential_managed_pool_owner: address,
) acquires Status {
permissions::assert_authority<TortugaGovernance>(tortuga_governance);
assert_initialized();
let signup_status = borrow_global_mut<Status>(@tortuga_governance);
let vetted_validators = &mut signup_status.vetted_validators;
assert!(
!simple_map::contains_key(
vetted_validators,
&potential_managed_pool_owner
),
error::already_exists(ECONTRACT_ALREADY_ISSUED)
);
simple_map::add(
vetted_validators,
potential_managed_pool_owner,
ValidatorContract {}
);
}
Function validator_begin_leaving_force
Forcibly remove a validator from the protocol.
The protocol may have to remove non-owner delegators via delegation::delegation_service module after calling this function, and before calling validator_offboard_force.
public fun validator_begin_leaving_force(tortuga_governance: &signer, managed_pool_address: address)
Implementation
public entry fun validator_begin_leaving_force(
tortuga_governance: &signer,
managed_pool_address: address
) acquires DelegationAccounts {
permissions::assert_authority<TortugaGovernance>(tortuga_governance);
validator_begin_leaving_protocol_internal(managed_pool_address);
}
Function validator_offboard_force
This function is called to forcible remove the validator at managed_pool_address. This can only be called after the validator has begin leaving, i.e., the ManagedStakePool is in self-destruct state.
This function call is permissionless.
Abort conditions
If the validator has non-zero balance.
public fun validator_offboard_force(managed_pool_address: address)
Implementation
public entry fun validator_offboard_force(
managed_pool_address: address,
) acquires DelegationAccounts, Status {
let stake_pool_owner_cap =
validator_offboard_internal(managed_pool_address);
let status = borrow_global_mut<Status>(@tortuga_governance);
iterable_table_custom::add(
&mut status.unclaimed_stake_pool_owner_caps,
managed_pool_address,
stake_pool_owner_cap
);
}
Function withdraw_to_reserve
Request withdrawal of the amount in Payout for the validator who is the owner of managed_pool_address.
The function call is permissionless.
Abort conditions
If the validator is not in the protocol.
public fun withdraw_to_reserve(managed_pool_address: address)
Implementation
public entry fun withdraw_to_reserve(
managed_pool_address: address
) acquires AptosCoinReserve, DelegationAccounts, Status {
assert!(
is_an_associated_validator(managed_pool_address),
error::not_found(EVALIDATOR_NOT_FOUND)
);
if (!is_withdrawable_amount_positive(managed_pool_address)) {
return
};
// Before modifying the state we pay commission to the validator,
// so that our calculation is as fresh as possible.
pay_commission_to_validator_internal(managed_pool_address);
// Try to withdraw coins
delegation_service::trigger_payout_dispersal(managed_pool_address);
// Extract the coins
let delegation_account_signer = get_delegation_account_signer(
managed_pool_address
);
let extracted_coins = delegation_state::extract_payout(
&delegation_account_signer
);
let withdrawn_amount = coin::value<AptosCoin>(&extracted_coins);
// send the money to the reserve
deposit_coins_to_reserve(extracted_coins);
// modify the state of the validator
update_validator_states(managed_pool_address, 0, withdrawn_amount, 0);
let delegation_accounts = borrow_global_mut<DelegationAccounts>(
@tortuga_governance
);
event::emit_event<WithdrawToReserveEvent>(
&mut delegation_accounts.withdraw_to_reserve_events,
WithdrawToReserveEvent {
managed_pool_address,
amount: withdrawn_amount,
}
);
}
Function pay_commission_to_the_validator
Pays commission to the validator at managed_pool_address.
Validator can call this function to make sure that their commission is compounded.
This function is permissionless.
Abort conditions
If the validator at address managed_pool_address is not an associated validator.
public fun pay_commission_to_the_validator(managed_pool_address: address)
Implementation
public entry fun pay_commission_to_the_validator(
managed_pool_address: address
) acquires DelegationAccounts, Status {
assert!(
is_an_associated_validator(managed_pool_address),
error::not_found(EVALIDATOR_NOT_FOUND)
);
// This assert keeps a validator's score to dip if the balance with them
// is all inactive.
// The validator's can still get their commission by calling
// `withdraw_to_reserve`, if this assert fails.
assert_no_withdrawable_amount(managed_pool_address);
let owner_commissions = pay_commission_to_validator_internal(
managed_pool_address
);
// modify states
update_validator_states(managed_pool_address, 0, 0, 0);
let delegation_accounts = borrow_global_mut<DelegationAccounts>(
@tortuga_governance
);
event::emit_event<PayCommissionEvent>(
&mut delegation_accounts.pay_commission_events,
PayCommissionEvent {
managed_pool_address,
commission: owner_commissions
}
);
}
Function validator_signup
A validator can sign up to become an associated validator. max_commission is the commission that validator charges outside delegators. protocol_commission is the commission that validator charges the protocol. commission_recipient_address is the address where the APT generated by the validator are sent.
Both commissions are provided with 6 decimal precision, i.e., 1000000 is 100%.
Abort conditions
If the validator is already in the protocol.
If the validator has not claimed their owner_cap after leaving the protocol previously.
If the validator's state is not ACTIVE.
If self-signup is not allowed.
If the maximum number of validators has already been reached.
public fun validator_signup(pool_owner: &signer, commission_recipient_address: address, max_commission: u64, protocol_commission: u64)
Implementation
public entry fun validator_signup(
pool_owner: &signer,
commission_recipient_address: address,
max_commission: u64,
protocol_commission: u64
) acquires Status, DelegationAccounts {
assert_initialized();
let new_pool_owner_address = signer::address_of(pool_owner);
let status = borrow_global_mut<Status>(@tortuga_governance);
assert!(
!delegation_account_exists(new_pool_owner_address),
error::already_exists(EALREADY_AN_ASSOCIATED_VALIDATOR)
);
assert!(
!iterable_table_custom::contains(
&status.unclaimed_stake_pool_owner_caps,
new_pool_owner_address
),
error::unauthenticated(ECANNOT_REJOIN_WITH_UNCLAIMED_OWNER_CAP),
);
assert!(
max_commission <= status.max_max_commission,
error::invalid_argument(EMAX_COMMISSION_TOO_HIGH),
);
let current_number_of_validators = get_current_number_of_validators();
// check if the pool owner has special permissions
let vetted_validators = &mut status.vetted_validators;
if (
simple_map::contains_key(
vetted_validators,
&new_pool_owner_address
)
) {
let (_k, ValidatorContract {} ) =
simple_map::remove<address, ValidatorContract>(
vetted_validators,
&new_pool_owner_address
);
}
else {
assert!(
current_number_of_validators < status.max_number_of_validators,
error::invalid_state(ETOO_MANY_VALIDATORS)
);
assert!(
status.allow_self_signup,
error::unauthenticated(ESELF_SIGNUP_NOT_ALLOWED)
);
};
let delegation_accounts = borrow_global_mut<DelegationAccounts>(
@tortuga_governance
);
let (
delegation_signer,
delegation_signer_cap
) = create_delegation_account(pool_owner);
let manage_cap = delegation_service::initialize(
pool_owner,
max_commission,
commission_recipient_address,
signer::address_of(&delegation_signer),
protocol_commission,
);
iterable_table_custom::add(
&mut delegation_accounts.accounts,
new_pool_owner_address,
DelegationAccount {
managed_pool_address: new_pool_owner_address,
signer_cap: delegation_signer_cap,
delegation_service_manage_cap: manage_cap,
},
);
let stake_pool_address =
delegation_state::get_stake_pool_address(new_pool_owner_address);
assert!(
stake::get_validator_state(stake_pool_address) ==
VALIDATOR_STATUS_ACTIVE,
error::invalid_state(EINACTIVE_VALIDATOR)
);
validator_states::validator_signup_internal(
new_pool_owner_address,
&status.validate_states_update_cap,
);
event::emit_event<ValidatorSignupEvent>(
&mut status.validator_signup_events,
ValidatorSignupEvent {
managed_pool_address: new_pool_owner_address,
}
);
}
Function validator_begin_leaving
A validator calls this to begin off-boarding and get their stake_pool back. This initiates self-destruct of the ManagedStakePool. A validator may have to remove non-owner delegators via delegation::delegation_service module after calling this function and before calling validator_offboard().
public fun validator_begin_leaving(pool_owner: &signer)
Implementation
public entry fun validator_begin_leaving(
pool_owner: &signer
) acquires DelegationAccounts {
validator_begin_leaving_protocol_internal(
signer::address_of(pool_owner)
);
}
Function claim_stake_pool_owner_cap
Validators can claim their OwnerCapability if they have been removed by the protocol (forced-offboarding).
Abort conditions
If the validator is not in the protocol.
public fun claim_stake_pool_owner_cap(old_pool_owner: &signer)
Implementation
public entry fun claim_stake_pool_owner_cap(
old_pool_owner: &signer
) acquires Status {
let status = borrow_global_mut<Status>(@tortuga_governance);
let old_pool_owner_address = signer::address_of(old_pool_owner);
assert!(
iterable_table_custom::contains(
&status.unclaimed_stake_pool_owner_caps,
old_pool_owner_address
),
error::unauthenticated(ENO_OWNER_CAP_TO_CLAIM)
);
let stake_pool_owner_cap = iterable_table_custom::remove(
&mut status.unclaimed_stake_pool_owner_caps,
old_pool_owner_address,
);
stake::deposit_owner_cap(old_pool_owner, stake_pool_owner_cap);
}
Function validator_offboard
A validator can call this to retrieve owner_cap. It must be called after the validator has called validator_begins_leaving(), and after removing all the non-owner delegators from the ManagedStakePool.
public fun validator_offboard(pool_owner: &signer)
Implementation
public entry fun validator_offboard(
pool_owner: &signer
) acquires DelegationAccounts, Status {
let stake_pool_owner_cap = validator_offboard_internal(
signer::address_of(pool_owner)
);
stake::deposit_owner_cap(pool_owner, stake_pool_owner_cap);
}
Function get_delegation_account_address
Get the delegation account that protocol uses for an associated validator at managed_pool_address.
public(friend) fun get_delegation_account_address(managed_pool_address: address): address
Delegates to a validator at managed_pool_address with amount APT. We allow delegations even if there is nonzero unlocking balance for simplicity.
Abort conditions
If there is any withdrawable amount.
If the validator is not in the protocol.
If the validator is in self-destruct state.
If the validator is not ACTIVE.
public(friend) fun delegate(managed_pool_address: address, amount: u64)
Implementation
public(friend) fun delegate(
managed_pool_address: address,
amount: u64,
) acquires DelegationAccounts, AptosCoinReserve, Status {
assert!(
is_an_associated_validator(managed_pool_address),
error::not_found(EVALIDATOR_NOT_FOUND)
);
// We must withdraw first, if there is a withdrawable balance
assert_no_withdrawable_amount(managed_pool_address);
// If the validator is in self-destruct the delegation will fail
// automatically, so we do not check for that condition here.
let stake_pool_address = delegation_state::get_stake_pool_address(
managed_pool_address
);
assert!(
stake::get_validator_state(stake_pool_address) ==
VALIDATOR_STATUS_ACTIVE,
error::invalid_state(EINACTIVE_VALIDATOR)
);
// Need to move coins to the delegation account to delegate
let coins_to_delegate = extract_coins_from_reserve(amount);
let delegation_account_signer_ref = &get_delegation_account_signer(
managed_pool_address
);
let delegation_account_address = signer::address_of(
delegation_account_signer_ref
);
coin::deposit<AptosCoin>(delegation_account_address, coins_to_delegate);
delegation_service::delegate(
delegation_account_signer_ref,
managed_pool_address,
amount,
);
// modify states and stats
update_validator_states(managed_pool_address, amount, 0, 0);
}
Function reserve_for_payout
Reserve amount to unlock from the validator at managed_pool_address at its next unlock.
public(friend) fun reserve_for_payout(managed_pool_address: address, amount: u64)
Implementation
public(friend) fun reserve_for_payout(
managed_pool_address: address,
amount: u64
) acquires DelegationAccounts, WithdrawSignature, Status {
if (amount == 0) {
return
};
assert!(
is_an_associated_validator(managed_pool_address),
error::not_found(EVALIDATOR_NOT_FOUND)
);
assert!(
verify_withdraw_signature_for_validator(managed_pool_address),
error::invalid_state(EVALIDATOR_UNLOCK_IS_TOO_FAR)
);
// We must withdraw first, if there is a withdrawable balance
assert_no_withdrawable_amount(managed_pool_address);
// We first pay commission to the owner,
// so that our balance and value to shares is calculated correctly
pay_commission_to_validator_internal(managed_pool_address);
let delegation_account_address =
get_delegation_account_address(managed_pool_address);
let num_shares_to_reserve =
delegation_state::get_num_shares_to_reserve_rounded_up(
managed_pool_address,
delegation_account_address,
amount,
);
// reserve shares for payout
delegation_service::reserve_shares_for_withdraw(
&get_delegation_account_signer(managed_pool_address),
managed_pool_address,
num_shares_to_reserve,
);
// modify the states
update_validator_states(managed_pool_address, 0, 0, amount);
}
Returns true if the managed_pool_address can be withdrawn from. If the WithdrawSignature has not been set, the validator in it is no longer associated with the protocol, or it has expired, returns true. This allows the withdrawals to go through even if the signatures are not updated on time. Otherwise, it returns true if the lockup period for the validator at the address managed_pool_address is earlier than WithdrawSignature.unlock_at.
Abort conditions
If the managed_pool_address is not associated with a validator.
fun verify_withdraw_signature_for_validator(managed_pool_address: address): bool
Implementation
fun verify_withdraw_signature_for_validator(
managed_pool_address: address
): bool acquires WithdrawSignature, DelegationAccounts {
assert!(
is_an_associated_validator(managed_pool_address),
error::not_found(EVALIDATOR_NOT_FOUND)
);
let withdraw_signature = borrow_global<WithdrawSignature>(
@tortuga_governance
);
let signature_managed_pool_address =
withdraw_signature.managed_pool_address;
// If the withdraw signature is no longer a valid validator in our set,
// then the signature is expired.
if (!is_an_associated_validator(signature_managed_pool_address)) {
return true
};
let signature_stake_pool_address =
delegation_state::get_stake_pool_address(
signature_managed_pool_address
);
if (
stake::get_lockup_secs(
signature_stake_pool_address
) != withdraw_signature.unlocking_at
) {
// signature has expired
return true
};
let stake_pool_address = delegation_state::get_stake_pool_address(
managed_pool_address
);
let unlocking_at = stake::get_lockup_secs(stake_pool_address);
return withdraw_signature.unlocking_at >= unlocking_at
}
Function validator_begin_leaving_protocol_internal
Starts the process of the validator leaving the protocol. This function will self-destruct the ManagedStakePool which the validator had created before joining the protocol.
Abort conditions
If the validator is not associated with the protocol.
fun validator_begin_leaving_protocol_internal(managed_pool_address: address)
Implementation
fun validator_begin_leaving_protocol_internal(
managed_pool_address: address
) acquires DelegationAccounts {
assert!(
is_an_associated_validator(managed_pool_address),
error::not_found(EVALIDATOR_NOT_FOUND)
);
let delegation_accounts = borrow_global<DelegationAccounts>(
@tortuga_governance
);
let account = iterable_table_custom::borrow<address, DelegationAccount>(
&delegation_accounts.accounts,
managed_pool_address,
);
delegation_service::begin_self_destruct(
&account.delegation_service_manage_cap
);
}
Function validator_offboard_internal
Function to offboard an associated validator at address managed_pool_address from the protocol after the reserved and unreserved balance with the protocol is zero.
Returns the OwnerCapability after a successful off-boarding.
Abort conditions
If the validator is not associated with the protocol.
If the validator has a non-zero balance of the protocol.
fun validator_offboard_internal(managed_pool_address: address): stake::OwnerCapability
Implementation
fun validator_offboard_internal(
managed_pool_address: address
): OwnerCapability acquires DelegationAccounts, Status {
let status = borrow_global_mut<Status>(@tortuga_governance);
assert!(
is_an_associated_validator(managed_pool_address),
error::unauthenticated(EVALIDATOR_NOT_FOUND)
);
validator_states::validator_removal_internal(
managed_pool_address,
&status.validate_states_update_cap,
);
let delegation_accounts = borrow_global_mut<DelegationAccounts>(
@tortuga_governance
);
let DelegationAccount {
managed_pool_address,
signer_cap: _sc,
delegation_service_manage_cap: manage_cap,
} = iterable_table_custom::remove(
&mut delegation_accounts.accounts,
managed_pool_address
);
let stake_pool_owner_cap =
delegation_service::finish_self_destruct(manage_cap);
event::emit_event<ValidatorOffboardEvent>(
&mut status.validator_offboard_events,
ValidatorOffboardEvent {
managed_pool_address,
}
);
stake_pool_owner_cap
}
Function create_delegation_account
Creates a new resource account for the pool_owner, register it to receive AptosCoin and returns the resource_signer and the SignerCapability for the resource_signer.