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)
}
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.
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) {
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.