Sui Move 验证者质押staking_pool 分析

  • justin
  • 更新于 2024-08-26 19:01
  • 阅读 235

前言Sui使用DPoS权益证明作为共识的核心。用户可以把手上的SUI委托质押给验证者节点,在每一个epoch周期内,交易的gas费、存储费用、质押补贴等收入都会分给验证节点和他们的质押用户。每个验证者节点都维护着自己的质押池子,并根据exchangerates算法来计算每个用户的质押奖励核心

前言

Sui使用DPoS权益证明作为共识的核心。用户可以把手上的SUI委托质押给验证者节点,在每一个epoch周期内,交易的gas费、存储费用、质押补贴等收入都会分给验证节点和他们的质押用户。

每个验证者节点都维护着自己的质押池子,并根据exchange rates算法来计算每个用户的质押奖励

核心的逻辑都是通过MOVE在链上完成的,staking_pool.move,本文将对该合约进行详细的分析

数据结构

每个validator都会维护一个StakingPool, 在SUI的POS共识中,都是按照纪元epoch周期来结算的, 当前质押SUI到validator后,要等到下一个epoch才会生效, 同样的withdraw取回质押后对应的兑换率生效也是在下一个epoch

public struct StakingPool has key, store {
    id: UID,
    activation_epoch: Option<u64>,                      // 池子生效的epoch
    deactivation_epoch: Option<u64>,                    // 池子失效的epoch
    sui_balance: u64,                                   // 池子里sui的总量,包括质押的sui和验证者奖励的sui
    rewards_pool: Balance<SUI>,                         // 在每一个epoch结束收到的验证者奖励
    pool_token_balance: u64,                            // 池子发行的token总量
    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将要生效取出的token数量
    extra_fields: Bag,                                  
}

兑换率其实就是SUI/TOKEN之间数量的比值

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

StakedSui是质押池的核心数据结构,这里的store能力是sip6新增的,方便第三方流动性质押协议(vSUI、haSUI、afSUI)通过StakedSui来管理质押的实际情况

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

核心方法

质押request_add_stake

request_add_stake 质押SUI操作,质押的数量添加到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

request_withdraw_stake 根据StakedSui取回质押的SUI

public(package) fun request_withdraw_stake(
    pool: &mut StakingPool,
    staked_sui: StakedSui,
    ctx: &TxContext
) : Balance<SUI> {
    // 如果质押还未生效,就直接解构StakedSui,取回里面的SUI balance
    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
    };

    // 根据StakedSui质押生效的兑换率得到需要取回的token数量
    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()
    );

    // 总共需要取回的SUI数量,包括质押的SUI和质押奖励的SUI
    let total_sui_withdraw_amount = principal_withdraw_amount + rewards_withdraw.value();

    // 加入到pending中
    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;

    // 如果池子已经inactive状态了,则直接取回,不用等下一个epoch了
    if (is_inactive(pool)) process_pending_stake_withdraw(pool);

    principal_withdraw.join(rewards_withdraw);
    principal_withdraw
}
public(package) fun withdraw_from_principal(
    pool: &StakingPool,
    staked_sui: StakedSui,
) : (u64, Balance<SUI>) {

    // Check that the stake information matches the pool.
    assert!(staked_sui.pool_id == object::id(pool), EWrongPool);

    // 根据StakedSui质押时候的兑换率
    let exchange_rate_at_staking_epoch = pool_token_exchange_rate_at_epoch(pool, staked_sui.stake_activation_epoch);
    let principal_withdraw = unwrap_staked_sui(staked_sui);
    let pool_token_withdraw_amount = get_token_amount(
    &exchange_rate_at_staking_epoch,
    principal_withdraw.value()
);

    (
        pool_token_withdraw_amount,
        principal_withdraw,
    )
}

计算质押的SUI收益,从rewards_pool中取出sui balance

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

epoch驱动方法process_pending_stakes_and_withdraws

每当新的epoch来临的时候,都会处理一下pending,同时把最新的兑换率添加到exchange_rates中去

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质押池的Move合约,该合约实现了对单个validator进行质押SUI,然后从validator的rewards_pool中获取质押收益

点赞 0
收藏 1
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
justin
justin
Move Newbie