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