-
Notifications
You must be signed in to change notification settings - Fork 0
/
OpenStakeFlat.sol
342 lines (265 loc) · 26.5 KB
/
OpenStakeFlat.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
// Sources flattened with hardhat v2.9.1 https://hardhat.org
// File contracts/Ownable.sol
// SPDX-License-Identifier: AGPL-3.0
pragma solidity ^0.8.0;
contract Ownable {
address private _owner;
event OwnershipTransferred(address indexed newOwner);
constructor() {
_owner = msg.sender;
emit OwnershipTransferred(msg.sender);
}
modifier onlyOwner() {
require(msg.sender == _owner,"Ownable: you are not the owner");
_;
}
function changeOwner(address newOwner) public onlyOwner {
_owner = newOwner;
emit OwnershipTransferred(_owner);
}
}
// File contracts/Constants.sol
pragma solidity ^0.8.0;
contract Constants is Ownable {
uint256 public _minRewardDuration;
uint256 public _minStakingAmount;
uint256 public _rewardMultiplierPerSecond;
uint256 public _valorDuration;
uint256 public _penaltyConstant;
event MinRewardDurationAdjusted(uint256 minRewardDuration, uint256 newMinRewardDuration);
event MinStakingAmountAdjusted(uint256 minStakingAmount, uint256 newMinStakingAmount);
event RewardMultiplierAdjusted(uint256 rewardMultiplierPerSecond, uint256 newRewardMultiplierPerSecond);
event ValorDurationAdjusted(uint256 valorDuration, uint256 newValorDuration);
event PenaltyConstantAdjusted(uint256 penaltyConstant, uint256 newPenaltyConstant);
// ONLY OWNER ADJUSTERS FOR CONSTANTS //
function adjustMinRewardDuration(uint256 newMinRewardDuration) public onlyOwner {
emit MinRewardDurationAdjusted(_minRewardDuration, newMinRewardDuration);
_minRewardDuration = newMinRewardDuration;
}
function adjustMinStakingAmount(uint256 newMinStakingAmount) public onlyOwner {
emit MinStakingAmountAdjusted(_minStakingAmount, newMinStakingAmount);
_minStakingAmount = newMinStakingAmount;
}
function adjustRewardMultiplier(uint256 newRewardMultiplierPerSecond) public onlyOwner {
emit RewardMultiplierAdjusted(_rewardMultiplierPerSecond, newRewardMultiplierPerSecond);
_rewardMultiplierPerSecond = newRewardMultiplierPerSecond;
}
function adjustValorDuration(uint256 newValorDuration) public onlyOwner {
emit ValorDurationAdjusted(_valorDuration, newValorDuration);
_valorDuration = newValorDuration;
}
function adjustPenaltyConstant(uint256 newPenaltyConstant) public onlyOwner {
emit PenaltyConstantAdjusted(_penaltyConstant, newPenaltyConstant);
_penaltyConstant = newPenaltyConstant;
}
}
// File contracts/Storage.sol
pragma solidity ^0.8.0;
contract Storage {
uint256 public _rewardsDistributed;
mapping(address => uint256) public _rewardsClaimed;
mapping(address => uint[]) public _stakes;
mapping(address => uint[]) public _stakeTimes;
mapping(address => uint[]) public _withdrawals;
mapping(address => uint[]) public _withdrawalTimes;
event StakeEntryAdded(address indexed user, uint256 amount, uint256 time);
event StakeEntryRemoved(address indexed user, uint256 amount, uint256 time);
event WithdrawalEntryRemoved(address indexed user, uint256 amount, uint256 time);
event WithdrawalEntryAdded(address indexed, uint256 amount, uint256 time);
modifier onlyStaker() {
require(_stakes[msg.sender].length != 0,"Storage: not a staker");
_;
}
function stakes(address user) public view returns (uint[] memory, uint[] memory) {
return (_stakes[user],_stakeTimes[user]);
}
function withdrawals(address user) public view returns (uint[] memory, uint[] memory) {
return (_withdrawals[user],_withdrawalTimes[user]);
}
function _hasStake(address user) internal view returns(bool) {
return _stakes[user].length != 0 ? true : false;
}
function _hasIndexedStake(address user, uint index) internal view returns(bool) {
return _stakes[user][index] != 0 ? true : false;
}
function _hasIndexedWithdrawal(address user, uint index) internal view returns(bool) {
return _withdrawals[user][index] != 0 ? true : false;
}
function _hasWithdrawal(address user) internal view returns(bool) {
return _withdrawals[user].length != 0 ? true : false;
}
function _stakedAmount(address user, uint index) internal view returns(uint256) {
return _stakes[user][index];
}
function _stakeTime(address user, uint index) internal view returns(uint256) {
return _stakeTimes[user][index];
}
function _withdrawalAmount(address user, uint index) internal view returns(uint256) {
return _withdrawals[user][index];
}
function _withdrawalTime(address user, uint index) internal view returns(uint256) {
return _withdrawalTimes[user][index];
}
function _deleteStakeEntry(address user, uint index) internal {
emit StakeEntryRemoved(user,_stakes[user][index],block.timestamp);
if(_stakes[user].length != index + 1) {
for (uint i = index; i < _stakes[user].length - 1; i++) {
_stakes[user][i] = _stakes[user][i+1];
_stakeTimes[user][i] = _stakeTimes[user][i+1];
}
}
_stakes[user].pop();
_stakeTimes[user].pop();
}
function _addStakeEntry(address user, uint256 amount) internal {
_stakes[user].push(amount);
_stakeTimes[user].push(block.timestamp);
emit StakeEntryAdded(user, amount, block.timestamp);
}
function _deleteWithdrawalEntry(address user, uint index) internal {
emit WithdrawalEntryRemoved(user,_withdrawals[user][index],block.timestamp);
if(_withdrawals[user].length != index + 1) {
for (uint i = index; i < _withdrawals[user].length - 1; i++) {
_withdrawals[user][i] = _withdrawals[user][i+1];
_withdrawalTimes[user][i] = _withdrawalTimes[user][i+1];
}
}
_withdrawals[user].pop();
_withdrawalTimes[user].pop();
}
function _addWithdrawalEntry(address user, uint256 amount) internal {
_withdrawals[user].push(amount);
_withdrawalTimes[user].push(block.timestamp);
emit WithdrawalEntryAdded(user,amount,block.timestamp);
}
function _addRewardsDistributed(address user, uint256 amount) internal {
_rewardsDistributed += amount;
_rewardsClaimed[user] += amount;
}
}
// File contracts/Calculations.sol
pragma solidity ^0.8.0;
contract Calculations is Constants, Storage {
function calculatePendingReward(address user, uint index) public view returns (uint256) {
return _canRemoveStake(user, index) ? _calculateReward(_elapsedTime(user, index),_stakedAmount(user, index)) : 0;
}
function calculateReward(address user, uint index) public view returns (uint256) {
return _calculateReward(_elapsedTime(user, index),_stakedAmount(user, index));
}
function _elapsedTime(address user, uint index) internal view returns (uint256) {
return block.timestamp - _stakeTime(user, index);
}
function _canRemoveStake(address user, uint index) internal view returns (bool) {
return _elapsedTime(user, index) > _minRewardDuration ? true : false;
}
function _calculateReward(uint256 elapsedTime, uint256 amount) internal view returns (uint) {
uint amountUint = amount / 10**18;
return amount + (amountUint * _rewardMultiplierPerSecond * elapsedTime);
}
function _calculatePenalty(uint256 amount) internal view returns (uint) {
return amount * _penaltyConstant / 10**18;
}
function _elapsedWithdrawalTime(address user, uint index) internal view returns (uint256) {
return block.timestamp - _withdrawalTime(user, index);
}
function _canWithdraw(address user, uint index) internal view returns (bool) {
return _elapsedWithdrawalTime(user, index) >= _valorDuration ? true : false;
}
}
// File contracts/OpenStake.sol
pragma solidity ^0.8.0;
contract OpenStake is Calculations {
bool private _isEmergency = false;
event Stake(address indexed user, uint256 amount, uint256 time);
event Unstake(address indexed user, uint256 amount, uint256 time);
event UnstakeWithPenalty(address indexed user, uint256 amount, uint256 time);
event Withdraw(address indexed user, uint256 amount, uint256 time);
event Compound(address indexed user, uint256 amount, uint256 time);
event EmergencySet(address indexed owner, uint256 time);
event EmergencyUnstake(address indexed user, uint256 amount, uint256 time);
event EmergencyWithdrawal(address indexed user, uint256 amount, uint256 time);
constructor() {
//Placeholder, will be updated
adjustMinRewardDuration(10);
//Placeholder, will be updated to value in ether
adjustMinStakingAmount(100);
//0.27 in decimal for %27 APY is 270000000000000000
//270000000000000000 / 31536000 is the number below
//for rewards in second in decimal
adjustRewardMultiplier(8561643835);
//Placeholder, will be updated
adjustValorDuration(10);
//0.9 in decimal for 10% penalty
adjustPenaltyConstant(900000000000000000);
}
function depositToTreasury() public payable onlyOwner {}
function withdrawFromTreasury(uint256 amount) public onlyOwner {
address payable receiver = payable(msg.sender);
receiver.transfer(amount);
}
function stake() public payable {
require(!_isEmergency,"OpenStake: There is an emergency");
require(msg.value >= _minStakingAmount,"OpenStake: stake amount too low");
_addStakeEntry(msg.sender, msg.value);
emit Stake(msg.sender, msg.value, block.timestamp);
}
function unstake(uint index) public {
require(!_isEmergency,"OpenStake: There is an emergency");
require(_hasIndexedStake(msg.sender, index),"OpenStake: You dont have a stake");
require(_canRemoveStake(msg.sender, index),"OpenStake: You cant remove this stake now");
uint calculatedReward = calculateReward(msg.sender, index);
_addRewardsDistributed(msg.sender, calculatedReward - _stakedAmount(msg.sender, index));
_addWithdrawalEntry(msg.sender, calculatedReward);
emit Unstake(msg.sender,calculatedReward,block.timestamp);
_deleteStakeEntry(msg.sender, index);
}
function unstakeWithPenalty(uint index) public {
require(!_isEmergency,"OpenStake: There is an emergency");
require(_hasIndexedStake(msg.sender, index),"OpenStake: You dont have a stake");
require(!_canRemoveStake(msg.sender, index),"OpenStake: You can remove stake normally");
uint penalty = _calculatePenalty(_stakedAmount(msg.sender, index));
_addWithdrawalEntry(msg.sender, penalty);
emit UnstakeWithPenalty(msg.sender, penalty, block.timestamp);
_deleteStakeEntry(msg.sender, index);
}
function withdraw(uint index) public {
require(!_isEmergency,"OpenStake: There is an emergency");
require(_hasIndexedWithdrawal(msg.sender, index),"OpenStake: You dont have a withdrawal");
require(_canWithdraw(msg.sender, index),"OpenStake: You cant withdraw now");
address payable receiver = payable(msg.sender);
receiver.transfer(_withdrawalAmount(msg.sender, index));
emit Withdraw(msg.sender, _withdrawalAmount(msg.sender, index), block.timestamp);
_deleteWithdrawalEntry(msg.sender, index);
}
function compound(uint index) public {
require(!_isEmergency,"OpenStake: There is an emergency");
require(_hasIndexedStake(msg.sender, index),"OpenStake: You dont have a stake");
require(_canRemoveStake(msg.sender, index),"OpenStake: You cant compound stake now");
uint reward = calculateReward(msg.sender, index);
emit Compound(msg.sender, reward, block.timestamp);
_deleteStakeEntry(msg.sender, index);
_addStakeEntry(msg.sender, reward);
}
function setEmergency(bool status) public onlyOwner {
_isEmergency = status;
emit EmergencySet(msg.sender, block.timestamp);
}
function emergencyUnstake(uint index) public {
require(_isEmergency,"OpenStake: there is no emergency");
require(_hasIndexedStake(msg.sender, index),"OpenStake: You dont have a stake");
address payable receiver = payable(msg.sender);
uint stakedAmount = _stakedAmount(msg.sender, index);
receiver.transfer(stakedAmount);
emit EmergencyUnstake(msg.sender, stakedAmount, block.timestamp);
_deleteStakeEntry(msg.sender, index);
}
function emergencyWithdrawal(uint index) public {
require(_isEmergency,"OpenStake: there is no emergency");
require(_hasIndexedWithdrawal(msg.sender, index),"OpenStake: You dont have a stake");
address payable receiver = payable(msg.sender);
uint withdrawalAmount = _withdrawalAmount(msg.sender, index);
receiver.transfer(withdrawalAmount);
emit EmergencyWithdrawal(msg.sender, withdrawalAmount, block.timestamp);
_deleteWithdrawalEntry(msg.sender, index);
}
}