-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathTrueMultiFarm.sol
More file actions
521 lines (437 loc) · 19.6 KB
/
TrueMultiFarm.sol
File metadata and controls
521 lines (437 loc) · 19.6 KB
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
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import {ITrueDistributor} from "./interfaces/ITrueDistributor.sol";
import {ITrueMultiFarm} from "./interfaces/ITrueMultiFarm.sol";
import {Upgradeable} from "./access/Upgradeable.sol";
/**
* @title TrueMultiFarm
* @notice Deposit liquidity tokens to earn TRU rewards over time
* @dev Staking pool where tokens are staked for TRU rewards
* A Distributor contract decides how much TRU all farms in total can earn over time
* Calling setShare() by owner decides ratio of rewards going to respective token farms
* You can think of this contract as of a farm that is a distributor to the multiple other farms
* A share of a farm in the multifarm is it's stake
*/
contract TrueMultiFarm is ITrueMultiFarm, Upgradeable {
using SafeERC20 for IERC20;
uint256 private constant PRECISION = 1e30;
struct Stakes {
uint256 totalStaked;
mapping(address => uint256) staked;
}
struct FarmRewards {
uint256 cumulativeRewardPerShare;
uint256 unclaimedRewards;
mapping(address => uint256) previousCumulatedRewardPerShare;
}
struct StakerRewards {
uint256 cumulativeRewardPerToken;
mapping(address => uint256) previousCumulatedRewardPerToken;
}
struct RewardDistribution {
ITrueDistributor distributor;
Stakes shares;
FarmRewards farmRewards;
}
// stakedToken => stake info
mapping(IERC20 => Stakes) public stakes;
// rewardToken => reward info
mapping(IERC20 => RewardDistribution) rewardDistributions;
// stakedToken => rewardToken[]
mapping(IERC20 => IERC20[]) public rewardsAvailable;
// rewardToken -> stakedToken -> Rewards
mapping(IERC20 => mapping(IERC20 => StakerRewards)) public stakerRewards;
// rewardToken => undistributedRewards
mapping(IERC20 => uint256) public undistributedRewards;
IERC20[] public rewardTokens;
function initialize() external initializer {
__Upgradeable_init(msg.sender);
}
/**
* @dev Emitted when an account stakes
* @param who Account staking
* @param amountStaked Amount of tokens staked
*/
event Stake(IERC20 indexed token, address indexed who, uint256 amountStaked);
/**
* @dev Emitted when an account unstakes
* @param who Account unstaking
* @param amountUnstaked Amount of tokens unstaked
*/
event Unstake(IERC20 indexed token, address indexed who, uint256 amountUnstaked);
/**
* @dev Emitted when an account claims TRU rewards
* @param who Account claiming
* @param amountClaimed Amount of TRU claimed
*/
event Claim(IERC20 indexed token, address indexed who, uint256 amountClaimed);
event DistributorAdded(IERC20 indexed rewardToken, ITrueDistributor indexed distributor);
event DistributorRemoved(IERC20 indexed rewardToken);
event SharesChanged(IERC20 indexed rewardToken, IERC20[] stakedTokens, uint256[] updatedShares);
/**
* @dev Update all rewards associated with the token and msg.sender
*/
modifier update(IERC20 token) {
IERC20[] memory _rewardsAvailable = rewardsAvailable[token];
uint256 _rewardsAvailableLength = _rewardsAvailable.length;
for (uint256 i; i < _rewardsAvailableLength; i++) {
_distribute(_rewardsAvailable[i]);
}
updateRewards(token);
_;
}
function getDistributor(IERC20 rewardToken) external view returns (ITrueDistributor) {
return rewardDistributions[rewardToken].distributor;
}
function getRewardTokens() external view returns (IERC20[] memory) {
return rewardTokens;
}
function getShares(IERC20 rewardToken, IERC20 stakedToken) external view returns (uint256) {
return rewardDistributions[rewardToken].shares.staked[address(stakedToken)];
}
function getTotalShares(IERC20 rewardToken) external view returns (uint256) {
return rewardDistributions[rewardToken].shares.totalStaked;
}
function getAvailableRewardsForToken(IERC20 stakedToken) external view returns (IERC20[] memory) {
return rewardsAvailable[stakedToken];
}
/**
* @dev How much is staked by staker on token farm
*/
function staked(IERC20 token, address staker) external view returns (uint256) {
return stakes[token].staked[staker];
}
function addDistributor(ITrueDistributor distributor) external onlyRole(DEFAULT_ADMIN_ROLE) {
require(distributor.farm() == address(this), "TrueMultiFarm: Distributor farm is not set");
IERC20 rewardToken = distributor.asset();
if (address(rewardDistributions[rewardToken].distributor) == address(0)) {
rewardTokens.push(rewardToken);
}
rewardDistributions[rewardToken].distributor = distributor;
emit DistributorAdded(rewardToken, distributor);
}
function removeDistributor(IERC20 rewardToken) external onlyRole(DEFAULT_ADMIN_ROLE) {
_distribute(rewardToken);
uint256 rewardTokensLength = rewardTokens.length;
for (uint256 i = 0; i < rewardTokensLength; i++) {
if (rewardTokens[i] == rewardToken) {
rewardTokens[i] = rewardTokens[rewardTokensLength - 1];
rewardTokens.pop();
break;
}
}
delete rewardDistributions[rewardToken].distributor;
emit DistributorRemoved(rewardToken);
}
/**
* @dev Stake tokens for TRU rewards.
* Also claims any existing rewards.
* @param amount Amount of tokens to stake
*/
function stake(IERC20 token, uint256 amount) external override update(token) {
_claimAll(token);
stakes[token].staked[msg.sender] += amount;
stakes[token].totalStaked += amount;
token.safeTransferFrom(msg.sender, address(this), amount);
emit Stake(token, msg.sender, amount);
}
/**
* @dev Remove staked tokens
* @param amount Amount of tokens to unstake
*/
function unstake(IERC20 token, uint256 amount) external override update(token) {
_claimAll(token);
_unstake(token, amount);
}
/**
* @dev Claim all rewards
*/
function claim(IERC20[] calldata stakedTokens) external override {
uint256 stakedTokensLength = stakedTokens.length;
distribute();
for (uint256 i = 0; i < stakedTokensLength; i++) {
updateRewards(stakedTokens[i]);
}
for (uint256 i = 0; i < stakedTokensLength; i++) {
_claimAll(stakedTokens[i]);
}
}
/**
* @dev Claim rewardTokens
*/
function claim(IERC20[] calldata stakedTokens, IERC20[] calldata rewards) external {
uint256 stakedTokensLength = stakedTokens.length;
uint256 rewardTokensLength = rewards.length;
for (uint256 i = 0; i < rewardTokensLength; i++) {
_distribute(rewards[i]);
}
for (uint256 i = 0; i < stakedTokensLength; i++) {
updateRewards(stakedTokens[i], rewards);
}
for (uint256 i = 0; i < stakedTokensLength; i++) {
_claim(stakedTokens[i], rewards);
}
}
/**
* @dev Unstake amount and claim rewards
*/
function exit(IERC20[] calldata tokens) external override {
distribute();
uint256 tokensLength = tokens.length;
for (uint256 i = 0; i < tokensLength; i++) {
updateRewards(tokens[i]);
}
for (uint256 i = 0; i < tokensLength; i++) {
_claimAll(tokens[i]);
_unstake(tokens[i], stakes[tokens[i]].staked[msg.sender]);
}
}
// Warning: calling this method will nullify your rewards. Never call it unless you're sure what you are doing!
function emergencyExit(IERC20 stakedToken) external {
_unstake(stakedToken, stakes[stakedToken].staked[msg.sender]);
}
/*
* What proportional share of rewards get distributed to this token?
* The denominator is visible in the public `shares()` view.
*/
function getShare(IERC20 rewardToken, IERC20 stakedToken) external view returns (uint256) {
return rewardDistributions[rewardToken].shares.staked[address(stakedToken)];
}
/**
* @dev Set shares for farms
* Example: setShares([DAI, USDC], [1, 2]) will ensure that 33.(3)% of rewards will go to DAI farm and rest to USDC farm
* If later setShares([DAI, TUSD], [2, 1]) will be called then shares of DAI will grow to 2, shares of USDC won't change and shares of TUSD will be 1
* So this will give 40% of rewards going to DAI farm, 40% to USDC and 20% to TUSD
* @param stakedTokens Token addresses
* @param updatedShares share of the i-th token in the multifarm
*/
function setShares(
IERC20 rewardToken,
IERC20[] calldata stakedTokens,
uint256[] calldata updatedShares
) external onlyRole(DEFAULT_ADMIN_ROLE) {
uint256 tokensLength = stakedTokens.length;
require(tokensLength == updatedShares.length, "TrueMultiFarm: Array lengths mismatch");
_distribute(rewardToken);
for (uint256 i = 0; i < tokensLength; i++) {
_updateTokenFarmRewards(rewardToken, stakedTokens[i]);
}
Stakes storage shares = rewardDistributions[rewardToken].shares;
for (uint256 i = 0; i < tokensLength; i++) {
IERC20 stakedToken = stakedTokens[i];
uint256 oldStaked = shares.staked[address(stakedToken)];
shares.staked[address(stakedToken)] = updatedShares[i];
shares.totalStaked = shares.totalStaked - oldStaked + updatedShares[i];
if (updatedShares[i] == 0) {
_removeReward(rewardToken, stakedToken);
} else if (oldStaked == 0) {
rewardsAvailable[stakedToken].push(rewardToken);
}
}
emit SharesChanged(rewardToken, stakedTokens, updatedShares);
}
function _removeReward(IERC20 rewardToken, IERC20 stakedToken) internal {
IERC20[] storage rewardsAvailableForToken = rewardsAvailable[stakedToken];
uint256 rewardsAvailableForTokenLength = rewardsAvailableForToken.length;
for (uint256 i = 0; i < rewardsAvailableForTokenLength; i++) {
if (rewardsAvailableForToken[i] == rewardToken) {
rewardsAvailableForToken[i] = rewardsAvailableForToken[rewardsAvailableForTokenLength - 1];
rewardsAvailableForToken.pop();
return;
}
}
}
/**
* @dev Internal unstake function
* @param amount Amount of tokens to unstake
*/
function _unstake(IERC20 token, uint256 amount) internal {
require(amount <= stakes[token].staked[msg.sender], "TrueMultiFarm: Cannot withdraw amount bigger than available balance");
stakes[token].staked[msg.sender] -= amount;
stakes[token].totalStaked -= amount;
token.safeTransfer(msg.sender, amount);
emit Unstake(token, msg.sender, amount);
}
function _claimAll(IERC20 token) internal {
IERC20[] memory rewards = rewardsAvailable[token];
_claim(token, rewards);
}
function _claim(IERC20 stakedToken, IERC20[] memory rewards) internal {
uint256 rewardsLength = rewards.length;
for (uint256 i = 0; i < rewardsLength; i++) {
IERC20 rewardToken = rewards[i];
StakerRewards storage _stakerRewards = stakerRewards[rewardToken][stakedToken];
uint256 rewardToClaim = 0;
if (stakes[stakedToken].staked[msg.sender] > 0) {
rewardToClaim = _nextReward(
_stakerRewards,
_stakerRewards.cumulativeRewardPerToken,
stakes[stakedToken].staked[msg.sender],
msg.sender
);
}
_stakerRewards.previousCumulatedRewardPerToken[msg.sender] = _stakerRewards.cumulativeRewardPerToken;
if (rewardToClaim == 0) {
continue;
}
FarmRewards storage farmRewards = rewardDistributions[rewardToken].farmRewards;
farmRewards.unclaimedRewards -= rewardToClaim;
rewardToken.safeTransfer(msg.sender, rewardToClaim);
emit Claim(stakedToken, msg.sender, rewardToClaim);
}
}
function claimable(
IERC20 rewardToken,
IERC20 stakedToken,
address account
) external view returns (uint256) {
return _claimable(rewardToken, stakedToken, account);
}
function rescue(IERC20 rewardToken) external {
uint256 amount = undistributedRewards[rewardToken];
if (amount == 0) {
return;
}
undistributedRewards[rewardToken] = 0;
rewardDistributions[rewardToken].farmRewards.unclaimedRewards -= amount;
rewardToken.safeTransfer(getRoleMember(DEFAULT_ADMIN_ROLE, 0), amount);
}
/**
* @dev Distribute rewards from distributor and increase cumulativeRewardPerShare in Multifarm
*/
function distribute() internal {
uint256 rewardTokensLength = rewardTokens.length;
for (uint256 i = 0; i < rewardTokensLength; i++) {
_distribute(rewardTokens[i]);
}
}
function _distribute(IERC20 rewardToken) internal {
ITrueDistributor distributor = rewardDistributions[rewardToken].distributor;
if (address(distributor) != address(0) && distributor.nextDistribution() > 0 && distributor.farm() == address(this)) {
distributor.distribute();
}
_updateCumulativeRewardPerShare(rewardToken);
}
/**
* @dev This function must be called before any change of token share in multifarm happens (e.g. before shares.totalStaked changes)
* This will also update cumulativeRewardPerToken after distribution has happened
* 1. Get total lifetime rewards as Balance of TRU plus total rewards that have already been claimed
* 2. See how much reward we got since previous update (R)
* 3. Increase cumulativeRewardPerToken by R/total shares
*/
function _updateCumulativeRewardPerShare(IERC20 rewardToken) internal {
FarmRewards storage farmRewards = rewardDistributions[rewardToken].farmRewards;
uint256 newUnclaimedRewards = _rewardBalance(rewardToken);
uint256 rewardSinceLastUpdate = (newUnclaimedRewards - farmRewards.unclaimedRewards) * PRECISION;
// if there are sub farms increase their value per share
uint256 totalStaked = rewardDistributions[rewardToken].shares.totalStaked;
if (totalStaked > 0) {
farmRewards.unclaimedRewards = newUnclaimedRewards;
farmRewards.cumulativeRewardPerShare += rewardSinceLastUpdate / totalStaked;
}
}
/**
* @dev Update rewards for the farm on token and for the staker.
* The function must be called before any modification of staker's stake and to update values when claiming rewards
*/
function updateRewards(IERC20 stakedToken) internal {
IERC20[] storage rewardsAvailableForToken = rewardsAvailable[stakedToken];
uint256 rewardLength = rewardsAvailableForToken.length;
for (uint256 i = 0; i < rewardLength; i++) {
_updateTokenFarmRewards(rewardsAvailableForToken[i], stakedToken);
}
}
function updateRewards(IERC20 stakedToken, IERC20[] memory rewards) internal {
uint256 rewardLength = rewards.length;
for (uint256 i = 0; i < rewardLength; i++) {
_updateTokenFarmRewards(rewards[i], stakedToken);
}
}
function _rewardBalance(IERC20 rewardToken) internal view returns (uint256) {
return rewardToken.balanceOf(address(this)) - stakes[rewardToken].totalStaked;
}
function _updateTokenFarmRewards(IERC20 rewardToken, IERC20 stakedToken) internal {
RewardDistribution storage distribution = rewardDistributions[rewardToken];
FarmRewards storage farmRewards = distribution.farmRewards;
uint256 totalStaked = stakes[stakedToken].totalStaked;
uint256 cumulativeRewardPerShareChange = farmRewards.cumulativeRewardPerShare -
farmRewards.previousCumulatedRewardPerShare[address(stakedToken)];
if (totalStaked > 0) {
stakerRewards[rewardToken][stakedToken].cumulativeRewardPerToken +=
(cumulativeRewardPerShareChange * distribution.shares.staked[address(stakedToken)]) /
totalStaked;
} else {
undistributedRewards[rewardToken] +=
(cumulativeRewardPerShareChange * distribution.shares.staked[address(stakedToken)]) /
PRECISION;
}
farmRewards.previousCumulatedRewardPerShare[address(stakedToken)] = farmRewards.cumulativeRewardPerShare;
}
function _claimable(
IERC20 rewardToken,
IERC20 stakedToken,
address account
) internal view returns (uint256) {
Stakes storage shares = rewardDistributions[rewardToken].shares;
FarmRewards storage farmRewards = rewardDistributions[rewardToken].farmRewards;
StakerRewards storage _stakerRewards = stakerRewards[rewardToken][stakedToken];
ITrueDistributor distributor = rewardDistributions[rewardToken].distributor;
uint256 stakedAmount = stakes[stakedToken].staked[account];
if (stakedAmount == 0) {
return 0;
}
uint256 rewardSinceLastUpdate = _rewardSinceLastUpdate(farmRewards, distributor, rewardToken);
uint256 nextCumulativeRewardPerToken = _nextCumulativeReward(
farmRewards,
_stakerRewards,
shares,
rewardSinceLastUpdate,
address(stakedToken)
);
return _nextReward(_stakerRewards, nextCumulativeRewardPerToken, stakedAmount, account);
}
function _rewardSinceLastUpdate(
FarmRewards storage farmRewards,
ITrueDistributor distributor,
IERC20 rewardToken
) internal view returns (uint256) {
uint256 pending = 0;
if (address(distributor) != address(0) && distributor.farm() == address(this)) {
pending = distributor.nextDistribution();
}
uint256 newUnclaimedRewards = _rewardBalance(rewardToken) + pending;
return newUnclaimedRewards - farmRewards.unclaimedRewards;
}
function _nextCumulativeReward(
FarmRewards storage farmRewards,
StakerRewards storage _stakerRewards,
Stakes storage shares,
uint256 rewardSinceLastUpdate,
address stakedToken
) internal view returns (uint256) {
uint256 cumulativeRewardPerShare = farmRewards.cumulativeRewardPerShare;
uint256 nextCumulativeRewardPerToken = _stakerRewards.cumulativeRewardPerToken;
uint256 totalStaked = stakes[IERC20(stakedToken)].totalStaked;
if (shares.totalStaked > 0) {
cumulativeRewardPerShare += (rewardSinceLastUpdate * PRECISION) / shares.totalStaked;
}
if (totalStaked > 0) {
nextCumulativeRewardPerToken +=
(shares.staked[stakedToken] * (cumulativeRewardPerShare - farmRewards.previousCumulatedRewardPerShare[stakedToken])) /
totalStaked;
}
return nextCumulativeRewardPerToken;
}
function _nextReward(
StakerRewards storage _stakerRewards,
uint256 _cumulativeRewardPerToken,
uint256 _stake,
address _account
) internal view returns (uint256) {
return ((_cumulativeRewardPerToken - _stakerRewards.previousCumulatedRewardPerToken[_account]) * _stake) / PRECISION;
}
}