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::pool
  • module helpers::pool
  • Struct Pool
  • Constants
  • Function create_pool
  • Function destroy_empty
  • Function is_shareholder
  • Function get_commission_exempt_amount
  • Function get_num_shares
  • Function share_supply
  • Function num_share_holders
  • Function buy_in
  • Function buy_out
  • Function remove_next_share_holder
  • Function reset_pool
  • Function pay_commission
  • Function calculate_owner_commission_shares_helper
  • Function calculate_commission_rebate_for_protocol
  • Function add_shares
  • Function remove_shares
  1. Protocol
  2. Reference

helpers::pool

Previoushelpers::mathNexthelpers::stake_pool_helpers

Last updated 2 years ago

Module 0xc0ded0c0::pool

module helpers::pool

A data structure to keep track of ownership in a shared pool of assets.

It handles:

  1. buy_in and buy_out of shares for a specific address.

  2. maintaining the cost basis (or commission_exempt_amount) of the shares for commission calculations.

  3. minting extra shares to represent commission.

use 0x1::error;
use 0x1::option;
use 0xc0ded0c0::iterable_table_custom;
use 0xc0ded0c0::math;

Struct Pool

struct Pool has store
Fields

Constants

When the pool is not empty and a destroy/reset is attempted.

const EPOOL_IS_NOT_EMPTY: u64 = 1;

When a shareholder more shares than they own.

const ENOT_ENOUGH_SHARES: u64 = 0;

Function create_pool

Create a new pool.

public fun create_pool(): pool::Pool
Implementation
public fun create_pool(): Pool {
    Pool {
        shares: iterable_table_custom::new<address, u64>(),
        share_supply: 0,
        commission_exempt_amount: 0,
    }
}

Function destroy_empty

Destroy an empty pool, or destroy a pool with a single shareholder, the owner, remaining.

Abort conditions

  • If the pool is not empty.

public fun destroy_empty(pool: pool::Pool, owner_address: address, remove_owner_first: bool)
Implementation
public fun destroy_empty(
    pool: Pool,
    owner_address: address,
    remove_owner_first: bool
) {
    let share_supply = pool.share_supply;
    if (remove_owner_first) {
        // if the pool is not empty, the owner should be the only one left
        remove_shares(
            &mut pool,
            owner_address,
            share_supply,
        )
    };

    assert!(pool.share_supply == 0, error::invalid_state(EPOOL_IS_NOT_EMPTY));
    let Pool {
        shares: shares,
        share_supply: _,
        commission_exempt_amount: _,
    } = pool;

    iterable_table_custom::destroy_empty<address, u64>(shares);
}

Function is_shareholder

public fun is_shareholder(pool: &pool::Pool, shareholder_in_question: address): bool
Implementation
public fun is_shareholder(pool: &Pool, shareholder_in_question: address): bool {
    iterable_table_custom::contains(&pool.shares, shareholder_in_question)
}

Function get_commission_exempt_amount

public fun get_commission_exempt_amount(pool: &pool::Pool): u64
Implementation
public fun get_commission_exempt_amount(pool: &Pool): u64 {
    pool.commission_exempt_amount
}

Function get_num_shares

public fun get_num_shares(pool: &pool::Pool, shareholder: address): u64
Implementation
public fun get_num_shares(pool: &Pool, shareholder: address): u64 {
    math::safe_get_from_iterable_table_custom(
        &pool.shares,
        shareholder,
    )
}

Function share_supply

public fun share_supply(pool: &pool::Pool): u64
Implementation
public fun share_supply(pool: &Pool): u64 {
    pool.share_supply
}

Function num_share_holders

public fun num_share_holders(pool: &pool::Pool): u64
Implementation
public fun num_share_holders(pool: &Pool): u64 {
    iterable_table_custom::length<address, u64>(&pool.shares)
}

Function buy_in

public fun buy_in(pool: &mut pool::Pool, shareholder: address, num_shares: u64, amount: u64)
Implementation
public fun buy_in(
    pool: &mut Pool,
    shareholder: address,
    num_shares: u64,
    amount: u64
) {
    add_shares(pool, shareholder, num_shares);
    let commission_exempt_amount = &mut pool.commission_exempt_amount;
    *commission_exempt_amount = *commission_exempt_amount + amount;
}

Function buy_out

public fun buy_out(pool: &mut pool::Pool, shareholder: address, num_shares: u64, amount: u64)
Implementation
public fun buy_out(pool: &mut Pool, shareholder: address, num_shares: u64, amount: u64) {
    remove_shares(pool, shareholder, num_shares);
    let commission_exempt_amount = &mut pool.commission_exempt_amount;
    *commission_exempt_amount = *commission_exempt_amount - amount;
}

Function remove_next_share_holder

Abort conditions

  • If the pool is empty, i.e., there are no shareholders.

Usage notes

This function is not safe. Only call it after checking there are shares to remove. This should only be used in a loop which removes all shareholders, together with reset_pool to set defaults at the end.

public fun remove_next_share_holder(pool: &mut pool::Pool): (address, u64)
Implementation
public fun remove_next_share_holder(pool: &mut Pool):(address, u64) {
    let key = iterable_table_custom::head_key<address, u64>(&pool.shares);
    let key_address = *option::borrow(&key);
    let (num_shares, _, _) = iterable_table_custom::remove_iter<address, u64>(
        &mut pool.shares,
        key_address,
    );
    (key_address, num_shares)
}

Function reset_pool

Reset share_supply and commission_exempt_amount to 0.

Abort conditions

public fun reset_pool(pool: &mut pool::Pool)
Implementation
public fun reset_pool(pool: &mut Pool) {
    assert!(
        iterable_table_custom::length<address, u64>(&pool.shares) == 0,
        EPOOL_IS_NOT_EMPTY
    );
    pool.commission_exempt_amount = 0;
    pool.share_supply = 0;
}

Function pay_commission

public fun pay_commission(pool: &mut pool::Pool, commission_recipient: address, new_total_balance: u64, commission: u64, protocol_commission: u64, commission_normalizer: u64, protocol_delegator_address: address): u64
Implementation
public fun pay_commission(
    pool: &mut Pool,
    commission_recipient: address,
    new_total_balance: u64,
    commission: u64,
    protocol_commission: u64,
    commission_normalizer: u64,
    protocol_delegator_address: address,
): u64 {
    // short circuit if no commission is due
    if (
        new_total_balance == pool.commission_exempt_amount ||
            commission == 0
    ) {
        return 0
    };

    // compute the new shares for commission recipient and potential rebate
    // for protocol delegator
    let (
        new_shares_for_owner,
        protocol_delegator_shares,
        total_owner_commission
    ) = calculate_owner_commission_shares_helper(
        pool.share_supply,
        new_total_balance,
        pool.commission_exempt_amount,
        commission,
        protocol_commission,
        commission_normalizer,
        get_num_shares(pool, protocol_delegator_address),
    );

    add_shares(pool, protocol_delegator_address, protocol_delegator_shares);
    add_shares(pool, commission_recipient, new_shares_for_owner);

    // reset exempt amount as we've already paid commissions
    let commission_exempt_amount = &mut pool.commission_exempt_amount;
    *commission_exempt_amount = new_total_balance;

    total_owner_commission
}

Function calculate_owner_commission_shares_helper

Returns the news shares for commission_recipient, for protocol_delegator, and the total value of the new shares minted.

fun calculate_owner_commission_shares_helper(share_supply: u64, total_balance: u64, commission_exempt: u64, current_commission: u64, protocol_commission: u64, commission_normalizer: u64, protocol_delegator_existing_shares: u64): (u64, u64, u64)
Implementation
fun calculate_owner_commission_shares_helper(
    share_supply: u64,
    total_balance: u64,
    commission_exempt: u64,
    current_commission: u64,
    protocol_commission: u64,
    commission_normalizer: u64,
    protocol_delegator_existing_shares: u64,
): (u64, u64, u64) {
    if (share_supply == 0) {
        return (0, 0, 0)
    };

    let total_generated = total_balance - commission_exempt;
    // Amount owed as commission
    let total_owner_commission = math::mul_div(
        total_generated,
        current_commission,
        commission_normalizer,
    );
    let total_worth_after_commission =
        total_balance - total_owner_commission;

    if (total_owner_commission == 0) {
        return (0, 0, 0)
    };

    assert!(
        commission_exempt > 0 &&
            total_worth_after_commission >= commission_exempt,
        error::invalid_state(0)
    );

    // Existing shares are now worth:
    //  (total_worth_after_commission / share_supply)
    // Shares are minted at that price for the amount of
    // `total_owner_commission`
    let new_shares_for_owner = math::mul_div(
        total_owner_commission,
        share_supply,
        total_worth_after_commission,
    );

    // rebate for the protocol delegator
    let rebate_shares = calculate_commission_rebate_for_protocol(
        protocol_delegator_existing_shares,
        new_shares_for_owner,
        share_supply,
        current_commission,
        protocol_commission,
    );

    (
        new_shares_for_owner - rebate_shares,
        rebate_shares,
        total_owner_commission,
    )
}

Function calculate_commission_rebate_for_protocol

Returns the rebate shares for the protocol delegator. This is calculated separately because the protocol might have a lower commission rate than the default commission rate charged to other delegators.

fun calculate_commission_rebate_for_protocol(protocol_delegator_existing_shares: u64, new_shares_for_owner: u64, share_supply: u64, current_commission: u64, protocol_commission: u64): u64
Implementation
fun calculate_commission_rebate_for_protocol(
    protocol_delegator_existing_shares: u64,
    new_shares_for_owner: u64,
    share_supply: u64,
    current_commission: u64,
    protocol_commission: u64,
): u64 {

    if (
        protocol_delegator_existing_shares > 0 &&
            current_commission > 0 &&
            protocol_commission < current_commission
    ) {
        // how many of the shares belong to protocol if the protocol
        // commission was zero
        let new_shares_for_protocol = math::mul_div(
            new_shares_for_owner,
            protocol_delegator_existing_shares,
            share_supply,
        );
        // percentage rebate for protocol
        return math::mul_div(
            new_shares_for_protocol,
            (current_commission - protocol_commission),
            current_commission,
        )
    };

    0
}

Function add_shares

fun add_shares(pool: &mut pool::Pool, shareholder: address, num_shares: u64)
Implementation
fun add_shares(pool: &mut Pool, shareholder: address, num_shares: u64) {
    math::safe_add_to_iterable_table_custom(
        &mut pool.shares,
        shareholder,
        num_shares,
    );
    let share_supply = &mut pool.share_supply;
    *share_supply = *share_supply + num_shares;
}

Function remove_shares

fun remove_shares(pool: &mut pool::Pool, shareholder: address, num_shares: u64)
Implementation
fun remove_shares(pool: &mut Pool, shareholder: address, num_shares: u64) {
    assert!(
        get_num_shares(pool, shareholder) >= num_shares,
        ENOT_ENOUGH_SHARES
    );

    math::subtract_from_iterable_table_custom(
        &mut pool.shares,
        shareholder,
        num_shares,
    );
    let share_supply = &mut pool.share_supply;
    *share_supply = *share_supply - num_shares;
}

shares: <address, u64>Mapping of address to number of shares they ownshare_supply: u64Total sharescommission_exempt_amount: u64Principal amount on which commission is calculated. Resets to total value of the pool after commission has been paid.

Returns whether the shareholder_in_question is in the . Because zero values are removed from the map, this means they must have non zero shares.

Returns get_commission_exempt_amount of the .

Returns number of shares for shareholder in the . Returns 0 if the shareholder does not exist.

Returns the total number of shares in the .

Returns the total number of shareholders in the .

Add num_shares to shareholder in the . Increase commission_exempt_amount by amount, which the new shares are worth.

Remove num_shares from shareholder in the . Decrease commission_exempt_amount by amount which removed shares are worth.

Removes the shareholder at the head of the iterable table in the . Returns the address of the removed shareholder and the number of shares removed.

If the has no shareholders.

This dilutes the shares of all shareholders of the according to commission amount due, by minting new shares for the commission_recipient. It allows a special (lower) commission rate protocol_commission for the protocol_delegator_address, which gets some of the newly minted shares as a rebate. Returns the total new number of shares minted.

Add num_shares for shareholder in the and increase the share supply

Remove num_shares from the and decrease the share supply. If a shareholders ends up with 0 shares, remove their entry from the table.

module helpers::pool
Struct Pool
Constants
Function create_pool
Function destroy_empty
Abort conditions
Function is_shareholder
Function get_commission_exempt_amount
Function get_num_shares
Function share_supply
Function num_share_holders
Function buy_in
Function buy_out
Function remove_next_share_holder
Abort conditions
Usage notes
Function reset_pool
Abort conditions
Function pay_commission
Function calculate_owner_commission_shares_helper
Function calculate_commission_rebate_for_protocol
Function add_shares
Function remove_shares
pool
pool
pool
pool
pool
pool
pool
pool
pool
pool
pool
pool
iterable_table_custom::IterableTableCustom