You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Solidity质押合约:奖励率变更时叠加奖励的实现问题

解决方案:实现多奖励率时段的奖励叠加

核心问题

现有代码的缺陷在于,计算奖励时始终用当前rewardRate覆盖整个质押时长,没有区分奖励率变更前后的时间段,导致奖励率调整时会错误地用新费率重新计算全部历史时长的奖励,无法实现不同费率下的奖励叠加。

修复方案:累积奖励因子模式

采用DeFi质押合约通用的高效计算逻辑,通过维护全局累积奖励因子和用户结算快照,实现不同奖励率时段的奖励分别计算并叠加。


1. 全局变量与结构体定义

// 全局状态:记录奖励率、上次更新时间、累积奖励因子(每个质押Token对应的累积奖励)
uint256 public rewardRate;
uint256 public lastUpdateTime;
uint256 public rewardPerTokenStored;
uint256 public totalSupply; // 总质押量,需同步维护

// 用户质押数据结构体
struct StakerData {
    uint256 totalStaked;
    uint256 reward; // 已累积未领取的奖励
    uint256 userRewardPerTokenPaid; // 用户上次结算时的累积奖励因子快照
}

mapping(address => StakerData) public stakers;

2. 辅助函数:计算当前累积奖励因子

// 返回当前每个质押Token对应的累积奖励值
function rewardPerToken() public view returns (uint256) {
    if (totalSupply == 0) {
        return rewardPerTokenStored;
    }
    uint256 timeElapsed = block.timestamp - lastUpdateTime;
    // 计算新增的累积奖励:(时间差 * 奖励率 * 精度因子) / 总质押量
    return rewardPerTokenStored + (timeElapsed * rewardRate * 1e18) / totalSupply;
}

3. 核心函数:更新奖励与状态

// 内部函数:结算用户未领取奖励,同步更新全局/用户状态
function updateReward(address account) internal {
    // 先更新全局累积奖励因子和时间戳,锁定当前奖励率的计算时段
    rewardPerTokenStored = rewardPerToken();
    lastUpdateTime = block.timestamp;

    // 针对用户地址,结算未领取的奖励
    if (account != address(0)) {
        StakerData storage staker = stakers[account];
        // 奖励 = 质押量 * (当前累积因子 - 用户上次快照的累积因子) / 精度因子
        staker.reward += (staker.totalStaked * (rewardPerTokenStored - staker.userRewardPerTokenPaid)) / 1e18;
        // 更新用户的累积因子快照
        staker.userRewardPerTokenPaid = rewardPerTokenStored;
    }
}

4. 奖励率变更函数

// 变更奖励率(需添加权限控制,例如仅合约Owner可调用)
function setRewardRate(uint256 _rewardRate) external {
    // 先更新全局状态,截止当前奖励率的计算时段
    updateReward(address(0));
    rewardRate = _rewardRate;
}

5. 用户交互函数示例

// 用户质押代币
function stake(uint256 amount) external {
    require(amount > 0, "Stake amount can't be zero");
    // 先结算用户未领取的奖励
    updateReward(msg.sender);
    
    StakerData storage staker = stakers[msg.sender];
    staker.totalStaked += amount;
    totalSupply += amount;
    
    // 此处添加代币转移逻辑(从用户钱包转至合约)
}

// 用户领取奖励
function claimReward() external {
    // 先结算最新的奖励
    updateReward(msg.sender);
    
    StakerData storage staker = stakers[msg.sender];
    uint256 rewardToClaim = staker.reward;
    require(rewardToClaim > 0, "No reward available");
    
    staker.reward = 0;
    // 此处添加奖励代币转移逻辑(从合约转至用户钱包)
}

逻辑说明

  1. 奖励率变更时:调用updateReward(address(0))锁定当前奖励率的计算时段,后续新奖励率从变更时刻开始生效,不会影响之前时段的奖励计算。
  2. 用户操作时:每次质押、提取或领取奖励前,先结算用户从上次操作到当前的奖励,确保不同奖励率时段的奖励被正确叠加到用户的reward余额中。
  3. 精度处理:用1e18作为精度因子,避免整数除法导致的精度丢失,符合Solidity通用数值处理规范。

内容的提问来源于stack exchange,提问作者jvn

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.24 12:25:08