-
Notifications
You must be signed in to change notification settings - Fork 9
/
GeneralizedTCR.sol
840 lines (735 loc) · 39.1 KB
/
GeneralizedTCR.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
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
/**
* @authors: [@unknownunknown1, @mtsalenc]
* @reviewers: [@clesaege*, @ferittuncer, @satello*, @remedcu]
* @auditors: []
* @bounties: [{ link: https://github.com/kleros/tcr/issues/20, maxPayout: 25 ETH }]
* @deployments: []
*/
pragma solidity ^0.5.16;
import { IArbitrable, IArbitrator } from "@kleros/erc-792/contracts/IArbitrator.sol";
import { IEvidence } from "@kleros/erc-792/contracts/erc-1497/IEvidence.sol";
import { CappedMath } from "./utils/CappedMath.sol";
/* solium-disable max-len */
/* solium-disable security/no-block-members */
/* solium-disable security/no-send */ // It is the user responsibility to accept ETH.
/**
* @title GeneralizedTCR
* This contract is a curated registry for any types of items. Just like a TCR contract it features the request-challenge protocol and appeal fees crowdfunding.
*/
contract GeneralizedTCR is IArbitrable, IEvidence {
using CappedMath for uint;
/* Enums */
enum Status {
Absent, // The item is not in the registry.
Registered, // The item is in the registry.
RegistrationRequested, // The item has a request to be added to the registry.
ClearingRequested // The item has a request to be removed from the registry.
}
enum Party {
None, // Party per default when there is no challenger or requester. Also used for unconclusive ruling.
Requester, // Party that made the request to change a status.
Challenger // Party that challenges the request to change a status.
}
/* Structs */
struct Item {
bytes data; // The data describing the item.
Status status; // The current status of the item.
Request[] requests; // List of status change requests made for the item in the form requests[requestID].
}
// Arrays with 3 elements map with the Party enum for better readability:
// - 0: is unused, matches `Party.None`.
// - 1: for `Party.Requester`.
// - 2: for `Party.Challenger`.
struct Request {
bool disputed; // True if a dispute was raised.
uint disputeID; // ID of the dispute, if any.
uint submissionTime; // Time when the request was made. Used to track when the challenge period ends.
bool resolved; // True if the request was executed and/or any raised disputes were resolved.
address payable[3] parties; // Address of requester and challenger, if any, in the form parties[party].
Round[] rounds; // Tracks each round of a dispute in the form rounds[roundID].
Party ruling; // The final ruling given, if any.
IArbitrator arbitrator; // The arbitrator trusted to solve disputes for this request.
bytes arbitratorExtraData; // The extra data for the trusted arbitrator of this request.
uint metaEvidenceID; // The meta evidence to be used in a dispute for this case.
}
struct Round {
uint[3] amountPaid; // Tracks the sum paid for each Party in this round. Includes arbitration fees, fee stakes and deposits.
bool[3] hasPaid; // True if the Party has fully paid its fee in this round.
uint feeRewards; // Sum of reimbursable fees and stake rewards available to the parties that made contributions to the side that ultimately wins a dispute.
mapping(address => uint[3]) contributions; // Maps contributors to their contributions for each side in the form contributions[address][party].
}
/* Storage */
IArbitrator public arbitrator; // The arbitrator contract.
bytes public arbitratorExtraData; // Extra data for the arbitrator contract.
uint RULING_OPTIONS = 2; // The amount of non 0 choices the arbitrator can give.
address public governor; // The address that can make changes to the parameters of the contract.
uint public submissionBaseDeposit; // The base deposit to submit an item.
uint public removalBaseDeposit; // The base deposit to remove an item.
uint public submissionChallengeBaseDeposit; // The base deposit to challenge a submission.
uint public removalChallengeBaseDeposit; // The base deposit to challenge a removal request.
uint public challengePeriodDuration; // The time after which a request becomes executable if not challenged.
uint public metaEvidenceUpdates; // The number of times the meta evidence has been updated. Used to track the latest meta evidence ID.
// Multipliers are in basis points.
uint public winnerStakeMultiplier; // Multiplier for calculating the fee stake paid by the party that won the previous round.
uint public loserStakeMultiplier; // Multiplier for calculating the fee stake paid by the party that lost the previous round.
uint public sharedStakeMultiplier; // Multiplier for calculating the fee stake that must be paid in the case where arbitrator refused to arbitrate.
uint public constant MULTIPLIER_DIVISOR = 10000; // Divisor parameter for multipliers.
bytes32[] public itemList; // List of IDs of all submitted items.
mapping(bytes32 => Item) public items; // Maps the item ID to its data in the form items[_itemID].
mapping(address => mapping(uint => bytes32)) public arbitratorDisputeIDToItem; // Maps a dispute ID to the ID of the item with the disputed request in the form arbitratorDisputeIDToItem[arbitrator][disputeID].
mapping(bytes32 => uint) public itemIDtoIndex; // Maps an item's ID to its position in the list in the form itemIDtoIndex[itemID].
/* Modifiers */
modifier onlyGovernor {require(msg.sender == governor, "The caller must be the governor."); _;}
/* Events */
/**
* @dev Emitted when a party makes a request, raises a dispute or when a request is resolved.
* @param _itemID The ID of the affected item.
* @param _requestIndex The index of the request.
* @param _roundIndex The index of the round.
* @param _disputed Whether the request is disputed.
* @param _resolved Whether the request is executed.
*/
event ItemStatusChange(
bytes32 indexed _itemID,
uint indexed _requestIndex,
uint indexed _roundIndex,
bool _disputed,
bool _resolved
);
/**
* @dev Emitted when someone submits an item for the first time.
* @param _itemID The ID of the new item.
* @param _submitter The address of the requester.
* @param _evidenceGroupID Unique identifier of the evidence group the evidence belongs to.
* @param _data The item data.
*/
event ItemSubmitted(
bytes32 indexed _itemID,
address indexed _submitter,
uint indexed _evidenceGroupID,
bytes _data
);
/**
* @dev Emitted when someone submits a request.
* @param _itemID The ID of the affected item.
* @param _requestIndex The index of the latest request.
* @param _requestType Whether it is a registration or a removal request.
*/
event RequestSubmitted(
bytes32 indexed _itemID,
uint indexed _requestIndex,
Status indexed _requestType
);
/**
* @dev Emitted when someone submits a request. This is useful to quickly find an item and request from an evidence event and vice-versa.
* @param _itemID The ID of the affected item.
* @param _requestIndex The index of the latest request.
* @param _evidenceGroupID The evidence group ID used for this request.
*/
event RequestEvidenceGroupID(
bytes32 indexed _itemID,
uint indexed _requestIndex,
uint indexed _evidenceGroupID
);
/**
* @dev Emitted when a party contributes to an appeal.
* @param _itemID The ID of the item.
* @param _contributor The address making the contribution.
* @param _request The index of the request.
* @param _round The index of the round receiving the contribution.
* @param _amount The amount of the contribution.
* @param _side The party receiving the contribution.
*/
event AppealContribution(
bytes32 indexed _itemID,
address indexed _contributor,
uint indexed _request,
uint _round,
uint _amount,
Party _side
);
/** @dev Emitted when one of the parties successfully paid its appeal fees.
* @param _itemID The ID of the item.
* @param _request The index of the request.
* @param _round The index of the round.
* @param _side The side that is fully funded.
*/
event HasPaidAppealFee(
bytes32 indexed _itemID,
uint indexed _request,
uint indexed _round,
Party _side
);
/** @dev Emitted when the address of the connected TCR is set. The connected TCR is an instance of the Generalized TCR contract where each item is the address of a TCR related to this one.
* @param _connectedTCR The address of the connected TCR.
*/
event ConnectedTCRSet(address indexed _connectedTCR);
/**
* @dev Deploy the arbitrable curated registry.
* @param _arbitrator Arbitrator to resolve potential disputes. The arbitrator is trusted to support appeal periods and not reenter.
* @param _arbitratorExtraData Extra data for the trusted arbitrator contract.
* @param _connectedTCR The address of the TCR that stores related TCR addresses. This parameter can be left empty.
* @param _registrationMetaEvidence The URI of the meta evidence object for registration requests.
* @param _clearingMetaEvidence The URI of the meta evidence object for clearing requests.
* @param _governor The trusted governor of this contract.
* @param _submissionBaseDeposit The base deposit to submit an item.
* @param _removalBaseDeposit The base deposit to remove an item.
* @param _submissionChallengeBaseDeposit The base deposit to challenge a submission.
* @param _removalChallengeBaseDeposit The base deposit to challenge a removal request.
* @param _challengePeriodDuration The time in seconds parties have to challenge a request.
* @param _stakeMultipliers Multipliers of the arbitration cost in basis points (see MULTIPLIER_DIVISOR) as follows:
* - The multiplier applied to each party's fee stake for a round when there is no winner/loser in the previous round (e.g. when the arbitrator refused to arbitrate).
* - The multiplier applied to the winner's fee stake for the subsequent round.
* - The multiplier applied to the loser's fee stake for the subsequent round.
*/
constructor(
IArbitrator _arbitrator,
bytes memory _arbitratorExtraData,
address _connectedTCR,
string memory _registrationMetaEvidence,
string memory _clearingMetaEvidence,
address _governor,
uint _submissionBaseDeposit,
uint _removalBaseDeposit,
uint _submissionChallengeBaseDeposit,
uint _removalChallengeBaseDeposit,
uint _challengePeriodDuration,
uint[3] memory _stakeMultipliers
) public {
emit MetaEvidence(0, _registrationMetaEvidence);
emit MetaEvidence(1, _clearingMetaEvidence);
emit ConnectedTCRSet(_connectedTCR);
arbitrator = _arbitrator;
arbitratorExtraData = _arbitratorExtraData;
governor = _governor;
submissionBaseDeposit = _submissionBaseDeposit;
removalBaseDeposit = _removalBaseDeposit;
submissionChallengeBaseDeposit = _submissionChallengeBaseDeposit;
removalChallengeBaseDeposit = _removalChallengeBaseDeposit;
challengePeriodDuration = _challengePeriodDuration;
sharedStakeMultiplier = _stakeMultipliers[0];
winnerStakeMultiplier = _stakeMultipliers[1];
loserStakeMultiplier = _stakeMultipliers[2];
}
/* External and Public */
// ************************ //
// * Requests * //
// ************************ //
/** @dev Submit a request to register an item. Accepts enough ETH to cover the deposit, reimburses the rest.
* @param _item The data describing the item.
*/
function addItem(bytes calldata _item) external payable {
bytes32 itemID = keccak256(_item);
require(items[itemID].status == Status.Absent, "Item must be absent to be added.");
requestStatusChange(_item, submissionBaseDeposit);
}
/** @dev Submit a request to remove an item from the list. Accepts enough ETH to cover the deposit, reimburses the rest.
* @param _itemID The ID of the item to remove.
* @param _evidence A link to an evidence using its URI. Ignored if not provided.
*/
function removeItem(bytes32 _itemID, string calldata _evidence) external payable {
require(items[_itemID].status == Status.Registered, "Item must be registered to be removed.");
Item storage item = items[_itemID];
// Emit evidence if it was provided.
if (bytes(_evidence).length > 0) {
// Using `length` instead of `length - 1` because a new request will be added on requestStatusChange().
uint requestIndex = item.requests.length;
uint evidenceGroupID = uint(keccak256(abi.encodePacked(_itemID, requestIndex)));
emit Evidence(arbitrator, evidenceGroupID, msg.sender, _evidence);
}
requestStatusChange(item.data, removalBaseDeposit);
}
/** @dev Challenges the request of the item. Accepts enough ETH to cover the deposit, reimburses the rest.
* @param _itemID The ID of the item which request to challenge.
* @param _evidence A link to an evidence using its URI. Ignored if not provided.
*/
function challengeRequest(bytes32 _itemID, string calldata _evidence) external payable {
Item storage item = items[_itemID];
require(
item.status == Status.RegistrationRequested || item.status == Status.ClearingRequested,
"The item must have a pending request."
);
Request storage request = item.requests[item.requests.length - 1];
require(now - request.submissionTime <= challengePeriodDuration, "Challenges must occur during the challenge period.");
require(!request.disputed, "The request should not have already been disputed.");
request.parties[uint(Party.Challenger)] = msg.sender;
Round storage round = request.rounds[0];
uint arbitrationCost = request.arbitrator.arbitrationCost(request.arbitratorExtraData);
uint challengerBaseDeposit = item.status == Status.RegistrationRequested
? submissionChallengeBaseDeposit
: removalChallengeBaseDeposit;
uint totalCost = arbitrationCost.addCap(challengerBaseDeposit);
contribute(round, Party.Challenger, msg.sender, msg.value, totalCost);
require(round.amountPaid[uint(Party.Challenger)] >= totalCost, "You must fully fund your side.");
round.hasPaid[uint(Party.Challenger)] = true;
// Raise a dispute.
request.disputeID = request.arbitrator.createDispute.value(arbitrationCost)(RULING_OPTIONS, request.arbitratorExtraData);
arbitratorDisputeIDToItem[address(request.arbitrator)][request.disputeID] = _itemID;
request.disputed = true;
request.rounds.length++;
round.feeRewards = round.feeRewards.subCap(arbitrationCost);
uint evidenceGroupID = uint(keccak256(abi.encodePacked(_itemID, item.requests.length - 1)));
emit Dispute(
request.arbitrator,
request.disputeID,
request.metaEvidenceID,
evidenceGroupID
);
if (bytes(_evidence).length > 0) {
emit Evidence(request.arbitrator, evidenceGroupID, msg.sender, _evidence);
}
}
/** @dev Takes up to the total amount required to fund a side of an appeal. Reimburses the rest. Creates an appeal if both sides are fully funded.
* @param _itemID The ID of the item which request to fund.
* @param _side The recipient of the contribution.
*/
function fundAppeal(bytes32 _itemID, Party _side) external payable {
require(_side == Party.Requester || _side == Party.Challenger, "Invalid side.");
require(
items[_itemID].status == Status.RegistrationRequested || items[_itemID].status == Status.ClearingRequested,
"The item must have a pending request."
);
Request storage request = items[_itemID].requests[items[_itemID].requests.length - 1];
require(request.disputed, "A dispute must have been raised to fund an appeal.");
(uint appealPeriodStart, uint appealPeriodEnd) = request.arbitrator.appealPeriod(request.disputeID);
require(
now >= appealPeriodStart && now < appealPeriodEnd,
"Contributions must be made within the appeal period."
);
/* solium-disable indentation */
uint multiplier;
{
Party winner = Party(request.arbitrator.currentRuling(request.disputeID));
Party loser;
if (winner == Party.Requester)
loser = Party.Challenger;
else if (winner == Party.Challenger)
loser = Party.Requester;
require(_side != loser || (now-appealPeriodStart < (appealPeriodEnd-appealPeriodStart)/2), "The loser must contribute during the first half of the appeal period.");
if (_side == winner)
multiplier = winnerStakeMultiplier;
else if (_side == loser)
multiplier = loserStakeMultiplier;
else
multiplier = sharedStakeMultiplier;
}
/* solium-enable indentation */
Round storage round = request.rounds[request.rounds.length - 1];
uint appealCost = request.arbitrator.appealCost(request.disputeID, request.arbitratorExtraData);
uint totalCost = appealCost.addCap((appealCost.mulCap(multiplier)) / MULTIPLIER_DIVISOR);
uint contribution = contribute(round, _side, msg.sender, msg.value, totalCost);
emit AppealContribution(
_itemID,
msg.sender,
items[_itemID].requests.length - 1,
request.rounds.length - 1,
contribution,
_side
);
if (round.amountPaid[uint(_side)] >= totalCost) {
round.hasPaid[uint(_side)] = true;
emit HasPaidAppealFee(_itemID, items[_itemID].requests.length - 1, request.rounds.length - 1, _side);
}
// Raise appeal if both sides are fully funded.
if (round.hasPaid[uint(Party.Challenger)] && round.hasPaid[uint(Party.Requester)]) {
request.arbitrator.appeal.value(appealCost)(request.disputeID, request.arbitratorExtraData);
request.rounds.length++;
round.feeRewards = round.feeRewards.subCap(appealCost);
}
}
/** @dev Reimburses contributions if no disputes were raised. If a dispute was raised, sends the fee stake rewards and reimbursements proportionally to the contributions made to the winner of a dispute.
* @param _beneficiary The address that made contributions to a request.
* @param _itemID The ID of the item submission to withdraw from.
* @param _request The request from which to withdraw from.
* @param _round The round from which to withdraw from.
*/
function withdrawFeesAndRewards(address payable _beneficiary, bytes32 _itemID, uint _request, uint _round) public {
Item storage item = items[_itemID];
Request storage request = item.requests[_request];
Round storage round = request.rounds[_round];
require(request.resolved, "Request must be resolved.");
uint reward;
if (!round.hasPaid[uint(Party.Requester)] || !round.hasPaid[uint(Party.Challenger)]) {
// Reimburse if not enough fees were raised to appeal the ruling.
reward = round.contributions[_beneficiary][uint(Party.Requester)] + round.contributions[_beneficiary][uint(Party.Challenger)];
} else if (request.ruling == Party.None) {
// Reimburse unspent fees proportionally if there is no winner or loser.
uint rewardRequester = round.amountPaid[uint(Party.Requester)] > 0
? (round.contributions[_beneficiary][uint(Party.Requester)] * round.feeRewards) / (round.amountPaid[uint(Party.Challenger)] + round.amountPaid[uint(Party.Requester)])
: 0;
uint rewardChallenger = round.amountPaid[uint(Party.Challenger)] > 0
? (round.contributions[_beneficiary][uint(Party.Challenger)] * round.feeRewards) / (round.amountPaid[uint(Party.Challenger)] + round.amountPaid[uint(Party.Requester)])
: 0;
reward = rewardRequester + rewardChallenger;
} else {
// Reward the winner.
reward = round.amountPaid[uint(request.ruling)] > 0
? (round.contributions[_beneficiary][uint(request.ruling)] * round.feeRewards) / round.amountPaid[uint(request.ruling)]
: 0;
}
round.contributions[_beneficiary][uint(Party.Requester)] = 0;
round.contributions[_beneficiary][uint(Party.Challenger)] = 0;
_beneficiary.send(reward);
}
/** @dev Executes an unchallenged request if the challenge period has passed.
* @param _itemID The ID of the item to execute.
*/
function executeRequest(bytes32 _itemID) external {
Item storage item = items[_itemID];
Request storage request = item.requests[item.requests.length - 1];
require(
now - request.submissionTime > challengePeriodDuration,
"Time to challenge the request must pass."
);
require(!request.disputed, "The request should not be disputed.");
if (item.status == Status.RegistrationRequested)
item.status = Status.Registered;
else if (item.status == Status.ClearingRequested)
item.status = Status.Absent;
else
revert("There must be a request.");
request.resolved = true;
emit ItemStatusChange(_itemID, item.requests.length - 1, request.rounds.length - 1, false, true);
withdrawFeesAndRewards(request.parties[uint(Party.Requester)], _itemID, item.requests.length - 1, 0); // Automatically withdraw for the requester.
}
/** @dev Give a ruling for a dispute. Can only be called by the arbitrator. TRUSTED.
* Accounts for the situation where the winner loses a case due to paying less appeal fees than expected.
* @param _disputeID ID of the dispute in the arbitrator contract.
* @param _ruling Ruling given by the arbitrator. Note that 0 is reserved for "Refused to arbitrate".
*/
function rule(uint _disputeID, uint _ruling) public {
Party resultRuling = Party(_ruling);
bytes32 itemID = arbitratorDisputeIDToItem[msg.sender][_disputeID];
Item storage item = items[itemID];
Request storage request = item.requests[item.requests.length - 1];
Round storage round = request.rounds[request.rounds.length - 1];
require(_ruling <= RULING_OPTIONS, "Invalid ruling option");
require(address(request.arbitrator) == msg.sender, "Only the arbitrator can give a ruling");
require(!request.resolved, "The request must not be resolved.");
// The ruling is inverted if the loser paid its fees.
if (round.hasPaid[uint(Party.Requester)] == true) // If one side paid its fees, the ruling is in its favor. Note that if the other side had also paid, an appeal would have been created.
resultRuling = Party.Requester;
else if (round.hasPaid[uint(Party.Challenger)] == true)
resultRuling = Party.Challenger;
emit Ruling(IArbitrator(msg.sender), _disputeID, uint(resultRuling));
executeRuling(_disputeID, uint(resultRuling));
}
/** @dev Submit a reference to evidence. EVENT.
* @param _itemID The ID of the item which the evidence is related to.
* @param _evidence A link to an evidence using its URI.
*/
function submitEvidence(bytes32 _itemID, string calldata _evidence) external {
Item storage item = items[_itemID];
Request storage request = item.requests[item.requests.length - 1];
require(!request.resolved, "The dispute must not already be resolved.");
uint evidenceGroupID = uint(keccak256(abi.encodePacked(_itemID, item.requests.length - 1)));
emit Evidence(request.arbitrator, evidenceGroupID, msg.sender, _evidence);
}
// ************************ //
// * Governance * //
// ************************ //
/** @dev Change the duration of the challenge period.
* @param _challengePeriodDuration The new duration of the challenge period.
*/
function changeTimeToChallenge(uint _challengePeriodDuration) external onlyGovernor {
challengePeriodDuration = _challengePeriodDuration;
}
/** @dev Change the base amount required as a deposit to submit an item.
* @param _submissionBaseDeposit The new base amount of wei required to submit an item.
*/
function changeSubmissionBaseDeposit(uint _submissionBaseDeposit) external onlyGovernor {
submissionBaseDeposit = _submissionBaseDeposit;
}
/** @dev Change the base amount required as a deposit to remove an item.
* @param _removalBaseDeposit The new base amount of wei required to remove an item.
*/
function changeRemovalBaseDeposit(uint _removalBaseDeposit) external onlyGovernor {
removalBaseDeposit = _removalBaseDeposit;
}
/** @dev Change the base amount required as a deposit to challenge a submission.
* @param _submissionChallengeBaseDeposit The new base amount of wei required to challenge a submission.
*/
function changeSubmissionChallengeBaseDeposit(uint _submissionChallengeBaseDeposit) external onlyGovernor {
submissionChallengeBaseDeposit = _submissionChallengeBaseDeposit;
}
/** @dev Change the base amount required as a deposit to challenge a removal request.
* @param _removalChallengeBaseDeposit The new base amount of wei required to challenge a removal request.
*/
function changeRemovalChallengeBaseDeposit(uint _removalChallengeBaseDeposit) external onlyGovernor {
removalChallengeBaseDeposit = _removalChallengeBaseDeposit;
}
/** @dev Change the governor of the curated registry.
* @param _governor The address of the new governor.
*/
function changeGovernor(address _governor) external onlyGovernor {
governor = _governor;
}
/** @dev Change the proportion of arbitration fees that must be paid as fee stake by parties when there is no winner or loser.
* @param _sharedStakeMultiplier Multiplier of arbitration fees that must be paid as fee stake. In basis points.
*/
function changeSharedStakeMultiplier(uint _sharedStakeMultiplier) external onlyGovernor {
sharedStakeMultiplier = _sharedStakeMultiplier;
}
/** @dev Change the proportion of arbitration fees that must be paid as fee stake by the winner of the previous round.
* @param _winnerStakeMultiplier Multiplier of arbitration fees that must be paid as fee stake. In basis points.
*/
function changeWinnerStakeMultiplier(uint _winnerStakeMultiplier) external onlyGovernor {
winnerStakeMultiplier = _winnerStakeMultiplier;
}
/** @dev Change the proportion of arbitration fees that must be paid as fee stake by the party that lost the previous round.
* @param _loserStakeMultiplier Multiplier of arbitration fees that must be paid as fee stake. In basis points.
*/
function changeLoserStakeMultiplier(uint _loserStakeMultiplier) external onlyGovernor {
loserStakeMultiplier = _loserStakeMultiplier;
}
/** @dev Change the arbitrator to be used for disputes that may be raised. The arbitrator is trusted to support appeal periods and not reenter.
* @param _arbitrator The new trusted arbitrator to be used in disputes.
* @param _arbitratorExtraData The extra data used by the new arbitrator.
*/
function changeArbitrator(IArbitrator _arbitrator, bytes calldata _arbitratorExtraData) external onlyGovernor {
arbitrator = _arbitrator;
arbitratorExtraData = _arbitratorExtraData;
}
/** @dev Change the address of connectedTCR, the Generalized TCR instance that stores addresses of TCRs related to this one.
* @param _connectedTCR The address of the connectedTCR contract to use.
*/
function changeConnectedTCR(address _connectedTCR) external onlyGovernor {
emit ConnectedTCRSet(_connectedTCR);
}
/** @dev Update the meta evidence used for disputes.
* @param _registrationMetaEvidence The meta evidence to be used for future registration request disputes.
* @param _clearingMetaEvidence The meta evidence to be used for future clearing request disputes.
*/
function changeMetaEvidence(string calldata _registrationMetaEvidence, string calldata _clearingMetaEvidence) external onlyGovernor {
metaEvidenceUpdates++;
emit MetaEvidence(2 * metaEvidenceUpdates, _registrationMetaEvidence);
emit MetaEvidence(2 * metaEvidenceUpdates + 1, _clearingMetaEvidence);
}
/* Internal */
/** @dev Submit a request to change item's status. Accepts enough ETH to cover the deposit, reimburses the rest.
* @param _item The data describing the item.
* @param _baseDeposit The base deposit for the request.
*/
function requestStatusChange(bytes memory _item, uint _baseDeposit) internal {
bytes32 itemID = keccak256(_item);
Item storage item = items[itemID];
// Using `length` instead of `length - 1` as index because a new request will be added.
uint evidenceGroupID = uint(keccak256(abi.encodePacked(itemID, item.requests.length)));
if (item.requests.length == 0) {
item.data = _item;
itemList.push(itemID);
itemIDtoIndex[itemID] = itemList.length - 1;
emit ItemSubmitted(itemID, msg.sender, evidenceGroupID, item.data);
}
Request storage request = item.requests[item.requests.length++];
if (item.status == Status.Absent) {
item.status = Status.RegistrationRequested;
request.metaEvidenceID = 2 * metaEvidenceUpdates;
} else if (item.status == Status.Registered) {
item.status = Status.ClearingRequested;
request.metaEvidenceID = 2 * metaEvidenceUpdates + 1;
}
request.parties[uint(Party.Requester)] = msg.sender;
request.submissionTime = now;
request.arbitrator = arbitrator;
request.arbitratorExtraData = arbitratorExtraData;
Round storage round = request.rounds[request.rounds.length++];
uint arbitrationCost = request.arbitrator.arbitrationCost(request.arbitratorExtraData);
uint totalCost = arbitrationCost.addCap(_baseDeposit);
contribute(round, Party.Requester, msg.sender, msg.value, totalCost);
require(round.amountPaid[uint(Party.Requester)] >= totalCost, "You must fully fund your side.");
round.hasPaid[uint(Party.Requester)] = true;
emit ItemStatusChange(itemID, item.requests.length - 1, request.rounds.length - 1, false, false);
emit RequestSubmitted(itemID, item.requests.length - 1, item.status);
emit RequestEvidenceGroupID(itemID, item.requests.length - 1, evidenceGroupID);
}
/** @dev Returns the contribution value and remainder from available ETH and required amount.
* @param _available The amount of ETH available for the contribution.
* @param _requiredAmount The amount of ETH required for the contribution.
* @return taken The amount of ETH taken.
* @return remainder The amount of ETH left from the contribution.
*/
function calculateContribution(uint _available, uint _requiredAmount)
internal
pure
returns(uint taken, uint remainder)
{
if (_requiredAmount > _available)
return (_available, 0); // Take whatever is available, return 0 as leftover ETH.
else
return (_requiredAmount, _available - _requiredAmount);
}
/** @dev Make a fee contribution.
* @param _round The round to contribute.
* @param _side The side for which to contribute.
* @param _contributor The contributor.
* @param _amount The amount contributed.
* @param _totalRequired The total amount required for this side.
* @return The amount of appeal fees contributed.
*/
function contribute(Round storage _round, Party _side, address payable _contributor, uint _amount, uint _totalRequired) internal returns (uint) {
// Take up to the amount necessary to fund the current round at the current costs.
uint contribution; // Amount contributed.
uint remainingETH; // Remaining ETH to send back.
(contribution, remainingETH) = calculateContribution(_amount, _totalRequired.subCap(_round.amountPaid[uint(_side)]));
_round.contributions[_contributor][uint(_side)] += contribution;
_round.amountPaid[uint(_side)] += contribution;
_round.feeRewards += contribution;
// Reimburse leftover ETH.
_contributor.send(remainingETH); // Deliberate use of send in order to not block the contract in case of reverting fallback.
return contribution;
}
/** @dev Execute the ruling of a dispute.
* @param _disputeID ID of the dispute in the arbitrator contract.
* @param _ruling Ruling given by the arbitrator. Note that 0 is reserved for "Refused to arbitrate".
*/
function executeRuling(uint _disputeID, uint _ruling) internal {
bytes32 itemID = arbitratorDisputeIDToItem[msg.sender][_disputeID];
Item storage item = items[itemID];
Request storage request = item.requests[item.requests.length - 1];
Party winner = Party(_ruling);
if (winner == Party.Requester) { // Execute Request.
if (item.status == Status.RegistrationRequested)
item.status = Status.Registered;
else if (item.status == Status.ClearingRequested)
item.status = Status.Absent;
} else {
if (item.status == Status.RegistrationRequested)
item.status = Status.Absent;
else if (item.status == Status.ClearingRequested)
item.status = Status.Registered;
}
request.resolved = true;
request.ruling = Party(_ruling);
emit ItemStatusChange(itemID, item.requests.length - 1, request.rounds.length - 1, true, true);
// Automatically withdraw first deposits and reimbursements (first round only).
if (winner == Party.None) {
withdrawFeesAndRewards(request.parties[uint(Party.Requester)], itemID, item.requests.length - 1, 0);
withdrawFeesAndRewards(request.parties[uint(Party.Challenger)], itemID, item.requests.length - 1, 0);
} else {
withdrawFeesAndRewards(request.parties[uint(winner)], itemID, item.requests.length - 1, 0);
}
}
// ************************ //
// * Getters * //
// ************************ //
/** @dev Returns the number of items that were submitted. Includes items that never made it to the list or were later removed.
* @return count The number of items on the list.
*/
function itemCount() external view returns (uint count) {
return itemList.length;
}
/** @dev Gets the contributions made by a party for a given round of a request.
* @param _itemID The ID of the item.
* @param _request The request to query.
* @param _round The round to query.
* @param _contributor The address of the contributor.
* @return contributions The contributions.
*/
function getContributions(
bytes32 _itemID,
uint _request,
uint _round,
address _contributor
) external view returns(uint[3] memory contributions) {
Item storage item = items[_itemID];
Request storage request = item.requests[_request];
Round storage round = request.rounds[_round];
contributions = round.contributions[_contributor];
}
/** @dev Returns item's information. Includes length of requests array.
* @param _itemID The ID of the queried item.
* @return data The data describing the item.
* @return status The current status of the item.
* @return numberOfRequests Length of list of status change requests made for the item.
*/
function getItemInfo(bytes32 _itemID)
external
view
returns (
bytes memory data,
Status status,
uint numberOfRequests
)
{
Item storage item = items[_itemID];
return (
item.data,
item.status,
item.requests.length
);
}
/** @dev Gets information on a request made for the item.
* @param _itemID The ID of the queried item.
* @param _request The request to be queried.
* @return disputed True if a dispute was raised.
* @return disputeID ID of the dispute, if any..
* @return submissionTime Time when the request was made.
* @return resolved True if the request was executed and/or any raised disputes were resolved.
* @return parties Address of requester and challenger, if any.
* @return numberOfRounds Number of rounds of dispute.
* @return ruling The final ruling given, if any.
* @return arbitrator The arbitrator trusted to solve disputes for this request.
* @return arbitratorExtraData The extra data for the trusted arbitrator of this request.
* @return metaEvidenceID The meta evidence to be used in a dispute for this case.
*/
function getRequestInfo(bytes32 _itemID, uint _request)
external
view
returns (
bool disputed,
uint disputeID,
uint submissionTime,
bool resolved,
address payable[3] memory parties,
uint numberOfRounds,
Party ruling,
IArbitrator arbitrator,
bytes memory arbitratorExtraData,
uint metaEvidenceID
)
{
Request storage request = items[_itemID].requests[_request];
return (
request.disputed,
request.disputeID,
request.submissionTime,
request.resolved,
request.parties,
request.rounds.length,
request.ruling,
request.arbitrator,
request.arbitratorExtraData,
request.metaEvidenceID
);
}
/** @dev Gets the information of a round of a request.
* @param _itemID The ID of the queried item.
* @param _request The request to be queried.
* @param _round The round to be queried.
* @return appealed Whether appealed or not.
* @return amountPaid Tracks the sum paid for each Party in this round.
* @return hasPaid True if the Party has fully paid its fee in this round.
* @return feeRewards Sum of reimbursable fees and stake rewards available to the parties that made contributions to the side that ultimately wins a dispute.
*/
function getRoundInfo(bytes32 _itemID, uint _request, uint _round)
external
view
returns (
bool appealed,
uint[3] memory amountPaid,
bool[3] memory hasPaid,
uint feeRewards
)
{
Item storage item = items[_itemID];
Request storage request = item.requests[_request];
Round storage round = request.rounds[_round];
return (
_round != (request.rounds.length - 1),
round.amountPaid,
round.hasPaid,
round.feeRewards
);
}
}