Sui Move 验证者质押池运作机制与源码解析

·

概述

Sui 区块链采用 DPoS(委托权益证明)作为其共识机制的核心。用户可将持有的 SUI 代币委托质押给验证者节点,在每个 epoch(纪元)周期结束时,系统会将交易手续费、存储费用及质押补贴等收入分配给验证者及其质押用户。每个验证者节点均维护独立的质押池,并基于 exchange rates(兑换率)算法计算每位用户的质押奖励。本文将深入解析 Sui 质押池的 Move 合约实现,帮助读者理解其核心逻辑与运作细节。

质押池的核心数据结构

在 Sui 的 POS 共识机制中,所有质押操作均按 epoch 周期进行结算。当前 epoch 内发起的质押或取回操作,需等到下一个 epoch 方能生效。

StakingPool 结构

每个验证者均拥有一个 StakingPool 结构,用于管理其质押池状态:

public struct StakingPool has key, store {
    id: UID,
    activation_epoch: Option<u64>,          // 池子生效的 epoch
    deactivation_epoch: Option<u64>,        // 池子失效的 epoch
    sui_balance: u64,                       // 池内 SUI 总量(含质押与奖励)
    rewards_pool: Balance<SUI>,             // 每 epoch 结束时收到的验证者奖励
    pool_token_balance: u64,                // 池子发行的代币总量
    exchange_rates: Table<u64, PoolTokenExchangeRate>, // 各 epoch 的 SUI/TOKEN 兑换率
    pending_stake: u64,                     // 下 epoch 将生效的质押 SUI 数量
    pending_total_sui_withdraw: u64,        // 下 epoch 将生效的取回 SUI 总量
    pending_pool_token_withdraw: u64,       // 下 epoch 将生效的取回代币数量
    extra_fields: Bag,
}

PoolTokenExchangeRate 结构

PoolTokenExchangeRate 定义了 SUI 与池代币之间的数量兑换比率:

public struct PoolTokenExchangeRate has store, copy, drop {
    sui_amount: u64,
    pool_token_amount: u64,
}

StakedSui 结构

StakedSui 是代表用户质押的核心资产,其 store 能力由 SIP-6 引入,便于第三方流动性质押协议(如 vSUI、haSUI、afSUI)基于此结构管理质押资产:

public struct StakedSui has key, store {
    id: UID,
    pool_id: ID,
    stake_activation_epoch: u64,    // 质押生效的 epoch
    principal: Balance<SUI>,        // 质押的 SUI 数量
}

核心操作方法与流程

质押:request_add_stake

用户执行质押操作时,质押数量将暂存于 pending_stake 中,待下一 epoch 生效,并返回代表质押权益的 StakedSui 资产:

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
}

取回质押:request_withdraw_stake

用户可根据持有的 StakedSui 取回质押的 SUI,此操作涉及本金与收益的计算:

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 principal_withdraw_amount = principal_withdraw.value();
    // 计算当前 epoch 可获得的 SUI 奖励
    let rewards_withdraw = withdraw_rewards(
        pool, 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 (is_inactive(pool)) process_pending_stake_withdraw(pool);
    principal_withdraw.join(rewards_withdraw);
    principal_withdraw
}

收益计算过程中,系统会从 rewards_pool 中提取对应的 SUI 余额:

fun withdraw_rewards(
    pool: &mut StakingPool,
    principal_withdraw_amount: u64,
    pool_token_withdraw_amount: u64,
    epoch: u64,
): Balance<SUI> {
    let exchange_rate = pool_token_exchange_rate_at_epoch(pool, epoch);
    let total_sui_withdraw_amount = get_sui_amount(&exchange_rate, pool_token_withdraw_amount);
    let mut reward_withdraw_amount =
        if (total_sui_withdraw_amount >= principal_withdraw_amount)
            total_sui_withdraw_amount - principal_withdraw_amount
        else 0;
    reward_withdraw_amount = reward_withdraw_amount.min(pool.rewards_pool.value());
    pool.rewards_pool.split(reward_withdraw_amount)
}

纪元更新:process_pending_stakes_and_withdraws

每个新 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);
}

待处理取回操作会实时更新池余额:

fun process_pending_stake_withdraw(pool: &mut StakingPool) {
    pool.sui_balance = pool.sui_balance - pool.pending_total_sui_withdraw;
    pool.pool_token_balance = pool.pool_token_balance - pool.pending_pool_token_withdraw;
    pool.pending_total_sui_withdraw = 0;
    pool.pending_pool_token_withdraw = 0;
}

待处理质押操作则基于最新兑换率计算新增的代币发行量:

public(package) fun process_pending_stake(pool: &mut StakingPool) {
    let latest_exchange_rate =
        PoolTokenExchangeRate { sui_amount: pool.sui_balance, pool_token_amount: pool.pool_token_balance };
    pool.sui_balance = pool.sui_balance + pool.pending_stake;
    pool.pool_token_balance = get_token_amount(&latest_exchange_rate, pool.sui_balance);
    pool.pending_stake = 0;
}

常见问题

什么是 Sui 的质押池?

Sui 质押池是验证者节点用于管理用户质押 SUI 及分配奖励的智能合约结构。每个验证者拥有独立池子,按 epoch 周期结算收益,并通过兑换率算法确保奖励分配公平。

质押后何时开始产生收益?

质押操作在当前 epoch 发起后,需待下一 epoch 方正式生效并开始计算收益。同样,取回操作也需等到下一 epoch 才能完成处理。

如何计算质押奖励?

奖励基于兑换率机制计算。系统会记录每个 epoch 的 SUI 与池代币兑换比率,用户取回时根据其质押时的兑换率与当前兑换率差异核算应得奖励。

StakedSui 资产有何作用?

StakedSui 代表用户质押资产的所有权,其具备 store 能力,可被集成至各类 DeFi 协议中用于流动性质押,👉 探索更多质押策略,提升资金利用效率。

验证者池失效如何处理?

若验证者池被标记为失效(inactive),用户发起的取回操作将立即处理,无需等待下一 epoch,保障资产可随时退出。

总结

Sui 的质押池通过 Move 合约实现了高效且透明的委托权益管理机制。用户通过质押 SUI 参与网络安全维护并获得收益,验证者则借助灵活的兑换率算法确保奖励分配准确无误。本文深入剖析了质押池的核心数据结构与关键操作流程,为开发者与用户理解 Sui 质押经济模型提供了详细技术参考。