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; // 此处添加奖励代币转移逻辑(从合约转至用户钱包) }
逻辑说明
- 奖励率变更时:调用
updateReward(address(0))锁定当前奖励率的计算时段,后续新奖励率从变更时刻开始生效,不会影响之前时段的奖励计算。 - 用户操作时:每次质押、提取或领取奖励前,先结算用户从上次操作到当前的奖励,确保不同奖励率时段的奖励被正确叠加到用户的
reward余额中。 - 精度处理:用
1e18作为精度因子,避免整数除法导致的精度丢失,符合Solidity通用数值处理规范。
内容的提问来源于stack exchange,提问作者jvn
相关产品推荐
相关产品推荐

