-
Notifications
You must be signed in to change notification settings - Fork 7
/
marketplace.sol
536 lines (420 loc) · 19.8 KB
/
marketplace.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
pragma solidity ^0.4.23;
contract FincontractMarketplace {
constructor() public { }
/***** GLOBAL CONSTANTS *****/
// Number of supported currencies
uint CURRENCIES = 6;
// For modeling 'immediate' execution: At(t0) = Timebound ( t0-delta/2, t0+delta/2 )
uint DELTA = 30 seconds;
// Upper bound on fincontracts validity time
uint EXPIRATION = 1 years;
// Gateway value must be more recent than this
uint FRESHNESS = 60 seconds;
/***** DATA TYPES *****/
// SCALE and TIMEBOUND are modelled as fields of other Primitives
enum Primitive { ZERO, ONE, GIVE, AND, OR, SCALEOBS, IF }
// NONE must be last in enum due to how register() works.
enum Currency { USD, EUR, GBP, JPY, CNY, SGD, NONE }
/*
Description is a recursive data structure reflecting all right and obligations.
Real-world analogy: a contract template with parties to be written in.
In many cases, some fields are left with defalut values (0x0, 1, etc).
*/
struct Description {
Primitive prim; // what this node does
Currency curr; // currency (valid only for ONE)
bytes32 dscId_1; // 1st child Description
bytes32 dscId_2; // 2nd child Description
int scaleCoeff; // scaling coefficient
address gateway; // address of external gateway contract
uint begin; // can't be executed before this time
uint end; // can't be executed after this time
}
// Right and obligations are reflected in the underlying Description.
struct Fincontract {
address issuer; // creator of fincontract
address owner; // can ecexute and transfer ownership
bytes32 dscId; // description id
address proposedOwner; // new owner (proposed by current owner)
}
// A User has an array of balances corresponding to currencies.
struct User {
bool registered;
int[] balance;
}
mapping (bytes32 => Description) descriptions;
mapping (bytes32 => Fincontract) fincontracts;
mapping (address => User) users;
// Event is how a user knows which new fincontracts it owns after execution.
event PrimitiveStoredAt(bytes32 id);
event Registered(address user);
event CreatedBy(address user, bytes32 fctId);
event Owned(address newOwner, bytes32 fctId);
event IssuedFor(address proposedOwner, bytes32 fctId);
event Executed(bytes32 fctId);
event Deleted(bytes32 fctId);
/***** HELPER FUNCTIONS *****/
// Registering an address means creating an zero array of proper length.
function register() public {
require(!isRegistered());
User memory newUser;
newUser.balance = new int[](CURRENCIES);
for (uint8 i = 0; i < CURRENCIES; i++) {
newUser.balance[i] = 0;
}
users[msg.sender] = newUser;
users[msg.sender].registered = true;
emit Registered(msg.sender);
}
function isRegistered() public constant returns (bool registered) { return users[msg.sender].registered; }
modifier onlyRegistered() { require(users[msg.sender].registered); _; }
modifier onlyOwner(bytes32 fctId) { require(fincontracts[fctId].owner == msg.sender); _; }
/*
Store a fincontract description in mapping.
Id is NOT randomized (determined only by description fields).
*/
function storeWithId(Description dsc) internal returns (bytes32 dscId) {
bytes32 id = keccak256(dsc.prim, dsc.curr, dsc.dscId_1, dsc.dscId_2, dsc.scaleCoeff, dsc.gateway, dsc.begin, dsc.end);
descriptions[id] = dsc;
return id;
}
function getBalance(address user) onlyRegistered internal constant returns (int[]) { return users[user].balance; }
function getMyBalance() public constant returns (int[]) { return getBalance(msg.sender); }
function getDescriptionInfo(bytes32 dscId) public constant returns (Primitive prim, Currency curr, bytes32 dscId_1, bytes32 dscId_2,
int coeff, address gateway, uint begin, uint end) {
Description storage dsc = descriptions[dscId];
return (dsc.prim, dsc.curr, dsc.dscId_1, dsc.dscId_2, dsc.scaleCoeff, dsc.gateway, dsc.begin, dsc.end);
}
function getFincontractInfo(bytes32 fctId) public constant returns (address issuer, address owner, address proposedOwner, bytes32 dscId) {
Fincontract storage fct = fincontracts[fctId];
return (fct.issuer, fct.owner, fct.proposedOwner, fct.dscId);
}
//function getTimestamp() constant returns (uint256 timestamp) { return block.timestamp; }
/****** DESCRIPTIONS *****/
// Generic constructor for Description
function GenericDescription(Primitive _prim, Currency _curr, bytes32 _dscId_1, bytes32 _dscId_2,
int _scaleCoeff, address _gateway, uint _begin, uint _end)
internal returns (bytes32) {
bytes32 id = storeWithId(Description({
prim: _prim,
curr: _curr,
dscId_1: _dscId_1,
dscId_2: _dscId_2,
scaleCoeff: _scaleCoeff,
gateway: _gateway,
begin: _begin,
end: _end
}));
emit PrimitiveStoredAt(id);
return id;
}
// ZERO: no right, no obligations
function Zero() public returns (bytes32 dcsId) {
return GenericDescription(Primitive.ZERO, Currency.NONE, 0x0, 0x0, 1, 0x0, 0, now + EXPIRATION);
}
// ONE: receive 1 unit of _curr Currency at execution
function One(Currency _curr) public returns (bytes32 dcsId) {
return GenericDescription(Primitive.ONE, _curr, 0x0, 0x0, 1, 0x0, 0, now + EXPIRATION);
}
// GIVE: swap issuer and owner of _c1id
function Give(bytes32 _dscId_1) public returns (bytes32 dcsId) {
return GenericDescription(Primitive.GIVE, Currency.NONE, _dscId_1, 0x0, 1, 0x0, 0, now + EXPIRATION);
}
// AND: execute _dscId_1 and then execute _dscId_2
function And(bytes32 _dscId_1, bytes32 _dscId_2) public returns (bytes32 dcsId) {
return GenericDescription(Primitive.AND, Currency.NONE, _dscId_1, _dscId_2, 1, 0x0, 0, now + EXPIRATION);
}
// OR: owner can choose between executing _dscId_1 or _dscId_2
function Or(bytes32 _dscId_1, bytes32 _dscId_2) public returns (bytes32 dcsId) {
return GenericDescription(Primitive.OR, Currency.NONE, _dscId_1, _dscId_2, 1, 0x0, 0, now + EXPIRATION);
}
// SCALE: multiply all payments by a constant factor
function Scale(int _scaleCoeff, bytes32 _dscId) public returns (bytes32 dcsId) {
if (_scaleCoeff == 1) return _dscId; // shortcut for efficiency
Description storage dsc = descriptions[_dscId];
return GenericDescription(dsc.prim, dsc.curr, dsc.dscId_1, dsc.dscId_2, dsc.scaleCoeff * _scaleCoeff, dsc.gateway, dsc.begin, dsc.end);
}
// SCALEOBS: multiply all payment by an observable obtained from the gateway (resolved at execution)
function ScaleObs(address _gateway, bytes32 _dscId) public returns (bytes32 dcsId) {
return GenericDescription(Primitive.SCALEOBS, Currency.NONE, _dscId, 0x0, 1, _gateway, 0, now + EXPIRATION);
}
// IF: if obsBool returns true, execute _dscId_1, else execute _dscId_2
function If(address _gatewayBool, bytes32 _dscId_1, bytes32 _dscId_2) public returns (bytes32 dcsId) {
return GenericDescription(Primitive.IF, Currency.NONE, _dscId_1, _dscId_2, 1, _gatewayBool, 0, now + EXPIRATION);
}
// TIMEBOUND: can execute only if lowerBound <= now <= upperBound
// Create NEW fc: same as cid, but with time bounds
// No designated Timebound in Combinators enum
function Timebound(uint lowerBound, uint upperBound, bytes32 _dscId_1) public returns (bytes32 dcsId) {
require(upperBound <= now + EXPIRATION);
Description storage dsc = descriptions[_dscId_1];
return GenericDescription(dsc.prim, dsc.curr, dsc.dscId_1, dsc.dscId_2, dsc.scaleCoeff, dsc.gateway, lowerBound, upperBound);
}
// Three constructing function for convenience, all map to TIMEBOUND
function At(uint exactTime, bytes32 _dscId_1) internal returns (bytes32 dcsId) {
return Timebound(exactTime - DELTA/2, exactTime + DELTA/2, _dscId_1);
}
function Before(uint upperBound, bytes32 _dscId_1) internal returns (bytes32 dcsId) {
return Timebound(0, upperBound, _dscId_1);
}
function After(uint lowerBound, bytes32 _dscId_1) internal returns (bytes32 dcsId) {
return Timebound(lowerBound, now + EXPIRATION, _dscId_1);
}
/****** FINCONTRACTS ******/
// Create new Fincontract of Description at _dscId with given parties.
function createFincontractWithParties(address _issuer, address _owner, bytes32 _dscId) internal
returns (bytes32 fctId) {
Fincontract memory fct;
fct.issuer = _issuer;
fct.owner = _owner;
fct.dscId = _dscId;
fct.proposedOwner = _owner;
bytes32 _fctId = keccak256(now, _issuer, _owner, _dscId);
fincontracts[_fctId] = fct;
emit CreatedBy(_issuer, _fctId);
emit Owned(_owner, _fctId);
return _fctId;
}
// Create a fincontract with oneself as issuer and owner.
function createFincontract(bytes32 _dscId) public returns (bytes32 fctId) {
return createFincontractWithParties(msg.sender, msg.sender, _dscId);
}
// Until the proposed owner joins, old owner holds all rights and obligations.
function issueFor(bytes32 _fctId, address _proposedOwner) public
onlyOwner(_fctId)
onlyRegistered
returns (bytes32 fctId) {
fincontracts[_fctId].proposedOwner = _proposedOwner;
emit IssuedFor(_proposedOwner, _fctId);
return _fctId; // useless, kept for uniformity
}
// Declare oneself as owner. Can be called only as part of join().
// Succeeds if proposedOwner is msg.sender or 0x0 (everyone).
function own(bytes32 fctId) internal
returns (bool success) {
if (fincontracts[fctId].proposedOwner == msg.sender || fincontracts[fctId].proposedOwner == 0x0) {
fincontracts[fctId].owner = msg.sender;
fincontracts[fctId].proposedOwner = msg.sender;
emit Owned(msg.sender, fctId);
return true;
}
return false;
}
// Own and execute a fincontract. NB: Can't separate owning and executing!
function join(bytes32 fctId) public
onlyRegistered
returns (bool executedCompletely) {
return own(fctId) ? execute(fctId) : false;
}
// This function ultimately makes the payment.
function enforcePayment(address issuer, address owner, Currency currency, int amount) internal
returns (bool success) {
users[issuer].balance[uint(currency)] -= amount;
users[owner].balance[uint(currency)] += amount;
return true;
}
/*
Internal function for recursive execution of a fincontract.
Parties and accumulated scaling coefficient are passed as parameters.
Returns true, if all subcontracts result in changing the parties' balances.
Returns false, if some subcontracts result in some party owning a new fincontract.
Note: this function knows nothing about the initial fincontract (i.f.)!
I.f. is only an "entry point" to the DAG of descriptions. It gets deleted anyway by execute().
*/
function executeRecursive(address issuer, address owner, bytes32 dscId, int scaleCoeffAcc) internal
returns (bool executedCompletely) {
Description memory dsc = descriptions[dscId];
if (now > dsc.end) return true; // expired: do nothing, treat as executed
if (now < dsc.begin) { // executable in future: create new fincontract
createFincontractWithParties(issuer, owner, Scale(scaleCoeffAcc, dscId));
return false;
}
if (dsc.prim == Primitive.ZERO) {
return true;
} else if (dsc.prim == Primitive.ONE) {
return enforcePayment(issuer, owner, dsc.curr, scaleCoeffAcc * dsc.scaleCoeff);
// don't currently handle the case when enforcePayment() returns false
} else if (dsc.prim == Primitive.GIVE) {
return executeRecursive(owner, issuer, dsc.dscId_1, scaleCoeffAcc * dsc.scaleCoeff); // issuer <--> owner
} else if (dsc.prim == Primitive.AND) {
bool executed1 = executeRecursive(issuer, owner, dsc.dscId_1, scaleCoeffAcc * dsc.scaleCoeff);
bool executed2 = executeRecursive(issuer, owner, dsc.dscId_2, scaleCoeffAcc * dsc.scaleCoeff);
return executed1 && executed2;
} else if (dsc.prim == Primitive.OR) {
createFincontractWithParties(issuer, owner, Scale(scaleCoeffAcc, dscId)); // sic! dsc.scaleCoeff handled inside
return false;
} else if (dsc.prim == Primitive.IF || dsc.prim == Primitive.SCALEOBS) {
Gateway gateway = Gateway(dsc.gateway);
require(now - gateway.getTimestamp() <= FRESHNESS);
if (dsc.prim == Primitive.IF) {
return executeRecursive(issuer, owner, (gateway.getValue() != 0) ? dsc.dscId_1 : dsc.dscId_2, scaleCoeffAcc * dsc.scaleCoeff);
} else if (dsc.prim == Primitive.SCALEOBS) {
return executeRecursive(issuer, owner, dsc.dscId_1, gateway.getValue() * scaleCoeffAcc * dsc.scaleCoeff);
}
} else {
return false;
}
}
// Recursively execute and then delete a fincontact.
function execute(bytes32 fctId) public
onlyOwner(fctId)
onlyRegistered
returns (bool executedCompletely) {
Fincontract storage fct = fincontracts[fctId];
bool executed = executeRecursive(fct.issuer, fct.owner, fct.dscId, 1);
if (executed) {
emit Executed(fctId);
}
// fincontract must be executable at most once, thus delete in any case.
deleteFincontract(fctId);
return executed;
}
// Create new fincontract depending on owner's choice (OR primitive).
function choose(bytes32 fctIdOr, bool chooseFirst) internal
onlyOwner(fctIdOr)
returns (bytes32 ftcId) {
Fincontract storage fct = fincontracts[fctIdOr];
Description storage dsc = descriptions[fct.dscId];
require(dsc.prim == Primitive.OR);
bytes32 chosenFctId = createFincontractWithParties(fct.issuer, fct.owner,
Timebound(dsc.begin, dsc.end, Scale(dsc.scaleCoeff, chooseFirst ? dsc.dscId_1 : dsc.dscId_2)));
deleteFincontract(fctIdOr);
return chosenFctId;
}
// Execute the chosen fincontract.
function executeOr(bytes32 fctIdOr, bool chooseFirst) public
onlyOwner(fctIdOr)
onlyRegistered
returns (bool executedCompletely) {
return execute(choose(fctIdOr, chooseFirst));
}
function deleteFincontract(bytes32 fctId) internal {
if (fincontracts[fctId].dscId != 0) {
delete fincontracts[fctId];
emit Deleted(fctId);
}
}
/***** TESTING *****/
function simpleTest(address addr) public returns (bytes32 fctId) {
//For performance measurement: uncomment exactly one line
//bytes32 testDsc = Zero();
// 1. One
//bytes32 testDsc = One(Currency.USD);
// 2. Simple currency exchange
bytes32 testDsc = And(Give(Scale(11,One(Currency.USD))),Scale(10,One(Currency.EUR)));
// 3. ZCB
//bytes32 testDsc = At(now + 1 minutes, Scale(10, One(Currency.USD)));
// 4. Bond with 2 coupons
/*
bytes32 testDsc =
And(
And(
At(now + 1 minutes, One(Currency.USD)),
At(now + 2 minutes, One(Currency.EUR))),
At(now + 3 minutes, Scale(5, One(Currency.USD))));
*/
// 5. European option
//bytes32 testDsc = At(now + 1 minutes, Or(One(Currency.USD), One(Currency.EUR)));
// 6. FC dependent on boolean Gateway, a.k.a Binary option
//bytes32 testDsc = If(gatewayB, One(Currency.USD), One(Currency.EUR));
// 7. FC dependent on numeric Gateway
//bytes32 testDsc = ScaleObs(gatewayI, One(Currency.USD));
return issueFor(createFincontract(testDsc), addr);
}
function complexScaleObsTest(address addr) public returns (bytes32 fctId) {
bytes32 testDsc = Scale(10,
And(
ScaleObs(gatewayI, Give(
Or(
Scale(5, One(Currency.USD)),
ScaleObs(gatewayI, Scale(10, One(Currency.EUR)))
))),
If(gatewayB,
Zero(),
And(
Scale(3, One(Currency.USD)),
Give(Scale(7, One(Currency.EUR)))
)
)
)
);
return issueFor(createFincontract(testDsc), addr);
}
function timeboundTest(address addr, uint lowerBound, uint upperBound) public returns (bytes32 fctId) {
bytes32 testDsc = Timebound(lowerBound, upperBound, ScaleObs(gatewayI, Give(
Or(
Scale(5, One(Currency.USD)),
Scale(10, One(Currency.EUR))
))));
return issueFor(createFincontract(testDsc), addr);
}
/***** GATEWAYS *****/
address gatewayI; // int
address gatewayB; // bool
function setGatewayI(address _addr) public {
gatewayI = _addr;
}
function setGatewayB(address _addr) public {
gatewayB = _addr;
}
}
/*
Contract that inherits from Gateway must implement two methods:
newValue(): assign new value to the value field
newProof(): assign new value to the proof field
(e.g., sign with known pub key)
*/
contract Gateway {
int value;
uint timestamp;
bytes32 proof;
constructor() public { update(); }
function getValue() public view returns (int) { return value; }
function getTimestamp() public view returns (uint) { return timestamp; }
function getProof() public view returns (bytes32) { return proof; }
function newProof() internal returns (bytes32);
function newValue() internal returns (int);
// bind updating value, timestamp, and proof to prevent inconsistency
function update() public {
value = newValue();
proof = newProof();
timestamp = now;
}
}
contract GatewayBool is Gateway {
function newBooleanValue() internal view returns (bool) {
return (block.timestamp % 2 == 0);
}
function newProof() internal returns (bytes32) {
return keccak256(value, timestamp); // let's pretend it proves something
}
function newValue() internal returns (int) {
if (newBooleanValue())
return(1);
else return(0);
}
}
contract GatewayFalse is Gateway {
function newProof() internal returns (bytes32) {
return keccak256(value, timestamp);
}
function newValue() internal returns (int) {
return 0;
}
}
contract GatewayTrue is Gateway {
function newProof() internal returns (bytes32) {
return keccak256(value, timestamp);
}
function newValue() internal returns (int) {
return 1;
}
}
contract GatewayInteger is Gateway {
function newProof() internal returns (bytes32) {
return keccak256(value, timestamp);
}
function newValue() internal returns (int) {
return 42;
}
}