-
Notifications
You must be signed in to change notification settings - Fork 2
/
WQLiquidityMining.sol
393 lines (347 loc) · 12 KB
/
WQLiquidityMining.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
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
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import '@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol';
import '@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol';
import '@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol';
import '@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol';
import '@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol';
import '@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol';
contract WQLiquidityMining is
Initializable,
AccessControlUpgradeable,
ReentrancyGuardUpgradeable,
UUPSUpgradeable
{
using SafeERC20Upgradeable for IERC20Upgradeable;
bytes32 public constant ADMIN_ROLE = keccak256('ADMIN_ROLE');
bytes32 public constant UPGRADER_ROLE = keccak256('UPGRADER_ROLE');
// Staker contains info related to each staker.
struct Staker {
uint256 amount; // amount of tokens currently staked to the contract
uint256 rewardAllowed; // amount of tokens
uint256 rewardDebt; // value needed for correct calculation staker's share
uint256 distributed; // amount of distributed earned tokens
}
// StakeInfo contains info related to stake.
struct StakeInfo {
uint256 startTime;
uint256 rewardTotal;
uint256 distributionTime;
uint256 totalStaked;
uint256 totalDistributed;
address stakeTokenAddress;
address rewardTokenAddress;
}
// Stakers info by token holders.
mapping(address => Staker) public stakes;
// ERC20 token staked to the contract.
IERC20Upgradeable public stakeToken;
// ERC20 token earned by stakers as reward.
IERC20Upgradeable public rewardToken;
/// @notice Common contract configuration variables
/// @notice Time of start staking
uint256 public startTime;
/// @notice Increase of rewards per distribution time
uint256 public rewardTotal;
/// @notice Distribution time
uint256 public distributionTime;
uint256 public tokensPerStake;
uint256 public rewardProduced;
uint256 public allProduced;
uint256 public producedTime;
uint256 public totalStaked;
uint256 public totalDistributed;
uint256 public minStakingAmount;
bool public stakingPaused;
bool public unstakingPaused;
bool public claimingPaused;
bool public paused;
event Staked(uint256 amount, uint256 time, address indexed sender);
event Claimed(uint256 amount, uint256 time, address indexed sender);
event Unstaked(uint256 amount, uint256 time, address indexed sender);
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() initializer {}
function initialize(
uint256 _startTime,
uint256 _rewardTotal,
uint256 _distributionTime,
address _rewardToken, // WQT
address _stakeToken // UNI V2
) public initializer {
__AccessControl_init();
__ReentrancyGuard_init();
__UUPSUpgradeable_init();
startTime = _startTime;
producedTime = _startTime;
rewardTotal = _rewardTotal;
distributionTime = _distributionTime;
rewardToken = IERC20Upgradeable(_rewardToken);
stakeToken = IERC20Upgradeable(_stakeToken);
_setupRole(DEFAULT_ADMIN_ROLE, msg.sender);
_setupRole(ADMIN_ROLE, msg.sender);
_setupRole(UPGRADER_ROLE, msg.sender);
_setRoleAdmin(UPGRADER_ROLE, ADMIN_ROLE);
}
function _authorizeUpgrade(
address newImplementation
) internal override onlyRole(UPGRADER_ROLE) {}
/**
* @dev stake `amount` of tokens to the contract
*
* Parameters:
*
* - `_amount` - stake amount
*/
function stake(uint256 _amount) external nonReentrant {
require(!stakingPaused, 'WQLiquidityMining: Staking is paused');
require(
block.timestamp > startTime,
'WQLiquidityMining: Staking time has not come yet'
);
require(
_amount >= minStakingAmount,
'Staking: staking amount is less then required'
);
Staker storage staker = stakes[msg.sender];
if (totalStaked > 0) {
update();
}
staker.rewardDebt =
staker.rewardDebt +
(_amount * tokensPerStake) /
1e20;
totalStaked = totalStaked + _amount;
staker.amount = staker.amount + _amount;
stakeToken.safeTransferFrom(msg.sender, address(this), _amount);
emit Staked(_amount, block.timestamp, msg.sender);
}
/**
* @dev unstake - return staked amount
*
* Parameters:
*
* - `_amount` - stake amount
*/
function unstake(uint256 _amount) external nonReentrant {
require(!unstakingPaused, 'WQLiquidityMining: Unstaking is paused');
Staker storage staker = stakes[msg.sender];
require(
staker.amount >= _amount,
'WQLiquidityMining: Not enough tokens to unstake'
);
update();
staker.rewardAllowed =
staker.rewardAllowed +
(_amount * tokensPerStake) /
1e20;
staker.amount = staker.amount - _amount;
totalStaked = totalStaked - _amount;
stakeToken.safeTransfer(msg.sender, _amount);
emit Unstaked(_amount, block.timestamp, msg.sender);
}
/**
* @dev claim available rewards
*/
function claim() external nonReentrant {
require(!claimingPaused, 'WQLiquidityMining: Claiming is paused');
if (totalStaked > 0) {
update();
}
uint256 reward = calcReward(msg.sender, tokensPerStake);
require(reward > 0, 'WQLiquidityMining: Nothing to claim');
Staker storage staker = stakes[msg.sender];
staker.distributed = staker.distributed + reward;
totalDistributed = totalDistributed + reward;
rewardToken.safeTransfer(msg.sender, reward);
emit Claimed(reward, block.timestamp, msg.sender);
}
/**
* @dev calcReward - calculates available reward
*/
function calcReward(
address _staker,
uint256 _tps
) private view returns (uint256 reward) {
Staker storage staker = stakes[_staker];
reward =
((staker.amount * _tps) / 1e20) +
staker.rewardAllowed -
staker.distributed -
staker.rewardDebt;
return reward;
}
/**
* @dev getClaim - returns available reward of `_staker`
*/
function getClaim(address _staker) public view returns (uint256 reward) {
uint256 _tps = tokensPerStake;
if (totalStaked > 0) {
uint256 rewardProducedAtNow = produced();
if (rewardProducedAtNow > rewardProduced) {
uint256 producedNew = rewardProducedAtNow - rewardProduced;
_tps += (producedNew * 1e20) / totalStaked;
}
}
reward = calcReward(_staker, _tps);
return reward;
}
/**
* @dev Calculates the necessary parameters for staking
*
*/
function produced() private view returns (uint256) {
return
allProduced +
(rewardTotal * (block.timestamp - producedTime)) /
distributionTime;
}
function update() public {
require(!paused, 'WQLiquidityMining: Updating is paused');
uint256 rewardProducedAtNow = produced();
if (rewardProducedAtNow > rewardProduced) {
uint256 producedNew = rewardProducedAtNow - rewardProduced;
if (totalStaked > 0) {
tokensPerStake += (producedNew * 1e20) / totalStaked;
}
rewardProduced = rewardProducedAtNow;
}
}
/**
* @dev Adds reward tokens to the contract
* @param _amount Specifies the amount of tokens to be transferred to the contract
*/
function addReward(uint256 _amount) external {
require(hasRole(ADMIN_ROLE, msg.sender));
rewardToken.safeTransferFrom(msg.sender, address(this), _amount);
}
/**
* @dev getInfoByAddress - return information about the staker
*/
function getInfoByAddress(
address user
)
external
view
returns (uint256 staked_, uint256 claim_, uint256 _balance)
{
Staker storage staker = stakes[user];
staked_ = staker.amount;
claim_ = getClaim(user);
return (staked_, claim_, stakeToken.balanceOf(user));
}
/**
* @dev Return information about the stake
*/
function getStakingInfo() external view returns (StakeInfo memory info_) {
info_ = StakeInfo({
startTime: startTime,
rewardTotal: rewardTotal,
distributionTime: distributionTime,
totalStaked: totalStaked,
totalDistributed: totalDistributed,
stakeTokenAddress: address(stakeToken),
rewardTokenAddress: address(rewardToken)
});
return info_;
}
/**
* @dev Allows to update 'tokens per stake' parameter
* @param _tps Specifeies the new tokens per stake value
*/
function updateTps(uint256 _tps) external onlyRole(ADMIN_ROLE) {
tokensPerStake = _tps;
}
/**
* @dev Allows to update the daily reward parameter
* @param _rewardTotal Specifeies the new daily reward value
*/
function updateRewardTotal(
uint256 _rewardTotal
) external onlyRole(ADMIN_ROLE) {
rewardTotal = _rewardTotal;
}
/**
* @dev Allows to update the value of staked tokens
* @param _totalStaked Specifeies the new value of totally staked tokens
*/
function updateTotalStaked(
uint256 _totalStaked
) external onlyRole(ADMIN_ROLE) {
totalStaked = _totalStaked;
}
/**
* @dev Allows to update the value of distributed rewards
* @param _totalDistributed Specifeies the new value of totally distributed rewards
*/
function updateTotalDistributed(
uint256 _totalDistributed
) external onlyRole(ADMIN_ROLE) {
totalDistributed = _totalDistributed;
}
/**
* @dev Update user information
*/
function updateStakerInfo(
address _user,
uint256 _amount,
uint256 _rewardAllowed,
uint256 _rewardDebt,
uint256 _distributed
) external onlyRole(ADMIN_ROLE) {
Staker storage staker = stakes[_user];
staker.amount = _amount;
staker.rewardAllowed = _rewardAllowed;
staker.rewardDebt = _rewardDebt;
staker.distributed = _distributed;
}
/**
* @dev Updates the minimum amount available to stake
* @param _amount Minimal value of tokens to ba available to stake
*/
function updateMinStakingAmount(uint256 _amount) external {
require(
hasRole(ADMIN_ROLE, msg.sender),
'Staking: caller is not an admin'
);
require(_amount >= 0, 'Amount must be possitive');
minStakingAmount = _amount;
}
/**
* @dev Pause staking
*/
function stakingPause(bool _paused) external onlyRole(ADMIN_ROLE) {
stakingPaused = _paused;
}
/**
* @dev Pause unstaking
*/
function unstakingPause(bool _paused) external onlyRole(ADMIN_ROLE) {
unstakingPaused = _paused;
}
/**
* @dev Unpause claiming
*/
function claimingPause(bool _paused) external onlyRole(ADMIN_ROLE) {
claimingPaused = _paused;
}
/**
* @dev Pause all
*/
function updatingPause(bool _paused) external onlyRole(ADMIN_ROLE) {
paused = _paused;
}
/**
* @dev Removes any token from the contract by its address
* @param _token Token address
* @param _to Recipient address
* @param _amount An amount to be removed from the contract
*/
function removeTokenByAddress(
address _token,
address _to,
uint256 _amount
) external onlyRole(ADMIN_ROLE) {
require(_to != address(0), 'Invalid recipient address');
IERC20Upgradeable(_token).safeTransfer(_to, _amount);
}
}