海外 RWA 合约实验:拆解 xStocks 股息分发器的派息计算架构

0 阅读9分钟

⚠️风险提示:本期仅为海外 Web3 技术合约案例科普,文中为境外实验项目合约逻辑演示,不对国内用户开放;我国境内禁止 RWA 代币化相关金融活动,不构成任何投资建议,切勿参与相关产品,参与风险全部自担


前言

在上一篇关于美股 RWA(真实世界资产)代币化防超发与合规准入的架构探讨中,我们解决了 “资产如何真实上链” 与 “身份如何合规隔离” 的问题。传统美股体系内存在现金派息机制,本文研究的是海外实验项目,如何在合约层对派息逻辑做链上映射实现:当传统美股产生派息时,对应的稳定币分红,在技术实验层面该如何精准、自动化且合规地分发到链上钱包。本文将硬核拆解 xStocks 的实验模块 ——DividendDistributor 股息分发器合约与自动化集成测试架构


一、 核心业务落地场景:美股派息的链上闭环

在传统金融中,美股上市公司派息需通过券商层层下发,流程长、效率低。而在 xStocks 的架构中,分红流转实现了一次颠覆性的链上闭环:

  1. 链下现金股利汇集(SPV 注入) : 传统券商收到美股上市公司的现金股利后,由 SPV(特殊目的载体)在链上将对应的法币收益转换为稳定币(如 USDC)。
  2. 全局每股累积收益计算: SPV 将 USDC 注入 DividendDistributor 合约。系统通过数学上的累积收益率算法(accRewardPerShare),自动将这笔资金按比例摊分给全网所有的股票代币持有者,彻底告别传统对账的繁琐。
  3. 持仓比例精准映射: 系统不采用笨重的逐个账户转账,而是基于持仓快照与动态增量,实现“多退少补、按股分配”。例如持仓 60% 的投资者 A 自动精准对应 60% 的分红额。
  4. 提取环节的终极 KYC 拦截: 即使分红已经计算并暂存,投资者在发起提取(claimDividend)的瞬间,合约仍会强制验证其当前是否依然具备合规的 KYC 白名单身份。一旦触发合规风险被取消白名单,资金绝无法被提取。

二、 核心代码实现架构与设计亮点

1. 累积收益率算法与防精度丢失

分发器核心采用了类似主流 DeFi 质押池的精确定向计算模型,引入了 ACC_PRECISION(10^18)放大因子,杜绝了整型除法运算中常见的精度丢失。

  • 收益计算修饰器 (updateReward) :在每次发生提取或状态交互前,自动结算用户的未领取分红:

    unpaidDelta=balance×(currentAccRewardPerShareuserRewardPerSharePaid[account])ACC_PRECISION\text{unpaidDelta} = \frac{\text{balance} \times (\text{currentAccRewardPerShare} - \text{userRewardPerSharePaid}[\text{account}])}{\text{ACC\_PRECISION}}

2. 双重安全合规防线

  • 安全转账保障:引入 OpenZeppelin 的 SafeERC20 库,防止非标准 ERC20 代币转账带来的安全漏洞。

  • 分红权的合规动态剥夺:在 claimDividend 方法中加入硬核拦截:

    require(rwaToken.isKycWhitelisted(msg.sender), "RWA: Recipient must be KYC whitelisted");
    

    这确保了即使投资者通过二级市场或早期获得了代币,一旦后续失去合规身份,其在合约中挂账的现金股利也会被严格锁死,完美契合全球监管对 AML/KYC 的动态合规要求。

3.股息分发器合约(DividendDistributor.sol)

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol";

// 引入 RwaStockToken 接口,用于读取持仓量及 KYC 白名单状态
interface IRwaStockToken {
 function balanceOf(address account) external view returns (uint256);
 function totalSupply() external view returns (uint256);
 function isKycWhitelisted(address account) external view returns (bool);
}

/**
* @title DividendDistributor
* @notice xStocks RWA 股息分发器:管理美股真实派息、按持仓比例计算并安全发放 USDC 分红
*/
contract DividendDistributor is AccessControl {
 using SafeERC20 for IERC20;

 // 绑定的 RWA 股票代币合约
 IRwaStockToken public immutable rwaToken;
 
 // 派发的股息稳定币合约 (如 USDC)
 IERC20 public immutable rewardToken;

 // 精度放大因子,防止整型除法精度丢失 (10^18)
 uint256 private constant ACC_PRECISION = 1e18;

 // 全局每股累积获得的股息收益率
 uint256 public accRewardPerShare;

 // 记录每个投资者上一次计算时的累积收益修正值 (rewardPerSharePaid)
 mapping(address => uint256) public userRewardPerSharePaid;
 
 // 记录每个投资者已产生但尚未提取的暂存收益
 mapping(address => uint256) public rewards;

 // 关键分红事件
 event DividendsDeposited(address indexed from, uint256 amount);
 event DividendClaimed(address indexed investor, uint256 amount);

 /**
  * @param _rwaToken RwaStockToken 部署地址
  * @param _rewardToken 稳定币 (USDC) 部署地址
  */
 constructor(address _rwaToken, address _rewardToken) {
     require(_rwaToken != address(0), "RWA: Invalid token address");
     require(_rewardToken != address(0), "RWA: Invalid reward token");

     rwaToken = IRwaStockToken(_rwaToken);
     rewardToken = IERC20(_rewardToken);

     _grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
 }

 /**
  * @notice 更新用户的未领取收益(内部状态更新函数)
  */
 modifier updateReward(address account) {
     if (account != address(0)) {
         rewards[account] = earned(account);
         userRewardPerSharePaid[account] = accRewardPerShare;
     }
     _;
 }

 /**
  * @notice 查询指定账户当前可领取的 USDC 股息总额
  */
 function earned(address account) public view returns (uint256) {
     uint256 balance = rwaToken.balanceOf(account);
     uint256 currentAccRewardPerShare = accRewardPerShare;
     
     uint256 totalSupply = rwaToken.totalSupply();
     // 防止初始分发前总供应量为 0 的异常
     if (totalSupply > 0 && balance > 0) {
         // 注意:如果通过 depositDividends 改变了 accRewardPerShare,这里计算增量
         // 由于我们在 depositDividends 中实时更新了 accRewardPerShare,这里直接用最新值
     }

     uint256 unpaidDelta = (balance * (currentAccRewardPerShare - userRewardPerSharePaid[account])) / ACC_PRECISION;
     return rewards[account] + unpaidDelta;
 }

 /**
  * @notice SPV 或管理员注入美股现金股利 (USDC)
  * @param amount 注入的 USDC 数量
  */
 function depositDividends(uint256 amount) external {
     require(amount > 0, "RWA: Cannot deposit zero dividends");

     uint256 totalSupply = rwaToken.totalSupply();
     require(totalSupply > 0, "RWA: No shares in circulation");

     // 从调用者安全转移 USDC 到本分红分发器合约
     rewardToken.safeTransferFrom(msg.sender, address(this), amount);

     // 核心数学:更新全局每股累积收益率
     accRewardPerShare += (amount * ACC_PRECISION) / totalSupply;

     emit DividendsDeposited(msg.sender, amount);
 }

 /**
  * @notice 投资者提取属于自己的链上 USDC 股息
  */
 function externalUpdate(address account) external updateReward(account) {}

 function claimDividend() external updateReward(msg.sender) {
     // 合规安全检查:提取前必须强制验证当前投资者依然拥有合法的 KYC 白名单身份
     require(rwaToken.isKycWhitelisted(msg.sender), "RWA: Recipient must be KYC whitelisted");

     uint256 payout = rewards[msg.sender];
     require(payout > 0, "RWA: No dividends to claim");

     // 清空暂存收益,防止重入与重复提取
     rewards[msg.sender] = 0;

     // 安全转账 USDC 给投资者
     rewardToken.safeTransfer(msg.sender, payout);

     emit DividendClaimed(msg.sender, payout);
 }
}

三、 自动化测试验证:全场景极限压测

结合配套的 Hardhat + Viem 自动化测试脚本,xStocks 股息分发系统通过了多项严苛验证:

  1. 初始持仓置信度测试: 验证投资者 A 与 B 分别持有 60 股与 40 股(总计 100 股),比例准确无误。
  2. 除权息业务闭环测试: 模拟 SPV 向分发器注入 1,000 USDC 股息。链上视图(earned)精准计算出 A 应分得 600 USDC,B 应分得 400 USDC。A 提取后到账精准无误,且重复领取时额度自动归零。
  3. 合规安全性压力测试: 模拟在注入 500 USDC 后,投资者 B 因突发合规风险被管理员移出白名单。此时 B 尝试提取分红,被 /RWA: Recipient must be KYC whitelisted/ 错误拦截,确保违规资金无法流出。
import assert from "node:assert/strict";
import { describe, it } from "node:test";
import { parseEther, getAddress } from "viem";
import { network } from "hardhat";

describe("xStocks RWA Dividend Distribution & DeFi Integration", function () {
  async function deployDividendFixture() {
    const { viem } = await (network as any).connect();
    const [owner, investorA, investorB, spvAccount] = await viem.getWalletClients();
    const publicClient = await viem.getPublicClient();

    // 1. 部署基础 Mock 稳定币 (USDC 模拟 6 位或 18 位,此处用 18 位方便 parseEther 演示)
    const mockUSDC = await viem.deployContract("BoykaYuriToken", [owner.account.address, owner.account.address]);

    // 2. 部署 RWA 股票代币合约
    const rwaToken = await viem.deployContract("RwaStockToken", [
      "xStocks Nvidia",
      "nvdax",
      owner.account.address
    ]);

    // 3. 部署分红分发器合约 (DividendDistributor)
    const distributor = await viem.deployContract("DividendDistributor", [
      rwaToken.address,
      mockUSDC.address
    ]);

    // 4. 权限初始化与基础白名单设置
    await rwaToken.write.setKycStatus([investorA.account.address, true], { account: owner.account });
    await rwaToken.write.setKycStatus([investorB.account.address, true], { account: owner.account });

    // 直接通过 MINTER_ROLE 铸造初始股票以构建持仓基础 (A 持有 60 股,B 持有 40 股,总计 100 股)
    const MINTER_ROLE = await rwaToken.read.MINTER_ROLE();
    await rwaToken.write.grantRole([MINTER_ROLE, owner.account.address], { account: owner.account });
    
    await rwaToken.write.mintStock([investorA.account.address, parseEther("60")], { account: owner.account });
    await rwaToken.write.mintStock([investorB.account.address, parseEther("40")], { account: owner.account });

    // 给 SPV 账户分发 10,000 个 USDC 用于模拟后续的真实美股除权息注资
    await mockUSDC.write.transfer([spvAccount.account.address, parseEther("10000")], { account: owner.account });

    return {
      mockUSDC,
      rwaToken,
      distributor,
      owner,
      investorA,
      investorB,
      spvAccount,
      publicClient
    };
  }

  it("分红前置置信度:两名投资者的初始股票持仓比例应分别为 60% 和 40%", async function () {
    const { rwaToken, investorA, investorB } = await deployDividendFixture();

    const total = await rwaToken.read.totalSupply();
    const balA = await rwaToken.read.balanceOf([investorA.account.address]);
    const balB = await rwaToken.read.balanceOf([investorB.account.address]);

    assert.equal(total, parseEther("100"), "总供应量应为 100 股");
    assert.equal((balA * 100n) / total, 60n, "投资者 A 应持仓 60%");
    assert.equal((balB * 100n) / total, 40n, "投资者 B 应持仓 40%");
  });

  it("业务闭环测试:美股派发股息 -> SPV 链上注资 -> 投资者按持仓比例精准冒领或提取 USDC", async function () {
    const { mockUSDC, distributor, investorA, investorB, spvAccount, publicClient } = await deployDividendFixture();

    // 1. 模拟链下美股除权息:SPV 收到券商法币分红并换成 1,000 USDC 注入分红合约
    const dividendAmount = parseEther("1000"); // 派发 1000 USDC 现金股利
    
    // SPV 授权并向分发器注入 USDC
    await mockUSDC.write.approve([distributor.address, dividendAmount], { account: spvAccount.account });
    await distributor.write.depositDividends([dividendAmount], { account: spvAccount.account });

    assert.equal(
      await mockUSDC.read.balanceOf([distributor.address]), 
      dividendAmount, 
      "分红合约应已足额锁仓 1,000 USDC 股息"
    );

    // 2. 检查链上可领取的股息视图 (Earnings View)
    // 理论上:A 应分得 1000 * 60% = 600 USDC;B 应分得 1000 * 40% = 400 USDC
    const earnedA = await distributor.read.earned([investorA.account.address]);
    const earnedB = await distributor.read.earned([investorB.account.address]);

    assert.equal(earnedA, parseEther("600"), "投资者 A 的链上预期应分得 600 USDC");
    assert.equal(earnedB, parseEther("400"), "投资者 B 的链上预期应分得 400 USDC");

    // 3. 投资者 A 发起提取分红 (Claim)
    const beforeUSDC_A = await mockUSDC.read.balanceOf([investorA.account.address]);
    
    await distributor.write.claimDividend({ account: investorA.account });
    
    const afterUSDC_A = await mockUSDC.read.balanceOf([investorA.account.address]);
    assert.equal(afterUSDC_A - beforeUSDC_A, parseEther("600"), "投资者 A 实际到账的 USDC 分红数量不正确");

    // 4. 重复领取拦截:再次领取时,可领取额度应归零
    const reEarnedA = await distributor.read.earned([investorA.account.address]);
    assert.equal(reEarnedA, 0n, "已领取的股息不应在账面上留存");
  });

  it("合规安全性:已被剔除 KYC 白名单的恶意地址无法提取属于它的分红", async function () {
    const { mockUSDC, rwaToken, distributor, investorB, spvAccount, owner } = await deployDividendFixture();

    // 1. 注入 500 USDC 股息
    await mockUSDC.write.approve([distributor.address, parseEther("500")], { account: spvAccount.account });
    await distributor.write.depositDividends([parseEther("500")], { account: spvAccount.account });

    // 2. 在提取前,因为突发监管不合规风险,管理员将投资者 B 移出 KYC 白名单
    await rwaToken.write.setKycStatus([investorB.account.address, false], { account: owner.account });

    // 3. 投资者 B 尝试强行领取分红,预期被拦截
    await assert.rejects(
      async () => {
        await distributor.write.claimDividend({ account: investorB.account });
      },
      /RWA: Recipient must be KYC whitelisted/,
      "失去 KYC 身份的持有者必须被剥夺链上股息提取权"
    );
  });
});

四、 总结

DividendDistributor 的加入,标志着 xStocks 从单纯的“资产上链映射”正式走向了“资产全生命周期收益闭环”。通过“预言机/SPV 稳定币注入 + 高精度累积收益算法 + 动态 KYC 提现拦截”,该架构不仅实现了美股派息的链上自动化,更在合规底线上做到滴水不漏,为 RWA 赛道树立了工程化落地的全新样板。