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 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.
struct ValidatorContract has store, key
Fields
dummy_field: bool
Resource Status
This struct stores validator signup details.
struct Status has key
Fields
Resource WithdrawSignature
Withdrawal to distribute claims should be from earliest validator to maximize the liquidity of the protocol. This struct helps with that mechanism as follows:
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
struct AptosCoinReserve has key
Fields
Resource DelegationAccounts
These resource accounts delegate to the individual validators
struct DelegationAccounts has store, key
Fields
Struct DelegationAccount
Delegation account for a validator, stores the address and capabilities.
struct DelegationAccount has store
Fields
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.
Struct WithdrawSignatureSetEvent
struct WithdrawSignatureSetEvent has drop, store
Fields
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;
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;
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
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
)
}
Function get_reserve_balance
public fun get_reserve_balance(): u64
Implementation
public fun get_reserve_balance(): u64 acquires AptosCoinReserve {
coin::value<AptosCoin>(
&borrow_global<AptosCoinReserve>(@tortuga_governance).coin
)
}
Function get_current_number_of_validators
Returns the number of associated validators.
public fun get_current_number_of_validators(): u64
Implementation
public fun get_current_number_of_validators(): u64
acquires DelegationAccounts {
let accounts_ref = &borrow_global<DelegationAccounts>(
@tortuga_governance
).accounts;
iterable_table_custom::length<address, DelegationAccount>(accounts_ref)
}
Function get_max_number_of_validators
Returns the current maximum number of validators.
public fun get_max_number_of_validators(): u64
Implementation
public fun get_max_number_of_validators(): u64 acquires Status {
borrow_global<Status>(@tortuga_governance).max_number_of_validators
}
Function get_allow_self_signup
Returns true if self-signup is allowed, false otherwise.
public fun get_allow_self_signup(): bool
Implementation
public fun get_allow_self_signup(): bool acquires Status {
borrow_global<Status>(@tortuga_governance).allow_self_signup
}
Function is_withdrawable_amount_positive
Return true if there is any withdrawable balance in the validator at managed_pool_address.
public fun is_withdrawable_amount_positive(managed_pool_address: address): bool
Implementation
public fun is_withdrawable_amount_positive(
managed_pool_address: address
): bool acquires DelegationAccounts {
if (
delegation_state::get_protocol_payout_value(
managed_pool_address
) > 0
) {
true
} else if (
delegation_state::get_reserved_shares_with_delegator(
managed_pool_address,
get_delegation_account_address(managed_pool_address)
) > 0
) {
let stake_pool_address =
delegation_state::get_stake_pool_address(
managed_pool_address
);
let total_withdrawable_amount =
stake_pool_helpers::get_stake_pool_total_withdrawable_amount(
stake_pool_address
);
total_withdrawable_amount > 0
} else {
false
}
}
Function is_an_associated_validator
Returns if the validator at managed_pool_address is associated with the protocol.
public fun is_an_associated_validator(managed_pool_address: address): bool
Implementation
public fun is_an_associated_validator(
managed_pool_address: address
): bool acquires DelegationAccounts {
let accounts_ref = &borrow_global<DelegationAccounts>(
@tortuga_governance
).accounts;
iterable_table_custom::contains<address, DelegationAccount>(
accounts_ref,
managed_pool_address
)
}
Function set_max_number_of_validators
Set the maximum number of validators that can be associated with the protocol to value.
Restrictions
public fun set_max_number_of_validators(tortuga_governance: &signer, value: u64)
Implementation
public entry fun set_max_number_of_validators(
tortuga_governance: &signer,
value: u64
) acquires Status {
permissions::assert_authority<TortugaGovernance>(tortuga_governance);
let signup_status = borrow_global_mut<Status>(@tortuga_governance);
signup_status.max_number_of_validators = value;
}
Function set_max_max_commission
Set the maximum max_commission for validators to value This does not affect existing validators.
Restrictions
public fun set_max_max_commission(tortuga_governance: &signer, value: u64)
Implementation
public entry fun set_max_max_commission(
tortuga_governance: &signer,
value: u64
) acquires Status {
permissions::assert_authority<TortugaGovernance>(tortuga_governance);
let signup_status = borrow_global_mut<Status>(@tortuga_governance);
signup_status.max_max_commission = value;
}
Function set_allow_self_signup
Set the allow_self_signup flag.
Restrictions
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.
Restrictions
Abort conditions
If managed_pool_address is not associated with the protocol.
public fun set_withdraw_signature(tortuga_governance: &signer, managed_pool_address: address)
Implementation
public entry fun set_withdraw_signature(
tortuga_governance: &signer,
managed_pool_address: address,
) acquires WithdrawSignature, DelegationAccounts, Status {
permissions::assert_authority<TortugaGovernance>(tortuga_governance);
assert!(
is_an_associated_validator(managed_pool_address),
error::not_found(EVALIDATOR_NOT_FOUND)
);
let stake_pool_address = delegation_state::get_stake_pool_address(
managed_pool_address
);
let unlocking_at = stake::get_lockup_secs(stake_pool_address);
let withdraw_signature = borrow_global_mut<WithdrawSignature>(
@tortuga_governance
);
withdraw_signature.managed_pool_address = managed_pool_address;
withdraw_signature.unlocking_at = unlocking_at;
event::emit_event<WithdrawSignatureSetEvent>(
&mut borrow_global_mut<Status>(@tortuga_governance)
.withdraw_signature_set_events,
WithdrawSignatureSetEvent {
managed_pool_address,
unlocking_at,
}
);
}
Function reset_validator_states_config
Update the configs of the oracle::validator_state module.
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.
Restrictions
public fun set_validator_state_permissioned_score_for_validator(tortuga_governance: &signer, managed_pool_address: address, value: u128)
Implementation
public entry fun set_validator_state_permissioned_score_for_validator(
tortuga_governance: &signer,
managed_pool_address: address,
value: u128
) acquires Status {
permissions::assert_authority<TortugaGovernance>(tortuga_governance);
validator_states::set_permissioned_score(
&borrow_global<Status>(
@tortuga_governance
).validate_states_update_cap,
managed_pool_address,
value
);
}
Function vet_a_validator
Vets a validator for becoming associated with the protocol.
Restrictions
Abort conditions
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
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.
Abort conditions
If the validator is not in the protocol.
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);
}
Function extract_coins_from_reserve
public(friend) fun extract_coins_from_reserve(amount: u64): coin::Coin<aptos_coin::AptosCoin>
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.
fun create_delegation_account(pool_owner: &signer): (signer, account::SignerCapability)
Implementation
fun create_delegation_account(
pool_owner: &signer
): (signer, SignerCapability) {
let seed: vector<u8> = bcs::to_bytes(×tamp::now_seconds());
let (
resource_signer,
resource_signer_cap
) = account::create_resource_account(pool_owner, seed);
if (
!coin::is_account_registered<AptosCoin>(
signer::address_of(&resource_signer)
)
) {
coin::register<AptosCoin>(&resource_signer);
};
(resource_signer, resource_signer_cap)
}
Function get_delegation_account_signer
fun get_delegation_account_signer(managed_pool_address: address): signer
Implementation
fun get_delegation_account_signer(
managed_pool_address: address
): signer acquires DelegationAccounts {
let accounts_ref = &borrow_global<DelegationAccounts>(
@tortuga_governance
).accounts;
let account = iterable_table_custom::borrow(
accounts_ref,
managed_pool_address,
);
account::create_signer_with_capability(&account.signer_cap)
}
Function delegation_account_exists
fun delegation_account_exists(managed_pool_address: address): bool
Implementation
fun delegation_account_exists(
managed_pool_address: address
): bool acquires DelegationAccounts {
let delegation_accounts = borrow_global<DelegationAccounts>(
@tortuga_governance
);
iterable_table_custom::contains(
&delegation_accounts.accounts,
managed_pool_address
)
}
Function get_shift_amount
Get shift amount. At genesis this will be set to 0.
fun get_shift_amount(managed_pool_address: address): u64
vetted_validators: <address, >Vetted validators, if self-signup is disabledallow_self_signup: boolWhether to allow self-signup for validators.max_number_of_validators: u64Maximum number of validator.max_max_commission: u64Maximum number of validator.unclaimed_stake_pool_owner_caps: <address, >Unclaimed stake_pool_owner_cap from forcibly removed validators.validate_states_update_cap: Stores the permission to update oracle::validator_states::ValidatorSystem.validator_signup_events: <>Event handle for .validator_offboard_events: <>Event handle for .withdraw_signature_set_events: <>Event handle for .
protocol sets a withdraw signature which is valid as long as () < unlocking_at and managed_pool_address is still a validator in our set.
This reserve store coins which can be either delegated to validators, or withdrawn from them to clear Tickets in module.
: <>Store for AptosCoin.
accounts: <address, >Map from address of an associated validator to their .pay_commission_events: <>Event handle for .withdraw_to_reserve_events: <>Event handle for DelegationAccountCreatedEvent.
managed_pool_address: addressAddress where the ManagedStakePool for the validator is stored.signer_cap: The SignerCapability for the resource account that delegates to the validator.delegation_service_manage_cap: The ManageCapability for the validator's ManagedStakePool.
Event generated when the contract sets a .
The following constants must match those of .move Validator state pending_active.
When a validator already has a .
When an unlock is requested from a validator but is not allowed by the current .
Returns the total balance in APT staked with the validators and in the . This may will only be 100% accurate if the oracle was updated in the current epoch.
Returns the balance in the .
Only the can call this function.
Only the can call this function.
Only the can call this function.
Only the can call this function.
Only the can call this function.
Only the can call this function.
If .allow_self_signup is set to false, then a validator can only join after it has been vetted.
Only the can call this function.
If managed_pool_address already has a .
The validator can then claim owner_cap via ().
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 ().
If the validator's unlock period is too far. See .
Extract amount APT from the of the protocol.
Deposit amount APT to the of the protocol.
Assert that the protocol has a resource.
If the protocol does not have a resource.
Returns true if the managed_pool_address can be withdrawn from. If the 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 .unlock_at.
It uses () as the seed for the resource account.
Returns a for the validator at managed_pool_address.
Returns true if the managed_pool_address has a from the Tortuga protocol.