-
Notifications
You must be signed in to change notification settings - Fork 1
/
fundraiser.sol
489 lines (425 loc) · 16.7 KB
/
fundraiser.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
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
pragma solidity ^0.4.25;
import "erc20.sol";
contract Fundraiser {
uint256 constant entropy = 8;
event Beginning(
bytes32 _causeSecret
);
event Participation(
address _participant,
bytes32 _message,
uint256 _entries,
uint256 _refund
);
event Raise(
address _participant,
uint256 _entries,
uint256 _refund
);
event Revelation(
bytes32 _ownerMessage
);
event Selection(
address _participant,
bytes32 _participantMessage,
bytes32 _causeMessage,
bytes32 _ownerMessage
);
event Cancellation();
event Withdrawal(
address _address
);
struct Deployment {
address _cause;
address _causeWallet;
uint256 _causeSplit;
uint256 _participantSplit;
address _owner;
address _ownerWallet;
uint256 _ownerSplit;
bytes32 _ownerSecret;
uint256 _valuePerEntry;
uint256 _deployTime;
uint256 _endTime;
uint256 _expireTime;
uint256 _destructTime;
uint256 _goal;
}
struct State {
bytes32 _causeSecret;
bytes32 _causeMessage;
bool _causeWithdrawn;
address _participant;
bool _participantWithdrawn;
bytes32 _ownerMessage;
bool _ownerWithdrawn;
bool _cancelled;
uint256 _participants;
uint256 _entries;
uint256 _revealBlockNumber;
uint256 _revealBlockHash;
}
struct Participant {
bytes32 _message;
uint256 _entries;
}
struct Fund {
address _participant;
uint256 _entries;
}
modifier onlyOwner() {
require(msg.sender == deployment._owner);
_;
}
modifier neverOwner() {
require(msg.sender != deployment._owner);
require(msg.sender != deployment._ownerWallet);
_;
}
modifier onlyCause() {
require(msg.sender == deployment._cause);
_;
}
modifier neverCause() {
require(msg.sender != deployment._cause);
require(msg.sender != deployment._causeWallet);
_;
}
modifier participationPhase() {
require(now < deployment._endTime);
_;
}
modifier recapPhase() {
require((now >= deployment._endTime) && (now < deployment._expireTime));
_;
}
modifier destructionPhase() {
require(now >= deployment._destructTime);
_;
}
Deployment public deployment;
mapping(address => Participant) public participants;
Fund[] private funds;
State private _state;
function Fundraiser(
address _cause,
address _causeWallet,
uint256 _causeSplit,
uint256 _participantSplit,
address _ownerWallet,
uint256 _ownerSplit,
bytes32 _ownerSecret,
uint256 _valuePerEntry,
uint256 _endTime,
uint256 _expireTime,
uint256 _destructTime,
uint256 _goal
) public {
require(_cause != 0x0);
require(_causeWallet != 0x0);
require(_causeSplit != 0);
require(_participantSplit != 0);
require(_ownerWallet != 0x0);
require(_causeSplit + _participantSplit + _ownerSplit == 1000);
require(_ownerSecret != 0x0);
require(_valuePerEntry != 0);
require(_endTime > now); // participation phase
require(_expireTime > _endTime); // end phase
require(_destructTime > _expireTime); // destruct phase
// set the deployment
deployment = Deployment(
_cause,
_causeWallet,
_causeSplit,
_participantSplit,
msg.sender,
_ownerWallet,
_ownerSplit,
_ownerSecret,
_valuePerEntry,
now,
_endTime,
_expireTime,
_destructTime,
_goal
);
}
// returns the post-deployment state of the contract
function state() public view returns (
bytes32 _causeSecret,
bytes32 _causeMessage,
bool _causeWithdrawn,
address _participant,
bytes32 _participantMessage,
bool _participantWithdrawn,
bytes32 _ownerMessage,
bool _ownerWithdrawn,
bool _cancelled,
uint256 _participants,
uint256 _entries,
uint256 _revealBlockNumber,
uint256 _revealBlockHash
) {
_causeSecret = _state._causeSecret;
_causeMessage = _state._causeMessage;
_causeWithdrawn = _state._causeWithdrawn;
_participant = _state._participant;
_participantMessage = participants[_participant]._message;
_participantWithdrawn = _state._participantWithdrawn;
_ownerMessage = _state._ownerMessage;
_ownerWithdrawn = _state._ownerWithdrawn;
_cancelled = _state._cancelled;
_participants = _state._participants;
_entries = _state._entries;
_revealBlockNumber = _state._revealBlockNumber;
_revealBlockHash = _state._revealBlockHash;
}
// returns the balance of a cause, selected participant, owner, or participant (refund)
function balance() public view returns (uint256) {
// check for fundraiser ended normally
if (_state._participant != address(0)) {
// selected, get split
uint256 _split;
// determine split based on sender
if (msg.sender == deployment._cause) {
if (_state._causeWithdrawn) {
return 0;
}
_split = deployment._causeSplit;
} else if (msg.sender == _state._participant) {
if (_state._participantWithdrawn) {
return 0;
}
_split = deployment._participantSplit;
} else if (msg.sender == deployment._owner) {
if (_state._ownerWithdrawn) {
return 0;
}
_split = deployment._ownerSplit;
} else {
return 0;
}
// multiply total entries by split % (non-revealed winnings are forfeited)
return _state._entries * deployment._valuePerEntry * _split / 1000;
} else if (_state._cancelled) {
// value per entry times participant entries == balance
Participant storage _participant = participants[msg.sender];
return _participant._entries * deployment._valuePerEntry;
}
return 0;
}
// called by the cause to begin their fundraiser with their secret
function begin(bytes32 _secret) public participationPhase onlyCause {
require(!_state._cancelled); // fundraiser not cancelled
require(_state._causeSecret == 0x0); // cause has not seeded secret
require(_secret != 0x0); // secret cannot be zero
// seed cause secret, starting the fundraiser
_state._causeSecret = _secret;
// broadcast event
Beginning(_secret);
}
// participate in this fundraiser by contributing messages and ether for entries
function participate(bytes32 _message) public participationPhase neverCause neverOwner payable {
require(!_state._cancelled); // fundraiser not cancelled
require(_state._causeSecret != 0x0); // cause has seeded secret
require(_message != 0x0); // message cannot be zero
// find and check for no existing participant
Participant storage _participant = participants[msg.sender];
require(_participant._message == 0x0);
require(_participant._entries == 0);
// add entries to participant
var (_entries, _refund) = _raise(_participant);
// save participant message, increment total participants
_participant._message = _message;
_state._participants++;
// send out participation update
Participation(msg.sender, _message, _entries, _refund);
}
// called by participate() and the fallback function for obtaining (additional) entries
function _raise(Participant storage _participant) private returns (
uint256 _entries,
uint256 _refund
) {
// calculate the number of entries from the wei sent
_entries = msg.value / deployment._valuePerEntry;
require(_entries >= 1); // ensure we have at least one entry
// update participant totals
_participant._entries += _entries;
_state._entries += _entries;
// get previous fund's entries
uint256 _previousFundEntries = (funds.length > 0) ?
funds[funds.length - 1]._entries : 0;
// create and save new fund with cumulative entries
Fund memory _fund = Fund(msg.sender, _previousFundEntries + _entries);
funds.push(_fund);
// calculate partial entry refund
_refund = msg.value % deployment._valuePerEntry;
// refund any excess wei immediately (partial entry)
if (_refund > 0) {
msg.sender.transfer(_refund);
}
}
// fallback function that accepts ether for additional entries after an initial participation
function () public participationPhase neverCause neverOwner payable {
require(!_state._cancelled); // fundraiser not cancelled
require(_state._causeSecret != 0x0); // cause has seeded secret
// find existing participant
Participant storage _participant = participants[msg.sender];
require(_participant._message != 0x0); // make sure they participated
// forward to raise
var (_entries, _refund) = _raise(_participant);
// send raise event
Raise(msg.sender, _entries, _refund);
}
// called by the owner to reveal their message after the end time but before the end() function
function reveal(bytes32 _message) public recapPhase onlyOwner {
require(!_state._cancelled); // fundraiser not cancelled
require(_state._ownerMessage == 0x0); // cannot have revealed already
require(_state._revealBlockNumber == 0); // block number of reveal should not be set
require(_decode(deployment._ownerSecret, _message)); // check for valid message
// save owner cause message
_state._ownerMessage = _message;
// save reveal block number
_state._revealBlockNumber = block.number;
// send reveal event
Revelation(_message);
}
// determines that validity of a message, given a secret
function _decode(bytes32 _secret, bytes32 _message) private view returns (bool) {
return _secret == keccak256(_message, msg.sender);
}
// called by the cause to end this fundraiser, select a participant to reward, and allocate
// funds for the cause, the selected participant, and the contract owner
function end(bytes32 _message) public recapPhase onlyCause {
require(!_state._cancelled); // fundraiser not cancelled
require(_state._ownerMessage != 0x0); // owner must have revealed
require(_state._revealBlockNumber != 0); // reveal block number must be set
require(_state._causeMessage == 0x0); // cannot have ended already
require(_decode(_state._causeSecret, _message)); // check for valid message
require(block.number > _state._revealBlockNumber); // verify reveal has been mined
// get the (owner) reveal blockhash and ensure within 256 blocks (non-zero)
_state._revealBlockHash = uint256(block.blockhash(_state._revealBlockNumber));
require(_state._revealBlockHash != 0);
// save revealed cause message
_state._causeMessage = _message;
bytes32 _randomNumber;
address _participant;
bytes32 _participantMessage;
// add additional entropy to the random from participant messages
for (uint256 i = 0; i < entropy; i++) {
// calculate the next random
_randomNumber = keccak256(
_message,
_state._ownerMessage,
_state._revealBlockHash,
_participantMessage
);
// calculate next entry and grab corresponding participant
uint256 _entry = uint256(_randomNumber) % _state._entries;
_participant = _findParticipant(_entry);
_participantMessage = participants[_participant]._message;
}
// the final participant receives the reward
_state._participant = _participant;
// send out select event
Selection(
_state._participant,
_participantMessage,
_message,
_state._ownerMessage
);
}
// given an entry number, find the corresponding participant (address)
function _findParticipant(uint256 _entry) private view returns (address) {
uint256 _leftFundIndex = 0;
uint256 _rightFundIndex = funds.length - 1;
// loop until participant found
while (true) {
// first or last fund (edge cases)
if (_leftFundIndex == _rightFundIndex) {
return funds[_leftFundIndex]._participant;
}
// get fund indexes for mid & next
uint256 _midFundIndex =
_leftFundIndex + ((_rightFundIndex - _leftFundIndex) / 2);
uint256 _nextFundIndex = _midFundIndex + 1;
// get mid and next funds
Fund memory _midFund = funds[_midFundIndex];
Fund memory _nextFund = funds[_nextFundIndex];
// binary search
if (_entry >= _midFund._entries) {
if (_entry < _nextFund._entries) {
// we are in range, participant found
return _nextFund._participant;
}
// entry is greater, move right
_leftFundIndex = _nextFundIndex;
} else {
// entry is less, move left
_rightFundIndex = _midFundIndex;
}
}
}
// called by the cause or Seedom before the end time to cancel the fundraiser, refunding all
// participants; this function is available to the entire community after the expire time
function cancel() public {
require(!_state._cancelled); // fundraiser not already cancelled
require(_state._participant == address(0)); // selected must not have been chosen
// open cancellation to community if past expire time (but before destruct time)
if ((msg.sender != deployment._owner) && (msg.sender != deployment._cause)) {
require((now >= deployment._expireTime) && (now < deployment._destructTime));
}
// immediately set us to cancelled
_state._cancelled = true;
// send out cancellation event
Cancellation();
}
// used to withdraw funds from the contract from an ended fundraiser or refunds when the
// fundraiser is cancelled
function withdraw() public {
// check for a balance
uint256 _balance = balance();
require (_balance > 0); // can only withdraw a balance
address _wallet;
// check for fundraiser ended normally
if (_state._participant != address(0)) {
// determine split based on sender
if (msg.sender == deployment._cause) {
_state._causeWithdrawn = true;
_wallet = deployment._causeWallet;
} else if (msg.sender == _state._participant) {
_state._participantWithdrawn = true;
_wallet = _state._participant;
} else if (msg.sender == deployment._owner) {
_state._ownerWithdrawn = true;
_wallet = deployment._ownerWallet;
} else {
revert();
}
} else if (_state._cancelled) {
// set participant entries to zero to prevent multiple refunds
Participant storage _participant = participants[msg.sender];
_participant._entries = 0;
_wallet = msg.sender;
} else {
// no selected and not cancelled
revert();
}
// execute the refund if we have one
_wallet.transfer(_balance);
// send withdrawal event
Withdrawal(msg.sender);
}
// destroy() will be used to clean up old contracts from the network
function destroy() public destructionPhase onlyOwner {
// destroy this contract and send remaining funds to owner
selfdestruct(msg.sender);
}
// recover() allows the owner to recover ERC20 tokens sent to this contract, for later
// distribution back to their original holders, upon request
function recover(address _token) public onlyOwner {
ERC20 _erc20 = ERC20(_token);
uint256 _balance = _erc20.balanceOf(this);
require(_erc20.transfer(deployment._owner, _balance));
}
}