helpers::pool
A data structure to keep track of ownership in a shared pool of assets.
It handles:
- 1.
buy_in
andbuy_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 has store
shares:
iterable_table_custom::IterableTableCustom
<
address
, u64>
Mapping of address to number of shares they ownshare_supply: u64
Total sharescommission_exempt_amount: u64
Principal amount on which commission is calculated. Resets to total value of the pool after commission has been paid.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;
Create a new pool.
public fun create_pool(): pool::Pool
Destroy an empty pool, or destroy a pool with a single shareholder, the owner, remaining.
- If the pool is not empty.
public fun destroy_empty(pool: pool::Pool, owner_address: address, remove_owner_first: bool)
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);
}
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
public fun get_commission_exempt_amount(pool: &pool::Pool): u64
public fun get_num_shares(pool: &pool::Pool, shareholder: address): u64
public fun share_supply(pool: &pool::Pool): u64
public fun num_share_holders(pool: &pool::Pool): u64
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)
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)
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.- If the pool is empty, i.e., there are no shareholders.
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)
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)
}
Reset
share_supply
and commission_exempt_amount
to 0
.public fun reset_pool(pool: &mut pool::Pool)
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
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
}
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)
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,
)
}
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
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
}
fun add_shares(pool: &mut pool::Pool, shareholder: address, num_shares: u64)
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)
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 modified 7mo ago