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
protocol_fee: coin::Coin<staked_aptos_coin::StakedAptosCoin>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::EventHandle<stake_router::IncreaseStakeInValidatorEvent>Event handle for increasing stake in a validator.unlock_from_validator_events: event::EventHandle<stake_router::UnlockFromValidatorEvent>Event handle for unlocking stake in a validator.
Resource EventStore
Struct which holds handles for events related to staking, unstaking, and claiming tickets. It is saved with the delegator's accounts.
id: u128unique id, used to ensure first-in-first-out for claimsamount: u64amount in APT that will be redeemedtimestamp: u64timestamp when the ticket was issued
Resource TicketStore
A user can have several tickets which are stored in their ticket store
delegator: addressaccount addressamount: u64amount staked in APTt_apt_coins: u64tAPT coins receivedtimestamp: u64timestamp when the event happened
Struct UnstakeEvent
Event emitted when a user unstakes
struct UnstakeEvent has drop, store
Fields
delegator: addressaccount addressamount: u64amount unstaked in APTt_apt_coins: u64tAPT coins burnedtimestamp: u64timestamp when the event happened
Struct ClaimEvent
Event emitted when a user claims a ticket
struct ClaimEvent has drop, store
Fields
delegator: addressAccount addressamount: u64Amount claimed in APTticket_index: u64Index of the ticket in the TicketStoretimestamp: u64timestamp when the event happened
Struct IncreaseStakeInValidatorEvent
Event emitted when a user increases stake in a validator
struct IncreaseStakeInValidatorEvent has drop, store
Fields
managed_pool_address: addressAddress of the validator's ManagedStakingPoolamount: u64The increase in stake in APTtimestamp: u64timestamp when the event happened
Struct UnlockFromValidatorEvent
Event emitted when a user unlocks funds from a validator
struct UnlockFromValidatorEvent has drop, store
Fields
managed_pool_address: addressAddress of the validator's ManagedStakingPoolamount: u64The amount of funds unlocked in APTtimestamp: u64timestamp when the event happened
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.
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,
timestamp: u64: timestamp when the ticket was issued.
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.
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.
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.
Note: The tickets in TicketStore are not guaranteed to be sorted by their timestamp field.
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.
If the validator balance will become more than the target delegation. See validator_states module for details.
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.
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
}
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