forked from miladmostavi/gnosis-contracts
-
Notifications
You must be signed in to change notification settings - Fork 0
/
EventFactory.sol
529 lines (502 loc) · 19.3 KB
/
EventFactory.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
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
pragma solidity ^0.4.0;
import "Oracles/AbstractOracle.sol";
import "DAO/AbstractDAO.sol";
import "Utils/Lockable.sol";
import "EventFactory/OutcomeToken.sol";
/// @title Event factory contract - Event management and share token storage.
/// @author Stefan George - <stefan.george@consensys.net>
/// @author Martin Koeppelmann - <martin.koeppelmann@consensys.net>
contract EventFactory is Lockable {
/*
* Events
*/
event EventCreation(address indexed creator, bytes32 indexed eventHash);
/*
* External contracts
*/
DAO dao = DAO({{DAO}});
/*
* Constants
*/
uint8 constant SHORT = 0;
uint8 constant LONG = 1;
uint16 constant OUTCOME_RANGE = 10000;
/*
* Data structures
*/
// event hash => Event
mapping (bytes32 => Event) events;
// description hash => creator => event hash
mapping (bytes32 => mapping (address => bytes32)) eventHashes;
// owner address => approved address => is approved?
mapping (address => mapping (address => bool)) permanentApprovals;
struct Event {
bytes32 descriptionHash;
bool isRanged;
int lowerBound;
int upperBound;
Token token;
Oracle oracle;
bytes32 oracleEventIdentifier;
bool isWinningOutcomeSet;
int winningOutcome;
OutcomeToken[] outcomeTokens;
}
/*
* Modifiers
*/
modifier isDAOContract() {
if (msg.sender != address(dao)) {
// Only DAO contract is allowed to proceed
throw;
}
_;
}
/*
* Read and write functions
*/
/// @dev Sets new DAO contract address.
/// @param daoAddress New DAO contract address.
function changeDAO(address daoAddress)
external
isDAOContract
{
dao = DAO(daoAddress);
}
function addEvent(
address creator,
bytes32 eventHash,
bytes32 descriptionHash,
bool isRanged,
int lowerBound,
int upperBound,
uint8 outcomeCount,
address token,
address oracle,
bytes32 oracleEventIdentifier
)
private
{
eventHashes[descriptionHash][creator] = eventHash;
events[eventHash].descriptionHash = descriptionHash;
events[eventHash].oracle = Oracle(oracle);
events[eventHash].oracleEventIdentifier = oracleEventIdentifier;
events[eventHash].isRanged = isRanged;
events[eventHash].lowerBound = lowerBound;
events[eventHash].upperBound = upperBound;
events[eventHash].token = Token(token);
// Create event tokens for each outcome
for (uint8 i=0; i<outcomeCount; i++) {
events[eventHash].outcomeTokens.push(new OutcomeToken());
}
}
/// @dev Creates event and pays fees to oracles used to resolve event. Returns event hash.
/// @param descriptionHash Hash identifying off chain event description.
/// @param isRanged Is event a ranged event?
/// @param lowerBound Lower bound for valid outcomes for ranged events.
/// @param upperBound Upper bound for valid outcomes for ranged events.
/// @param outcomeCount Number of outcomes.
/// @param token Address of token contract in which market is traded.
/// @param oracle Address of resolving/oracle contract.
/// @param data Encoded data used to resolve event.
/// @return eventHash Returns event hash.
function createEvent(
bytes32 descriptionHash,
bool isRanged,
int lowerBound,
int upperBound,
uint8 outcomeCount,
address token,
address oracle,
bytes32[] data
)
external
returns (bytes32 eventHash)
{
// Calculate event hash
eventHash = sha3(descriptionHash, isRanged, lowerBound, upperBound, outcomeCount, token, oracle, data);
// Check that event doesn't exist and is valid
if ( events[eventHash].descriptionHash > 0
|| isRanged && lowerBound >= upperBound
|| descriptionHash == 0
|| outcomeCount < 2
|| token == 0
|| oracle == 0)
{
// Event exists already or bounds or outcome count are invalid or description hash or token or oracle are not set
throw;
}
// Pay fee
var (oracleFee, oracleToken) = Oracle(oracle).getFee(data);
if ( oracleFee > 0
&& ( !Token(oracleToken).transferFrom(msg.sender, this, oracleFee)
|| !Token(oracleToken).approve(oracle, oracleFee)))
{
// Tokens could not be transferred or approved
throw;
}
// Register event with oracle
bytes32 oracleEventIdentifier = Oracle(oracle).registerEvent(data);
if (oracleEventIdentifier == 0) {
// Event could not be registered with oracle
throw;
}
// Add event to storage
addEvent(
msg.sender,
eventHash,
descriptionHash,
isRanged,
lowerBound,
upperBound,
outcomeCount,
token,
oracle,
oracleEventIdentifier
);
EventCreation(msg.sender, eventHash);
}
/// @dev Buys equal number of shares of all outcomes, exchanging invested tokens and all outcomes 1:1. Returns success.
/// @param eventHash Hash identifying an event.
/// @param tokenCount Number of tokens to invest.
function buyAllOutcomes(bytes32 eventHash, uint tokenCount)
external
{
// Transfer tokens to events contract
if (tokenCount > 0 && !events[eventHash].token.transferFrom(msg.sender, this, tokenCount)) {
// Tokens could not be transferred
throw;
}
// Calculate base fee
uint fee = calcBaseFee(events[eventHash].token, tokenCount);
uint addedShares = tokenCount - fee;
// Transfer fee to DAO contract
if (fee > 0 && !events[eventHash].token.transfer(dao, fee)) {
// Sending failed
throw;
}
// Issue new event tokens to owner.
for (uint8 i=0; i<events[eventHash].outcomeTokens.length; i++) {
events[eventHash].outcomeTokens[i].issueTokens(msg.sender, addedShares);
}
}
/// @dev Sells equal number of shares of all outcomes, exchanging invested tokens and all outcomes 1:1. Returns success.
/// @param eventHash Hash identifying an event.
/// @param shareCount Number of shares to sell.
function sellAllOutcomes(bytes32 eventHash, uint shareCount)
external
{
// Revoke tokens of all outcomes
for (uint i=0; i<events[eventHash].outcomeTokens.length; i++) {
events[eventHash].outcomeTokens[i].revokeTokens(msg.sender, shareCount);
}
// Transfer redeemed tokens
if (shareCount > 0 && !events[eventHash].token.transfer(msg.sender, shareCount)) {
// Tokens could not be transferred
throw;
}
}
/// @dev Redeems winnings of sender for given event. Returns success.
/// @param eventHash Hash identifying an event.
/// @return winnings Returns winnings.
function redeemWinnings(bytes32 eventHash)
external
isUnlocked
returns (uint winnings)
{
// Check is winning outcome is already set
if (!events[eventHash].isWinningOutcomeSet) {
if (!events[eventHash].oracle.isOutcomeSet(events[eventHash].oracleEventIdentifier)) {
// Winning outcome is not set
throw;
}
// Set winning outcome
events[eventHash].winningOutcome = events[eventHash].oracle.getOutcome(events[eventHash].oracleEventIdentifier);
events[eventHash].isWinningOutcomeSet = true;
}
// Calculate winnings for ranged events
if (events[eventHash].isRanged) {
uint16 convertedWinningOutcome;
// Outcome is lower than defined lower bound
if (events[eventHash].winningOutcome < events[eventHash].lowerBound) {
convertedWinningOutcome = 0;
}
// Outcome is higher than defined upper bound
else if (events[eventHash].winningOutcome > events[eventHash].upperBound) {
convertedWinningOutcome = OUTCOME_RANGE;
}
// Map outcome on outcome range
else {
convertedWinningOutcome = uint16(
OUTCOME_RANGE * (events[eventHash].winningOutcome - events[eventHash].lowerBound)
/ (events[eventHash].upperBound - events[eventHash].lowerBound)
);
}
uint factorShort = OUTCOME_RANGE - convertedWinningOutcome;
uint factorLong = OUTCOME_RANGE - factorShort;
winnings = (
events[eventHash].outcomeTokens[SHORT].balanceOf(msg.sender) * factorShort +
events[eventHash].outcomeTokens[LONG].balanceOf(msg.sender) * factorLong
) / OUTCOME_RANGE;
}
// Calculate winnings for non ranged events
else {
winnings = events[eventHash].outcomeTokens[uint(events[eventHash].winningOutcome)].balanceOf(msg.sender);
}
// Revoke all tokens of all outcomes
for (uint8 i=0; i<events[eventHash].outcomeTokens.length; i++) {
uint shareCount = events[eventHash].outcomeTokens[i].balanceOf(msg.sender);
events[eventHash].outcomeTokens[i].revokeTokens(msg.sender, shareCount);
}
// Payout winnings
if (winnings > 0 && !events[eventHash].token.transfer(msg.sender, winnings)) {
// Tokens could not be transferred
throw;
}
}
/// @dev Approves address to trade unlimited event shares.
/// @param spender Address of allowed account.
function permitPermanentApproval(address spender)
external
{
permanentApprovals[msg.sender][spender] = true;
}
/// @dev Revokes approval for address to trade unlimited event shares.
/// @param spender Address of allowed account.
function revokePermanentApproval(address spender)
external
{
permanentApprovals[msg.sender][spender] = false;
}
/*
* Read functions
*/
/// @dev Returns base fee for amount of tokens.
/// @param token Token address.
/// @param tokenCount Amount of invested tokens.
/// @return fee Returns fee.
function calcBaseFee(address token, uint tokenCount)
constant
public
returns (uint fee)
{
return dao.calcBaseFee(msg.sender, token, tokenCount);
}
/// @dev Returns base fee for wanted amount of shares.
/// @param token Token address.
/// @param shareCount Amount of shares to buy.
/// @return fee Returns fee.
function calcBaseFeeForShares(address token, uint shareCount)
constant
external
returns (uint fee)
{
return dao.calcBaseFeeForShares(msg.sender, token, shareCount);
}
/// @dev Returns whether the address is allowed to trade unlimited event shares.
/// @param owner Address of allowed account.
/// @return approved Returns approval status.
function isPermanentlyApproved(address owner, address spender)
constant
external
returns (bool approved)
{
return permanentApprovals[owner][spender];
}
/// @dev Returns DAO address.
/// @return dao Returns DAO address.
function getDAO()
constant
external
returns (address daoAddress)
{
return dao;
}
/// @dev Returns all event hashes for all given description hashes.
/// @param descriptionHashes Array of hashes identifying off chain event descriptions.
/// @param creators Array event creator addresses.
/// @return allEventHashes Encoded event hashes.
function getEventHashes(bytes32[] descriptionHashes, address[] creators)
constant
external
returns (uint[] allEventHashes)
{
// Calculate array size
uint arrPos = 0;
uint count;
for (uint i=0; i<descriptionHashes.length; i++) {
count = 0;
for (uint j=0; j<creators.length; j++) {
if (eventHashes[descriptionHashes[i]][creators[j]] > 0) {
count += 1;
}
}
if (count > 0) {
arrPos += 2 + count;
}
}
// Fill array
allEventHashes = new uint[](arrPos);
arrPos = 0;
for (i=0; i<descriptionHashes.length; i++) {
count = 0;
for (j=0; j<creators.length; j++) {
if (eventHashes[descriptionHashes[i]][creators[j]] > 0) {
allEventHashes[arrPos + 2 + count] = uint(eventHashes[descriptionHashes[i]][creators[j]]);
count += 1;
}
}
if (count > 0) {
allEventHashes[arrPos] = uint(descriptionHashes[i]);
allEventHashes[arrPos + 1] = count;
arrPos += 2 + count;
}
}
}
/// @dev Returns all encoded events for all given event hashes.
/// @param _eventHashes Array of hashes identifying events.
/// @param oracle Filter events by oracle.
/// @param token Filter events by token.
/// @return allEvents Encoded events.
function getEvents(bytes32[] _eventHashes, address oracle, address token)
constant
external
returns (uint[] allEvents)
{
// Calculate array size
uint arrPos = 0;
for (uint i=0; i<_eventHashes.length; i++) {
if (events[_eventHashes[i]].descriptionHash > 0
&& (oracle == 0 || events[_eventHashes[i]].oracle == oracle)
&& (token == 0 || events[_eventHashes[i]].token == token))
{
arrPos += 11 + events[_eventHashes[i]].outcomeTokens.length;
}
}
// Fill array
allEvents = new uint[](arrPos);
arrPos = 0;
for (i=0; i<_eventHashes.length; i++) {
if (events[_eventHashes[i]].descriptionHash > 0
&& (oracle == 0 || events[_eventHashes[i]].oracle == oracle)
&& (token == 0 || events[_eventHashes[i]].token == token))
{
Event _event = events[_eventHashes[i]];
allEvents[arrPos] = uint(_eventHashes[i]);
allEvents[arrPos + 1] = uint(_event.descriptionHash);
if (_event.isRanged) {
allEvents[arrPos + 2] = 1;
}
else {
allEvents[arrPos + 2] = 0;
}
allEvents[arrPos + 3] = uint(_event.lowerBound);
allEvents[arrPos + 4] = uint(_event.upperBound);
allEvents[arrPos + 5] = uint(_event.token);
allEvents[arrPos + 6] = uint(_event.oracle);
allEvents[arrPos + 7] = uint(_event.oracleEventIdentifier);
// Event result
if (_event.isWinningOutcomeSet) {
allEvents[arrPos + 8] = 1;
}
else {
allEvents[arrPos + 8] = 0;
}
allEvents[arrPos + 9] = uint(_event.winningOutcome);
// Event token addresses
allEvents[arrPos + 10] = _event.outcomeTokens.length;
for (uint j=0; j<_event.outcomeTokens.length; j++) {
allEvents[arrPos + 11 + j] = uint(_event.outcomeTokens[j]);
}
arrPos += 11 + _event.outcomeTokens.length;
}
}
}
/// @dev Returns event for event hash.
/// @param eventHash Hash identifying an event.
/// @return descriptionHash Hash identifying off chain event description.
/// @return isRanged Is event a ranged event?
/// @return lowerBound Lower bound for valid outcomes for ranged events.
/// @return upperBound Upper bound for valid outcomes for ranged events.
/// @return outcomeCount Number of outcomes.
/// @return token Address of token contract in which market is traded.
/// @return oracle Address of resolving/oracle contract.
/// @return oracleEventIdentifier Identifier to get oracle result.
/// @return isWinningOutcomeSet Was winning outcome set?
/// @return winningOutcome Winning outcome.
function getEvent(bytes32 eventHash)
constant
external
returns (
bytes32 descriptionHash,
bool isRanged,
int lowerBound,
int upperBound,
uint outcomeCount,
address token,
address oracle,
bytes32 oracleEventIdentifier,
bool isWinningOutcomeSet,
int winningOutcome
)
{
descriptionHash = events[eventHash].descriptionHash;
isRanged = events[eventHash].isRanged;
lowerBound = events[eventHash].lowerBound;
upperBound = events[eventHash].upperBound;
outcomeCount = events[eventHash].outcomeTokens.length;
token = events[eventHash].token;
oracle = events[eventHash].oracle;
oracleEventIdentifier = events[eventHash].oracleEventIdentifier;
isWinningOutcomeSet = events[eventHash].isWinningOutcomeSet;
winningOutcome = events[eventHash].winningOutcome;
}
/// @dev Returns token address of outcome.
/// @param eventHash Hash identifying an event.
/// @param outcomeIndex Index of outcome.
/// @return outcomeToken Returns address of event token.
function getOutcomeToken(bytes32 eventHash, uint outcomeIndex)
constant
external
returns (address outcomeToken)
{
return events[eventHash].outcomeTokens[outcomeIndex];
}
/// @dev Returns array of encoded shares sender holds in events identified with event hashes.
/// @param owner Shareholder's address.
/// @param _eventHashes Array of hashes identifying events.
/// @return allShares Encoded shares in events by owner.
function getShares(address owner, bytes32[] _eventHashes)
constant
external
returns (uint[] allShares)
{
// Calculate array size
uint arrPos = 0;
for (uint i=0; i<_eventHashes.length; i++) {
for (uint8 j=0; j<events[_eventHashes[i]].outcomeTokens.length; j++) {
if (events[_eventHashes[i]].outcomeTokens[j].balanceOf(owner) > 0) {
arrPos += 2 + events[_eventHashes[i]].outcomeTokens.length;
break;
}
}
}
// Fill array
allShares = new uint[](arrPos);
arrPos = 0;
for (i=0; i<_eventHashes.length; i++) {
for (j=0; j<events[_eventHashes[i]].outcomeTokens.length; j++) {
if (events[_eventHashes[i]].outcomeTokens[j].balanceOf(owner) > 0) {
// Add shares
allShares[arrPos] = uint(_eventHashes[i]); // event hash
allShares[arrPos + 1] = events[_eventHashes[i]].outcomeTokens.length;
for (j=0; j<events[_eventHashes[i]].outcomeTokens.length; j++) {
allShares[arrPos + 2 + j] = events[_eventHashes[i]].outcomeTokens[j].balanceOf(owner);
}
arrPos += 2 + j;
break;
}
}
}
}
}