-
Notifications
You must be signed in to change notification settings - Fork 369
/
LockedGold.sol
327 lines (298 loc) · 13 KB
/
LockedGold.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
pragma solidity ^0.5.3;
import "openzeppelin-solidity/contracts/math/Math.sol";
import "openzeppelin-solidity/contracts/utils/ReentrancyGuard.sol";
import "openzeppelin-solidity/contracts/math/SafeMath.sol";
import "openzeppelin-solidity/contracts/ownership/Ownable.sol";
import "./interfaces/ILockedGold.sol";
import "../common/Initializable.sol";
import "../common/Signatures.sol";
import "../common/UsingRegistry.sol";
contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistry {
using SafeMath for uint256;
struct PendingWithdrawal {
// The value of the pending withdrawal.
uint256 value;
// The timestamp at which the pending withdrawal becomes available.
uint256 timestamp;
}
// NOTE: This contract does not store an account's locked gold that is being used in electing
// validators.
struct Balances {
// The amount of locked gold that this account has that is not currently participating in
// validator elections.
uint256 nonvoting;
// Gold that has been unlocked and will become available for withdrawal.
PendingWithdrawal[] pendingWithdrawals;
}
mapping(address => Balances) private balances;
mapping(address => bool) public isSlasher;
modifier onlySlasher() {
require(isSlasher[msg.sender], "Caller must be registered slasher");
_;
}
uint256 public totalNonvoting;
uint256 public unlockingPeriod;
event UnlockingPeriodSet(uint256 period);
event GoldLocked(address indexed account, uint256 value);
event GoldUnlocked(address indexed account, uint256 value, uint256 available);
event GoldWithdrawn(address indexed account, uint256 value);
event SlasherWhitelistAdded(address indexed slasher);
event SlasherWhitelistRemoved(address indexed slasher);
event AccountSlashed(
address indexed slashed,
uint256 penalty,
address indexed reporter,
uint256 reward
);
function initialize(address registryAddress, uint256 _unlockingPeriod) external initializer {
_transferOwnership(msg.sender);
setRegistry(registryAddress);
unlockingPeriod = _unlockingPeriod;
}
/**
* @notice Sets the duration in seconds users must wait before withdrawing gold after unlocking.
* @param value The unlocking period in seconds.
*/
function setUnlockingPeriod(uint256 value) external onlyOwner {
require(value != unlockingPeriod, "Unlocking period not changed");
unlockingPeriod = value;
emit UnlockingPeriodSet(value);
}
/**
* @notice Locks gold to be used for voting.
*/
function lock() external payable nonReentrant {
require(getAccounts().isAccount(msg.sender), "not account");
require(msg.value > 0, "no value");
_incrementNonvotingAccountBalance(msg.sender, msg.value);
emit GoldLocked(msg.sender, msg.value);
}
/**
* @notice Increments the non-voting balance for an account.
* @param account The account whose non-voting balance should be incremented.
* @param value The amount by which to increment.
* @dev Can only be called by the registered Election smart contract.
*/
function incrementNonvotingAccountBalance(address account, uint256 value)
external
onlyRegisteredContract(ELECTION_REGISTRY_ID)
{
_incrementNonvotingAccountBalance(account, value);
}
/**
* @notice Decrements the non-voting balance for an account.
* @param account The account whose non-voting balance should be decremented.
* @param value The amount by which to decrement.
* @dev Can only be called by the registered "Election" smart contract.
*/
function decrementNonvotingAccountBalance(address account, uint256 value)
external
onlyRegisteredContract(ELECTION_REGISTRY_ID)
{
_decrementNonvotingAccountBalance(account, value);
}
/**
* @notice Increments the non-voting balance for an account.
* @param account The account whose non-voting balance should be incremented.
* @param value The amount by which to increment.
*/
function _incrementNonvotingAccountBalance(address account, uint256 value) private {
balances[account].nonvoting = balances[account].nonvoting.add(value);
totalNonvoting = totalNonvoting.add(value);
}
/**
* @notice Decrements the non-voting balance for an account.
* @param account The account whose non-voting balance should be decremented.
* @param value The amount by which to decrement.
*/
function _decrementNonvotingAccountBalance(address account, uint256 value) private {
balances[account].nonvoting = balances[account].nonvoting.sub(value);
totalNonvoting = totalNonvoting.sub(value);
}
/**
* @notice Unlocks gold that becomes withdrawable after the unlocking period.
* @param value The amount of gold to unlock.
*/
function unlock(uint256 value) external nonReentrant {
require(getAccounts().isAccount(msg.sender), "Unknown account");
Balances storage account = balances[msg.sender];
// Prevent unlocking gold when voting on governance proposals so that the gold cannot be
// used to vote more than once.
require(!getGovernance().isVoting(msg.sender), "Account locked");
uint256 balanceRequirement = getValidators().getAccountLockedGoldRequirement(msg.sender);
require(
balanceRequirement == 0 ||
balanceRequirement <= getAccountTotalLockedGold(msg.sender).sub(value),
"Trying to unlock too much gold"
);
_decrementNonvotingAccountBalance(msg.sender, value);
uint256 available = now.add(unlockingPeriod);
account.pendingWithdrawals.push(PendingWithdrawal(value, available));
emit GoldUnlocked(msg.sender, value, available);
}
/**
* @notice Relocks gold that has been unlocked but not withdrawn.
* @param index The index of the pending withdrawal to relock from.
* @param value The value to relock from the specified pending withdrawal.
*/
function relock(uint256 index, uint256 value) external nonReentrant {
require(getAccounts().isAccount(msg.sender), "Unknown account");
Balances storage account = balances[msg.sender];
require(index < account.pendingWithdrawals.length, "Bad pending withdrawal index");
PendingWithdrawal storage pendingWithdrawal = account.pendingWithdrawals[index];
require(value <= pendingWithdrawal.value, "Requested value larger than pending value");
if (value == pendingWithdrawal.value) {
deletePendingWithdrawal(account.pendingWithdrawals, index);
} else {
pendingWithdrawal.value = pendingWithdrawal.value.sub(value);
}
_incrementNonvotingAccountBalance(msg.sender, value);
emit GoldLocked(msg.sender, value);
}
/**
* @notice Withdraws gold that has been unlocked after the unlocking period has passed.
* @param index The index of the pending withdrawal to withdraw.
*/
function withdraw(uint256 index) external nonReentrant {
require(getAccounts().isAccount(msg.sender), "Unknown account");
Balances storage account = balances[msg.sender];
require(index < account.pendingWithdrawals.length, "Bad pending withdrawal index");
PendingWithdrawal storage pendingWithdrawal = account.pendingWithdrawals[index];
require(now >= pendingWithdrawal.timestamp, "Pending withdrawal not available");
uint256 value = pendingWithdrawal.value;
deletePendingWithdrawal(account.pendingWithdrawals, index);
require(getGoldToken().transfer(msg.sender, value), "Transfer failed");
emit GoldWithdrawn(msg.sender, value);
}
/**
* @notice Returns the total amount of locked gold in the system. Note that this does not include
* gold that has been unlocked but not yet withdrawn.
* @return The total amount of locked gold in the system.
*/
function getTotalLockedGold() external view returns (uint256) {
return totalNonvoting.add(getElection().getTotalVotes());
}
/**
* @notice Returns the total amount of locked gold not being used to vote in elections.
* @return The total amount of locked gold not being used to vote in elections.
*/
function getNonvotingLockedGold() external view returns (uint256) {
return totalNonvoting;
}
/**
* @notice Returns the total amount of locked gold for an account.
* @param account The account.
* @return The total amount of locked gold for an account.
*/
function getAccountTotalLockedGold(address account) public view returns (uint256) {
uint256 total = balances[account].nonvoting;
return total.add(getElection().getTotalVotesByAccount(account));
}
/**
* @notice Returns the total amount of non-voting locked gold for an account.
* @param account The account.
* @return The total amount of non-voting locked gold for an account.
*/
function getAccountNonvotingLockedGold(address account) external view returns (uint256) {
return balances[account].nonvoting;
}
/**
* @notice Returns the pending withdrawals from unlocked gold for an account.
* @param account The address of the account.
* @return The value and timestamp for each pending withdrawal.
*/
function getPendingWithdrawals(address account)
external
view
returns (uint256[] memory, uint256[] memory)
{
require(getAccounts().isAccount(account), "Unknown account");
uint256 length = balances[account].pendingWithdrawals.length;
uint256[] memory values = new uint256[](length);
uint256[] memory timestamps = new uint256[](length);
for (uint256 i = 0; i < length; i++) {
PendingWithdrawal memory pendingWithdrawal = (balances[account].pendingWithdrawals[i]);
values[i] = pendingWithdrawal.value;
timestamps[i] = pendingWithdrawal.timestamp;
}
return (values, timestamps);
}
/**
* @notice Deletes a pending withdrawal.
* @param list The list of pending withdrawals from which to delete.
* @param index The index of the pending withdrawal to delete.
*/
function deletePendingWithdrawal(PendingWithdrawal[] storage list, uint256 index) private {
uint256 lastIndex = list.length.sub(1);
list[index] = list[lastIndex];
list.length = lastIndex;
}
/**
* @notice Adds `slasher` to whitelist of approved slashing addresses.
* @param slasher Address to whitelist.
*/
function addSlasher(address slasher) external onlyOwner {
require(slasher != address(0) && !isSlasher[slasher], "Invalid address to `addSlasher`.");
isSlasher[slasher] = true;
emit SlasherWhitelistAdded(slasher);
}
/**
* @notice Removes `slasher` from whitelist of approved slashing addresses.
* @param slasher Address to remove from whitelist.
*/
function removeSlasher(address slasher) external onlyOwner {
require(isSlasher[slasher], "Address must be valid slasher.");
isSlasher[slasher] = false;
emit SlasherWhitelistRemoved(slasher);
}
/**
* @notice Slashes `account` by reducing its nonvoting locked gold by `penalty`.
* If there is not enough nonvoting locked gold to slash, calls into
* `Election.slashVotes` to slash the remaining gold. If `account` does not have
* `penalty` worth of locked gold, slashes `account`'s total locked gold.
* Also sends `reward` gold to the reporter, and penalty-reward to the Community Fund.
* @param account Address of account being slashed.
* @param penalty Amount to slash account.
* @param reporter Address of account reporting the slasher.
* @param reward Reward to give reporter.
* @param lessers The groups receiving fewer votes than i'th group, or 0 if the i'th group has
* the fewest votes of any validator group.
* @param greaters The groups receiving more votes than the i'th group, or 0 if the i'th group
* has the most votes of any validator group.
* @param indices The indices of the i'th group in `account`'s voting list.
* @dev Fails if `reward` is greater than `account`'s total locked gold.
*/
function slash(
address account,
uint256 penalty,
address reporter,
uint256 reward,
address[] calldata lessers,
address[] calldata greaters,
uint256[] calldata indices
) external onlySlasher {
uint256 maxSlash = Math.min(penalty, getAccountTotalLockedGold(account));
require(maxSlash >= reward, "reward cannot exceed penalty.");
// Local scoping is required to avoid Solc "stack too deep" error from too many locals.
{
uint256 nonvotingBalance = balances[account].nonvoting;
uint256 difference = 0;
// If not enough nonvoting, revoke the difference
if (nonvotingBalance < maxSlash) {
difference = maxSlash.sub(nonvotingBalance);
require(
getElection().forceDecrementVotes(account, difference, lessers, greaters, indices) ==
difference,
"Cannot revoke enough voting gold."
);
}
// forceDecrementVotes does not increment nonvoting account balance, so we can't double count
_decrementNonvotingAccountBalance(account, maxSlash.sub(difference));
_incrementNonvotingAccountBalance(reporter, reward);
}
address communityFund = registry.getAddressForOrDie(GOVERNANCE_REGISTRY_ID);
address payable communityFundPayable = address(uint160(communityFund));
communityFundPayable.transfer(maxSlash.sub(reward));
emit AccountSlashed(account, maxSlash, reporter, reward);
}
}