Cover photo

StakingRewards质押奖励

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

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