all files / contracts/ StakingRewards.sol

87.76% Statements 43/49
62.5% Branches 10/16
78.57% Functions 11/14
88% Lines 44/50
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193                                                                                                85× 85× 85×                                 606×         595× 430×   165×               152×                       13× 13× 13×     13×   13× 11×         37× 37× 37× 37× 33×         52× 52× 52× 52× 52×         50× 50× 50× 50× 47×           52× 52×             289× 289×                     289× 289×   289× 284× 274×           443× 441× 441× 152× 152×   441×                                      
pragma solidity ^0.7.6;
 
import "../openzeppelin-solidity-3.4.0/contracts/math/Math.sol";
import "../openzeppelin-solidity-3.4.0/contracts/math/SafeMath.sol";
import "../openzeppelin-solidity-3.4.0/contracts/token/ERC20/SafeERC20.sol";
import "../openzeppelin-solidity-3.4.0/contracts/utils/ReentrancyGuard.sol";
 
// Inheritance
import "./interfaces/IStakingRewards.sol";
import "./RewardsDistributionRecipient.sol";
 
contract StakingRewards is IStakingRewards, RewardsDistributionRecipient, ReentrancyGuard {
    using SafeMath for uint256;
    using SafeERC20 for IERC20;
 
    /* ========== STATE VARIABLES ========== */
 
    /// The token staking rewards will be paid in.
    IERC20 public rewardsToken;
    /// The token which must be staked to earn rewards.
    IERC20 public stakingToken;
    /// The time at which reward distribution will be complete.
    uint256 public periodFinish = 0;
    /// The rate at which rewards will be distributed.
    uint256 public rewardRate = 0;
    /// How long rewards will be distributed after the staking period begins.
    uint256 public rewardsDuration = 135 days;
    /// The last time rewardPerTokenStored was updated.
    uint256 public lastUpdateTime;
    /// The lastest snapshot of the amount of reward allocated to each staked token.
    uint256 public rewardPerTokenStored;
 
    /// How much reward-per-token has been paid out to each user who has withdrawn their stake.
    mapping(address => uint256) public userRewardPerTokenPaid;
    /// How much reward each user has earned.
    mapping(address => uint256) public rewards;
 
    uint256 private _totalSupply;
    mapping(address => uint256) private _balances;
 
    /* ========== CONSTRUCTOR ========== */
 
    /// Deploy a new StakingRewards contract with the specified parameters. (This should only be done by the StakingRewardsFactory.)
    constructor(
        address _rewardsDistribution,
        address _rewardsToken,
        address _stakingToken
    ) {
        rewardsToken = IERC20(_rewardsToken);
        stakingToken = IERC20(_stakingToken);
        rewardsDistribution = _rewardsDistribution;
    }
 
    /* ========== VIEWS ========== */
 
    /// Returns the total number of LP tokens staked.
    function totalSupply() external view override returns (uint256) {
        return _totalSupply;
    }
 
    /// Returns the total number of LP tokens staked by a given address.
    function balanceOf(address account) external view override returns (uint256) {
        return _balances[account];
    }
 
    /// Returns the last time for which a rewards have already been earned.
    function lastTimeRewardApplicable() public view override returns (uint256) {
        return Math.min(block.timestamp, periodFinish);
    }
 
    /// Returns the current amount of reward allocated per staked LP token.
    function rewardPerToken() public view override returns (uint256) {
        if (_totalSupply == 0) {
            return rewardPerTokenStored;
        }
        return
            rewardPerTokenStored.add(
                lastTimeRewardApplicable().sub(lastUpdateTime).mul(rewardRate).mul(1e18).div(_totalSupply)
            );
    }
 
    /// Returns the total reward earnings associated with a given address.
    function earned(address account) public view override returns (uint256) {
        return _balances[account].mul(rewardPerToken().sub(userRewardPerTokenPaid[account])).div(1e18).add(rewards[account]);
    }
 
    /// Returns the total reward amount.
    function getRewardForDuration() external view override returns (uint256) {
        return rewardRate.mul(rewardsDuration);
    }
 
    /* ========== MUTATIVE FUNCTIONS ========== */
 
    /// Stake a number of LP tokens to earn rewards, using a signed permit instead of a balance approval.
    function stakeWithPermit(uint256 amount, uint deadline, uint8 v, bytes32 r, bytes32 s) external nonReentrant updateReward(msg.sender) {
        Erequire(amount > 0, "Cannot stake 0");
        _totalSupply = _totalSupply.add(amount);
        _balances[msg.sender] = _balances[msg.sender].add(amount);
 
        // permit
        IUniswapV2ERC20(address(stakingToken)).permit(msg.sender, address(this), amount, deadline, v, r, s);
 
        stakingToken.safeTransferFrom(msg.sender, address(this), amount);
        emit Staked(msg.sender, amount);
    }
 
    /// Stake a number of LP tokens to earn rewards.
    function stake(uint256 amount) external override nonReentrant updateReward(msg.sender) {
        Erequire(amount > 0, "Cannot stake 0");
        _totalSupply = _totalSupply.add(amount);
        _balances[msg.sender] = _balances[msg.sender].add(amount);
        stakingToken.safeTransferFrom(msg.sender, address(this), amount);
        emit Staked(msg.sender, amount);
    }
 
    /// Withdraw a number of LP tokens.
    function withdraw(uint256 amount) public override nonReentrant updateReward(msg.sender) {
        Erequire(amount > 0, "Cannot withdraw 0");
        _totalSupply = _totalSupply.sub(amount);
        _balances[msg.sender] = _balances[msg.sender].sub(amount);
        stakingToken.safeTransfer(msg.sender, amount);
        emit Withdrawn(msg.sender, amount);
    }
 
    /// Transfer the caller's earned rewards.
    function getReward() public override nonReentrant updateReward(msg.sender) {
        uint256 reward = rewards[msg.sender];
        Eif (reward > 0) {
            rewards[msg.sender] = 0;
            rewardsToken.safeTransfer(msg.sender, reward);
            emit RewardPaid(msg.sender, reward);
        }
    }
 
    /// Withdraw all staked LP tokens and any pending rewards.
    function exit() external override {
        withdraw(_balances[msg.sender]);
        getReward();
    }
 
    /* ========== RESTRICTED FUNCTIONS ========== */
 
    /// Called by the StakingRewardsFactory to begin reward distribution.
    function notifyRewardAmount(uint256 reward) external override onlyRewardsDistribution updateReward(address(0)) {
        Eif (block.timestamp >= periodFinish) {
            rewardRate = reward.div(rewardsDuration);
        } else {
            uint256 remaining = periodFinish.sub(block.timestamp);
            uint256 leftover = remaining.mul(rewardRate);
            rewardRate = reward.add(leftover).div(rewardsDuration);
        }
 
        // Ensure the provided reward amount is not more than the balance in the contract.
        // This keeps the reward rate in the right range, preventing overflows due to
        // very high values of rewardRate in the earned and rewardsPerToken functions;
        // Reward + leftover must be less than 2^256 / 10^18 to avoid overflow.
        uint balance = rewardsToken.balanceOf(address(this));
        Erequire(rewardRate <= balance.div(rewardsDuration), "Provided reward too high");
 
        lastUpdateTime = block.timestamp;
        periodFinish = block.timestamp.add(rewardsDuration);
        emit RewardAdded(reward);
    }
 
    /* ========== MODIFIERS ========== */
 
    modifier updateReward(address account) {
        rewardPerTokenStored = rewardPerToken();
        lastUpdateTime = lastTimeRewardApplicable();
        if (account != address(0)) {
            rewards[account] = earned(account);
            userRewardPerTokenPaid[account] = rewardPerTokenStored;
        }
        _;
    }
 
    /* ========== EVENTS ========== */
 
    /// Emitted when the StakingRewardsFactory has allocated a reward balance to a StakingRewards contract, starting the staking period.
    event RewardAdded(uint256 reward);
    /// Emitted when a user stakes their LP tokens.
    event Staked(address indexed user, uint256 amount);
    /// Emitted when a user withdraws their LP tokens.
    event Withdrawn(address indexed user, uint256 amount);
    /// Emitted when a user has been paid a reward.
    event RewardPaid(address indexed user, uint256 reward);
}
 
interface IUniswapV2ERC20 {
    /// Allows a user to permit a contract to access their tokens by signing a permit.
    function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external;
}