# StakingRewards质押奖励

By [web3zoom](https://paragraph.com/@web3zoom) · 2025-10-13

---

仔细研究了奖励质押的原理：

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;
        }
    }

---

*Originally published on [web3zoom](https://paragraph.com/@web3zoom/stakingrewards)*
