forked from Certora/Secureum_Staking_Rewards
-
Notifications
You must be signed in to change notification settings - Fork 0
/
StakingRewards.sol
166 lines (137 loc) · 5.57 KB
/
StakingRewards.sol
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
// SPDX-License-Identifier: MIT
pragma solidity ^0.8;
contract StakingRewards {
IERC20 public immutable stakingToken;
IERC20 public immutable rewardsToken;
address public owner;
// Duration of rewards to be paid out (in seconds)
uint public duration;
// Timestamp of when the rewards finish
uint public finishAt;
// Minimum of last updated time and reward finish time
uint public updatedAt;
// Reward to be paid out per second
uint public rewardRate;
// Sum of (reward rate * dt * 1e18 / total supply)
uint public rewardPerTokenStored;
// User address => rewardPerTokenStored
mapping(address => uint) public userRewardPerTokenPaid;
// User address => rewards to be claimed
mapping(address => uint) public rewards;
// Total staked
uint public totalSupply;
// User address => staked amount
mapping(address => uint) public balanceOf;
constructor(address _stakingToken, address _rewardToken) {
owner = msg.sender;
stakingToken = IERC20(_stakingToken);
rewardsToken = IERC20(_rewardToken);
}
modifier onlyOwner() {
require(msg.sender == owner, "not authorized");
_;
}
// Updates the rewardPerToken amount of the account
modifier updateReward(address _account) {
rewardPerTokenStored = rewardPerToken();
updatedAt = lastTimeRewardApplicable();
if (_account != address(0)) {
rewards[_account] = earned(_account);
userRewardPerTokenPaid[_account] = rewardPerTokenStored;
}
_;
}
// Returns the latest time up to the finish time
function lastTimeRewardApplicable() public view returns (uint) {
return _min(finishAt, block.timestamp);
}
// Calculates the rewardPerToken value based on the time elapsed since the value has last been updated
// newRewardPerToken = oldRewardPerToken + (rewardRate * timeElapsedSinceLastUpdate) / totalSupplyOfRewardTokens
// Only count timeElapsedSinceLastUpdate up to the finish time of the current reward round
function rewardPerToken() public view returns (uint) {
if (totalSupply == 0) {
return rewardPerTokenStored;
}
return
rewardPerTokenStored +
(rewardRate * (lastTimeRewardApplicable() - updatedAt) * 1e18) /
totalSupply;
}
function stake(uint _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(uint _amount) external updateReward(msg.sender) {
require(_amount > 0, "amount = 0");
balanceOf[msg.sender] -= _amount;
totalSupply -= _amount;
stakingToken.transfer(msg.sender, _amount);
}
// Calculates how much _account has earned in rewards
// newRewards = oldRewards + ( amountStaked * rewardPerTokenOutstanding )
function earned(address _account) public view returns (uint) {
return
((balanceOf[_account] *
(rewardPerToken() - userRewardPerTokenPaid[_account])) / 1e18) +
rewards[_account];
}
function getReward() external updateReward(msg.sender) {
uint reward = rewards[msg.sender];
if (reward > 0) {
rewards[msg.sender] = 0;
rewardsToken.transfer(msg.sender, reward);
}
}
// Specifies how long a reward round should last
// Can only call this function when not currently in a reward round
function setRewardsDuration(uint _duration) external onlyOwner {
require(finishAt < block.timestamp, "reward duration not finished");
duration = _duration;
}
// Owner specifies a reward amount that's distributed for a certain duration
// If the last reward round has ended, start a new one
// The reward amount is split into a rewardRate per second
// If the current reward round isn't finished yet, add _amount to the remaining rewards left
function notifyRewardAmount(
uint _amount
) external onlyOwner updateReward(address(0)) {
if (block.timestamp >= finishAt) {
rewardRate = _amount / duration;
} else {
uint remainingRewards = (finishAt - block.timestamp) * rewardRate;
rewardRate = (_amount + remainingRewards) / 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 _min(uint x, uint y) private pure returns (uint) {
return x <= y ? x : y;
}
}
// transfer function for bugs
// stake for
// remove duration and assume constant reward emission rate per unit time
interface IERC20 {
function totalSupply() external view returns (uint);
function balanceOf(address account) external view returns (uint);
function transfer(address recipient, uint amount) external returns (bool);
function allowance(
address owner,
address spender
) external view returns (uint);
function approve(address spender, uint amount) external returns (bool);
function transferFrom(
address sender,
address recipient,
uint amount
) external returns (bool);
event Transfer(address indexed from, address indexed to, uint value);
event Approval(address indexed owner, address indexed spender, uint value);
}