-
Notifications
You must be signed in to change notification settings - Fork 0
/
LitlabPreStakingBox.sol
197 lines (159 loc) · 8.52 KB
/
LitlabPreStakingBox.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
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "../utils/Ownable.sol";
/// PRESTAKING BOX
/// @notice Staking contract for investors. At deployement we send all the tokens for each investor to this contract with a plus amount of rewards
contract LitlabPreStakingBox is Ownable {
using SafeERC20 for IERC20;
enum InvestorType {
ANGEL,
SEED,
STRATEGIC
}
struct UserStake {
uint256 amount;
uint256 withdrawn;
uint256 lastRewardsWithdrawn;
uint256 lastUserWithdrawn;
InvestorType investorType;
bool claimedInitial;
bool withdrawnFirst;
}
address public token;
uint256 public stakeStartDate;
uint256 public stakeEndDate;
uint256 public totalStakedAmount;
uint256 public totalRewards;
mapping(address => UserStake) private balances;
event onInitialWithdraw(address _user, uint256 _amount);
event onWithdrawRewards(address _user, uint256 _rewards);
event onWithdraw(address _user, uint256 _amount);
event onEmergencyWithdraw();
/// @notice Constructor
/// @param _token Address of the litlab token
/// @param _stakeStartDate Start date of staking
/// @param _stakeEndDate End date of staking
/// @param _totalRewards Rewards for the staking
constructor(address _token, uint256 _stakeStartDate, uint256 _stakeEndDate, uint256 _totalRewards) {
token = _token;
stakeStartDate = _stakeStartDate;
stakeEndDate = _stakeEndDate;
totalRewards = _totalRewards;
}
/// @notice Stake function. Call at the deployment by the owner only one time to fill the investors amounts
/// @param _users Array with all the address of the investors
/// @param _amounts Array with the investment amounts
/// @param _investorTypes Array with the investor types (to calculate the vesting period)
function stake(address[] memory _users, uint256[] memory _amounts, uint8[] memory _investorTypes) external onlyOwner {
require(_users.length == _amounts.length, "BadLenghts");
require(_investorTypes.length == _amounts.length, "BadLengths");
uint total = 0;
for (uint256 i=0; i<_users.length; i++) {
address user = _users[i];
uint256 amount = _amounts[i];
InvestorType investorType = InvestorType(_investorTypes[i]);
require(amount > 0, "BadAmount");
balances[user] = UserStake({
amount: amount,
withdrawn: 0,
lastRewardsWithdrawn: 0,
lastUserWithdrawn: 0,
investorType: investorType,
claimedInitial: false,
withdrawnFirst: false
});
total += amount;
}
totalStakedAmount = total;
}
/// @notice At TGE users can withdraw the 15% of their investment. Only one time
function withdrawInitial() external {
require(block.timestamp >= stakeStartDate, "NotTGE");
require(balances[msg.sender].amount != 0, "NoStaked");
require(balances[msg.sender].claimedInitial == false, "Claimed");
uint256 amount = balances[msg.sender].amount * 15 / 100;
balances[msg.sender].withdrawn += amount;
balances[msg.sender].claimedInitial = true;
IERC20(token).safeTransfer(msg.sender, amount);
emit onInitialWithdraw(msg.sender, amount);
}
/// @notice Users can withdraw rewards whenever they want with no penalty only if they don't withdraw previously
function withdrawRewards() external {
require(balances[msg.sender].amount != 0, "NoStaked");
require(balances[msg.sender].withdrawnFirst == false, "Withdrawn");
require(block.timestamp >= stakeStartDate, "NotYet");
(, , , , , uint256 pendingRewards) = _getData(msg.sender);
require(pendingRewards > 0, "NoRewardsToClaim");
balances[msg.sender].lastRewardsWithdrawn = block.timestamp;
IERC20(token).safeTransfer(msg.sender, pendingRewards);
emit onWithdrawRewards(msg.sender, pendingRewards);
}
/// @notice Users withdraws all the balance according their vesting, but they couldn't withdraw rewards any more with the witdrawRewards function
function withdraw() external {
require(balances[msg.sender].amount > 0, "NoStaked");
require(balances[msg.sender].withdrawn < balances[msg.sender].amount, "Max");
(uint256 userAmount, , , , , uint256 pendingRewards) = _getData(msg.sender);
uint256 tokensToSend = 0;
if (balances[msg.sender].withdrawnFirst == false) {
// This is the last time this user can get rewards, and the rest of the rewards are splitted for the other users.
totalStakedAmount -= userAmount;
totalRewards -= pendingRewards;
uint256 tokens = _calculateVestingTokens(msg.sender);
balances[msg.sender].withdrawn += tokens;
balances[msg.sender].lastUserWithdrawn = block.timestamp;
balances[msg.sender].withdrawnFirst = true;
tokensToSend = tokens + pendingRewards;
IERC20(token).safeTransfer(msg.sender, tokensToSend);
} else {
tokensToSend = _calculateVestingTokens(msg.sender);
balances[msg.sender].withdrawn += tokensToSend;
balances[msg.sender].lastUserWithdrawn = block.timestamp;
IERC20(token).safeTransfer(msg.sender, tokensToSend);
}
emit onWithdraw(msg.sender, tokensToSend);
}
/// @notice Get the data for each user (to show in the frontend dapp)
function getData(address _user) external view returns (uint256 userAmount, uint256 withdrawn, uint256 rewardsTokensPerSec, uint256 lastRewardsWithdrawn, uint256 lastUserWithdrawn, uint256 pendingRewards) {
return _getData(_user);
}
/// @notice Quick way to know how many tokens are pending in the contract
function getTokensInContract() external view returns (uint256 tokens) {
tokens = IERC20(token).balanceOf(address(this));
}
/// @notice If there's any problem, contract owner can withdraw all funds (this is not a public and open stake, it's only for authorized investors)
function emergencyWithdraw() external onlyOwner {
uint256 balance = IERC20(token).balanceOf(address(this));
IERC20(token).safeTransfer(msg.sender, balance);
emit onEmergencyWithdraw();
}
/// @notice Calculate the token vesting according the investor type
function _calculateVestingTokens(address _user) internal view returns (uint256) {
InvestorType investorType = balances[_user].investorType;
uint256 vestingDays;
if (investorType == InvestorType.ANGEL) vestingDays = 36 * 30 days;
else if (investorType == InvestorType.SEED) vestingDays = 30 * 30 days;
else if (investorType == InvestorType.STRATEGIC) vestingDays = 24 * 30 days;
uint256 amountMinusFirstWithdraw = balances[_user].amount - (balances[_user].amount * 15 / 100);
uint256 tokensPerSec = amountMinusFirstWithdraw / vestingDays;
uint256 from = balances[_user].lastUserWithdrawn == 0 ? stakeStartDate : balances[_user].lastUserWithdrawn;
uint256 to = block.timestamp > stakeStartDate + vestingDays ? stakeStartDate + vestingDays : block.timestamp;
uint256 diffTime = from <= to ? to - from : 0;
uint256 tokens = diffTime * tokensPerSec;
if (balances[_user].amount - balances[_user].withdrawn < tokens) tokens = balances[_user].amount - balances[_user].withdrawn;
return tokens;
}
/// Return contract data needed in the frontend
function _getData(address _user) internal view returns (uint256 userAmount, uint256 withdrawn, uint256 rewardsTokensPerSec, uint256 lastRewardsWithdraw, uint256 lastUserWithdrawn, uint256 pendingRewards) {
userAmount = balances[_user].amount;
withdrawn = balances[_user].withdrawn;
lastRewardsWithdraw = balances[_user].lastRewardsWithdrawn;
lastUserWithdrawn = balances[_user].lastUserWithdrawn;
uint256 from = lastRewardsWithdraw == 0 ? stakeStartDate : lastRewardsWithdraw;
uint256 to = block.timestamp > stakeEndDate ? stakeEndDate : block.timestamp;
if (totalStakedAmount > 0) {
rewardsTokensPerSec = (totalRewards * (balances[_user].amount / 10 ** 18)) / ((stakeEndDate - stakeStartDate) * (totalStakedAmount / 10 ** 18));
pendingRewards = balances[_user].withdrawnFirst == false ? (to - from) * rewardsTokensPerSec : 0;
}
}
}