-
Notifications
You must be signed in to change notification settings - Fork 47
/
LockedSOV.sol
421 lines (351 loc) · 16.5 KB
/
LockedSOV.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
pragma solidity ^0.5.17;
import "../openzeppelin/SafeMath.sol";
import "../interfaces/IERC20.sol";
import "../governance/Vesting/VestingRegistry.sol";
import "../governance/Vesting/VestingLogic.sol";
import "./ILockedSOV.sol";
/**
* @title The Locked SOV Contract.
* @author Franklin Richards - [email protected]
* @notice This contract is used to receive reward from other contracts, Create Vesting and Stake Tokens.
*/
contract LockedSOV is ILockedSOV {
using SafeMath for uint256;
uint256 public constant MAX_BASIS_POINT = 10000;
uint256 public constant MAX_DURATION = 37;
/* Storage */
/// @notice True if the migration to a new Locked SOV Contract has started.
bool public migration;
/// @notice The cliff is the time period after which the tokens begin to unlock.
uint256 public cliff;
/// @notice The duration is the time period after all tokens will have been unlocked.
uint256 public duration;
/// @notice The SOV token contract.
IERC20 public SOV;
/// @notice The Vesting registry contract.
VestingRegistry public vestingRegistry;
/// @notice The New (Future) Locked SOV.
ILockedSOV public newLockedSOV;
/// @notice The locked user balances.
mapping(address => uint256) private lockedBalances;
/// @notice The unlocked user balances.
mapping(address => uint256) private unlockedBalances;
/// @notice The contracts/wallets with admin power.
mapping(address => bool) private isAdmin;
/* Events */
/// @notice Emitted when a new Admin is added to the admin list.
/// @param _initiator The address which initiated this event to be emitted.
/// @param _newAdmin The address of the new admin.
event AdminAdded(address indexed _initiator, address indexed _newAdmin);
/// @notice Emitted when an admin is removed from the admin list.
/// @param _initiator The address which initiated this event to be emitted.
/// @param _removedAdmin The address of the removed admin.
event AdminRemoved(address indexed _initiator, address indexed _removedAdmin);
/// @notice Emitted when Vesting Registry, Duration and/or Cliff is updated.
/// @param _initiator The address which initiated this event to be emitted.
/// @param _vestingRegistry The Vesting Registry Contract.
/// @param _cliff The time period after which the tokens begin to unlock.
/// @param _duration The time period after all tokens will have been unlocked.
event RegistryCliffAndDurationUpdated(
address indexed _initiator,
address indexed _vestingRegistry,
uint256 _cliff,
uint256 _duration
);
/// @notice Emitted when a new deposit is made.
/// @param _initiator The address which initiated this event to be emitted.
/// @param _userAddress The user to whose un/locked balance a new deposit was made.
/// @param _sovAmount The amount of SOV to be added to the un/locked balance.
/// @param _basisPoint The % (in Basis Point) which determines how much will be unlocked immediately.
event Deposited(
address indexed _initiator,
address indexed _userAddress,
uint256 _sovAmount,
uint256 _basisPoint
);
/// @notice Emitted when a user withdraws the fund.
/// @param _initiator The address which initiated this event to be emitted.
/// @param _userAddress The user whose unlocked balance has to be withdrawn.
/// @param _sovAmount The amount of SOV withdrawn from the unlocked balance.
event Withdrawn(address indexed _initiator, address indexed _userAddress, uint256 _sovAmount);
/// @notice Emitted when a user creates a vesting for himself.
/// @param _initiator The address which initiated this event to be emitted.
/// @param _userAddress The user whose unlocked balance has to be withdrawn.
/// @param _vesting The Vesting Contract.
event VestingCreated(
address indexed _initiator,
address indexed _userAddress,
address indexed _vesting
);
/// @notice Emitted when a user stakes tokens.
/// @param _initiator The address which initiated this event to be emitted.
/// @param _vesting The Vesting Contract.
/// @param _amount The amount of locked tokens staked by the user.
event TokenStaked(address indexed _initiator, address indexed _vesting, uint256 _amount);
/// @notice Emitted when an admin initiates a migration to new Locked SOV Contract.
/// @param _initiator The address which initiated this event to be emitted.
/// @param _newLockedSOV The address of the new Locked SOV Contract.
event MigrationStarted(address indexed _initiator, address indexed _newLockedSOV);
/// @notice Emitted when a user initiates the transfer to a new Locked SOV Contract.
/// @param _initiator The address which initiated this event to be emitted.
/// @param _amount The amount of locked tokens to transfer from this contract to the new one.
event UserTransfered(address indexed _initiator, uint256 _amount);
/* Modifiers */
modifier onlyAdmin() {
require(isAdmin[msg.sender], "Only admin can call this.");
_;
}
modifier migrationAllowed() {
require(migration, "Migration has not yet started.");
_;
}
/* Constructor */
/**
* @notice Setup the required parameters.
* @param _SOV The SOV Token Address.
* @param _vestingRegistry The Vesting Registry Address.
* @param _cliff The time period after which the tokens begin to unlock.
* @param _duration The time period after all tokens will have been unlocked.
* @param _admins The list of Admins to be added.
*/
constructor(
address _SOV,
address _vestingRegistry,
uint256 _cliff,
uint256 _duration,
address[] memory _admins
) public {
require(_SOV != address(0), "Invalid SOV Address.");
require(_vestingRegistry != address(0), "Vesting registry address is invalid.");
require(_duration < MAX_DURATION, "Duration is too long.");
SOV = IERC20(_SOV);
vestingRegistry = VestingRegistry(_vestingRegistry);
cliff = _cliff * 4 weeks;
duration = _duration * 4 weeks;
for (uint256 index = 0; index < _admins.length; index++) {
isAdmin[_admins[index]] = true;
}
}
/* Public or External Functions */
/**
* @notice The function to add a new admin.
* @param _newAdmin The address of the new admin.
* @dev Only callable by an Admin.
*/
function addAdmin(address _newAdmin) public onlyAdmin {
require(_newAdmin != address(0), "Invalid Address.");
require(!isAdmin[_newAdmin], "Address is already admin.");
isAdmin[_newAdmin] = true;
emit AdminAdded(msg.sender, _newAdmin);
}
/**
* @notice The function to remove an admin.
* @param _adminToRemove The address of the admin which should be removed.
* @dev Only callable by an Admin.
*/
function removeAdmin(address _adminToRemove) public onlyAdmin {
require(isAdmin[_adminToRemove], "Address is not an admin.");
isAdmin[_adminToRemove] = false;
emit AdminRemoved(msg.sender, _adminToRemove);
}
/**
* @notice The function to update the Vesting Registry, Duration and Cliff.
* @param _vestingRegistry The Vesting Registry Address.
* @param _cliff The time period after which the tokens begin to unlock.
* @param _duration The time period after all tokens will have been unlocked.
* @dev IMPORTANT 1: You have to change Vesting Registry if you want to change Duration and/or Cliff.
* IMPORTANT 2: `_cliff` and `_duration` is multiplied by 4 weeks in this function.
*/
function changeRegistryCliffAndDuration(
address _vestingRegistry,
uint256 _cliff,
uint256 _duration
) external onlyAdmin {
require(
address(vestingRegistry) != _vestingRegistry,
"Vesting Registry has to be different for changing duration and cliff."
);
/// If duration is also zero, then it is similar to Unlocked SOV.
require(_duration != 0, "Duration cannot be zero.");
require(_duration < MAX_DURATION, "Duration is too long.");
vestingRegistry = VestingRegistry(_vestingRegistry);
cliff = _cliff * 4 weeks;
duration = _duration * 4 weeks;
emit RegistryCliffAndDurationUpdated(msg.sender, _vestingRegistry, _cliff, _duration);
}
/**
* @notice Adds SOV to the user balance (Locked and Unlocked Balance based on `_basisPoint`).
* @param _userAddress The user whose locked balance has to be updated with `_sovAmount`.
* @param _sovAmount The amount of SOV to be added to the locked and/or unlocked balance.
* @param _basisPoint The % (in Basis Point)which determines how much will be unlocked immediately.
*/
function deposit(address _userAddress, uint256 _sovAmount, uint256 _basisPoint) external {
_deposit(_userAddress, _sovAmount, _basisPoint);
}
/**
* @notice Adds SOV to the locked balance of a user.
* @param _userAddress The user whose locked balance has to be updated with _sovAmount.
* @param _sovAmount The amount of SOV to be added to the locked balance.
* @dev This is here because there are dependency with other contracts.
*/
function depositSOV(address _userAddress, uint256 _sovAmount) external {
_deposit(_userAddress, _sovAmount, 0);
}
function _deposit(address _userAddress, uint256 _sovAmount, uint256 _basisPoint) private {
// MAX_BASIS_POINT is not included because if 100% is unlocked, then LockedSOV is not required to be used.
require(_basisPoint < MAX_BASIS_POINT, "Basis Point has to be less than 10000.");
bool txStatus = SOV.transferFrom(msg.sender, address(this), _sovAmount);
require(txStatus, "Token transfer was not successful. Check receiver address.");
uint256 unlockedBal = _sovAmount.mul(_basisPoint).div(MAX_BASIS_POINT);
unlockedBalances[_userAddress] = unlockedBalances[_userAddress].add(unlockedBal);
lockedBalances[_userAddress] = lockedBalances[_userAddress].add(_sovAmount).sub(
unlockedBal
);
emit Deposited(msg.sender, _userAddress, _sovAmount, _basisPoint);
}
/**
* @notice A function to withdraw the unlocked balance.
* @param _receiverAddress If specified, the unlocked balance will go to this address, else to msg.sender.
*/
function withdraw(address _receiverAddress) public {
_withdraw(msg.sender, _receiverAddress);
}
function _withdraw(address _sender, address _receiverAddress) private {
address userAddr = _receiverAddress;
if (_receiverAddress == address(0)) {
userAddr = _sender;
}
uint256 amount = unlockedBalances[_sender];
unlockedBalances[_sender] = 0;
bool txStatus = SOV.transfer(userAddr, amount);
require(txStatus, "Token transfer was not successful. Check receiver address.");
emit Withdrawn(_sender, userAddr, amount);
}
/**
* @notice Creates vesting if not already created and Stakes tokens for a user.
* @dev Only use this function if the `duration` is small.
*/
function createVestingAndStake() public {
_createVestingAndStake(msg.sender);
}
function _createVestingAndStake(address _sender) private {
address vestingAddr = _getVesting(_sender);
if (vestingAddr == address(0)) {
vestingAddr = _createVesting(_sender);
}
_stakeTokens(_sender, vestingAddr);
}
/**
* @notice Creates vesting contract (if it hasn't been created yet) for the calling user.
* @return _vestingAddress The New Vesting Contract Created.
*/
function createVesting() public returns (address _vestingAddress) {
_vestingAddress = _createVesting(msg.sender);
}
/**
* @notice Stakes tokens for a user who already have a vesting created.
* @dev The user should already have a vesting created, else this function will throw error.
*/
function stakeTokens() public {
VestingLogic vesting = VestingLogic(_getVesting(msg.sender));
require(
cliff == vesting.cliff() && duration == vesting.duration(),
"Wrong Vesting Schedule."
);
_stakeTokens(msg.sender, address(vesting));
}
/**
* @notice Withdraws unlocked tokens and Stakes Locked tokens for a user who already have a vesting created.
* @param _receiverAddress If specified, the unlocked balance will go to this address, else to msg.sender.
*/
function withdrawAndStakeTokens(address _receiverAddress) external {
_withdraw(msg.sender, _receiverAddress);
_createVestingAndStake(msg.sender);
}
/**
* @notice Withdraws unlocked tokens and Stakes Locked tokens for a user who already have a vesting created.
* @param _userAddress The address of user tokens will be withdrawn.
*/
function withdrawAndStakeTokensFrom(address _userAddress) external {
_withdraw(_userAddress, _userAddress);
_createVestingAndStake(_userAddress);
}
/**
* @notice Function to start the process of migration to new contract.
* @param _newLockedSOV The new locked sov contract address.
*/
function startMigration(address _newLockedSOV) external onlyAdmin {
require(_newLockedSOV != address(0), "New Locked SOV Address is Invalid.");
newLockedSOV = ILockedSOV(_newLockedSOV);
SOV.approve(_newLockedSOV, uint256(-1));
migration = true;
emit MigrationStarted(msg.sender, _newLockedSOV);
}
/**
* @notice Function to transfer the locked balance from this contract to new LockedSOV Contract.
* @dev Address is not specified to discourage selling lockedSOV to other address.
*/
function transfer() external migrationAllowed {
uint256 amount = lockedBalances[msg.sender];
lockedBalances[msg.sender] = 0;
newLockedSOV.depositSOV(msg.sender, amount);
emit UserTransfered(msg.sender, amount);
}
/* Internal Functions */
/**
* @notice Creates a Vesting Contract for a user.
* @param _tokenOwner The owner of the vesting contract.
* @return _vestingAddress The Vesting Contract Address.
* @dev Does not do anything if Vesting Contract was already created.
*/
function _createVesting(address _tokenOwner) internal returns (address _vestingAddress) {
/// Here zero is given in place of amount, as amount is not really used in `vestingRegistry.createVesting()`.
vestingRegistry.createVesting(_tokenOwner, 0, cliff, duration);
_vestingAddress = _getVesting(_tokenOwner);
emit VestingCreated(msg.sender, _tokenOwner, _vestingAddress);
}
/**
* @notice Returns the Vesting Contract Address.
* @param _tokenOwner The owner of the vesting contract.
* @return _vestingAddress The Vesting Contract Address.
*/
function _getVesting(address _tokenOwner) internal view returns (address _vestingAddress) {
return vestingRegistry.getVesting(_tokenOwner);
}
/**
* @notice Stakes the tokens in a particular vesting contract.
* @param _vesting The Vesting Contract Address.
*/
function _stakeTokens(address _sender, address _vesting) internal {
uint256 amount = lockedBalances[_sender];
lockedBalances[_sender] = 0;
require(SOV.approve(_vesting, amount), "Approve failed.");
VestingLogic(_vesting).stakeTokens(amount);
emit TokenStaked(_sender, _vesting, amount);
}
/* Getter or Read Functions */
/**
* @notice The function to get the locked balance of a user.
* @param _addr The address of the user to check the locked balance.
* @return _balance The locked balance of the address `_addr`.
*/
function getLockedBalance(address _addr) external view returns (uint256 _balance) {
return lockedBalances[_addr];
}
/**
* @notice The function to get the unlocked balance of a user.
* @param _addr The address of the user to check the unlocked balance.
* @return _balance The unlocked balance of the address `_addr`.
*/
function getUnlockedBalance(address _addr) external view returns (uint256 _balance) {
return unlockedBalances[_addr];
}
/**
* @notice The function to check is an address is admin or not.
* @param _addr The address of the user to check the admin status.
* @return _status True if admin, False otherwise.
*/
function adminStatus(address _addr) external view returns (bool _status) {
return isAdmin[_addr];
}
}