Introduction
Sui utilizes Delegated Proof-of-Stake (DPoS) as the core of its consensus mechanism. Users can stake their SUI tokens with validator nodes, sharing transaction fees, storage costs, and staking rewards during each epoch cycle. Each validator maintains its own staking pool, calculating user rewards through an exchange rate algorithm.
All core logic is implemented via Move on-chain (staking_pool.move). This article provides a detailed analysis of this contract.
Key Features
- Epoch-based operations: All staking operations become effective in the next epoch
- Exchange rate mechanism: Calculates SUI/token conversion rates for fair reward distribution
- Validator rewards: Distributed from gas fees and network subsidies
- Transparent accounting: Tracks all pending and active stakes precisely
Data Structures
Each validator maintains a StakingPool with these critical components:
public struct StakingPool has key, store {
id: UID,
activation_epoch: Option<u64>, // Epoch when pool becomes active
deactivation_epoch: Option<u64>, // Epoch when pool becomes inactive
sui_balance: u64, // Total SUI including staked tokens and rewards
rewards_pool: Balance<SUI>, // Validator rewards received each epoch
pool_token_balance: u64, // Total issued pool tokens
exchange_rates: Table<u64, PoolTokenExchangeRate>, // SUI/token rates per epoch
pending_stake: u64, // SUI staking to activate next epoch
pending_total_sui_withdraw: u64, // SUI withdrawals pending
pending_pool_token_withdraw: u64, // Token withdrawals pending
extra_fields: Bag,
}The exchange rate represents the SUI/token ratio:
public struct PoolTokenExchangeRate has store, copy, drop {
sui_amount: u64,
pool_token_amount: u64,
}StakedSui serves as the core staking receipt, with SIP-6 enabled store capability for compatibility with third-party liquid staking protocols:
public struct StakedSui has key, store {
id: UID,
pool_id: ID,
stake_activation_epoch: u64, // Epoch when stake becomes active
principal: Balance<SUI>, // Staked SUI amount
}Core Methods
Staking: request_add_stake
This method initiates staking by:
- Adding SUI to pending stake
- Creating a StakedSui receipt
- Making changes effective next epoch
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!(!is_inactive(pool), EDelegationToInactivePool);
assert!(sui_amount > 0, EDelegationOfZeroSui);
let staked_sui = StakedSui {
id: object::new(ctx),
pool_id: object::id(pool),
stake_activation_epoch,
principal: stake,
};
pool.pending_stake = pool.pending_stake + sui_amount;
staked_sui
}Withdrawal: request_withdraw_stake
This method processes withdrawals by:
- Calculating principal and rewards
- Adding to pending withdrawals
- Processing immediately for inactive pools
public(package) fun request_withdraw_stake(
pool: &mut StakingPool,
staked_sui: StakedSui,
ctx: &TxContext
) : Balance<SUI> {
if (staked_sui.stake_activation_epoch > ctx.epoch()) {
let principal = unwrap_staked_sui(staked_sui);
pool.pending_stake = pool.pending_stake - principal.value();
return principal
};
let (pool_token_withdraw_amount, mut principal_withdraw) =
withdraw_from_principal(pool, staked_sui);
let rewards_withdraw = withdraw_rewards(
pool, principal_withdraw.value(), pool_token_withdraw_amount, ctx.epoch()
);
let total_sui_withdraw_amount = principal_withdraw.value() + 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 (is_inactive(pool)) process_pending_stake_withdraw(pool);
principal_withdraw.join(rewards_withdraw);
principal_withdraw
}Epoch Processing: process_pending_stakes_and_withdraws
This method handles:
- Processing pending withdrawals
- Activating new stakes
- Updating exchange rates for the new epoch
public(package) fun process_pending_stakes_and_withdraws(pool: &mut StakingPool, ctx: &TxContext) {
let new_epoch = ctx.epoch() + 1;
process_pending_stake_withdraw(pool);
process_pending_stake(pool);
pool.exchange_rates.add(
new_epoch,
PoolTokenExchangeRate {
sui_amount: pool.sui_balance,
pool_token_amount: pool.pool_token_balance
},
);
check_balance_invariants(pool, new_epoch);
}Key Considerations
- Epoch Timing: All staking changes take effect in the next epoch
- Reward Calculation: Uses precise exchange rate tracking
- Pool Status: Different rules apply for active vs. inactive pools
- Security Checks: Strict validation of all operations
👉 Explore more about Sui staking mechanics
FAQ
When do staking rewards become available?
Rewards are distributed at the end of each epoch and can be withdrawn in subsequent epochs.
How are exchange rates calculated?
Rates reflect the ratio between total SUI in the pool (stakes + rewards) and issued pool tokens.
Can I withdraw my stake immediately?
Withdrawals enter pending status and process in the next epoch, except for inactive pools.
What happens if a validator becomes inactive?
The pool stops accepting new stakes but processes all pending withdrawals immediately.
How are rewards calculated?
Rewards come from network fees and are proportional to your stake amount and duration.
👉 Learn about advanced staking strategies
Conclusion
The Sui staking pool contract demonstrates an elegant implementation of validator delegation with:
- Precise reward distribution via exchange rates
- Clear epoch-based operation processing
- Robust accounting for both active and pending stakes
- Flexible architecture supporting third-party integrations
This system ensures fair and transparent reward distribution while maintaining network security through validator staking.