RWA 代币化 —— 分红模块

分红模块实现了 RWA 代币持有者的收益分配机制,支持多代币池、锁仓期限、按份额分红等功能。

1. 概述

RWADividendPool 实现了基于份额的分红系统:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
┌─────────────────────────────────────────────────────────┐
│ 分红池流程 │
├─────────────────────────────────────────────────────────┤
│ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ 用户存入 │ ──► │ 锁仓期 │ ──► │ 领取分红 │ │
│ │ RWA代币 │ │ │ │ 或提取 │ │
│ └─────────┘ └─────────┘ └─────────┘ │
│ │
│ ┌─────────┐ │
│ │ 管理员 │ │
│ │ 分配收益 │ │
│ └─────────┘ │
│ │ │
│ ▼ │
│ 按份额比例分配给所有存款用户 │
└─────────────────────────────────────────────────────────┘

2. 核心概念

2.1 revenuePerShare 机制

采用累积分红模式,避免遍历所有用户:

1
2
3
4
5
// 每份额累积收益(放大 1e18)
uint256 public revenuePerShare;

// 分红时更新
revenuePerShare += (amount * PRECISION) / totalDeposited;

原理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
初始状态:
revenuePerShare = 0

分红 1000 USDC(总存款 10,000 代币):
revenuePerShare = 0 + (1000 * 1e18) / 10000 = 1e17

用户持有 100 代币:
待领取 = 100 * 1e17 / 1e18 = 10 USDC

再分红 2000 USDC:
revenuePerShare = 1e17 + (2000 * 1e18) / 10000 = 3e17

用户待领取(累计):
= 100 * 3e17 / 1e18 = 30 USDC

2.2 revenueDebt 机制

记录用户已计入的累积收益,避免重复领取:

1
2
3
4
5
6
struct UserDeposit {
uint256 amount; // 存款数量
uint256 revenueDebt; // 已计入的累积收益
uint256 pendingRewards; // 待领取的收益
// ...
}

计算待领取收益

1
pendingRewards = (amount * revenuePerShare / PRECISION) - revenueDebt;

3. 数据结构

3.1 代币池信息

1
2
3
4
5
6
7
8
9
10
struct TokenPool {
address admin; // 池管理员(可分配收益)
uint256 totalDeposited; // 总存款量
uint256 totalRevenue; // 累计分配的总收益
uint256 revenuePerShare; // 每份额累积收益
uint256 minLockDuration; // 最小锁仓期
uint256 maxLockDuration; // 最大锁仓期
bool isActive; // 是否激活
uint256 lastRevenueTime; // 上次分红时间
}

3.2 用户存款信息

1
2
3
4
5
6
7
8
struct UserDeposit {
uint256 amount; // 存款数量
uint256 lockEndTime; // 锁仓结束时间
uint256 depositTime; // 存款时间
uint256 revenueDebt; // 已计入的收益
uint256 pendingRewards; // 待领取收益
uint256 lastClaimTime; // 上次领取时间
}

3.3 分红记录

1
2
3
4
5
6
struct RevenueRecord {
uint256 amount; // 分红金额
uint256 timestamp; // 分红时间
address distributor; // 分配者
uint256 totalShares; // 分红时的总份额
}

4. 代币池管理

4.1 创建代币池

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
function createTokenPool(
address token, // RWA 代币地址
address admin, // 池管理员
uint256 minLockDuration, // 最小锁仓期(秒)
uint256 maxLockDuration // 最大锁仓期(秒)
) external onlyOwner {
require(token != address(0), "Invalid token address");
require(admin != address(0), "Invalid admin address");
require(!tokenExists[token], "Token pool already exists");
require(supportedTokens.length < MAX_TOKENS, "Max tokens reached");
require(minLockDuration >= MIN_LOCK_DURATION, "Lock duration too short"); // >= 1 day
require(maxLockDuration <= MAX_LOCK_DURATION, "Lock duration too long"); // <= 365 days
require(minLockDuration <= maxLockDuration, "Invalid lock duration range");

tokenPools[token] = TokenPool({
admin: admin,
totalDeposited: 0,
totalRevenue: 0,
revenuePerShare: 0,
minLockDuration: minLockDuration,
maxLockDuration: maxLockDuration,
isActive: true,
lastRevenueTime: block.timestamp
});

supportedTokens.push(token);
tokenExists[token] = true;

emit TokenPoolCreated(token, admin, minLockDuration, maxLockDuration);
}

4.2 更新池参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function updateTokenPool(
address token,
uint256 minLockDuration,
uint256 maxLockDuration
) external onlyTokenAdmin(token) tokenPoolExists(token) {
require(minLockDuration >= MIN_LOCK_DURATION, "Lock duration too short");
require(maxLockDuration <= MAX_LOCK_DURATION, "Lock duration too long");
require(minLockDuration <= maxLockDuration, "Invalid lock duration range");

TokenPool storage pool = tokenPools[token];
pool.minLockDuration = minLockDuration;
pool.maxLockDuration = maxLockDuration;

emit TokenPoolUpdated(token, pool.admin, minLockDuration, maxLockDuration);
}

4.3 更改池管理员

1
2
3
4
5
6
7
8
function changeTokenAdmin(address token, address newAdmin) external onlyOwner tokenPoolExists(token) {
require(newAdmin != address(0), "Invalid admin address");

address oldAdmin = tokenPools[token].admin;
tokenPools[token].admin = newAdmin;

emit AdminChanged(token, oldAdmin, newAdmin);
}

4.4 池状态控制

1
2
3
4
function setPoolStatus(address token, bool isActive) external onlyOwner tokenPoolExists(token) {
tokenPools[token].isActive = isActive;
emit PoolStatusChanged(token, isActive);
}

5. 用户存款

5.1 deposit

用户存入 RWA 代币并设置锁仓期。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
function deposit(
address token,
uint256 amount,
uint256 lockDuration
) external nonReentrant whenNotPaused tokenPoolExists(token) tokenPoolActive(token) {
require(amount > 0, "Amount must be greater than 0");

TokenPool storage pool = tokenPools[token];
require(lockDuration >= pool.minLockDuration, "Lock duration too short");
require(lockDuration <= pool.maxLockDuration, "Lock duration too long");

// 先更新待领取收益
_updatePendingRewards(token, msg.sender);

// 转入代币
IERC20(token).safeTransferFrom(msg.sender, address(this), amount);

// 更新用户存款
UserDeposit storage userDep = userDeposits[token][msg.sender];

if (userDep.amount > 0) {
// 已有存款,延长锁仓期(如果新的更长)
uint256 newLockEnd = block.timestamp + lockDuration;
if (newLockEnd > userDep.lockEndTime) {
userDep.lockEndTime = newLockEnd;
}
} else {
// 首次存款
userDep.lockEndTime = block.timestamp + lockDuration;
userDep.depositTime = block.timestamp;
}

userDep.amount += amount;
userDep.revenueDebt = userDep.amount * pool.revenuePerShare / PRECISION;

// 更新池总量
pool.totalDeposited += amount;

emit Deposited(msg.sender, token, amount, userDep.lockEndTime);
}

5.2 存款示例

1
2
3
4
5
6
7
8
9
10
11
12
13
池参数:
├── minLockDuration = 30 days
└── maxLockDuration = 365 days

Alice 存入 1000 RWA 代币,锁仓 90 天:
├── amount = 1000
├── lockEndTime = now + 90 days
└── revenueDebt = 1000 * revenuePerShare / 1e18

30 天后,Alice 追加 500 代币,锁仓 60 天:
├── newLockEnd = now + 60 days < lockEndTime(不更新锁仓期)
├── amount = 1500
└── revenueDebt 重新计算

6. 收益分配

6.1 distributeRevenue

池管理员分配收益给所有存款用户。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
function distributeRevenue(
address token,
uint256 amount
) external nonReentrant onlyTokenAdmin(token) tokenPoolExists(token) tokenPoolActive(token) {
require(amount > 0, "Amount must be greater than 0");

TokenPool storage pool = tokenPools[token];
require(pool.totalDeposited > 0, "No deposits in pool");

// 管理员转入收益代币
IERC20(token).safeTransferFrom(msg.sender, address(this), amount);

// 更新 revenuePerShare
pool.revenuePerShare += (amount * PRECISION) / pool.totalDeposited;
pool.totalRevenue += amount;
pool.lastRevenueTime = block.timestamp;

// 记录分红历史
revenueHistory[token].push(RevenueRecord({
amount: amount,
timestamp: block.timestamp,
distributor: msg.sender,
totalShares: pool.totalDeposited
}));

emit RevenueDistributed(token, amount, msg.sender);
}

6.2 分红计算示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
初始状态:
├── Alice 存款:1000 代币
├── Bob 存款:4000 代币
├── 总存款:5000 代币
└── revenuePerShare = 0

第一次分红 500 代币:
├── revenuePerShare = 500 * 1e18 / 5000 = 1e17
├── Alice 待领取 = 1000 * 1e17 / 1e18 = 100 代币
└── Bob 待领取 = 4000 * 1e17 / 1e18 = 400 代币

第二次分红 1000 代币:
├── revenuePerShare = 1e17 + 1000 * 1e18 / 5000 = 3e17
├── Alice 累计待领取 = 1000 * 3e17 / 1e18 = 300 代币
└── Bob 累计待领取 = 4000 * 3e17 / 1e18 = 1200 代币

7. 收益领取

7.1 claimRewards

领取累积的收益。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function claimRewards(address token) external nonReentrant whenNotPaused tokenPoolExists(token) {
_updatePendingRewards(token, msg.sender);

UserDeposit storage userDep = userDeposits[token][msg.sender];
uint256 rewards = userDep.pendingRewards;
require(rewards > 0, "No rewards to claim");

userDep.pendingRewards = 0;
userDep.lastClaimTime = block.timestamp;
userTotalClaimed[token][msg.sender] += rewards;

// 转移收益
IERC20(token).safeTransfer(msg.sender, rewards);

emit RewardsClaimed(msg.sender, token, rewards);
}

7.2 更新待领取收益

1
2
3
4
5
6
7
8
9
10
11
12
function _updatePendingRewards(address token, address user) internal {
UserDeposit storage userDep = userDeposits[token][user];
if (userDep.amount == 0) return;

TokenPool storage pool = tokenPools[token];
uint256 pendingRevenue = (userDep.amount * pool.revenuePerShare / PRECISION) - userDep.revenueDebt;

if (pendingRevenue > 0) {
userDep.pendingRewards += pendingRevenue;
userDep.revenueDebt = userDep.amount * pool.revenuePerShare / PRECISION;
}
}

8. 用户提取

8.1 withdraw

锁仓期结束后提取存款。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function withdraw(
address token,
uint256 amount
) external nonReentrant whenNotPaused tokenPoolExists(token) {
UserDeposit storage userDep = userDeposits[token][msg.sender];
require(userDep.amount >= amount, "Insufficient balance");
require(block.timestamp >= userDep.lockEndTime, "Still in lock period");
require(amount > 0, "Amount must be greater than 0");

// 先更新待领取收益
_updatePendingRewards(token, msg.sender);

// 更新存款
userDep.amount -= amount;
userDep.revenueDebt = userDep.amount * tokenPools[token].revenuePerShare / PRECISION;

// 更新池总量
tokenPools[token].totalDeposited -= amount;

// 转出代币
IERC20(token).safeTransfer(msg.sender, amount);

emit Withdrawn(msg.sender, token, amount);
}

8.2 紧急提取

锁仓期内提取,需支付 10% 罚金。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
function emergencyWithdraw(address token) external nonReentrant {
UserDeposit storage userDep = userDeposits[token][msg.sender];
require(userDep.amount > 0, "No deposit found");

uint256 amount = userDep.amount;
uint256 penalty = 0;

// 锁仓期内提取,扣除 10% 罚金
if (block.timestamp < userDep.lockEndTime) {
penalty = amount * 10 / 100;
amount = amount - penalty;
}

// 更新待领取收益
_updatePendingRewards(token, msg.sender);
uint256 rewards = userDep.pendingRewards;

// 重置用户存款
userDep.amount = 0;
userDep.pendingRewards = 0;
userDep.revenueDebt = 0;

// 更新池总量
tokenPools[token].totalDeposited -= (amount + penalty);

// 转出本金和收益
if (amount > 0) {
IERC20(token).safeTransfer(msg.sender, amount);
}
if (rewards > 0) {
IERC20(token).safeTransfer(msg.sender, rewards);
userTotalClaimed[token][msg.sender] += rewards;
}

// 罚金转给池管理员
if (penalty > 0) {
IERC20(token).safeTransfer(tokenPools[token].admin, penalty);
}

emit EmergencyWithdraw(msg.sender, token, amount);
}

9. 查询函数

9.1 收益查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 获取待领取收益
function getPendingRewards(address token, address user) external view returns (uint256) {
UserDeposit memory userDep = userDeposits[token][user];
if (userDep.amount == 0) return userDep.pendingRewards;

TokenPool memory pool = tokenPools[token];
uint256 pendingRevenue = (userDep.amount * pool.revenuePerShare / PRECISION) - userDep.revenueDebt;

return userDep.pendingRewards + pendingRevenue;
}

// 获取总收益(已领取 + 待领取)
function getUserTotalEarnings(address token, address user) external view returns (uint256 total) {
uint256 claimed = userTotalClaimed[token][user];
uint256 pending = this.getPendingRewards(token, user);
total = claimed + pending;
}

9.2 份额查询

1
2
3
4
5
6
7
8
// 获取用户份额百分比(放大 100 倍,100 = 1%)
function getUserSharePercentage(address token, address user) external view returns (uint256) {
TokenPool memory pool = tokenPools[token];
if (pool.totalDeposited == 0) return 0;

UserDeposit memory userDep = userDeposits[token][user];
return (userDep.amount * 10000) / pool.totalDeposited;
}

9.3 池信息查询

1
2
3
4
5
6
7
8
9
10
11
// 获取支持的代币列表
function getSupportedTokens() external view returns (address[] memory);

// 获取分红历史
function getRevenueHistory(address token) external view returns (RevenueRecord[] memory);

// 获取用户存款信息
function getUserDeposit(address token, address user) external view returns (UserDeposit memory);

// 获取代币池信息
function getTokenPool(address token) external view returns (TokenPool memory);

10. 事件

1
2
3
4
5
6
7
8
9
event TokenPoolCreated(address indexed token, address admin, uint256 minLockDuration, uint256 maxLockDuration);
event TokenPoolUpdated(address indexed token, address admin, uint256 minLockDuration, uint256 maxLockDuration);
event Deposited(address indexed user, address indexed token, uint256 amount, uint256 lockEndTime);
event Withdrawn(address indexed user, address indexed token, uint256 amount);
event RevenueDistributed(address indexed token, uint256 amount, address indexed distributor);
event RewardsClaimed(address indexed user, address indexed token, uint256 amount);
event AdminChanged(address indexed token, address oldAdmin, address newAdmin);
event PoolStatusChanged(address indexed token, bool isActive);
event EmergencyWithdraw(address indexed user, address indexed token, uint256 amount);

11. 常量与限制

1
2
3
4
uint256 public constant PRECISION = 1e18;         // 计算精度
uint256 public constant MIN_LOCK_DURATION = 1 days; // 最小锁仓期
uint256 public constant MAX_LOCK_DURATION = 365 days; // 最大锁仓期
uint256 public constant MAX_TOKENS = 100; // 最大支持代币数

12. 安全考虑

12.1 精度处理

1
2
// 乘法在前,除法在后
revenuePerShare += (amount * PRECISION) / totalDeposited;

12.2 重入防护

所有外部调用前更新状态:

1
2
3
4
// 先更新状态
userDep.pendingRewards = 0;
// 后转账
IERC20(token).safeTransfer(msg.sender, rewards);

12.3 锁仓安全

  • 追加存款不会缩短锁仓期
  • 紧急提取有罚金机制
  • 只有到期后才能正常提取

12.4 溢出防护

  • 使用 Solidity 0.8+ 内置溢出检查
  • 使用 SafeERC20 处理代币转账