{"author":{"address":null,"user":"https://learnblockchain.cn/people/19525"},"content":{"body":"## 前言\r\nSui使用DPoS权益证明作为共识的核心。用户可以把手上的SUI委托质押给验证者节点，在每一个epoch周期内，交易的gas费、存储费用、质押补贴等收入都会分给验证节点和他们的质押用户。\r\n\r\n每个验证者节点都维护着自己的质押池子，并根据exchange rates算法来计算每个用户的质押奖励\r\n\r\n核心的逻辑都是通过MOVE在链上完成的，[staking_pool.move](https://github.com/MystenLabs/sui/blob/main/crates/sui-framework/packages/sui-system/sources/staking_pool.move)，本文将对该合约进行详细的分析\r\n\r\n## 数据结构\r\n每个validator都会维护一个**StakingPool**, 在SUI的POS共识中，都是按照纪元**epoch**周期来结算的, 当前质押SUI到validator后，要等到下一个**epoch**才会生效, 同样的withdraw取回质押后对应的兑换率生效也是在下一个**epoch**中\r\n\r\n```\r\npublic struct StakingPool has key, store {\r\n    id: UID,\r\n    activation_epoch: Option\u003cu64\u003e,                      // 池子生效的epoch\r\n    deactivation_epoch: Option\u003cu64\u003e,                    // 池子失效的epoch\r\n    sui_balance: u64,                                   // 池子里sui的总量，包括质押的sui和验证者奖励的sui\r\n    rewards_pool: Balance\u003cSUI\u003e,                         // 在每一个epoch结束收到的验证者奖励\r\n    pool_token_balance: u64,                            // 池子发行的token总量\r\n    exchange_rates: Table\u003cu64, PoolTokenExchangeRate\u003e,  // 每个epoch对应的SUI/TOKEN兑换率\r\n    pending_stake: u64,                                 // 下一个epoch将要生效质押的sui数量\r\n    pending_total_sui_withdraw: u64,                    // 下一个epoch将要生效取出的sui数量\r\n    pending_pool_token_withdraw: u64,                   // 下一个epoch将要生效取出的token数量\r\n    extra_fields: Bag,                                  \r\n}\r\n```\r\n\r\n兑换率其实就是SUI/TOKEN之间数量的比值\r\n```\r\npublic struct PoolTokenExchangeRate has store, copy, drop {\r\n    sui_amount: u64,\r\n    pool_token_amount: u64,\r\n}\r\n```\r\n\r\n**StakedSui**是质押池的核心数据结构，这里的store能力是[sip6](https://github.com/sui-foundation/sips/blob/main/sips/sip-6.md)新增的，方便第三方流动性质押协议(vSUI、haSUI、afSUI)通过**StakedSui**来管理质押的实际情况\r\n\r\n```\r\npublic struct StakedSui has key, store {\r\n    id: UID,\r\n    pool_id: ID,        \r\n    stake_activation_epoch: u64, //质押生效的epoch\r\n    principal: Balance\u003cSUI\u003e,     //质押的SUI数量\r\n}\r\n```\r\n\r\n## 核心方法\r\n### 质押request_add_stake\r\n**request_add_stake** 质押SUI操作，质押的数量添加到pending_stake中等下一个epoch生效，最后返回**StakedSui**\r\n\r\n```\r\npublic(package) fun request_add_stake(\r\n    pool: \u0026mut StakingPool,\r\n    stake: Balance\u003cSUI\u003e,\r\n    stake_activation_epoch: u64,\r\n    ctx: \u0026mut TxContext\r\n) : StakedSui {\r\n    let sui_amount = stake.value();\r\n    assert!(!is_inactive(pool), EDelegationToInactivePool);\r\n    assert!(sui_amount \u003e 0, EDelegationOfZeroSui);\r\n    let staked_sui = StakedSui {\r\n        id: object::new(ctx),\r\n        pool_id: object::id(pool),\r\n        stake_activation_epoch,\r\n        principal: stake,\r\n    };\r\n    pool.pending_stake = pool.pending_stake + sui_amount;\r\n    staked_sui\r\n}\r\n```\r\n\r\n### 取回质押request_withdraw_stake\r\n**request_withdraw_stake** 根据StakedSui取回质押的SUI\r\n```\r\npublic(package) fun request_withdraw_stake(\r\n    pool: \u0026mut StakingPool,\r\n    staked_sui: StakedSui,\r\n    ctx: \u0026TxContext\r\n) : Balance\u003cSUI\u003e {\r\n    // 如果质押还未生效，就直接解构StakedSui，取回里面的SUI balance\r\n    if (staked_sui.stake_activation_epoch \u003e ctx.epoch()) {\r\n        let principal = unwrap_staked_sui(staked_sui);\r\n        pool.pending_stake = pool.pending_stake - principal.value();\r\n\r\n        return principal\r\n    };\r\n\r\n    // 根据StakedSui质押生效的兑换率得到需要取回的token数量\r\n    let (pool_token_withdraw_amount, mut principal_withdraw) =\r\n        withdraw_from_principal(pool, staked_sui);\r\n    let principal_withdraw_amount = principal_withdraw.value();\r\n\r\n    // 根据当前epoch的兑换率，计算得到可以获得的SUI奖励\r\n    let rewards_withdraw = withdraw_rewards(\r\n        pool, principal_withdraw_amount, pool_token_withdraw_amount, ctx.epoch()\r\n    );\r\n\r\n    // 总共需要取回的SUI数量，包括质押的SUI和质押奖励的SUI\r\n    let total_sui_withdraw_amount = principal_withdraw_amount + rewards_withdraw.value();\r\n\r\n    // 加入到pending中\r\n    pool.pending_total_sui_withdraw = pool.pending_total_sui_withdraw + total_sui_withdraw_amount;\r\n    pool.pending_pool_token_withdraw = pool.pending_pool_token_withdraw + pool_token_withdraw_amount;\r\n\r\n    // 如果池子已经inactive状态了，则直接取回，不用等下一个epoch了\r\n    if (is_inactive(pool)) process_pending_stake_withdraw(pool);\r\n\r\n    principal_withdraw.join(rewards_withdraw);\r\n    principal_withdraw\r\n}\r\n```\r\n\r\n```\r\npublic(package) fun withdraw_from_principal(\r\n    pool: \u0026StakingPool,\r\n    staked_sui: StakedSui,\r\n) : (u64, Balance\u003cSUI\u003e) {\r\n\r\n    // Check that the stake information matches the pool.\r\n    assert!(staked_sui.pool_id == object::id(pool), EWrongPool);\r\n\r\n    // 根据StakedSui质押时候的兑换率\r\n    let exchange_rate_at_staking_epoch = pool_token_exchange_rate_at_epoch(pool, staked_sui.stake_activation_epoch);\r\n    let principal_withdraw = unwrap_staked_sui(staked_sui);\r\n    let pool_token_withdraw_amount = get_token_amount(\r\n    \u0026exchange_rate_at_staking_epoch,\r\n    principal_withdraw.value()\r\n);\r\n\r\n    (\r\n        pool_token_withdraw_amount,\r\n        principal_withdraw,\r\n    )\r\n}\r\n```\r\n\r\n计算质押的SUI收益，从rewards_pool中取出sui balance\r\n```\r\nfun withdraw_rewards(\r\n    pool: \u0026mut StakingPool,\r\n    principal_withdraw_amount: u64,\r\n    pool_token_withdraw_amount: u64,\r\n    epoch: u64,\r\n) : Balance\u003cSUI\u003e {\r\n    let exchange_rate = pool_token_exchange_rate_at_epoch(pool, epoch);\r\n    let total_sui_withdraw_amount = get_sui_amount(\u0026exchange_rate, pool_token_withdraw_amount);\r\n    let mut reward_withdraw_amount =\r\n        if (total_sui_withdraw_amount \u003e= principal_withdraw_amount)\r\n            total_sui_withdraw_amount - principal_withdraw_amount\r\n        else 0;\r\n    reward_withdraw_amount = reward_withdraw_amount.min(pool.rewards_pool.value());\r\n    pool.rewards_pool.split(reward_withdraw_amount)\r\n}\r\n```\r\n\r\n### epoch驱动方法process_pending_stakes_and_withdraws\r\n每当新的epoch来临的时候，都会处理一下pending，同时把最新的兑换率添加到exchange_rates中去\r\n\r\n```\r\npublic(package) fun process_pending_stakes_and_withdraws(pool: \u0026mut StakingPool, ctx: \u0026TxContext) {\r\n    let new_epoch = ctx.epoch() + 1;\r\n    process_pending_stake_withdraw(pool);\r\n    process_pending_stake(pool);\r\n    pool.exchange_rates.add(\r\n        new_epoch,\r\n        PoolTokenExchangeRate { sui_amount: pool.sui_balance, pool_token_amount: pool.pool_token_balance },\r\n    );\r\n    check_balance_invariants(pool, new_epoch);\r\n}\r\n```\r\n\r\n```\r\nfun process_pending_stake_withdraw(pool: \u0026mut StakingPool) {\r\n    pool.sui_balance = pool.sui_balance - pool.pending_total_sui_withdraw;\r\n    pool.pool_token_balance = pool.pool_token_balance - pool.pending_pool_token_withdraw;\r\n    pool.pending_total_sui_withdraw = 0;\r\n    pool.pending_pool_token_withdraw = 0;\r\n}\r\n```\r\n\r\n```\r\npublic(package) fun process_pending_stake(pool: \u0026mut StakingPool) {\r\n    let latest_exchange_rate =\r\n        PoolTokenExchangeRate { sui_amount: pool.sui_balance, pool_token_amount: pool.pool_token_balance };\r\n    pool.sui_balance = pool.sui_balance + pool.pending_stake;\r\n    pool.pool_token_balance = get_token_amount(\u0026latest_exchange_rate, pool.sui_balance);\r\n    pool.pending_stake = 0;\r\n}\r\n```\r\n\r\n## 总结\r\n本文分析了Sui质押池的Move合约，该合约实现了对单个validator进行质押SUI，然后从validator的rewards_pool中获取质押收益","title":"Sui Move 验证者质押staking_pool 分析"},"history":null,"timestamp":1724670108,"version":1}