Tortuga Finance Docs
  • Overview
    • Liquid Staking on Aptos
  • Stake APT
    • Tutorial: Stake APT via Tortuga
    • Tutorial: Buy tAPT from DEX
    • Use tAPT
    • Fees
    • Before You Stake
  • For Validators
    • How It Works
    • How to Join
  • For Developers
    • Integration Resources
    • Tortuga <> Pyth
  • Protocol
    • Overview
    • Whitepaper
    • Governance
    • Tokenomics
    • Reference
      • helpers::circular_buffer
      • helpers::math
      • helpers::pool
      • helpers::stake_pool_helpers
      • helpers::iterable_table_custom
      • oracle::validator_states
      • delegation::delegation_state
      • delegation::delegation_service
      • tortuga::validator_router
      • tortuga::stake_router
      • governance::permissions
      • governance::utils
      • tortuga_governance::staked_aptos
      • tortuga_governance::tortuga_governance
    • Security Audits
Powered by GitBook
On this page
  • Module 0xc0ded0c0::stake_router
  • Resource StakingStatus
  • Resource EventStore
  • Resource StakedAptosCapabilities
  • Struct Ticket
  • Resource TicketStore
  • Struct StakeEvent
  • Struct UnstakeEvent
  • Struct ClaimEvent
  • Struct IncreaseStakeInValidatorEvent
  • Struct UnlockFromValidatorEvent
  • Constants
  • Function get_t_apt_supply
  • Function get_total_value
  • Function current_total_stakable_amount
  • Function current_deficit
  • Function get_num_tickets
  • Function get_ticket
  • Function initialize_tortuga_liquid_staking
  • Function set_commission
  • Function set_community_rebate
  • Function set_cooldown_period
  • Function set_min_transaction_amount
  • Function stake
  • Function unstake
  • Function claim
  • Function increase_stake_in_validator
  • Function unlock_from_validator
  • Function stake_coins
  • Function ensure_event_store
  • Function charge_protocol_fee_internal
  • Function burn_ticket
  • Function calc_shares_to_mint
  • Function calc_shares_to_value
  1. Protocol
  2. Reference

tortuga::stake_router

Previoustortuga::validator_routerNextgovernance::permissions

Last updated 2 years ago

Module 0xc0ded0c0::stake_router

This module is the main user facing module of the Tortuga protocol.

It handles:

  • APT -> tAPT transactions,

  • issuing of Tickets and claiming them for tAPT -> APT, and,

  • permissionless staking/withdrawal from validators according to their scores

The validators will interface with the protocol through the module.

use 0x1::account;
use 0x1::aptos_coin;
use 0x1::coin;
use 0x1::error;
use 0x1::event;
use 0x1::option;
use 0x1::signer;
use 0x1::string;
use 0x1::timestamp;
use 0x1::vector;
use 0xc0ded0c0::math;
use 0xc0ded0c0::validator_router;
use 0xc0ded0c0::validator_states;
use 0xc0ded0c1::permissions;
use 0xc0ded0c2::staked_aptos_coin;
use 0xc0ded0c2::tortuga_governance;

Resource StakingStatus

Struct which holds the current state of the protocol, including the storage for the protocol fee.

struct StakingStatus has key
Fields

Resource EventStore

Struct which holds handles for events related to staking, unstaking, and claiming tickets. It is saved with the delegator's accounts.

struct EventStore has key
Fields

Resource StakedAptosCapabilities

Capabilities associated with tAPT coin

struct StakedAptosCapabilities has key
Fields

Struct Ticket

The ticket which is issued when a user unstakes.

struct Ticket has store
Fields

Resource TicketStore

A user can have several tickets which are stored in their ticket store

struct TicketStore has key
Fields

Struct StakeEvent

Event emitted when a user stakes

struct StakeEvent has drop, store
Fields

Struct UnstakeEvent

Event emitted when a user unstakes

struct UnstakeEvent has drop, store
Fields

Struct ClaimEvent

Event emitted when a user claims a ticket

struct ClaimEvent has drop, store
Fields

Struct IncreaseStakeInValidatorEvent

Event emitted when a user increases stake in a validator

struct IncreaseStakeInValidatorEvent has drop, store
Fields

Struct UnlockFromValidatorEvent

Event emitted when a user unlocks funds from a validator

struct UnlockFromValidatorEvent has drop, store
Fields

Constants

When a non-protocol user tries to do any permissioned operation.

const EPERMISSION_DENIED: u64 = 6;

The normalizer for the commission rates.

const COMMISSION_NORMALIZER: u64 = 1000000;

When the amount in APT is too small for the operation.

const EAMOUNT_TOO_SMALL: u64 = 9;

When the commission being set is too high.

const ECOMMISSION_TOO_HIGH: u64 = 11;

When the new withdrawal fee is too high.

const ECOMMUNITY_REBATE_TOO_HIGH: u64 = 15;

When the cooldown period being set is too long.

const ECOOLDOWN_TOO_LONG: u64 = 12;

When a ticket cannot be claimed because the funds are not available.

const EFUND_NOT_AVAILABLE_YET: u64 = 5;

When a user tries to increase too much stake in a validator or unlock too much from a validator.

const EINVALID_AMOUNT: u64 = 10;

When a user tries to unstake a non-existent ticket.

const EINVALID_TICKET_INDEX: u64 = 8;

When the new min transaction amount is too high.

const EMIN_TRANSACTION_AMOUNT_TOO_HIGH: u64 = 14;

When a user tries to claim a ticket without ever having unstaked.

const ENO_REMAINING_CLAIMS: u64 = 4;

When the cooldown for a ticket has not elapsed.

const ETICKET_NOT_READY_YET: u64 = 3;

When the calculation of APT from shares fails because of incorrect arguments

const ETOO_MANY_SHARES: u64 = 2;

When the unstake operation is called before the cooldown period since contract deployment has elapsed.

const EUNSTAKE_NOT_READY_YET: u64 = 13;

The maximum withdrawal fee amount

const MAX_COMMUNITY_REBATE: u64 = 30000;

The maximum cooldown period in seconds.

const MAX_COOLDOWN_PERIOD: u64 = 1209600;

The maximum minimum transaction amount

const MAX_MIN_TRANSACTION_AMOUNT: u64 = 100000000000;

Function get_t_apt_supply

Returns the total tAPT minted.

public fun get_t_apt_supply(): u64
Implementation
public fun get_t_apt_supply(): u64 {
    (option::extract(&mut coin::supply<StakedAptosCoin>()) as u64)
}

Function get_total_value

Returns the total value locked with the protocol in APT (legacy name).

public fun get_total_value(): u64
Implementation
public fun get_total_value(): u64 acquires StakingStatus {
    let staking_status = borrow_global<StakingStatus>(@tortuga_governance);
    let unclaimed_balance =
        staking_status.total_claims_balance -
            staking_status.total_claims_balance_cleared;
    validator_router::get_total_balance() - (unclaimed_balance as u64)
}

Function current_total_stakable_amount

Returns how much APT can be staked with validators.

public fun current_total_stakable_amount(): u64
Implementation
public fun current_total_stakable_amount(): u64 acquires StakingStatus {
    let staking_status = borrow_global<StakingStatus>(@tortuga_governance);
    let unclaimed_balance = (
        (
            staking_status.total_claims_balance -
                staking_status.total_claims_balance_cleared
        ) as u64
    );
    let reserve_balance = validator_router::get_reserve_balance();
    let total_unlocking_balance =
        validator_states::get_total_unlocking_balance(@tortuga_governance);

    if (reserve_balance + total_unlocking_balance < unclaimed_balance) {
        0
    } else {
        reserve_balance + total_unlocking_balance - unclaimed_balance
    }
}

Function current_deficit

This function calculates how much needs to be unlocked from the validators, to be able to process unclaimed funds.

public fun current_deficit(): u64
Implementation
public fun current_deficit(): u64 acquires StakingStatus {
    let staking_status = borrow_global<StakingStatus>(@tortuga_governance);
    let unclaimed_balance = (
        (staking_status.total_claims_balance -
            staking_status.total_claims_balance_cleared)
        as u64
    );
    let reserve_balance = validator_router::get_reserve_balance();
    let total_unlocking_balance =
        validator_states::get_total_unlocking_balance(@tortuga_governance);

    if (reserve_balance + total_unlocking_balance > unclaimed_balance) {
        0
    } else {
        unclaimed_balance - (reserve_balance + total_unlocking_balance)
    }
}

Function get_num_tickets

Returns the total number of tickets that delegator has.

public fun get_num_tickets(delegator: address): u64
Implementation
public fun get_num_tickets(delegator: address): u64 acquires TicketStore {
    if (!exists<TicketStore>(delegator)) {
        0
    } else {
        let ticket_store = borrow_global<TicketStore>(delegator);
        vector::length(&ticket_store.tickets)
    }
}

Function get_ticket

Returns the ticket at position index for delegator as a 3-tuple of:

  • id: u128: unique id, used to ensure first-in-first-out for claims,

  • amount: u64, amount in APT that will be redeemed, and,

Abort condition

  • If delegator does not have a ticket at index.

public fun get_ticket(delegator: address, index: u64): (u128, u64, u64)
Implementation
public fun get_ticket(
    delegator: address,
    index: u64
): (u128, u64, u64) acquires TicketStore {
    assert!(
        exists<TicketStore>(delegator),
        error::invalid_argument(EINVALID_TICKET_INDEX)
    );
    let ticket_store = borrow_global<TicketStore>(delegator);

    assert!(
        index < vector::length(&ticket_store.tickets),
        error::invalid_argument(EINVALID_TICKET_INDEX)
    );

    let Ticket {
        id,
        amount,
        timestamp
    } = vector::borrow(&ticket_store.tickets, index);

    (*id, *amount, *timestamp)
}

Function initialize_tortuga_liquid_staking

This initializes the liquid staking protocol for tortuga with the given commission, cooldown_period and max_number_of_validators.

Restrictions

Abort conditions

public fun initialize_tortuga_liquid_staking(tortuga_governance_deployer: &signer, commission: u64, cooldown_period: u64, max_number_of_validators: u64)
Implementation
public entry fun initialize_tortuga_liquid_staking(
    tortuga_governance_deployer: &signer,
    commission: u64,
    cooldown_period: u64,
    max_number_of_validators: u64
) {
    // Since this function initializes a coin whose type is declared in
    // @tortuga_governance, this function can run successfully only if
    // called via the governance address. This assert is a reminder for
    // that.
    assert!(
        signer::address_of(tortuga_governance_deployer) == @tortuga_governance,
        error::unauthenticated(EPERMISSION_DENIED)
    );

    assert!(
        commission <= COMMISSION_NORMALIZER,
        error::invalid_argument(ECOMMISSION_TOO_HIGH)
    );

    // initialize the staked aptos coin first
    let (burn_cap, freeze_cap, mint_cap) =
        coin::initialize<StakedAptosCoin>(
            tortuga_governance_deployer,
            string::utf8(b"Tortuga Staked APT"),
            string::utf8(b"tAPT"),
            8,
            true,
        );

    move_to(tortuga_governance_deployer, StakedAptosCapabilities {
        mint_cap: mint_cap,
        freeze_cap: freeze_cap,
        burn_cap: burn_cap,
    });

    move_to(tortuga_governance_deployer, StakingStatus {
        protocol_fee: coin::zero<StakedAptosCoin>(),
        commission: commission,
        community_rebate: 0,
        total_claims_balance: 0,
        total_claims_balance_cleared: 0,
        cooldown_period: cooldown_period,
        deployment_time: timestamp::now_seconds(),
        commission_exempt_amount: 0,
        min_transaction_amount: 1000000, // 0.01 APT // tAPT
        increase_stake_in_validator_events:
            account::new_event_handle<IncreaseStakeInValidatorEvent>(
                tortuga_governance_deployer
            ),
        unlock_from_validator_events:
            account::new_event_handle<UnlockFromValidatorEvent>(
                tortuga_governance_deployer
            ),
    });

    // finally initialize the validator router
    validator_router::initialize(tortuga_governance_deployer, max_number_of_validators);

    // Initialize phase 0 of governance
    permissions::initialize_permissions<TortugaGovernance>(tortuga_governance_deployer);
}

Function set_commission

Set the commission that tortuga protocol charges to value.

Restrictions

Abort conditions

public fun set_commission(tortuga_governance: &signer, value: u64)
Implementation
public entry fun set_commission(
    tortuga_governance: &signer,
    value: u64
) acquires StakingStatus {
    permissions::assert_authority<TortugaGovernance>(tortuga_governance);

    let staking_status = borrow_global_mut<StakingStatus>(
        @tortuga_governance
    );
    assert!(
        value <= COMMISSION_NORMALIZER,
        error::invalid_argument(ECOMMISSION_TOO_HIGH)
    );
    staking_status.commission = value;
}

Function set_community_rebate

Set the community rebate that tortuga users get to value.

Restrictions

Abort conditions

  • If the value is greater than MAX_WITHDRAWAL_FEE, i.e. 0.05%.

public fun set_community_rebate(tortuga_governance: &signer, value: u64)
Implementation
public entry fun set_community_rebate(
    tortuga_governance: &signer,
    value: u64
) acquires StakingStatus {
    permissions::assert_authority<TortugaGovernance>(tortuga_governance);

    let staking_status = borrow_global_mut<StakingStatus>(
        @tortuga_governance
    );
    assert!(
        value <= MAX_COMMUNITY_REBATE,
        error::invalid_argument(ECOMMUNITY_REBATE_TOO_HIGH)
    );
    staking_status.community_rebate = value;
}

Function set_cooldown_period

Set wait period between ticket issuance and redemption.

Restrictions

Abort conditions

public fun set_cooldown_period(tortuga_governance: &signer, value: u64)
Implementation
public entry fun set_cooldown_period(
    tortuga_governance: &signer,
    value: u64
) acquires StakingStatus {
    permissions::assert_authority<TortugaGovernance>(tortuga_governance);

    let staking_status = borrow_global_mut<StakingStatus>(
        @tortuga_governance
    );
    assert!(
        value <= MAX_COOLDOWN_PERIOD,
        error::invalid_argument(ECOOLDOWN_TOO_LONG)
    );
    staking_status.cooldown_period = value;
}

Function set_min_transaction_amount

Set minimum delegation amount.

Restrictions

public fun set_min_transaction_amount(tortuga_governance: &signer, value: u64)
Implementation
public entry fun set_min_transaction_amount(
    tortuga_governance: &signer,
    value: u64
) acquires StakingStatus {
    permissions::assert_authority<TortugaGovernance>(tortuga_governance);

    assert!(
        value <= MAX_MIN_TRANSACTION_AMOUNT,
        error::invalid_argument(EMIN_TRANSACTION_AMOUNT_TOO_HIGH)
    );

    let staking_status = borrow_global_mut<StakingStatus>(
        @tortuga_governance
    );
    staking_status.min_transaction_amount = value;
}

Function stake

This is the endpoint for a user who wants to stake amount APT and get tAPT in return. The coins are directly deposited to the user's account.

Abort conditions

  • If the amount is less than min_transaction_amount.

public fun stake(delegator: &signer, amount: u64)
Implementation
public entry fun stake(
    delegator: &signer,
    amount: u64
) acquires StakingStatus, StakedAptosCapabilities, EventStore {
    let staking_status = borrow_global<StakingStatus>(@tortuga_governance);

    assert!(
        amount >= staking_status.min_transaction_amount,
        error::invalid_argument(EAMOUNT_TOO_SMALL)
    );

    let delegator_address = signer::address_of(delegator);
    let coins_to_stake = coin::withdraw<AptosCoin>(delegator, amount);
    register_for_t_apt(delegator);

    let minted_t_apt_coin = stake_coins(coins_to_stake);

    ensure_event_store(delegator);
    let event_store = borrow_global_mut<EventStore>(
        delegator_address
    );
    event::emit_event<StakeEvent>(
        &mut event_store.stake_events,
        StakeEvent {
            delegator: delegator_address,
            amount: amount,
            t_apt_coins: coin::value<StakedAptosCoin>(&minted_t_apt_coin),
            timestamp: timestamp::now_seconds(),
        }
    );

    // send shares to the delegator
    coin::deposit(delegator_address, minted_t_apt_coin);
}

Function unstake

Request to redeem APT by burn t_apt_amount tAPT.

Abort conditions

  • If the t_apt_amount is less than min_transaction_amount.

  • If the t_apt_amount is greater than the delegator's tAPT balance.

  • If cooldown_period has not passed since the contract's first deployment.

public fun unstake(delegator: &signer, t_apt_amount: u64)
Implementation
public entry fun unstake(
    delegator: &signer,
    t_apt_amount: u64
) acquires
    StakingStatus,
    StakedAptosCapabilities,
    TicketStore,
    EventStore
{
    let staking_status = borrow_global<StakingStatus>(@tortuga_governance);
    assert!(
        timestamp::now_seconds() >=
            staking_status.cooldown_period +
                staking_status.deployment_time,
        EUNSTAKE_NOT_READY_YET,
    );

    // Check against minimum transaction account
    let t_apt_supply = get_t_apt_supply();
    assert!(
        t_apt_amount >= staking_status.min_transaction_amount
        || t_apt_amount == t_apt_supply,
        error::invalid_argument(EAMOUNT_TOO_SMALL)
    );

    let t_apt_coins_to_unstake = coin::withdraw(delegator, t_apt_amount);
    let t_apt_remaining = coin::balance<StakedAptosCoin>(
        signer::address_of(delegator)
    );

    // must charge protocol fee to be able to calculate shares value
    // correctly
    charge_protocol_fee_internal();

    // calculate total worth
    let total_worth = get_total_value();
    // Need to borrow again because previous line also borrow this
    // resource.
    staking_status = borrow_global<StakingStatus>(@tortuga_governance);
    // If too little t_apt_coins remain in the wallet, then unstake all
    if (t_apt_remaining < staking_status.min_transaction_amount) {
        coin::merge<StakedAptosCoin>(
            &mut t_apt_coins_to_unstake,
            coin::withdraw(delegator, t_apt_remaining)
        );
        t_apt_amount = t_apt_amount + t_apt_remaining;
    };
    let shares_value = calc_shares_to_value(
        t_apt_amount,
        total_worth,
        t_apt_supply
    );

    // Distribute community rebate
    // to the remaining (t_apt_supply - t_apt_amount)
    // shares
    if (t_apt_supply > t_apt_amount) {
        let community_rebate = math::mul_div(
            shares_value,
            staking_status.community_rebate,
            COMMISSION_NORMALIZER
        );
        shares_value = shares_value - community_rebate;
    };

    // burn the tAPT coins
    let capabilities = borrow_global<StakedAptosCapabilities>(
        @tortuga_governance
    );
    coin::burn<StakedAptosCoin>(
        t_apt_coins_to_unstake,
        &capabilities.burn_cap
    );

    // modify total claims balance, and commission exempt amount
    let staking_status = borrow_global_mut<StakingStatus>(
        @tortuga_governance
    );
    staking_status.total_claims_balance = (
        staking_status.total_claims_balance + (shares_value as u128)
    );
    staking_status.commission_exempt_amount = (
        staking_status.commission_exempt_amount - shares_value
    );

    // issue claim ticket
    let delegator_address = signer::address_of(delegator);
    if (!exists<TicketStore>(delegator_address)) {
        move_to(delegator, TicketStore { tickets: vector::empty() });
    };
    let ticket_store = borrow_global_mut<TicketStore>(delegator_address);
    let ticket_id = staking_status.total_claims_balance;
    let now = timestamp::now_seconds();

    vector::push_back(
        &mut ticket_store.tickets,
        Ticket {
            id: ticket_id,
            amount: shares_value,
            timestamp: now
        }
    );

    ensure_event_store(delegator);
    let event_store = borrow_global_mut<EventStore>(
        delegator_address
    );
    event::emit_event<UnstakeEvent>(
        &mut event_store.unstake_events,
        UnstakeEvent {
            delegator: delegator_address,
            amount: shares_value,
            t_apt_coins: t_apt_amount,
            timestamp: now
        }
    );
}

Function claim

Users can claim their unstaked APTs using this function, after their funds have unlocked. A person who unstakes first will become eligible to claim first.

Abort conditions

  • If the ticket_index does not exist (anymore).

  • If the cooldown_period has not passed since the ticket's creation.

  • If the funds are still locked and not available to be claimed.

public fun claim(delegator: &signer, ticket_index: u64)
Implementation
public entry fun claim(
    delegator: &signer,
    ticket_index: u64
) acquires TicketStore, StakingStatus, EventStore {
    let delegator_address = signer::address_of(delegator);
    assert!(
        exists<TicketStore>(delegator_address),
        error::not_found(ENO_REMAINING_CLAIMS)
    );

    // get claim ticket
    let ticket_store = borrow_global_mut<TicketStore>(delegator_address);
    assert!(
        ticket_index < vector::length(&ticket_store.tickets),
        error::invalid_argument(EINVALID_TICKET_INDEX)
    );
    let ticket = vector::remove(
        &mut ticket_store.tickets,
        ticket_index
    );

    // assert that ticket has cooled down
    let staking_status = borrow_global_mut<StakingStatus>(
        @tortuga_governance
    );
    assert!(
        timestamp::now_seconds() - ticket.timestamp >
            staking_status.cooldown_period,
        error::invalid_argument(ETICKET_NOT_READY_YET)
    );

    // assert that the funds for this ticket has arrived
    let reserve_balance = validator_router::get_reserve_balance();
    assert!(
        ticket.id <=
            (reserve_balance as u128) +
                staking_status.total_claims_balance_cleared,
        error::unavailable(EFUND_NOT_AVAILABLE_YET)
    );

    // Pay aptos coins to the delegator
    let coins_to_repay = validator_router::extract_coins_from_reserve(
        ticket.amount
    );

    // The user may not have registered for aptos coins yet
    if (!coin::is_account_registered<AptosCoin>(delegator_address)) {
        coin::register<AptosCoin>(delegator);
    };

    coin::deposit<AptosCoin>(delegator_address, coins_to_repay);

    // update staking status
    staking_status.total_claims_balance_cleared = (
        staking_status.total_claims_balance_cleared + (
            ticket.amount as u128
        )
    );

    ensure_event_store(delegator);
    let event_store = borrow_global_mut<EventStore>(
        delegator_address
    );
    event::emit_event<ClaimEvent>(
        &mut event_store.claim_events,
        ClaimEvent {
            delegator: delegator_address,
            amount: ticket.amount,
            ticket_index: ticket_index,
            timestamp: timestamp::now_seconds()
        }
    );

    // burn the claim ticket
    burn_ticket(ticket);
}

Function increase_stake_in_validator

Increase the stake in the given managed_pool_address by amount if there are coins to stake in the coin reserve of the protocol and the current score and stake in the validator allows for it.

This allows permissionless staking into validators.

Abort conditions

  • If the amount is smaller than 1 APT.

public fun increase_stake_in_validator(managed_pool_address: address, amount: u64)
Implementation
public entry fun increase_stake_in_validator(
    managed_pool_address: address,
    amount: u64
) acquires StakingStatus {
    // Require the increase to be at least 1 APT
    assert!(
        amount >= 100000000,
        error::invalid_argument(EAMOUNT_TOO_SMALL)
    );

    let required_tvl = get_total_value();
    let target_delegation = validator_states::get_target_delegation(
        @tortuga_governance,
        managed_pool_address,
        required_tvl
    );
    let validator_balance = validator_states::get_balance_at_last_update(
        @tortuga_governance,
        managed_pool_address
    );

    assert!(
        target_delegation >= validator_balance + amount &&
            amount <= current_total_stakable_amount(),
        error::invalid_argument(EINVALID_AMOUNT)
    );
    validator_router::delegate(managed_pool_address, amount);

    let staking_status = borrow_global_mut<StakingStatus>(
        @tortuga_governance
    );
    event::emit_event<IncreaseStakeInValidatorEvent>(
        &mut staking_status.increase_stake_in_validator_events,
        IncreaseStakeInValidatorEvent {
            managed_pool_address: managed_pool_address,
            amount: amount,
            timestamp: timestamp::now_seconds()
        }
    );
}

Function unlock_from_validator

This function can be called if the total free balance is not enough to settle outstanding tickets. Anyone can call this function to unlock from the earliest unlocking validator, if current deficit is positive.

Abort conditions

  • If the amount is smaller than 1 APT.

public fun unlock_from_validator(managed_pool_address: address, amount: u64)
Implementation
public entry fun unlock_from_validator(
    managed_pool_address: address,
    amount: u64
) acquires StakingStatus {
    // If the validator is leaving us and we want to unlock more thant the
    // current deficit, we simply use remove_non_owner_delegator endpoint
    // provided in delegation_service
    assert!(
        amount <= current_deficit(),
        error::invalid_argument(EINVALID_AMOUNT)
    );

    // Require the unlock to be at least 1 APT
    assert!(
        amount >= 100000000,
        error::invalid_argument(EAMOUNT_TOO_SMALL)
    );

    validator_router::reserve_for_payout(managed_pool_address, amount);

    let staking_status = borrow_global_mut<StakingStatus>(
        @tortuga_governance
    );
    event::emit_event<UnlockFromValidatorEvent>(
        &mut staking_status.unlock_from_validator_events,
        UnlockFromValidatorEvent {
            managed_pool_address: managed_pool_address,
            amount: amount,
            timestamp: timestamp::now_seconds()
        }
    );
}

Function stake_coins

Stakes the given APT coins and returns tAPT coins.

fun stake_coins(coins_to_stake: coin::Coin<aptos_coin::AptosCoin>): coin::Coin<staked_aptos_coin::StakedAptosCoin>
Implementation
fun stake_coins(
    coins_to_stake: coin::Coin<AptosCoin>
): coin::Coin<StakedAptosCoin>
acquires
    StakingStatus,
    StakedAptosCapabilities
{
    // must charge protocol fee so that the new delegator is not subject to
    // pending commission
    charge_protocol_fee_internal();

    let t_apt_supply = get_t_apt_supply();
    let total_worth = get_total_value();
    let amount = coin::value(&coins_to_stake);
    let shares_to_mint = calc_shares_to_mint(
        amount,
        total_worth,
        t_apt_supply
    );

    // mint shares
    let capabilities = borrow_global<StakedAptosCapabilities>(@tortuga_governance);
    let minted_t_apt_coin = coin::mint<StakedAptosCoin>(
        shares_to_mint,
        &capabilities.mint_cap
    );

    // store aptos coin in the reserve
    let staking_status = borrow_global_mut<StakingStatus>(@tortuga_governance);
    validator_router::deposit_coins_to_reserve(coins_to_stake);

    // modify staking status
    staking_status.commission_exempt_amount =
        staking_status.commission_exempt_amount + amount;

    minted_t_apt_coin
}

Function ensure_event_store

fun ensure_event_store(account: &signer)
Implementation
fun ensure_event_store(
    account: &signer
) {
    let account_address = signer::address_of(account);
    if (!exists<EventStore>(account_address)) {
        move_to(account, EventStore {
            stake_events: account::new_event_handle<StakeEvent>(account),
            unstake_events:
                account::new_event_handle<UnstakeEvent>(account),
            claim_events: account::new_event_handle<ClaimEvent>(account),
        });
    };
}

Function charge_protocol_fee_internal

Charge the protocol fee based on the rewards earned over the commission_exempt_amount and the current reward_commission.

fun charge_protocol_fee_internal()
Implementation
fun charge_protocol_fee_internal()
acquires
    StakedAptosCapabilities,
    StakingStatus
{
    let total_worth = get_total_value();
    let t_apt_supply = get_t_apt_supply();
    let staking_status = borrow_global_mut<StakingStatus>(@tortuga_governance);

    // This potentially underflows because when you first stake with a
    // validator.
    // You pay for last epoch's commissions, leading to a lower total worth
    // than your original deposit.
    let total_surplus_since_last_payment = math::safe_sub(
        total_worth,
        staking_status.commission_exempt_amount,
    );

    if (total_surplus_since_last_payment == 0) {
        return
    };
    let commission_amount = mul_div(
        total_surplus_since_last_payment,
        staking_status.commission,
        COMMISSION_NORMALIZER,
    );
    let total_worth_after_commission = total_worth - commission_amount;
    let new_shares_for_protocol = calc_shares_to_mint(
        commission_amount,
        total_worth_after_commission,
        t_apt_supply
    );

    // mint shares
    let capabilities = borrow_global<StakedAptosCapabilities>(
        @tortuga_governance
    );
    let minted_t_apt_coin = coin::mint<StakedAptosCoin>(
        new_shares_for_protocol,
        &capabilities.mint_cap
    );

    // deposit shares in the protocol_fee
    coin::merge<StakedAptosCoin>(
        &mut staking_status.protocol_fee,
        minted_t_apt_coin
    );

    // reset commission exempt amount as the commission so far has been paid
    staking_status.commission_exempt_amount = total_worth;
}

Function burn_ticket

Burn the passed ticket.

fun burn_ticket(ticket: stake_router::Ticket)
Implementation
fun burn_ticket(ticket: Ticket) {
    let Ticket { id: _id, amount: _amount, timestamp: _timestamp } = ticket;
}

Function calc_shares_to_mint

Calculate the number of shares to mint based on the value_being_added in APT, the total_worth (i.e. the TVL) of the protocol in APT, and the t_apt_supply.

fun calc_shares_to_mint(value_being_added: u64, total_worth: u64, t_apt_supply: u64): u64
Implementation
fun calc_shares_to_mint(
    value_being_added: u64,
    total_worth: u64,
    t_apt_supply: u64
): u64 {
    // Note: `t_apt_supply` can actually be 0 here, and `total_worth > 0`.
    // This can happen when the only person with tAPT unstakes and more
    // value accrues during the last epoch.
    //
    // In this case, the next staker gets more value,
    // but we mint them shares such that 1 tAPT = 1 APT.
    if (t_apt_supply == 0 && total_worth > 0 && value_being_added > 0) {
        return total_worth + value_being_added
    };

    math::mul_div_with_init(
        value_being_added,
        t_apt_supply,
        total_worth,
        1,
    )
}

Function calc_shares_to_value

Calculate the value of num_shares if the total_worth (TVL) of the protocol in APT, and the t_apt_supply.

Abort conditions

  • If t_apt_supply < num_shares.

fun calc_shares_to_value(num_shares: u64, total_worth: u64, t_apt_supply: u64): u64
Implementation
fun calc_shares_to_value(
    num_shares: u64,
    total_worth: u64,
    t_apt_supply: u64
): u64 {
    assert!(
        t_apt_supply >= num_shares,
        error::invalid_state(ETOO_MANY_SHARES)
    );
    math::safe_mul_div(num_shares, total_worth, t_apt_supply, 0)
}

protocol_fee: <>Stores the protocol commission collected so farcommission: u64The rate at which protocol charges commission.community_rebate: u64When a user calls unstake, this much amount is taken from the user and distributed among all the current users of the protocol.total_claims_balance: u128Total balance of all the tickets issues since genesis.total_claims_balance_cleared: u128Total balance of all the redeemed tickets since genesis.cooldown_period: u64time that a user must wait between ticket issuance and ticket redemption, and time that must elapse after contract deployment before unstake is available.deployment_time: u64timestamp of the contract deployment.commission_exempt_amount: u64principal over which protocol's commission is calculated.min_transaction_amount: u64minimum amount to delegate. Also, used for clearing tAPT dust.increase_stake_in_validator_events: <>Event handle for increasing stake in a validator.unlock_from_validator_events: <>Event handle for unlocking stake in a validator.

stake_events: <>Stores StakeEvents for the delegatorunstake_events: <>Stores UnstakeEvents for the delegatorclaim_events: <>Stores ClaimEvents for the delegator

mint_cap: <>Mint capabilityfreeze_cap: <>Freeze capabilityburn_cap: <>Burn capability

id: u128unique id, used to ensure first-in-first-out for claimsamount: u64amount in APT that will be redeemed: u64timestamp when the ticket was issued

tickets: <>List of all unclaimed tickets the user has.

delegator: addressaccount addressamount: u64amount staked in APTt_apt_coins: u64tAPT coins received: u64timestamp when the event happened

delegator: addressaccount addressamount: u64amount unstaked in APTt_apt_coins: u64tAPT coins burned: u64timestamp when the event happened

delegator: addressAccount addressamount: u64Amount claimed in APTticket_index: u64Index of the ticket in the : u64timestamp when the event happened

managed_pool_address: addressAddress of the validator's ManagedStakingPoolamount: u64The increase in stake in APT: u64timestamp when the event happened

managed_pool_address: addressAddress of the validator's ManagedStakingPoolamount: u64The amount of funds unlocked in APT: u64timestamp when the event happened

: u64: timestamp when the ticket was issued.

Only deployer can call this function.

If the commission is greater than , i.e. 100%.

Only can call this function.

If the value is greater than , i.e. 100%.

Only can call this function.

Only can call this function.

If the value is greater than .

Only can call this function.

The value must be less than .

Note: The tickets in are not guaranteed to be sorted by their field.

If the delegator does not have a .

If the validator balance will become more than the target delegation. See module for details.

If the amount is greater than the ().

If the amount is greater than the ().

Creates an for the given if it does not exist.

coin::MintCapability
staked_aptos_coin::StakedAptosCoin
coin::FreezeCapability
staked_aptos_coin::StakedAptosCoin
coin::BurnCapability
staked_aptos_coin::StakedAptosCoin
timestamp
timestamp
timestamp
timestamp
timestamp
timestamp
tortuga_governance
tortuga_governance
tortuga_governance
tortuga_governance
tortuga_governance
validator_states
coin::Coin
staked_aptos_coin::StakedAptosCoin
event::EventHandle
event::EventHandle
stake_router::IncreaseStakeInValidatorEvent
stake_router::UnlockFromValidatorEvent
event::EventHandle
event::EventHandle
event::EventHandle
stake_router::StakeEvent
stake_router::UnstakeEvent
stake_router::ClaimEvent
vector
stake_router::Ticket
timestamp
TicketStore
COMMISSION_NORMALIZER
COMMISSION_NORMALIZER
MAX_COOLDOWN_PERIOD
MAX_MIN_TRANSACTION_AMOUNT
timestamp
TicketStore
TicketStore
current_total_stakable_amount
current_deficit
account
EventStore
Resource StakingStatus
Resource EventStore
Resource StakedAptosCapabilities
Struct Ticket
Resource TicketStore
Struct StakeEvent
Struct UnstakeEvent
Struct ClaimEvent
Struct IncreaseStakeInValidatorEvent
Struct UnlockFromValidatorEvent
Constants
Function get_t_apt_supply
Function get_total_value
Function current_total_stakable_amount
Function current_deficit
Function get_num_tickets
Function get_ticket
Abort condition
Function initialize_tortuga_liquid_staking
Restrictions
Abort conditions
Function set_commission
Restrictions
Abort conditions
Function set_community_rebate
Restrictions
Abort conditions
Function set_cooldown_period
Restrictions
Abort conditions
Function set_min_transaction_amount
Restrictions
Function stake
Abort conditions
Function unstake
Abort conditions
Function claim
Abort conditions
Function increase_stake_in_validator
Abort conditions
Function unlock_from_validator
Abort conditions
Function stake_coins
Function ensure_event_store
Function charge_protocol_fee_internal
Function burn_ticket
Function calc_shares_to_mint
Function calc_shares_to_value
Abort conditions
validator_router