helpers::pool

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

shares: iterable_table_custom::IterableTableCustom<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.

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

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

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

Returns get_commission_exempt_amount of the pool.

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

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

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

Returns the total number of shares in the pool.

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

Function num_share_holders

Returns the total number of shareholders in the pool.

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

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

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

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

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

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

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

  • If the pool has no shareholders.

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

This dilutes the shares of all shareholders of the pool 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.

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

Add num_shares for shareholder in the pool and increase the share supply

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

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

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;
}

Last updated