-
Notifications
You must be signed in to change notification settings - Fork 0
/
ColabXController.sol
486 lines (443 loc) · 17.3 KB
/
ColabXController.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
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.6;
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "./SharedStructs.sol";
import "./IColabXController.sol";
import "./IColabXStorage.sol";
import "./IColabXPayments.sol";
import "./IColabXMediation.sol";
contract ColabXController is IColabXController, Ownable, Initializable {
/**
* Types
*/
address private storageAddress;
address private userAddress;
address private paymentsAddress;
address private mediationAddress;
address private jobCreatorAddress;
IColabXStorage private storageContract;
IColabXPayments private paymentsContract;
IColabXMediationHandler private mediationContract;
/**
* Init
*/
// https://docs.openzeppelin.com/upgrades-plugins/1.x/writing-upgradeable
function initialize(
address _storageAddress,
address _usersAddress,
address _paymentsAddress,
address _mediationAddress,
address _jobCreatorAddress
) public initializer {
setStorageAddress(_storageAddress);
setUsersAddress(_usersAddress);
setPaymentsAddress(_paymentsAddress);
setMediationAddress(_mediationAddress);
setJobCreatorAddress(_jobCreatorAddress);
}
function setStorageAddress(address _storageAddress) public onlyOwner {
require(_storageAddress != address(0), "Storage address");
storageAddress = _storageAddress;
storageContract = IColabXStorage(storageAddress);
}
function getStorageAddress() public view returns (address) {
return storageAddress;
}
function setUsersAddress(address _usersAddress) public onlyOwner {
require(_usersAddress != address(0), "Users address");
userAddress = _usersAddress;
}
function getUsersAddress() public view returns (address) {
return userAddress;
}
function setPaymentsAddress(address _paymentsAddress) public onlyOwner {
require(_paymentsAddress != address(0), "Payments address");
paymentsAddress = _paymentsAddress;
paymentsContract = IColabXPayments(_paymentsAddress);
}
function getPaymentsAddress() public view returns (address) {
return paymentsAddress;
}
function setMediationAddress(address _mediationAddress) public onlyOwner {
require(_mediationAddress != address(0), "Mediation address");
mediationAddress = _mediationAddress;
mediationContract = IColabXMediationHandler(_mediationAddress);
}
function getMediationAddress() public view returns (address) {
return mediationAddress;
}
function setJobCreatorAddress(address _jobCreatorAddress) public onlyOwner {
require(_jobCreatorAddress != address(0), "JobCreator address");
jobCreatorAddress = _jobCreatorAddress;
}
function getJobCreatorAddress() public view returns (address) {
return jobCreatorAddress;
}
/**
* Agreements
*/
// * create the deal if not exists
// * otherwise compare the values to ensure 2 sided agreement
// * check the RP or JC is calling this
// * if RP:
// * mark the deal as RP agreed
// * pay in the timeout collateral
// * if JC:
// * mark the deal as JC agreed
// * pay in the payment collateral and timeout collateral
// * if both sides have agreed then mark the deal as agreed
// * emit the event
function agree(
string memory dealId,
SharedStructs.DealMembers memory members,
SharedStructs.DealTimeouts memory timeouts,
SharedStructs.DealPricing memory pricing
) public override returns (SharedStructs.Agreement memory) {
SharedStructs.Deal memory deal = storageContract.ensureDeal(
dealId,
members,
timeouts,
pricing
);
bool isResourceProvider = tx.origin == deal.members.resourceProvider;
bool isJobCreator = tx.origin == deal.members.jobCreator;
require(isResourceProvider || isJobCreator, "Only RP / JC");
if (isResourceProvider) {
storageContract.agreeResourceProvider(dealId);
paymentsContract.agreeResourceProvider(
dealId,
deal.members.resourceProvider,
deal.timeouts.submitResults.collateral
);
} else if (isJobCreator) {
storageContract.agreeJobCreator(dealId);
paymentsContract.agreeJobCreator(
dealId,
deal.members.jobCreator,
deal.pricing.paymentCollateral,
// the JC paus the judge results collateral
deal.timeouts.judgeResults.collateral
);
}
return storageContract.getAgreement(dealId);
}
/**
* Results
*/
// * check the RP is calling this
// * mark the deal as results submitted
// * calculate the cost of the job
// * calculate the job collateral based on the multiple
// * work out the difference between the timeout and results collateral
// * pay the difference into / out of the contract to the RP
// * emit the event
function addResult(
string memory dealId,
string memory resultsId,
// this is the CID of the actual data
// otherwise onchain clients cannot know the actual data they want to fetch
string memory dataId,
uint256 instructionCount
) public override {
require(
storageContract.isState(
dealId,
SharedStructs.AgreementState.DealAgreed
),
"DealAgreed"
);
SharedStructs.Deal memory deal = storageContract.getDeal(dealId);
require(deal.members.resourceProvider == tx.origin, "Only RP");
storageContract.addResult(dealId, resultsId, dataId, instructionCount);
// how many multiple of the job cost must the RP put up as collateral
// we need to do this after having added the result otherwise
// we don't know the instruction count
uint256 resultsCollateral = storageContract.getResultsCollateral(
dealId
);
paymentsContract.addResult(
dealId,
deal.members.resourceProvider,
resultsCollateral,
// this is the RP adding a results so they get their submit results timeout collateral back
deal.timeouts.submitResults.collateral
);
}
// * check the JC is calling this
// * check we are in Submitted state
// * mark the deal as results accepted
// * calculate the cost of the job
// * deduct the cost of the job from the JC payment collateral
// * pay the RP the cost of the job
// * refund the RP the results collateral
// * refund the JC the job collateral minus the cost
// * refund the JC the timeout collateral
function acceptResult(string memory dealId) public override {
require(
storageContract.isState(
dealId,
SharedStructs.AgreementState.ResultsSubmitted
),
"ResultsSubmitted"
);
SharedStructs.Deal memory deal = storageContract.getDeal(dealId);
require(deal.members.jobCreator == tx.origin, "Only JC");
uint256 jobCost = storageContract.getJobCost(dealId);
uint256 resultsCollateral = storageContract.getResultsCollateral(
dealId
);
storageContract.acceptResult(dealId);
paymentsContract.acceptResult(
dealId,
deal.members.resourceProvider,
deal.members.jobCreator,
jobCost,
deal.pricing.paymentCollateral,
resultsCollateral,
// this is the JC judging their result so they get their timeout collateral back
deal.timeouts.judgeResults.collateral
);
}
// * check the JC is calling this
// * check we are in Submitted state
// * check the mediator is in the list of RP trusted mediators
// * mark the deal as results checked
// * charge the JC the mediation fee
// * refund the JC the timeout collateral
// * emit the Mediation event so the mediator kicks in
function checkResult(string memory dealId) public override {
require(
storageContract.isState(
dealId,
SharedStructs.AgreementState.ResultsSubmitted
),
"ResultsSubmitted"
);
SharedStructs.Deal memory deal = storageContract.getDeal(dealId);
require(deal.members.jobCreator == tx.origin, "Only JC");
// this function will require that the mediator is in the RP's list of trusted mediators
storageContract.checkResult(dealId);
paymentsContract.checkResult(
dealId,
deal.members.jobCreator,
// this is the JC judging their result so they get their timeout collateral back
deal.timeouts.judgeResults.collateral,
deal.pricing.mediationFee
);
// trigger the mediation process by calling the contract
mediationContract.mediationRequest(deal);
}
/**
* Mediation
*/
// the mediator calls this to say that the resource provider did the correct job
// * check the state is ResultsChecked
// * check the mediator is calling this
// * mark the deal as mediation accepted
// * refund the JC what is left from the payment collateral (if any)
// * pay the RP the cost of the job
// * refund the RP the results collateral
// * pay the mediator for mediating
function mediationAcceptResult(string memory dealId) public override {
require(mediationAddress == _msgSender(), "Only mediation");
require(_canMediateResult(dealId), "Cannot mediate");
SharedStructs.Deal memory deal = storageContract.getDeal(dealId);
uint256 jobCost = storageContract.getJobCost(dealId);
uint256 resultsCollateral = storageContract.getResultsCollateral(
dealId
);
storageContract.mediationAcceptResult(dealId);
paymentsContract.mediationAcceptResult(
dealId,
deal.members.resourceProvider,
deal.members.jobCreator,
jobCost,
deal.pricing.paymentCollateral,
resultsCollateral,
deal.pricing.mediationFee
);
}
// the mediator calls this to say that the resource provider did the bad job
// * check the state is ResultsChecked
// * check the mediator is calling this
// * mark the deal as mediation rejected
// * refund the JC their payment collateral
// * slash the RP's results collateral
// * pay the mediator for mediating
function mediationRejectResult(string memory dealId) public override {
// only the current mediation contract can call this
require(mediationAddress == _msgSender(), "Only mediation");
require(_canMediateResult(dealId), "Cannot mediate");
SharedStructs.Deal memory deal = storageContract.getDeal(dealId);
uint256 resultsCollateral = storageContract.getResultsCollateral(
dealId
);
storageContract.mediationRejectResult(dealId);
paymentsContract.mediationRejectResult(
dealId,
deal.members.resourceProvider,
deal.members.jobCreator,
deal.pricing.paymentCollateral,
resultsCollateral,
deal.pricing.mediationFee
);
}
function _canMediateResult(string memory dealId) private returns (bool) {
require(
storageContract.isState(
dealId,
SharedStructs.AgreementState.ResultsChecked
),
"ResultsChecked"
);
return true;
}
/**
* Timeouts
*/
function timeoutAgree(string memory dealId) public override {
SharedStructs.Deal memory deal = storageContract.getDeal(dealId);
SharedStructs.Agreement memory agreement = storageContract.getAgreement(
dealId
);
require(
deal.members.jobCreator == tx.origin ||
deal.members.resourceProvider == tx.origin,
"Only JC or RP"
);
require(
agreement.state == SharedStructs.AgreementState.DealNegotiating,
"Not correct state"
);
require(
block.timestamp >
agreement.dealCreatedAt + deal.timeouts.agree.timeout,
"Not timed out"
);
storageContract.timeoutAgree(dealId);
if (agreement.resourceProviderAgreedAt > 0) {
// this is an RP refund
paymentsContract.timeoutAgreeRefundResourceProvider(
dealId,
deal.members.resourceProvider,
deal.timeouts.submitResults.collateral
);
} else if (agreement.jobCreatorAgreedAt > 0) {
// this is an JC refund
paymentsContract.timeoutAgreeRefundJobCreator(
dealId,
deal.members.jobCreator,
deal.pricing.paymentCollateral,
deal.timeouts.submitResults.collateral
);
}
}
// the job creator calls this after the timeout has passed and there are no results submitted
// * check the JC is calling this
// * mark the deal as timedout
// * pay back the JC's job collateral
// * slash the RP's results collateral
// * emit the event
function timeoutSubmitResult(string memory dealId) public override {
SharedStructs.Deal memory deal = storageContract.getDeal(dealId);
SharedStructs.Agreement memory agreement = storageContract.getAgreement(
dealId
);
require(deal.members.jobCreator == tx.origin, "Only JC");
require(
agreement.state == SharedStructs.AgreementState.DealAgreed,
"Not correct state"
);
require(
block.timestamp >
agreement.dealAgreedAt + deal.timeouts.submitResults.timeout,
"Not timed out"
);
storageContract.timeoutSubmitResult(dealId);
paymentsContract.timeoutSubmitResult(
dealId,
deal.members.resourceProvider,
deal.members.jobCreator,
deal.pricing.paymentCollateral,
deal.timeouts.submitResults.collateral
);
}
// the resource provider calls this after the timeout has passed after submitting results
// and the job creator has not yet submitted their judgement on those results
// * check the RP is calling this
// * mark the deal as timedout
// * pay back the RP's results collateral
// * pay the RP the cost of the job
// * slash the JC's timeout collateral
// * slash the JC's job collateral
// * emit the event
function timeoutJudgeResult(string memory dealId) public override {
SharedStructs.Deal memory deal = storageContract.getDeal(dealId);
SharedStructs.Agreement memory agreement = storageContract.getAgreement(
dealId
);
require(deal.members.resourceProvider == tx.origin, "Only RP");
require(
agreement.state == SharedStructs.AgreementState.ResultsSubmitted,
"Not correct state"
);
require(
block.timestamp >
agreement.resultsSubmittedAt +
deal.timeouts.judgeResults.timeout,
"Not timed out"
);
uint256 resultsCollateral = storageContract.getResultsCollateral(
dealId
);
storageContract.timeoutJudgeResult(dealId);
paymentsContract.timeoutJudgeResult(
dealId,
deal.members.resourceProvider,
deal.members.jobCreator,
resultsCollateral,
deal.timeouts.judgeResults.collateral
);
}
// either the JC or RP call this after the timeout has passed after results being checked
// this refunds both the payment and results collateral to both the JC and RP
// * check the RP or JC is calling this
// * mark the deal as timedout
// * pay back the RP's results collateral
// * pay back the JC's paymnet collateral
// * emit the event
function timeoutMediateResult(string memory dealId) public override {
SharedStructs.Deal memory deal = storageContract.getDeal(dealId);
SharedStructs.Agreement memory agreement = storageContract.getAgreement(
dealId
);
require(
deal.members.resourceProvider == tx.origin ||
deal.members.jobCreator == tx.origin,
"Only RP or JC"
);
require(
agreement.state == SharedStructs.AgreementState.ResultsChecked,
"Not correct state"
);
require(
block.timestamp >
agreement.resultsSubmittedAt +
deal.timeouts.judgeResults.timeout,
"Not timed out"
);
uint256 resultsCollateral = storageContract.getResultsCollateral(
dealId
);
storageContract.timeoutMediateResult(dealId);
paymentsContract.timeoutMediateResult(
dealId,
deal.members.resourceProvider,
deal.members.jobCreator,
deal.pricing.paymentCollateral,
resultsCollateral,
deal.pricing.mediationFee
);
}
}