-
Notifications
You must be signed in to change notification settings - Fork 131
/
Registry.sol
443 lines (365 loc) · 17.7 KB
/
Registry.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
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
pragma solidity ^0.4.11;
import "tokens/eip20/EIP20Interface.sol";
import "./Parameterizer.sol";
import "plcrvoting/PLCRVoting.sol";
import "zeppelin/math/SafeMath.sol";
contract Registry {
// ------
// EVENTS
// ------
event _Application(bytes32 indexed listingHash, uint deposit, uint appEndDate, string data);
event _Challenge(bytes32 indexed listingHash, uint challengeID, string data, uint commitEndDate, uint revealEndDate);
event _Deposit(bytes32 indexed listingHash, uint added, uint newTotal);
event _Withdrawal(bytes32 indexed listingHash, uint withdrew, uint newTotal);
event _ApplicationWhitelisted(bytes32 indexed listingHash);
event _ApplicationRemoved(bytes32 indexed listingHash);
event _ListingRemoved(bytes32 indexed listingHash);
event _ListingWithdrawn(bytes32 indexed listingHash);
event _TouchAndRemoved(bytes32 indexed listingHash);
event _ChallengeFailed(bytes32 indexed listingHash, uint indexed challengeID, uint rewardPool, uint totalTokens);
event _ChallengeSucceeded(bytes32 indexed listingHash, uint indexed challengeID, uint rewardPool, uint totalTokens);
event _RewardClaimed(uint indexed challengeID, uint reward);
using SafeMath for uint;
struct Listing {
uint applicationExpiry; // Expiration date of apply stage
bool whitelisted; // Indicates registry status
address owner; // Owner of Listing
uint unstakedDeposit; // Number of tokens in the listing not locked in a challenge
uint challengeID; // Corresponds to a PollID in PLCRVoting
}
struct Challenge {
uint rewardPool; // (remaining) Pool of tokens to be distributed to winning voters
address challenger; // Owner of Challenge
bool resolved; // Indication of if challenge is resolved
uint stake; // Number of tokens at stake for either party during challenge
uint totalTokens; // (remaining) Number of tokens used in voting by the winning side
mapping(address => bool) tokenClaims; // Indicates whether a voter has claimed a reward yet
}
// Maps challengeIDs to associated challenge data
mapping(uint => Challenge) public challenges;
// Maps listingHashes to associated listingHash data
mapping(bytes32 => Listing) public listings;
// Global Variables
EIP20Interface public token;
PLCRVoting public voting;
Parameterizer public parameterizer;
string public version = '1';
// ------------
// CONSTRUCTOR:
// ------------
/**
@dev Contructor Sets the addresses for token, voting, and parameterizer
@param _tokenAddr Address of the TCR's intrinsic ERC20 token
@param _plcrAddr Address of a PLCR voting contract for the provided token
@param _paramsAddr Address of a Parameterizer contract
*/
function Registry(
address _tokenAddr,
address _plcrAddr,
address _paramsAddr
) public {
token = EIP20Interface(_tokenAddr);
voting = PLCRVoting(_plcrAddr);
parameterizer = Parameterizer(_paramsAddr);
}
// --------------------
// PUBLISHER INTERFACE:
// --------------------
/**
@dev Allows a user to start an application. Takes tokens from user and sets
apply stage end time.
@param _listingHash The hash of a potential listing a user is applying to add to the registry
@param _amount The number of ERC20 tokens a user is willing to potentially stake
@param _data Extra data relevant to the application. Think IPFS hashes.
*/
function apply(bytes32 _listingHash, uint _amount, string _data) external {
require(!isWhitelisted(_listingHash));
require(!appWasMade(_listingHash));
require(_amount >= parameterizer.get("minDeposit"));
// Sets owner
Listing storage listing = listings[_listingHash];
listing.owner = msg.sender;
// Sets apply stage end time
listing.applicationExpiry = block.timestamp.add(parameterizer.get("applyStageLen"));
listing.unstakedDeposit = _amount;
// Transfers tokens from user to Registry contract
require(token.transferFrom(listing.owner, this, _amount));
_Application(_listingHash, _amount, listing.applicationExpiry, _data);
}
/**
@dev Allows the owner of a listingHash to increase their unstaked deposit.
@param _listingHash A listingHash msg.sender is the owner of
@param _amount The number of ERC20 tokens to increase a user's unstaked deposit
*/
function deposit(bytes32 _listingHash, uint _amount) external {
Listing storage listing = listings[_listingHash];
require(listing.owner == msg.sender);
listing.unstakedDeposit += _amount;
require(token.transferFrom(msg.sender, this, _amount));
_Deposit(_listingHash, _amount, listing.unstakedDeposit);
}
/**
@dev Allows the owner of a listingHash to decrease their unstaked deposit.
@param _listingHash A listingHash msg.sender is the owner of.
@param _amount The number of ERC20 tokens to withdraw from the unstaked deposit.
*/
function withdraw(bytes32 _listingHash, uint _amount) external {
Listing storage listing = listings[_listingHash];
require(listing.owner == msg.sender);
require(_amount <= listing.unstakedDeposit);
require(listing.unstakedDeposit - _amount >= parameterizer.get("minDeposit"));
listing.unstakedDeposit -= _amount;
require(token.transfer(msg.sender, _amount));
_Withdrawal(_listingHash, _amount, listing.unstakedDeposit);
}
/**
@dev Allows the owner of a listingHash to remove the listingHash from the whitelist
Returns all tokens to the owner of the listingHash
@param _listingHash A listingHash msg.sender is the owner of.
*/
function exit(bytes32 _listingHash) external {
Listing storage listing = listings[_listingHash];
require(msg.sender == listing.owner);
require(isWhitelisted(_listingHash));
// Cannot exit during ongoing challenge
require(listing.challengeID == 0 || challenges[listing.challengeID].resolved);
// Remove listingHash & return tokens
resetListing(_listingHash);
_ListingWithdrawn(_listingHash);
}
// -----------------------
// TOKEN HOLDER INTERFACE:
// -----------------------
/**
@dev Starts a poll for a listingHash which is either in the apply stage or
already in the whitelist. Tokens are taken from the challenger and the
applicant's deposits are locked.
@param _listingHash The listingHash being challenged, whether listed or in application
@param _data Extra data relevant to the challenge. Think IPFS hashes.
*/
function challenge(bytes32 _listingHash, string _data) external returns (uint challengeID) {
Listing storage listing = listings[_listingHash];
uint deposit = parameterizer.get("minDeposit");
// Listing must be in apply stage or already on the whitelist
require(appWasMade(_listingHash) || listing.whitelisted);
// Prevent multiple challenges
require(listing.challengeID == 0 || challenges[listing.challengeID].resolved);
if (listing.unstakedDeposit < deposit) {
// Not enough tokens, listingHash auto-delisted
resetListing(_listingHash);
_TouchAndRemoved(_listingHash);
return 0;
}
// Starts poll
uint pollID = voting.startPoll(
parameterizer.get("voteQuorum"),
parameterizer.get("commitStageLen"),
parameterizer.get("revealStageLen")
);
challenges[pollID] = Challenge({
challenger: msg.sender,
rewardPool: ((100 - parameterizer.get("dispensationPct")) * deposit) / 100,
stake: deposit,
resolved: false,
totalTokens: 0
});
// Updates listingHash to store most recent challenge
listing.challengeID = pollID;
// Locks tokens for listingHash during challenge
listing.unstakedDeposit -= deposit;
// Takes tokens from challenger
require(token.transferFrom(msg.sender, this, deposit));
var (commitEndDate, revealEndDate,) = voting.pollMap(pollID);
_Challenge(_listingHash, pollID, _data, commitEndDate, revealEndDate);
return pollID;
}
/**
@dev Updates a listingHash's status from 'application' to 'listing' or resolves
a challenge if one exists.
@param _listingHash The listingHash whose status is being updated
*/
function updateStatus(bytes32 _listingHash) public {
if (canBeWhitelisted(_listingHash)) {
whitelistApplication(_listingHash);
} else if (challengeCanBeResolved(_listingHash)) {
resolveChallenge(_listingHash);
} else {
revert();
}
}
// ----------------
// TOKEN FUNCTIONS:
// ----------------
/**
@dev Called by a voter to claim their reward for each completed vote. Someone
must call updateStatus() before this can be called.
@param _challengeID The PLCR pollID of the challenge a reward is being claimed for
@param _salt The salt of a voter's commit hash in the given poll
*/
function claimReward(uint _challengeID, uint _salt) public {
// Ensures the voter has not already claimed tokens and challenge results have been processed
require(challenges[_challengeID].tokenClaims[msg.sender] == false);
require(challenges[_challengeID].resolved == true);
uint voterTokens = voting.getNumPassingTokens(msg.sender, _challengeID, _salt);
uint reward = voterReward(msg.sender, _challengeID, _salt);
// Subtracts the voter's information to preserve the participation ratios
// of other voters compared to the remaining pool of rewards
challenges[_challengeID].totalTokens -= voterTokens;
challenges[_challengeID].rewardPool -= reward;
// Ensures a voter cannot claim tokens again
challenges[_challengeID].tokenClaims[msg.sender] = true;
require(token.transfer(msg.sender, reward));
_RewardClaimed(_challengeID, reward);
}
// --------
// GETTERS:
// --------
/**
@dev Calculates the provided voter's token reward for the given poll.
@param _voter The address of the voter whose reward balance is to be returned
@param _challengeID The pollID of the challenge a reward balance is being queried for
@param _salt The salt of the voter's commit hash in the given poll
@return The uint indicating the voter's reward
*/
function voterReward(address _voter, uint _challengeID, uint _salt)
public view returns (uint) {
uint totalTokens = challenges[_challengeID].totalTokens;
uint rewardPool = challenges[_challengeID].rewardPool;
uint voterTokens = voting.getNumPassingTokens(_voter, _challengeID, _salt);
return (voterTokens * rewardPool) / totalTokens;
}
/**
@dev Determines whether the given listingHash be whitelisted.
@param _listingHash The listingHash whose status is to be examined
*/
function canBeWhitelisted(bytes32 _listingHash) view public returns (bool) {
uint challengeID = listings[_listingHash].challengeID;
// Ensures that the application was made,
// the application period has ended,
// the listingHash can be whitelisted,
// and either: the challengeID == 0, or the challenge has been resolved.
if (
appWasMade(_listingHash) &&
listings[_listingHash].applicationExpiry < now &&
!isWhitelisted(_listingHash) &&
(challengeID == 0 || challenges[challengeID].resolved == true)
) { return true; }
return false;
}
/**
@dev Returns true if the provided listingHash is whitelisted
@param _listingHash The listingHash whose status is to be examined
*/
function isWhitelisted(bytes32 _listingHash) view public returns (bool whitelisted) {
return listings[_listingHash].whitelisted;
}
/**
@dev Returns true if apply was called for this listingHash
@param _listingHash The listingHash whose status is to be examined
*/
function appWasMade(bytes32 _listingHash) view public returns (bool exists) {
return listings[_listingHash].applicationExpiry > 0;
}
/**
@dev Returns true if the application/listingHash has an unresolved challenge
@param _listingHash The listingHash whose status is to be examined
*/
function challengeExists(bytes32 _listingHash) view public returns (bool) {
uint challengeID = listings[_listingHash].challengeID;
return (listings[_listingHash].challengeID > 0 && !challenges[challengeID].resolved);
}
/**
@dev Determines whether voting has concluded in a challenge for a given
listingHash. Throws if no challenge exists.
@param _listingHash A listingHash with an unresolved challenge
*/
function challengeCanBeResolved(bytes32 _listingHash) view public returns (bool) {
uint challengeID = listings[_listingHash].challengeID;
require(challengeExists(_listingHash));
return voting.pollEnded(challengeID);
}
/**
@dev Determines the number of tokens awarded to the winning party in a challenge.
@param _challengeID The challengeID to determine a reward for
*/
function determineReward(uint _challengeID) public view returns (uint) {
require(!challenges[_challengeID].resolved && voting.pollEnded(_challengeID));
// Edge case, nobody voted, give all tokens to the challenger.
if (voting.getTotalNumberOfTokensForWinningOption(_challengeID) == 0) {
return 2 * challenges[_challengeID].stake;
}
return (2 * challenges[_challengeID].stake) - challenges[_challengeID].rewardPool;
}
/**
@dev Getter for Challenge tokenClaims mappings
@param _challengeID The challengeID to query
@param _voter The voter whose claim status to query for the provided challengeID
*/
function tokenClaims(uint _challengeID, address _voter) public view returns (bool) {
return challenges[_challengeID].tokenClaims[_voter];
}
// ----------------
// PRIVATE FUNCTIONS:
// ----------------
/**
@dev Determines the winner in a challenge. Rewards the winner tokens and
either whitelists or de-whitelists the listingHash.
@param _listingHash A listingHash with a challenge that is to be resolved
*/
function resolveChallenge(bytes32 _listingHash) private {
uint challengeID = listings[_listingHash].challengeID;
// Calculates the winner's reward,
// which is: (winner's full stake) + (dispensationPct * loser's stake)
uint reward = determineReward(challengeID);
// Sets flag on challenge being processed
challenges[challengeID].resolved = true;
// Stores the total tokens used for voting by the winning side for reward purposes
challenges[challengeID].totalTokens =
voting.getTotalNumberOfTokensForWinningOption(challengeID);
// Case: challenge failed
if (voting.isPassed(challengeID)) {
whitelistApplication(_listingHash);
// Unlock stake so that it can be retrieved by the applicant
listings[_listingHash].unstakedDeposit += reward;
_ChallengeFailed(_listingHash, challengeID, challenges[challengeID].rewardPool, challenges[challengeID].totalTokens);
}
// Case: challenge succeeded or nobody voted
else {
resetListing(_listingHash);
// Transfer the reward to the challenger
require(token.transfer(challenges[challengeID].challenger, reward));
_ChallengeSucceeded(_listingHash, challengeID, challenges[challengeID].rewardPool, challenges[challengeID].totalTokens);
}
}
/**
@dev Called by updateStatus() if the applicationExpiry date passed without a
challenge being made. Called by resolveChallenge() if an
application/listing beat a challenge.
@param _listingHash The listingHash of an application/listingHash to be whitelisted
*/
function whitelistApplication(bytes32 _listingHash) private {
if (!listings[_listingHash].whitelisted) { _ApplicationWhitelisted(_listingHash); }
listings[_listingHash].whitelisted = true;
}
/**
@dev Deletes a listingHash from the whitelist and transfers tokens back to owner
@param _listingHash The listing hash to delete
*/
function resetListing(bytes32 _listingHash) private {
Listing storage listing = listings[_listingHash];
// Emit events before deleting listing to check whether is whitelisted
if (listing.whitelisted) {
_ListingRemoved(_listingHash);
} else {
_ApplicationRemoved(_listingHash);
}
// Deleting listing to prevent reentry
address owner = listing.owner;
uint unstakedDeposit = listing.unstakedDeposit;
delete listings[_listingHash];
// Transfers any remaining balance back to the owner
if (unstakedDeposit > 0){
require(token.transfer(owner, unstakedDeposit));
}
}
}