-
Notifications
You must be signed in to change notification settings - Fork 131
/
Parameterizer.sol
400 lines (325 loc) · 15.5 KB
/
Parameterizer.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
pragma solidity^0.4.11;
import "plcr-revival/PLCRVoting.sol";
import "tokens/eip20/EIP20Interface.sol";
import "zeppelin/math/SafeMath.sol";
contract Parameterizer {
// ------
// EVENTS
// ------
event _ReparameterizationProposal(string name, uint value, bytes32 propID, uint deposit, uint appEndDate, address indexed proposer);
event _NewChallenge(bytes32 indexed propID, uint challengeID, uint commitEndDate, uint revealEndDate, address indexed challenger);
event _ProposalAccepted(bytes32 indexed propID, string name, uint value);
event _ProposalExpired(bytes32 indexed propID);
event _ChallengeSucceeded(bytes32 indexed propID, uint indexed challengeID, uint rewardPool, uint totalTokens);
event _ChallengeFailed(bytes32 indexed propID, uint indexed challengeID, uint rewardPool, uint totalTokens);
event _RewardClaimed(uint indexed challengeID, uint reward, address indexed voter);
// ------
// DATA STRUCTURES
// ------
using SafeMath for uint;
struct ParamProposal {
uint appExpiry;
uint challengeID;
uint deposit;
string name;
address owner;
uint processBy;
uint value;
}
struct Challenge {
uint rewardPool; // (remaining) pool of tokens distributed amongst winning voters
address challenger; // owner of Challenge
bool resolved; // indication of if challenge is resolved
uint stake; // number of tokens at risk for either party during challenge
uint winningTokens; // (remaining) amount of tokens used for voting by the winning side
mapping(address => bool) tokenClaims;
}
// ------
// STATE
// ------
mapping(bytes32 => uint) public params;
// maps challengeIDs to associated challenge data
mapping(uint => Challenge) public challenges;
// maps pollIDs to intended data change if poll passes
mapping(bytes32 => ParamProposal) public proposals;
// Global Variables
EIP20Interface public token;
PLCRVoting public voting;
uint public PROCESSBY = 604800; // 7 days
/**
@dev Initializer Can only be called once
@param _token The address where the ERC20 token contract is deployed
@param _plcr address of a PLCR voting contract for the provided token
@notice _parameters array of canonical parameters
*/
function init(
address _token,
address _plcr,
uint[] _parameters
) public {
require(_token != 0 && address(token) == 0);
require(_plcr != 0 && address(voting) == 0);
token = EIP20Interface(_token);
voting = PLCRVoting(_plcr);
// minimum deposit for listing to be whitelisted
set("minDeposit", _parameters[0]);
// minimum deposit to propose a reparameterization
set("pMinDeposit", _parameters[1]);
// period over which applicants wait to be whitelisted
set("applyStageLen", _parameters[2]);
// period over which reparmeterization proposals wait to be processed
set("pApplyStageLen", _parameters[3]);
// length of commit period for voting
set("commitStageLen", _parameters[4]);
// length of commit period for voting in parameterizer
set("pCommitStageLen", _parameters[5]);
// length of reveal period for voting
set("revealStageLen", _parameters[6]);
// length of reveal period for voting in parameterizer
set("pRevealStageLen", _parameters[7]);
// percentage of losing party's deposit distributed to winning party
set("dispensationPct", _parameters[8]);
// percentage of losing party's deposit distributed to winning party in parameterizer
set("pDispensationPct", _parameters[9]);
// type of majority out of 100 necessary for candidate success
set("voteQuorum", _parameters[10]);
// type of majority out of 100 necessary for proposal success in parameterizer
set("pVoteQuorum", _parameters[11]);
// minimum length of time user has to wait to exit the registry
set("exitTimeDelay", _parameters[12]);
// maximum length of time user can wait to exit the registry
set("exitPeriodLen", _parameters[13]);
}
// -----------------------
// TOKEN HOLDER INTERFACE
// -----------------------
/**
@notice propose a reparamaterization of the key _name's value to _value.
@param _name the name of the proposed param to be set
@param _value the proposed value to set the param to be set
*/
function proposeReparameterization(string _name, uint _value) public returns (bytes32) {
uint deposit = get("pMinDeposit");
bytes32 propID = keccak256(abi.encodePacked(_name, _value));
if (keccak256(abi.encodePacked(_name)) == keccak256(abi.encodePacked("dispensationPct")) ||
keccak256(abi.encodePacked(_name)) == keccak256(abi.encodePacked("pDispensationPct"))) {
require(_value <= 100);
}
require(!propExists(propID)); // Forbid duplicate proposals
require(get(_name) != _value); // Forbid NOOP reparameterizations
// attach name and value to pollID
proposals[propID] = ParamProposal({
appExpiry: now.add(get("pApplyStageLen")),
challengeID: 0,
deposit: deposit,
name: _name,
owner: msg.sender,
processBy: now.add(get("pApplyStageLen"))
.add(get("pCommitStageLen"))
.add(get("pRevealStageLen"))
.add(PROCESSBY),
value: _value
});
require(token.transferFrom(msg.sender, this, deposit)); // escrow tokens (deposit amt)
emit _ReparameterizationProposal(_name, _value, propID, deposit, proposals[propID].appExpiry, msg.sender);
return propID;
}
/**
@notice challenge the provided proposal ID, and put tokens at stake to do so.
@param _propID the proposal ID to challenge
*/
function challengeReparameterization(bytes32 _propID) public returns (uint challengeID) {
ParamProposal memory prop = proposals[_propID];
uint deposit = prop.deposit;
require(propExists(_propID) && prop.challengeID == 0);
//start poll
uint pollID = voting.startPoll(
get("pVoteQuorum"),
get("pCommitStageLen"),
get("pRevealStageLen")
);
challenges[pollID] = Challenge({
challenger: msg.sender,
rewardPool: SafeMath.sub(100, get("pDispensationPct")).mul(deposit).div(100),
stake: deposit,
resolved: false,
winningTokens: 0
});
proposals[_propID].challengeID = pollID; // update listing to store most recent challenge
//take tokens from challenger
require(token.transferFrom(msg.sender, this, deposit));
(uint commitEndDate, uint revealEndDate,,,) = voting.pollMap(pollID);
emit _NewChallenge(_propID, pollID, commitEndDate, revealEndDate, msg.sender);
return pollID;
}
/**
@notice for the provided proposal ID, set it, resolve its challenge, or delete it depending on whether it can be set, has a challenge which can be resolved, or if its "process by" date has passed
@param _propID the proposal ID to make a determination and state transition for
*/
function processProposal(bytes32 _propID) public {
ParamProposal storage prop = proposals[_propID];
address propOwner = prop.owner;
uint propDeposit = prop.deposit;
// Before any token transfers, deleting the proposal will ensure that if reentrancy occurs the
// prop.owner and prop.deposit will be 0, thereby preventing theft
if (canBeSet(_propID)) {
// There is no challenge against the proposal. The processBy date for the proposal has not
// passed, but the proposal's appExpirty date has passed.
set(prop.name, prop.value);
emit _ProposalAccepted(_propID, prop.name, prop.value);
delete proposals[_propID];
require(token.transfer(propOwner, propDeposit));
} else if (challengeCanBeResolved(_propID)) {
// There is a challenge against the proposal.
resolveChallenge(_propID);
} else if (now > prop.processBy) {
// There is no challenge against the proposal, but the processBy date has passed.
emit _ProposalExpired(_propID);
delete proposals[_propID];
require(token.transfer(propOwner, propDeposit));
} else {
// There is no challenge against the proposal, and neither the appExpiry date nor the
// processBy date has passed.
revert();
}
assert(get("dispensationPct") <= 100);
assert(get("pDispensationPct") <= 100);
// verify that future proposal appExpiry and processBy times will not overflow
now.add(get("pApplyStageLen"))
.add(get("pCommitStageLen"))
.add(get("pRevealStageLen"))
.add(PROCESSBY);
delete proposals[_propID];
}
/**
@notice Claim the tokens owed for the msg.sender in the provided challenge
@param _challengeID the challenge ID to claim tokens for
*/
function claimReward(uint _challengeID) public {
Challenge storage challenge = challenges[_challengeID];
// ensure voter has not already claimed tokens and challenge results have been processed
require(challenge.tokenClaims[msg.sender] == false);
require(challenge.resolved == true);
uint voterTokens = voting.getNumPassingTokens(msg.sender, _challengeID);
uint reward = voterReward(msg.sender, _challengeID);
// subtract voter's information to preserve the participation ratios of other voters
// compared to the remaining pool of rewards
challenge.winningTokens -= voterTokens;
challenge.rewardPool -= reward;
// ensures a voter cannot claim tokens again
challenge.tokenClaims[msg.sender] = true;
emit _RewardClaimed(_challengeID, reward, msg.sender);
require(token.transfer(msg.sender, reward));
}
/**
@dev Called by a voter to claim their rewards for each completed vote.
Someone must call updateStatus() before this can be called.
@param _challengeIDs The PLCR pollIDs of the challenges rewards are being claimed for
*/
function claimRewards(uint[] _challengeIDs) public {
// loop through arrays, claiming each individual vote reward
for (uint i = 0; i < _challengeIDs.length; i++) {
claimReward(_challengeIDs[i]);
}
}
// --------
// 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 ID of the challenge the voter's reward is being calculated for
@return The uint indicating the voter's reward
*/
function voterReward(address _voter, uint _challengeID)
public view returns (uint) {
uint winningTokens = challenges[_challengeID].winningTokens;
uint rewardPool = challenges[_challengeID].rewardPool;
uint voterTokens = voting.getNumPassingTokens(_voter, _challengeID);
return (voterTokens * rewardPool) / winningTokens;
}
/**
@notice Determines whether a proposal passed its application stage without a challenge
@param _propID The proposal ID for which to determine whether its application stage passed without a challenge
*/
function canBeSet(bytes32 _propID) view public returns (bool) {
ParamProposal memory prop = proposals[_propID];
return (now > prop.appExpiry && now < prop.processBy && prop.challengeID == 0);
}
/**
@notice Determines whether a proposal exists for the provided proposal ID
@param _propID The proposal ID whose existance is to be determined
*/
function propExists(bytes32 _propID) view public returns (bool) {
return proposals[_propID].processBy > 0;
}
/**
@notice Determines whether the provided proposal ID has a challenge which can be resolved
@param _propID The proposal ID whose challenge to inspect
*/
function challengeCanBeResolved(bytes32 _propID) view public returns (bool) {
ParamProposal memory prop = proposals[_propID];
Challenge memory challenge = challenges[prop.challengeID];
return (prop.challengeID > 0 && challenge.resolved == false && voting.pollEnded(prop.challengeID));
}
/**
@notice Determines the number of tokens to awarded to the winning party in a challenge
@param _challengeID The challengeID to determine a reward for
*/
function challengeWinnerReward(uint _challengeID) public view returns (uint) {
if(voting.getTotalNumberOfTokensForWinningOption(_challengeID) == 0) {
// Edge case, nobody voted, give all tokens to the challenger.
return 2 * challenges[_challengeID].stake;
}
return (2 * challenges[_challengeID].stake) - challenges[_challengeID].rewardPool;
}
/**
@notice gets the parameter keyed by the provided name value from the params mapping
@param _name the key whose value is to be determined
*/
function get(string _name) public view returns (uint value) {
return params[keccak256(abi.encodePacked(_name))];
}
/**
@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 resolves a challenge for the provided _propID. It must be checked in advance whether the _propID has a challenge on it
@param _propID the proposal ID whose challenge is to be resolved.
*/
function resolveChallenge(bytes32 _propID) private {
ParamProposal memory prop = proposals[_propID];
Challenge storage challenge = challenges[prop.challengeID];
// winner gets back their full staked deposit, and dispensationPct*loser's stake
uint reward = challengeWinnerReward(prop.challengeID);
challenge.winningTokens = voting.getTotalNumberOfTokensForWinningOption(prop.challengeID);
challenge.resolved = true;
if (voting.isPassed(prop.challengeID)) { // The challenge failed
if(prop.processBy > now) {
set(prop.name, prop.value);
}
emit _ChallengeFailed(_propID, prop.challengeID, challenge.rewardPool, challenge.winningTokens);
require(token.transfer(prop.owner, reward));
}
else { // The challenge succeeded or nobody voted
emit _ChallengeSucceeded(_propID, prop.challengeID, challenge.rewardPool, challenge.winningTokens);
require(token.transfer(challenges[prop.challengeID].challenger, reward));
}
}
/**
@dev sets the param keted by the provided name to the provided value
@param _name the name of the param to be set
@param _value the value to set the param to be set
*/
function set(string _name, uint _value) private {
params[keccak256(abi.encodePacked(_name))] = _value;
}
}