# StakingRewards质押奖励 **Published by:** [web3zoom](https://paragraph.com/@web3zoom/) **Published on:** 2025-10-13 **URL:** https://paragraph.com/@web3zoom/stakingrewards ## Content 仔细研究了奖励质押的原理: 1、奖励速率,是跟时间有关系的变量,把时间按秒进行折算; 2、需要考虑时间节点是否在质押期间?// SPDX-License-Identifier: MIT pragma solidity ^0.8.30; import "@openzeppelin/contracts/interfaces/IERC20.sol"; contract StakingRewards { // 奖励token IERC20 public immutable rewardsToken; // 质押token IERC20 public immutable stakingToken; // 管理员 address public owner; uint256 public duration; // 持续时间 uint256 public finishAt; // 完成时间点 uint256 public updatedAt; // 更新时间点 uint256 public rewardRate; // 奖励比率 uint256 public rewardPerTokenStored; // 奖励数额 mapping (address => uint256) public userRewardPerTokenPaid; // 每个代币对应的奖励数量 mapping (address => uint256) public rewards; // 奖励数值 mapping (address => uint256) public balanceOf; // 每个账户的余额 uint256 public totalSupply; // 总的代币数量 constructor(address _stakingToken, address _rewardsToken){ owner = msg.sender; rewardsToken = IERC20(_rewardsToken); stakingToken = IERC20(_stakingToken); } // 验证是否是管理员 modifier onlyOwner(){ require(owner == msg.sender, "Not Owner"); _; } function earned(address _account) public view returns(uint256){ return balanceOf[_account] * (rewardPerToken() - userRewardPerTokenPaid[_account]) / 1e18 + rewards[_account]; } modifier updateReward(address _account){ rewardPerTokenStored = rewardPerToken(); updatedAt = lastTimeRewardApplicable(); if(_account != address(0)){ rewards[_account] = earned(_account); userRewardPerTokenPaid[_account] = rewardPerTokenStored; } _; } function rewardPerToken() public view returns (uint256){ if (totalSupply == 0){ return rewardPerTokenStored; } return rewardPerTokenStored + (rewardRate * (lastTimeRewardApplicable() - updatedAt) *1e18)/totalSupply; } function lastTimeRewardApplicable() public view returns(uint256){ return _min(block.timestamp, finishAt); } function _min(uint256 x, uint256 y) private pure returns(uint256){ return x < y ? x : y; } // 设置持续时间和奖励速率 function notifyRewardAmount(uint256 _amount) external onlyOwner updateReward(address(0)){ // 当前时间超过完成时间 if (block.timestamp > finishAt){ rewardRate = _amount / duration; }else{ uint256 remainingRewards = rewardRate * (finishAt - block.timestamp); rewardRate = (remainingRewards + _amount) / duration; // 奖励速率计算 } require(rewardRate > 0 , "reward rate = 0"); require(rewardRate * duration <= rewardsToken.balanceOf(address(this)), "reward amount > balance"); finishAt = block.timestamp + duration; updatedAt = block.timestamp; } // 质押 function stake(uint256 _amount) external updateReward(msg.sender){ require(_amount > 0, "amount = 0"); stakingToken.transferFrom(msg.sender, address(this), _amount); balanceOf[msg.sender] += _amount; totalSupply += _amount; } // 提现 function withdraw(uint256 _amount) external updateReward(msg.sender){ require(_amount > 0, "amount = 0"); balanceOf[msg.sender] -= _amount; totalSupply -= _amount; } // 获得奖励 function getReward() external updateReward(msg.sender){ uint256 reward = rewards[msg.sender]; if(reward > 0){ rewards[msg.sender] = 0; rewardsToken.transfer(msg.sender, reward); } } // 设置奖励时间 function setRewardDuration(uint _duration) external onlyOwner{ require(finishAt < block.timestamp, "reward duration not finished"); duration = _duration; } } ## Publication Information - [web3zoom](https://paragraph.com/@web3zoom/): Publication homepage - [All Posts](https://paragraph.com/@web3zoom/): More posts from this publication - [RSS Feed](https://api.paragraph.com/blogs/rss/@web3zoom): Subscribe to updates - [Twitter](https://twitter.com/primer2011): Follow on Twitter