This repository has been archived by the owner on Apr 8, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathZivoeRewardsVesting.sol
517 lines (431 loc) · 24 KB
/
ZivoeRewardsVesting.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
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
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.17;
import "./libraries/ZivoeVotes.sol";
import "../lib/openzeppelin-contracts/contracts/security/ReentrancyGuard.sol";
import "../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
import "../lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol";
import "../lib/openzeppelin-contracts/contracts/utils/Context.sol";
import "../lib/openzeppelin-contracts/contracts/utils/math/Math.sol";
import "../lib/openzeppelin-contracts/contracts/utils/math/SafeMath.sol";
interface IZivoeGlobals_ZivoeRewardsVesting {
/// @notice Returns the address of the ZivoeITO contract.
function ITO() external view returns (address);
/// @notice Returns the address of the ZivoeToken contract.
function ZVE() external view returns (address);
/// @notice Returns the address of the Zivoe Laboratory.
function ZVL() external view returns (address);
}
interface IZivoeITO_ZivoeRewardsVesting {
/// @dev Tracks $pZVE (credits) an individual has from juniorDeposit().
function juniorCredits(address) external returns(uint256);
/// @dev Tracks $pZVE (credits) an individual has from seniorDeposit().
function seniorCredits(address) external returns(uint256);
}
/// @notice This contract facilitates staking and yield distribution, as well as vesting tokens.
/// This contract has the following responsibilities:
/// - Allows creation of vesting schedules (and revocation) for "vestingToken".
/// - Allows unstaking of vested tokens.
/// - Allows claiming yield distributed / "deposited" to this contract.
/// - Allows multiple assets to be added as "rewardToken" for distributions (except for "vestingToken").
/// - Vests rewardTokens linearly overtime to stakers.
contract ZivoeRewardsVesting is ReentrancyGuard, Context, ZivoeVotes {
using SafeMath for uint256;
using SafeERC20 for IERC20;
// ---------------------
// State Variables
// ---------------------
struct Reward {
uint256 rewardsDuration; /// @dev How long rewards take to vest, e.g. 30 days.
uint256 periodFinish; /// @dev When current rewards will finish vesting.
uint256 rewardRate; /// @dev Rewards emitted per second.
uint256 lastUpdateTime; /// @dev Last time this data struct was updated.
uint256 rewardPerTokenStored; /// @dev Last snapshot of rewardPerToken taken.
}
struct VestingSchedule {
uint256 start; /// @dev The block.timestamp at which tokens will start vesting.
uint256 cliff; /// @dev The block.timestamp at which tokens are first claimable.
uint256 end; /// @dev The block.timestamp at which tokens will stop vesting (finished).
uint256 totalVesting; /// @dev The total amount to vest.
uint256 totalWithdrawn; /// @dev The total amount withdrawn so far.
uint256 vestingPerSecond; /// @dev The amount of vestingToken that vests per second.
bool revokable; /// @dev Whether or not this vesting schedule can be revoked.
}
address public immutable GBL; /// @dev The ZivoeGlobals contract.
address public vestingToken; /// @dev The token vesting, in this case Zivoe ($ZVE).
address[] public rewardTokens; /// @dev Array of ERC20 tokens distributed as rewards (if present).
uint256 public vestingTokenAllocated; /// @dev The amount of vestingToken currently allocated.
uint256 private _totalSupply; /// @dev Total supply of (non-transferrable) LP tokens for reards contract.
/// @dev Contains rewards information for each rewardToken.
mapping(address => Reward) public rewardData;
/// @dev Tracks if a wallet has been assigned a schedule.
mapping(address => bool) public vestingScheduleSet;
/// @dev Tracks the vesting schedule of accounts.
mapping(address => VestingSchedule) public vestingScheduleOf;
/// @dev The order is account -> rewardAsset -> amount.
mapping(address => mapping(address => uint256)) public accountRewardPerTokenPaid;
/// @dev The order is account -> rewardAsset -> amount.
mapping(address => mapping(address => uint256)) public rewards;
/// @dev Contains LP token balance of each account (is 1:1 ratio with amount deposited).
mapping(address => uint256) private _balances;
IERC20 public stakingToken; /// @dev IERC20 wrapper for the stakingToken (deposited to receive LP tokens).
// -----------------
// Constructor
// -----------------
/// @notice Initializes the ZivoeRewardsVesting contract.
/// @param _stakingToken The ERC20 asset deposited to mint LP tokens (and returned when burning LP tokens).
/// @param _GBL The ZivoeGlobals contract.
constructor(address _stakingToken, address _GBL) {
stakingToken = IERC20(_stakingToken);
vestingToken = _stakingToken;
GBL = _GBL;
}
// ------------
// Events
// ------------
/// @notice Emitted during addReward().
/// @param reward The asset now supported as a reward.
event RewardAdded(address indexed reward);
/// @notice Emitted during depositReward().
/// @param reward The asset that's being deposited.
/// @param amount The amout deposited.
/// @param depositor The _msgSender() who deposited said reward.
event RewardDeposited(address indexed reward, uint256 amount, address indexed depositor);
/// @notice Emitted during _getRewardAt().
/// @param account The account receiving a reward.
/// @param rewardsToken The ERC20 asset distributed as a reward.
/// @param reward The amount of "rewardsToken" distributed.
event RewardDistributed(address indexed account, address indexed rewardsToken, uint256 reward);
/// @notice Emitted during stake().
/// @param account The account staking "stakingToken".
/// @param amount The amount of "stakingToken" staked.
event Staked(address indexed account, uint256 amount);
/// @notice Emitted during createVestingSchedule().
/// @param account The account that was given a vesting schedule.
/// @return start The block.timestamp at which tokens will start vesting.
/// @return cliff The block.timestamp at which tokens are first claimable.
/// @return end The block.timestamp at which tokens will stop vesting (finished).
/// @return totalVesting The total amount to vest.
/// @return vestingPerSecond The amount of vestingToken that vests per second.
/// @return revokable Whether or not this vesting schedule can be revoked.
event VestingScheduleCreated(
address indexed account,
uint256 start,
uint256 cliff,
uint256 end,
uint256 totalVesting,
uint256 vestingPerSecond,
bool revokable
);
/// @notice Emitted during revokeVestingSchedule().
/// @param account The account that was revoked a vesting schedule.
/// @param amountRevoked The amount of tokens revoked.
/// @return cliff The updated value for cliff.
/// @return end The updated value for end.
/// @return totalVesting The total amount vested (claimable).
/// @return revokable The final revokable status of schedule (always false after revocation).
event VestingScheduleRevoked(
address indexed account,
uint256 amountRevoked,
uint256 cliff,
uint256 end,
uint256 totalVesting,
bool revokable
);
/// @notice Emitted during withdraw().
/// @param account The account withdrawing "stakingToken".
/// @param amount The amount of "stakingToken" withdrawn.
event Withdrawn(address indexed account, uint256 amount);
// ---------------
// Modifiers
// ---------------
/// @notice This modifier ensures the caller of a function is ZVL or ZivoeITO.
modifier onlyZVLOrITO() {
require(
_msgSender() == IZivoeGlobals_ZivoeRewardsVesting(GBL).ZVL() ||
_msgSender() == IZivoeGlobals_ZivoeRewardsVesting(GBL).ITO(),
"ZivoeRewardsVesting::onlyZVLOrITO() _msgSender() != ZVL && _msgSender() != ITO"
);
_;
}
/// @notice This modifier ensures account rewards information is updated BEFORE mutative actions.
/// @param account The account to update personal rewards information of (if not address(0)).
modifier updateReward(address account) {
for (uint256 i; i < rewardTokens.length; i++) {
address token = rewardTokens[i];
rewardData[token].rewardPerTokenStored = rewardPerToken(token);
rewardData[token].lastUpdateTime = lastTimeRewardApplicable(token);
if (account != address(0)) {
rewards[account][token] = earned(account, token);
accountRewardPerTokenPaid[account][token] = rewardData[token].rewardPerTokenStored;
}
}
_;
}
// ---------------
// Functions
// ---------------
/// @notice Returns the amount of tokens owned by "account", received when depositing via stake().
/// @param account The account to view information of.
/// @return amount The amount of tokens owned by "account".
function balanceOf(address account) external view returns (uint256 amount) { return _balances[account]; }
/// @notice Returns the total amount of rewards being distributed to everyone for current rewardsDuration.
/// @param _rewardsToken The asset that's being distributed.
/// @return amount The amount of rewards being distributed.
function getRewardForDuration(address _rewardsToken) external view returns (uint256 amount) {
return rewardData[_rewardsToken].rewardRate.mul(rewardData[_rewardsToken].rewardsDuration);
}
/// @notice Returns the amount of tokens in existence; these are minted and burned when depositing or withdrawing.
/// @return amount The amount of tokens in existence.
function totalSupply() external view returns (uint256 amount) { return _totalSupply; }
/// @notice Returns the last snapshot of rewardPerTokenStored taken for a reward asset.
/// @param account The account to view information of.
/// @param rewardAsset The reward token for which we want to return the rewardPerTokenstored.
/// @return amount The latest up-to-date value of rewardPerTokenStored.
function viewAccountRewardPerTokenPaid(
address account, address rewardAsset
) external view returns (uint256 amount) {
return accountRewardPerTokenPaid[account][rewardAsset];
}
/// @notice Returns the rewards earned of a specific rewardToken for an address.
/// @param account The account to view information of.
/// @param rewardAsset The asset earned as a reward.
/// @return amount The amount of rewards earned.
function viewRewards(address account, address rewardAsset) external view returns (uint256 amount) {
return rewards[account][rewardAsset];
}
/// @notice Provides information for a vesting schedule.
/// @param account The account to view information of.
/// @return start The block.timestamp at which tokens will start vesting.
/// @return cliff The block.timestamp at which tokens are first claimable.
/// @return end The block.timestamp at which tokens will stop vesting (finished).
/// @return totalVesting The total amount to vest.
/// @return totalWithdrawn The total amount withdrawn so far.
/// @return vestingPerSecond The amount of vestingToken that vests per second.
/// @return revokable Whether or not this vesting schedule can be revoked.
function viewSchedule(address account) external view returns (
uint256 start,
uint256 cliff,
uint256 end,
uint256 totalVesting,
uint256 totalWithdrawn,
uint256 vestingPerSecond,
bool revokable
) {
start = vestingScheduleOf[account].start;
cliff = vestingScheduleOf[account].cliff;
end = vestingScheduleOf[account].end;
totalVesting = vestingScheduleOf[account].totalVesting;
totalWithdrawn = vestingScheduleOf[account].totalWithdrawn;
vestingPerSecond = vestingScheduleOf[account].vestingPerSecond;
revokable = vestingScheduleOf[account].revokable;
}
/// @notice Returns the amount of $ZVE tokens an account can withdraw.
/// @param account The account to be withdrawn from.
/// @return amount Withdrawable amount of $ZVE tokens.
function amountWithdrawable(address account) public view returns (uint256 amount) {
if (block.timestamp < vestingScheduleOf[account].cliff) { return 0; }
if (
block.timestamp >= vestingScheduleOf[account].cliff &&
block.timestamp < vestingScheduleOf[account].end
) {
return vestingScheduleOf[account].vestingPerSecond * (
block.timestamp - vestingScheduleOf[account].start
) - vestingScheduleOf[account].totalWithdrawn;
}
else if (block.timestamp >= vestingScheduleOf[account].end) {
return vestingScheduleOf[account].totalVesting - vestingScheduleOf[account].totalWithdrawn;
}
else { return 0; }
}
/// @notice Provides information on the rewards available for claim.
/// @param account The account to view information of.
/// @param _rewardsToken The asset that's being distributed.
/// @return amount The amount of rewards earned.
function earned(address account, address _rewardsToken) public view returns (uint256 amount) {
return _balances[account].mul(
rewardPerToken(_rewardsToken).sub(accountRewardPerTokenPaid[account][_rewardsToken])
).div(1e18).add(rewards[account][_rewardsToken]);
}
/// @notice Helper function for assessing distribution timelines.
/// @param _rewardsToken The asset that's being distributed.
/// @return timestamp The most recent time (in UNIX format) at which rewards are available for distribution.
function lastTimeRewardApplicable(address _rewardsToken) public view returns (uint256 timestamp) {
return Math.min(block.timestamp, rewardData[_rewardsToken].periodFinish);
}
/// @notice Cumulative amount of rewards distributed per LP token.
/// @param _rewardsToken The asset that's being distributed.
/// @return amount The cumulative amount of rewards distributed per LP token.
function rewardPerToken(address _rewardsToken) public view returns (uint256 amount) {
if (_totalSupply == 0) { return rewardData[_rewardsToken].rewardPerTokenStored; }
return rewardData[_rewardsToken].rewardPerTokenStored.add(
lastTimeRewardApplicable(_rewardsToken).sub(
rewardData[_rewardsToken].lastUpdateTime
).mul(rewardData[_rewardsToken].rewardRate).mul(1e18).div(_totalSupply)
);
}
/// @notice Adds a new asset as a reward to this contract.
/// @param _rewardsToken The asset that's being distributed.
/// @param _rewardsDuration How long rewards take to vest, e.g. 30 days (denoted in seconds).
function addReward(address _rewardsToken, uint256 _rewardsDuration) external {
require(
_msgSender() == IZivoeGlobals_ZivoeRewardsVesting(GBL).ZVL(),
"_msgSender() != IZivoeGlobals_ZivoeRewardsVesting(GBL).ZVL()"
);
require(
_rewardsToken != IZivoeGlobals_ZivoeRewardsVesting(GBL).ZVE(),
"ZivoeRewardsVesting::addReward() _rewardsToken == IZivoeGlobals_ZivoeRewardsVesting(GBL).ZVE()"
);
require(_rewardsDuration > 0, "ZivoeRewardsVesting::addReward() _rewardsDuration == 0");
require(
rewardData[_rewardsToken].rewardsDuration == 0,
"ZivoeRewardsVesting::addReward() rewardData[_rewardsToken].rewardsDuration != 0"
);
require(rewardTokens.length < 10, "ZivoeRewardsVesting::addReward() rewardTokens.length >= 10");
rewardTokens.push(_rewardsToken);
rewardData[_rewardsToken].rewardsDuration = _rewardsDuration;
emit RewardAdded(_rewardsToken);
}
/// @notice Deposits a reward to this contract for distribution.
/// @param _rewardsToken The asset that's being distributed.
/// @param reward The amount of the _rewardsToken to deposit.
function depositReward(address _rewardsToken, uint256 reward) external updateReward(address(0)) nonReentrant {
IERC20(_rewardsToken).safeTransferFrom(_msgSender(), address(this), reward);
// Update vesting accounting for reward (if existing rewards being distributed, increase proportionally).
if (block.timestamp >= rewardData[_rewardsToken].periodFinish) {
rewardData[_rewardsToken].rewardRate = reward.div(rewardData[_rewardsToken].rewardsDuration);
} else {
uint256 remaining = rewardData[_rewardsToken].periodFinish.sub(block.timestamp);
uint256 leftover = remaining.mul(rewardData[_rewardsToken].rewardRate);
rewardData[_rewardsToken].rewardRate = reward.add(leftover).div(rewardData[_rewardsToken].rewardsDuration);
}
rewardData[_rewardsToken].lastUpdateTime = block.timestamp;
rewardData[_rewardsToken].periodFinish = block.timestamp.add(rewardData[_rewardsToken].rewardsDuration);
emit RewardDeposited(_rewardsToken, reward, _msgSender());
}
/// @notice Simultaneously calls withdraw() and getRewards() for convenience.
function fullWithdraw() external {
withdraw();
getRewards();
}
/// @notice Sets the vestingSchedule for an account.
/// @param account The account vesting $ZVE.
/// @param daysToCliff The number of days before vesting is claimable (a.k.a. cliff period).
/// @param daysToVest The number of days for the entire vesting period, from beginning to end.
/// @param amountToVest The amount of tokens being vested.
/// @param revokable If the vested amount can be revoked.
function createVestingSchedule(
address account,
uint256 daysToCliff,
uint256 daysToVest,
uint256 amountToVest,
bool revokable
) external onlyZVLOrITO {
require(
!vestingScheduleSet[account],
"ZivoeRewardsVesting::createVestingSchedule() vestingScheduleSet[account]"
);
require(
IERC20(vestingToken).balanceOf(address(this)) - vestingTokenAllocated >= amountToVest,
"ZivoeRewardsVesting::createVestingSchedule() amountToVest > vestingToken.balanceOf(address(this)) - vestingTokenAllocated"
);
require(daysToVest <= 1800 days, "ZivoeRewardsVesting::createVestingSchedule() daysToVest > 1800 days");
require(daysToCliff <= daysToVest, "ZivoeRewardsVesting::createVestingSchedule() daysToCliff > daysToVest");
require(
IZivoeITO_ZivoeRewardsVesting(IZivoeGlobals_ZivoeRewardsVesting(GBL).ITO()).seniorCredits(account) == 0 &&
IZivoeITO_ZivoeRewardsVesting(IZivoeGlobals_ZivoeRewardsVesting(GBL).ITO()).juniorCredits(account) == 0,
"ZivoeRewardsVesting::createVestingSchedule() seniorCredits(_msgSender) > 0 || juniorCredits(_msgSender) > 0"
);
vestingScheduleSet[account] = true;
vestingTokenAllocated += amountToVest;
vestingScheduleOf[account].start = block.timestamp;
vestingScheduleOf[account].cliff = block.timestamp + daysToCliff * 1 days;
vestingScheduleOf[account].end = block.timestamp + daysToVest * 1 days;
vestingScheduleOf[account].totalVesting = amountToVest;
vestingScheduleOf[account].vestingPerSecond = amountToVest / (daysToVest * 1 days);
vestingScheduleOf[account].revokable = revokable;
emit VestingScheduleCreated(
account,
vestingScheduleOf[account].start,
vestingScheduleOf[account].cliff,
vestingScheduleOf[account].end,
vestingScheduleOf[account].totalVesting,
vestingScheduleOf[account].vestingPerSecond,
vestingScheduleOf[account].revokable
);
_stake(amountToVest, account);
}
/// @notice Ends vesting schedule for a given account (if revokable).
/// @param account The acount to revoke a vesting schedule for.
function revokeVestingSchedule(address account) external updateReward(account) onlyZVLOrITO nonReentrant {
require(
vestingScheduleSet[account],
"ZivoeRewardsVesting::revokeVestingSchedule() !vestingScheduleSet[account]"
);
require(
vestingScheduleOf[account].revokable,
"ZivoeRewardsVesting::revokeVestingSchedule() !vestingScheduleOf[account].revokable"
);
uint256 amount = amountWithdrawable(account);
uint256 vestingAmount = vestingScheduleOf[account].totalVesting;
vestingTokenAllocated -= amount;
vestingScheduleOf[account].totalWithdrawn += amount;
vestingScheduleOf[account].totalVesting = vestingScheduleOf[account].totalWithdrawn;
vestingScheduleOf[account].cliff = block.timestamp - 1;
vestingScheduleOf[account].end = block.timestamp;
vestingTokenAllocated -= (vestingAmount - vestingScheduleOf[account].totalWithdrawn);
_totalSupply = _totalSupply.sub(vestingAmount);
_writeCheckpoint(_totalSupplyCheckpoints, _subtract, vestingAmount);
_writeCheckpoint(_checkpoints[account], _subtract, amount);
_balances[account] = 0;
stakingToken.safeTransfer(account, amount);
vestingScheduleOf[account].revokable = false;
emit VestingScheduleRevoked(
account,
vestingAmount - vestingScheduleOf[account].totalWithdrawn,
vestingScheduleOf[account].cliff,
vestingScheduleOf[account].end,
vestingScheduleOf[account].totalVesting,
false
);
}
/// @notice Stakes the specified amount of stakingToken to this contract.
/// @dev Intended to be private, so only callable via createVestingSchedule().
/// @param amount The amount of the _rewardsToken to deposit.
/// @param account The account to stake for.
function _stake(uint256 amount, address account) private nonReentrant updateReward(account) {
require(amount > 0, "ZivoeRewardsVesting::_stake() amount == 0");
_totalSupply = _totalSupply.add(amount);
_writeCheckpoint(_totalSupplyCheckpoints, _add, amount);
_writeCheckpoint(_checkpoints[account], _add, amount);
_balances[account] = _balances[account].add(amount);
emit Staked(account, amount);
}
/// @notice Claim rewards for all possible _rewardTokens.
function getRewards() public updateReward(_msgSender()) {
for (uint256 i = 0; i < rewardTokens.length; i++) { _getRewardAt(i); }
}
/// @notice Claim rewards for a specific _rewardToken.
/// @param index The index to claim, corresponds to a given index of rewardToken[].
function _getRewardAt(uint256 index) internal nonReentrant {
address _rewardsToken = rewardTokens[index];
uint256 reward = rewards[_msgSender()][_rewardsToken];
if (reward > 0) {
rewards[_msgSender()][_rewardsToken] = 0;
IERC20(_rewardsToken).safeTransfer(_msgSender(), reward);
emit RewardDistributed(_msgSender(), _rewardsToken, reward);
}
}
/// @notice Withdraws the available amount of stakingToken from this contract.
function withdraw() public nonReentrant updateReward(_msgSender()) {
uint256 amount = amountWithdrawable(_msgSender());
require(amount > 0, "ZivoeRewardsVesting::withdraw() amountWithdrawable(_msgSender()) == 0");
vestingScheduleOf[_msgSender()].totalWithdrawn += amount;
vestingTokenAllocated -= amount;
_totalSupply = _totalSupply.sub(amount);
_writeCheckpoint(_totalSupplyCheckpoints, _subtract, amount);
_writeCheckpoint(_checkpoints[_msgSender()], _subtract, amount);
_balances[_msgSender()] = _balances[_msgSender()].sub(amount);
stakingToken.safeTransfer(_msgSender(), amount);
emit Withdrawn(_msgSender(), amount);
}
}