Module sui_system::staking_pool
- Struct StakingPool
- Struct PoolTokenExchangeRate
- Struct StakedSui
- Struct FungibleStakedSui
- Struct FungibleStakedSuiData
- Struct FungibleStakedSuiDataKey
- Struct UnderflowSuiBalance
- Constants
- Function new
- Function request_add_stake
- Function request_withdraw_stake
- Function redeem_fungible_staked_sui
- Function calculate_fungible_staked_sui_withdraw_amount
- Function convert_to_fungible_staked_sui
- Function withdraw_from_principal
- Function unwrap_staked_sui
- Function deposit_rewards
- Function process_pending_stakes_and_withdraws
- Function process_pending_stake_withdraw
- Function process_pending_stake
- Function withdraw_rewards
- Function activate_staking_pool
- Function deactivate_staking_pool
- Function sui_balance
- Function pool_id
- Function fungible_staked_sui_pool_id
- Function staked_sui_amount
- Function stake_activation_epoch
- Function is_preactive
- Function activation_epoch
- Function is_inactive
- Function fungible_staked_sui_value
- Function split_fungible_staked_sui
- Function join_fungible_staked_sui
- Function split
- Function split_staked_sui
- Function join_staked_sui
- Function is_equal_staking_metadata
- Function pool_token_exchange_rate_at_epoch
- Function pending_stake_amount
- Function pending_stake_withdraw_amount
- Function exchange_rates
- Function sui_amount
- Function pool_token_amount
- Function is_preactive_at_epoch
- Function get_sui_amount
- Function get_token_amount
- Function initial_exchange_rate
- Function check_balance_invariants
- Macro function mul_div
- Function calculate_rewards
use std::address;
use std::ascii;
use std::bcs;
use std::option;
use std::string;
use std::type_name;
use std::u64;
use std::vector;
use sui::accumulator;
use sui::accumulator_metadata;
use sui::accumulator_settlement;
use sui::address;
use sui::bag;
use sui::balance;
use sui::bcs;
use sui::coin;
use sui::config;
use sui::deny_list;
use sui::dynamic_field;
use sui::dynamic_object_field;
use sui::event;
use sui::funds_accumulator;
use sui::hash;
use sui::hex;
use sui::object;
use sui::party;
use sui::sui;
use sui::table;
use sui::transfer;
use sui::tx_context;
use sui::types;
use sui::url;
use sui::vec_map;
use sui::vec_set;
Struct StakingPool
A staking pool embedded in each validator struct in the system state object.
public struct StakingPool has key, store
Fields
-
id: sui::object::UID -
activation_epoch: std::option::Option<u64> -
The epoch at which this pool became active.
The value is
Noneif the pool is pre-active andSome(<epoch_number>)if active or inactive. -
deactivation_epoch: std::option::Option<u64> -
The epoch at which this staking pool ceased to be active.
None= {pre-active, active},Some(<epoch_number>)if in-active, and it was de-activated at epoch<epoch_number>. -
sui_balance: u64 -
The total number of SUI tokens in this pool, including the SUI in the rewards_pool, as well as in all the principal
in the
object, updated at epoch boundaries.StakedSui -
rewards_pool: sui::balance::Balance<sui::sui::SUI> - The epoch stake rewards will be added here at the end of each epoch.
-
pool_token_balance: u64 - Total number of pool tokens issued by the pool.
-
exchange_rates: sui::table::Table<u64, sui_system::staking_pool::PoolTokenExchangeRate> -
Exchange rate history of previous epochs. Key is the epoch number.
The entries start from the
of this pool and contains exchange rates at the beginning of each epoch, i.e., right after the rewards for the previous epoch have been deposited into the pool.activation_epoch -
pending_stake: u64 - Pending stake amount for this epoch, emptied at epoch boundaries.
-
pending_total_sui_withdraw: u64 - Pending stake withdrawn during the current epoch, emptied at epoch boundaries. This includes both the principal and rewards SUI withdrawn.
-
pending_pool_token_withdraw: u64 - Pending pool token withdrawn during the current epoch, emptied at epoch boundaries.
-
extra_fields: sui::bag::Bag - Any extra fields that's not defined statically.
Struct PoolTokenExchangeRate
Struct representing the exchange rate of the stake pool token to SUI.
public struct PoolTokenExchangeRate has copy, drop, store
Fields
-
sui_amount: u64 -
pool_token_amount: u64
Struct StakedSui
A self-custodial object holding the staked SUI tokens.
public struct StakedSui has key, store
Fields
-
id: sui::object::UID -
pool_id: sui::object::ID - ID of the staking pool we are staking with.
-
stake_activation_epoch: u64 - The epoch at which the stake becomes active.
-
principal: sui::balance::Balance<sui::sui::SUI> - The staked SUI tokens.
Struct FungibleStakedSui
An alternative to
StakedSui that holds the pool token amount instead of the SUI balance.
StakedSui objects can be converted to FungibleStakedSuis after the initial warmup period.
The advantage of this is that you can now merge multiple StakedSui objects from different
activation epochs into a single FungibleStakedSui object.
public struct FungibleStakedSui has key, store
Fields
-
id: sui::object::UID -
pool_id: sui::object::ID - ID of the staking pool we are staking with.
-
value: u64 - The pool token amount.
Struct FungibleStakedSuiData
Holds useful information
public struct FungibleStakedSuiData has key, store
Fields
-
id: sui::object::UID -
total_supply: u64 - fungible_staked_sui supply
-
principal: sui::balance::Balance<sui::sui::SUI> - principal balance. Rewards are withdrawn from the reward pool
Struct FungibleStakedSuiDataKey
public struct FungibleStakedSuiDataKey has copy, drop, store
Fields
Struct UnderflowSuiBalance
Holds the amount of SUI that was underflowed when withdrawing from the pool post safe mode. Cleaned up in the same transaction.
public struct UnderflowSuiBalance has copy, drop, store
Fields
Constants
StakedSui objects cannot be split to below this amount.
const MIN_STAKING_THRESHOLD: u64 = 1000000000;
const EInsufficientPoolTokenBalance: u64 = 0;
const EWrongPool: u64 = 1;
const EWithdrawAmountCannotBeZero: u64 = 2;
const EInsufficientSuiTokenBalance: u64 = 3;
const EInsufficientRewardsPoolBalance: u64 = 4;
const EDestroyNonzeroBalance: u64 = 5;
const ETokenTimeLockIsSome: u64 = 6;
const EWrongDelegation: u64 = 7;
const EPendingDelegationDoesNotExist: u64 = 8;
const ETokenBalancesDoNotMatchExchangeRate: u64 = 9;
const EDelegationToInactivePool: u64 = 10;
const EDeactivationOfInactivePool: u64 = 11;
const EIncompatibleStakedSui: u64 = 12;
const EWithdrawalInSameEpoch: u64 = 13;
const EPoolAlreadyActive: u64 = 14;
const EPoolPreactiveOrInactive: u64 = 15;
const EActivationOfInactivePool: u64 = 16;
const EDelegationOfZeroSui: u64 = 17;
const EStakedSuiBelowThreshold: u64 = 18;
const ECannotMintFungibleStakedSuiYet: u64 = 19;
const EInvariantFailure: u64 = 20;
Function new
Create a new, empty staking pool.
public(package) fun new(ctx: &mut sui::tx_context::TxContext): sui_system::staking_pool::StakingPool
Implementation
public(package) fun new(ctx: &mut TxContext): StakingPool {
StakingPool {
id: object::new(ctx),
activation_epoch: option::none(),
deactivation_epoch: option::none(),
sui_balance: 0,
rewards_pool: balance::zero(),
pool_token_balance: 0,
exchange_rates: table::new(ctx),
pending_stake: 0,
pending_total_sui_withdraw: 0,
pending_pool_token_withdraw: 0,
extra_fields: bag::new(ctx),
}
}
Function request_add_stake
Request to stake to a staking pool. The stake starts counting at the beginning of the next epoch,
public(package) fun request_add_stake(pool: &mut sui_system::staking_pool::StakingPool, stake: sui::balance::Balance<sui::sui::SUI>, stake_activation_epoch: u64, ctx: &mut sui::tx_context::TxContext): sui_system::staking_pool::StakedSui
Implementation
public(package) fun request_add_stake(
pool: &mut StakingPool,
stake: Balance<SUI>,
stake_activation_epoch: u64,
ctx: &mut TxContext,
): StakedSui {
let sui_amount = stake.value();
assert!(!pool.is_inactive(), EDelegationToInactivePool);
assert!(sui_amount > 0, EDelegationOfZeroSui);
pool.pending_stake = pool.pending_stake + sui_amount;
StakedSui {
id: object::new(ctx),
pool_id: object::id(pool),
stake_activation_epoch,
principal: stake,
}
}
Function request_withdraw_stake
Request to withdraw the given stake plus rewards from a staking pool. Both the principal and corresponding rewards in SUI are withdrawn. A proportional amount of pool token withdraw is recorded and processed at epoch change time.
public(package) fun request_withdraw_stake(pool: &mut sui_system::staking_pool::StakingPool, staked_sui: sui_system::staking_pool::StakedSui, ctx: &sui::tx_context::TxContext): sui::balance::Balance<sui::sui::SUI>
Implementation
public(package) fun request_withdraw_stake(
pool: &mut StakingPool,
staked_sui: StakedSui,
ctx: &TxContext,
): Balance<SUI> {
// stake is inactive and the pool is not preactive - allow direct withdraw
// the reason why we exclude preactive pools is to avoid potential underflow
// on subtraction, and we need to enforce pending_stake_withdraw call.
if (staked_sui.stake_activation_epoch > ctx.epoch() && !pool.is_preactive()) {
let principal = staked_sui.into_balance();
pool.pending_stake = pool.pending_stake - principal.value();
return principal
};
let (pool_token_withdraw_amount, mut principal_withdraw) = pool.withdraw_from_principal(
staked_sui,
);
let principal_withdraw_amount = principal_withdraw.value();
let rewards_withdraw = pool.withdraw_rewards(
principal_withdraw_amount,
pool_token_withdraw_amount,
ctx.epoch(),
);
let total_sui_withdraw_amount = principal_withdraw_amount + rewards_withdraw.value();
pool.pending_total_sui_withdraw = pool.pending_total_sui_withdraw + total_sui_withdraw_amount;
pool.pending_pool_token_withdraw =
pool.pending_pool_token_withdraw + pool_token_withdraw_amount;
// If the pool is inactive or preactive, we immediately process the withdrawal.
if (pool.is_inactive() || pool.is_preactive()) pool.process_pending_stake_withdraw();
// TODO: implement withdraw bonding period here.
principal_withdraw.join(rewards_withdraw);
principal_withdraw
}
Function redeem_fungible_staked_sui
public(package) fun redeem_fungible_staked_sui(pool: &mut sui_system::staking_pool::StakingPool, fungible_staked_sui: sui_system::staking_pool::FungibleStakedSui, ctx: &sui::tx_context::TxContext): sui::balance::Balance<sui::sui::SUI>
Implementation
public(package) fun redeem_fungible_staked_sui(
pool: &mut StakingPool,
fungible_staked_sui: FungibleStakedSui,
ctx: &TxContext,
): Balance<SUI> {
let FungibleStakedSui { id, pool_id, value } = fungible_staked_sui;
assert!(pool_id == object::id(pool), EWrongPool);
id.delete();
let latest_exchange_rate = pool.pool_token_exchange_rate_at_epoch(ctx.epoch());
let fungible_staked_sui_data: &mut FungibleStakedSuiData =
&mut pool.extra_fields[FungibleStakedSuiDataKey {}];
let (
principal_amount,
rewards_amount,
) = latest_exchange_rate.calculate_fungible_staked_sui_withdraw_amount(
value,
fungible_staked_sui_data.principal.value(),
fungible_staked_sui_data.total_supply,
);
fungible_staked_sui_data.total_supply = fungible_staked_sui_data.total_supply - value;
let mut sui_out = fungible_staked_sui_data.principal.split(principal_amount);
sui_out.join(pool.rewards_pool.split(rewards_amount));
pool.pending_total_sui_withdraw = pool.pending_total_sui_withdraw + sui_out.value();
pool.pending_pool_token_withdraw = pool.pending_pool_token_withdraw + value;
sui_out
}
Function calculate_fungible_staked_sui_withdraw_amount
written in separate function so i can test with random values returns (principal_withdraw_amount, rewards_withdraw_amount)
fun calculate_fungible_staked_sui_withdraw_amount(latest_exchange_rate: sui_system::staking_pool::PoolTokenExchangeRate, fungible_staked_sui_value: u64, fungible_staked_sui_data_principal_amount: u64, fungible_staked_sui_data_total_supply: u64): (u64, u64)
Implementation
fun calculate_fungible_staked_sui_withdraw_amount(
latest_exchange_rate: PoolTokenExchangeRate,
fungible_staked_sui_value: u64,
fungible_staked_sui_data_principal_amount: u64, // fungible_staked_sui_data.principal.value()
fungible_staked_sui_data_total_supply: u64, // fungible_staked_sui_data.total_supply
): (u64, u64) {
// 1. if the entire FungibleStakedSuiData supply is redeemed, how much sui should we receive?
let total_sui_amount = latest_exchange_rate.get_sui_amount(
fungible_staked_sui_data_total_supply,
);
// min with total_sui_amount to prevent underflow
let fungible_staked_sui_data_principal_amount = fungible_staked_sui_data_principal_amount.min(
total_sui_amount,
);
// 2. how much do we need to withdraw from the rewards pool?
let total_rewards = total_sui_amount - fungible_staked_sui_data_principal_amount;
// 3. proportionally withdraw from both wrt the fungible_staked_sui_value.
let principal_withdraw_amount = mul_div!(
fungible_staked_sui_value,
fungible_staked_sui_data_principal_amount,
fungible_staked_sui_data_total_supply,
);
let rewards_withdraw_amount = mul_div!(
fungible_staked_sui_value,
total_rewards,
fungible_staked_sui_data_total_supply,
);
// invariant check, just in case
let expected_sui_amount = latest_exchange_rate.get_sui_amount(fungible_staked_sui_value);
assert!(
principal_withdraw_amount + rewards_withdraw_amount <= expected_sui_amount,
EInvariantFailure,
);
(principal_withdraw_amount, rewards_withdraw_amount)
}