forked from BarnBridge/BarnBridge-Barn
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathBarnFacet.sol
376 lines (291 loc) · 15.7 KB
/
BarnFacet.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
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.7.6;
pragma experimental ABIEncoderV2;
import "../interfaces/IBarn.sol";
import "../libraries/LibBarnStorage.sol";
import "../libraries/LibOwnership.sol";
import "@openzeppelin/contracts/math/SafeMath.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
contract BarnFacet {
using SafeMath for uint256;
uint256 constant public MAX_LOCK = 365 days;
uint256 constant BASE_MULTIPLIER = 1e18;
event Deposit(address indexed user, uint256 amount, uint256 newBalance);
event Withdraw(address indexed user, uint256 amountWithdrew, uint256 amountLeft);
event Lock(address indexed user, uint256 timestamp);
event Delegate(address indexed from, address indexed to);
event DelegatedPowerIncreased(address indexed from, address indexed to, uint256 amount, uint256 to_newDelegatedPower);
event DelegatedPowerDecreased(address indexed from, address indexed to, uint256 amount, uint256 to_newDelegatedPower);
function initBarn(address _ara, address _rewards) public {
require(_ara != address(0), "ARA address must not be 0x0");
LibBarnStorage.Storage storage ds = LibBarnStorage.barnStorage();
require(!ds.initialized, "Barn: already initialized");
LibOwnership.enforceIsContractOwner();
ds.initialized = true;
ds.ara = IERC20(_ara);
ds.rewards = IRewards(_rewards);
}
// deposit allows a user to add more ara to his staked balance
function deposit(uint256 amount) public {
require(amount > 0, "Amount must be greater than 0");
LibBarnStorage.Storage storage ds = LibBarnStorage.barnStorage();
uint256 allowance = ds.ara.allowance(msg.sender, address(this));
require(allowance >= amount, "Token allowance too small");
// this must be called before the user's balance is updated so the rewards contract can calculate
// the amount owed correctly
if (address(ds.rewards) != address(0)) {
ds.rewards.registerUserAction(msg.sender);
}
uint256 newBalance = balanceOf(msg.sender).add(amount);
_updateUserBalance(ds.userStakeHistory[msg.sender], newBalance);
_updateLockedAra(araStakedAtTs(block.timestamp).add(amount));
address delegatedTo = userDelegatedTo(msg.sender);
if (delegatedTo != address(0)) {
uint256 newDelegatedPower = delegatedPower(delegatedTo).add(amount);
_updateDelegatedPower(ds.delegatedPowerHistory[delegatedTo], newDelegatedPower);
emit DelegatedPowerIncreased(msg.sender, delegatedTo, amount, newDelegatedPower);
}
ds.ara.transferFrom(msg.sender, address(this), amount);
emit Deposit(msg.sender, amount, newBalance);
}
// withdraw allows a user to withdraw funds if the balance is not locked
function withdraw(uint256 amount) public {
require(amount > 0, "Amount must be greater than 0");
require(userLockedUntil(msg.sender) <= block.timestamp, "User balance is locked");
uint256 balance = balanceOf(msg.sender);
require(balance >= amount, "Insufficient balance");
LibBarnStorage.Storage storage ds = LibBarnStorage.barnStorage();
// this must be called before the user's balance is updated so the rewards contract can calculate
// the amount owed correctly
if (address(ds.rewards) != address(0)) {
ds.rewards.registerUserAction(msg.sender);
}
_updateUserBalance(ds.userStakeHistory[msg.sender], balance.sub(amount));
_updateLockedAra(araStakedAtTs(block.timestamp).sub(amount));
address delegatedTo = userDelegatedTo(msg.sender);
if (delegatedTo != address(0)) {
uint256 newDelegatedPower = delegatedPower(delegatedTo).sub(amount);
_updateDelegatedPower(ds.delegatedPowerHistory[delegatedTo], newDelegatedPower);
emit DelegatedPowerDecreased(msg.sender, delegatedTo, amount, newDelegatedPower);
}
ds.ara.transfer(msg.sender, amount);
emit Withdraw(msg.sender, amount, balance.sub(amount));
}
// lock a user's currently staked balance until timestamp & add the bonus to his voting power
function lock(uint256 timestamp) public {
require(timestamp > block.timestamp, "Timestamp must be in the future");
require(timestamp <= block.timestamp + MAX_LOCK, "Timestamp too big");
require(balanceOf(msg.sender) > 0, "Sender has no balance");
LibBarnStorage.Storage storage ds = LibBarnStorage.barnStorage();
LibBarnStorage.Stake[] storage checkpoints = ds.userStakeHistory[msg.sender];
LibBarnStorage.Stake storage currentStake = checkpoints[checkpoints.length - 1];
require(timestamp > currentStake.expiryTimestamp, "New timestamp lower than current lock timestamp");
_updateUserLock(checkpoints, timestamp);
emit Lock(msg.sender, timestamp);
}
function depositAndLock(uint256 amount, uint256 timestamp) public {
deposit(amount);
lock(timestamp);
}
// delegate allows a user to delegate his voting power to another user
function delegate(address to) public {
require(msg.sender != to, "Can't delegate to self");
uint256 senderBalance = balanceOf(msg.sender);
require(senderBalance > 0, "No balance to delegate");
LibBarnStorage.Storage storage ds = LibBarnStorage.barnStorage();
emit Delegate(msg.sender, to);
address delegatedTo = userDelegatedTo(msg.sender);
if (delegatedTo != address(0)) {
uint256 newDelegatedPower = delegatedPower(delegatedTo).sub(senderBalance);
_updateDelegatedPower(ds.delegatedPowerHistory[delegatedTo], newDelegatedPower);
emit DelegatedPowerDecreased(msg.sender, delegatedTo, senderBalance, newDelegatedPower);
}
if (to != address(0)) {
uint256 newDelegatedPower = delegatedPower(to).add(senderBalance);
_updateDelegatedPower(ds.delegatedPowerHistory[to], newDelegatedPower);
emit DelegatedPowerIncreased(msg.sender, to, senderBalance, newDelegatedPower);
}
_updateUserDelegatedTo(ds.userStakeHistory[msg.sender], to);
}
// stopDelegate allows a user to take back the delegated voting power
function stopDelegate() public {
return delegate(address(0));
}
// balanceOf returns the current ARA balance of a user (bonus not included)
function balanceOf(address user) public view returns (uint256) {
return balanceAtTs(user, block.timestamp);
}
// balanceAtTs returns the amount of ARA that the user currently staked (bonus NOT included)
function balanceAtTs(address user, uint256 timestamp) public view returns (uint256) {
LibBarnStorage.Stake memory stake = stakeAtTs(user, timestamp);
return stake.amount;
}
// stakeAtTs returns the Stake object of the user that was valid at `timestamp`
function stakeAtTs(address user, uint256 timestamp) public view returns (LibBarnStorage.Stake memory) {
LibBarnStorage.Storage storage ds = LibBarnStorage.barnStorage();
LibBarnStorage.Stake[] storage stakeHistory = ds.userStakeHistory[user];
if (stakeHistory.length == 0 || timestamp < stakeHistory[0].timestamp) {
return LibBarnStorage.Stake(block.timestamp, 0, block.timestamp, address(0));
}
uint256 min = 0;
uint256 max = stakeHistory.length - 1;
if (timestamp >= stakeHistory[max].timestamp) {
return stakeHistory[max];
}
// binary search of the value in the array
while (max > min) {
uint256 mid = (max + min + 1) / 2;
if (stakeHistory[mid].timestamp <= timestamp) {
min = mid;
} else {
max = mid - 1;
}
}
return stakeHistory[min];
}
// votingPower returns the voting power (bonus included) + delegated voting power for a user at the current block
function votingPower(address user) public view returns (uint256) {
return votingPowerAtTs(user, block.timestamp);
}
// votingPowerAtTs returns the voting power (bonus included) + delegated voting power for a user at a point in time
function votingPowerAtTs(address user, uint256 timestamp) public view returns (uint256) {
LibBarnStorage.Stake memory stake = stakeAtTs(user, timestamp);
uint256 ownVotingPower;
// if the user delegated his voting power to another user, then he doesn't have any voting power left
if (stake.delegatedTo != address(0)) {
ownVotingPower = 0;
} else {
uint256 balance = stake.amount;
uint256 multiplier = _stakeMultiplier(stake, timestamp);
ownVotingPower = balance.mul(multiplier).div(BASE_MULTIPLIER);
}
uint256 delegatedVotingPower = delegatedPowerAtTs(user, timestamp);
return ownVotingPower.add(delegatedVotingPower);
}
// araStaked returns the total raw amount of ARA staked at the current block
function araStaked() public view returns (uint256) {
return araStakedAtTs(block.timestamp);
}
// araStakedAtTs returns the total raw amount of ARA users have deposited into the contract
// it does not include any bonus
function araStakedAtTs(uint256 timestamp) public view returns (uint256) {
return _checkpointsBinarySearch(LibBarnStorage.barnStorage().araStakedHistory, timestamp);
}
// delegatedPower returns the total voting power that a user received from other users
function delegatedPower(address user) public view returns (uint256) {
return delegatedPowerAtTs(user, block.timestamp);
}
// delegatedPowerAtTs returns the total voting power that a user received from other users at a point in time
function delegatedPowerAtTs(address user, uint256 timestamp) public view returns (uint256) {
return _checkpointsBinarySearch(LibBarnStorage.barnStorage().delegatedPowerHistory[user], timestamp);
}
// same as multiplierAtTs but for the current block timestamp
function multiplierOf(address user) public view returns (uint256) {
return multiplierAtTs(user, block.timestamp);
}
// multiplierAtTs calculates the multiplier at a given timestamp based on the user's stake a the given timestamp
// it includes the decay mechanism
function multiplierAtTs(address user, uint256 timestamp) public view returns (uint256) {
LibBarnStorage.Stake memory stake = stakeAtTs(user, timestamp);
return _stakeMultiplier(stake, timestamp);
}
// userLockedUntil returns the timestamp until the user's balance is locked
function userLockedUntil(address user) public view returns (uint256) {
LibBarnStorage.Stake memory c = stakeAtTs(user, block.timestamp);
return c.expiryTimestamp;
}
// userDelegatedTo returns the address to which a user delegated their voting power; address(0) if not delegated
function userDelegatedTo(address user) public view returns (address) {
LibBarnStorage.Stake memory c = stakeAtTs(user, block.timestamp);
return c.delegatedTo;
}
// _checkpointsBinarySearch executes a binary search on a list of checkpoints that's sorted chronologically
// looking for the closest checkpoint that matches the specified timestamp
function _checkpointsBinarySearch(LibBarnStorage.Checkpoint[] storage checkpoints, uint256 timestamp) internal view returns (uint256) {
if (checkpoints.length == 0 || timestamp < checkpoints[0].timestamp) {
return 0;
}
uint256 min = 0;
uint256 max = checkpoints.length - 1;
if (timestamp >= checkpoints[max].timestamp) {
return checkpoints[max].amount;
}
// binary search of the value in the array
while (max > min) {
uint256 mid = (max + min + 1) / 2;
if (checkpoints[mid].timestamp <= timestamp) {
min = mid;
} else {
max = mid - 1;
}
}
return checkpoints[min].amount;
}
// _stakeMultiplier calculates the multiplier for the given stake at the given timestamp
function _stakeMultiplier(LibBarnStorage.Stake memory stake, uint256 timestamp) internal view returns (uint256) {
if (timestamp >= stake.expiryTimestamp) {
return BASE_MULTIPLIER;
}
uint256 diff = stake.expiryTimestamp - timestamp;
if (diff >= MAX_LOCK) {
return BASE_MULTIPLIER.mul(2);
}
return BASE_MULTIPLIER.add(diff.mul(BASE_MULTIPLIER).div(MAX_LOCK));
}
// _updateUserBalance manages an array of checkpoints
// if there's already a checkpoint for the same timestamp, the amount is updated
// otherwise, a new checkpoint is inserted
function _updateUserBalance(LibBarnStorage.Stake[] storage checkpoints, uint256 amount) internal {
if (checkpoints.length == 0) {
checkpoints.push(LibBarnStorage.Stake(block.timestamp, amount, block.timestamp, address(0)));
} else {
LibBarnStorage.Stake storage old = checkpoints[checkpoints.length - 1];
if (old.timestamp == block.timestamp) {
old.amount = amount;
} else {
checkpoints.push(LibBarnStorage.Stake(block.timestamp, amount, old.expiryTimestamp, old.delegatedTo));
}
}
}
// _updateUserLock updates the expiry timestamp on the user's stake
// it assumes that if the user already has a balance, which is checked for in the lock function
// then there must be at least 1 checkpoint
function _updateUserLock(LibBarnStorage.Stake[] storage checkpoints, uint256 expiryTimestamp) internal {
LibBarnStorage.Stake storage old = checkpoints[checkpoints.length - 1];
if (old.timestamp < block.timestamp) {
checkpoints.push(LibBarnStorage.Stake(block.timestamp, old.amount, expiryTimestamp, old.delegatedTo));
} else {
old.expiryTimestamp = expiryTimestamp;
}
}
// _updateUserDelegatedTo updates the delegateTo property on the user's stake
// it assumes that if the user already has a balance, which is checked for in the delegate function
// then there must be at least 1 checkpoint
function _updateUserDelegatedTo(LibBarnStorage.Stake[] storage checkpoints, address to) internal {
LibBarnStorage.Stake storage old = checkpoints[checkpoints.length - 1];
if (old.timestamp < block.timestamp) {
checkpoints.push(LibBarnStorage.Stake(block.timestamp, old.amount, old.expiryTimestamp, to));
} else {
old.delegatedTo = to;
}
}
// _updateDelegatedPower updates the power delegated TO the user in the checkpoints history
function _updateDelegatedPower(LibBarnStorage.Checkpoint[] storage checkpoints, uint256 amount) internal {
if (checkpoints.length == 0 || checkpoints[checkpoints.length - 1].timestamp < block.timestamp) {
checkpoints.push(LibBarnStorage.Checkpoint(block.timestamp, amount));
} else {
LibBarnStorage.Checkpoint storage old = checkpoints[checkpoints.length - 1];
old.amount = amount;
}
}
// _updateLockedAra stores the new `amount` into the ARA locked history
function _updateLockedAra(uint256 amount) internal {
LibBarnStorage.Storage storage ds = LibBarnStorage.barnStorage();
if (ds.araStakedHistory.length == 0 || ds.araStakedHistory[ds.araStakedHistory.length - 1].timestamp < block.timestamp) {
ds.araStakedHistory.push(LibBarnStorage.Checkpoint(block.timestamp, amount));
} else {
LibBarnStorage.Checkpoint storage old = ds.araStakedHistory[ds.araStakedHistory.length - 1];
old.amount = amount;
}
}
}