-
Notifications
You must be signed in to change notification settings - Fork 10
/
LightGeneralizedTCR.sol
1113 lines (944 loc) · 48.4 KB
/
LightGeneralizedTCR.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
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/**
* @authors: [@unknownunknown1*, @mtsalenc*, @hbarcelos*]
* @reviewers: [@fnanni-0*, @greenlucid, @shalzz]
* @auditors: []
* @bounties: []
* @deployments: []
*/
pragma solidity 0.5.17;
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";
import {CappedMath128} from "./utils/CappedMath128.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 LightGeneralizedTCR
* 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.
* The difference between LightGeneralizedTCR and GeneralizedTCR is that instead of storing item data in storage and event logs, LightCurate only stores the URI of item in the logs. This makes it considerably cheaper to use and allows more flexibility with the item columns.
*/
contract LightGeneralizedTCR is IArbitrable, IEvidence {
using CappedMath for uint256;
using CappedMath128 for uint128;
/* 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.
}
enum RequestType {
Registration, // Identifies a request to register an item to the registry.
Clearing // Identifies a request to remove an item from the registry.
}
enum DisputeStatus {
None, // No dispute was created.
AwaitingRuling, // Dispute was created, but the final ruling was not given yet.
Resolved // Dispute was ruled.
}
/* Structs */
struct Item {
Status status; // The current status of the item.
uint128 sumDeposit; // The total deposit made by the requester and the challenger (if any).
uint120 requestCount; // The number of requests.
mapping(uint256 => 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 {
RequestType requestType;
uint64 submissionTime; // Time when the request was made. Used to track when the challenge period ends.
uint24 arbitrationParamsIndex; // The index for the arbitration params for the request.
address payable requester; // Address of the requester.
// Pack the requester together with the other parameters, as they are written in the same request.
address payable challenger; // Address of the challenger, if any.
}
struct DisputeData {
uint256 disputeID; // The ID of the dispute on the arbitrator.
DisputeStatus status; // The current status of the dispute.
Party ruling; // The ruling given to a dispute. Only set after it has been resolved.
uint240 roundCount; // The number of rounds.
mapping(uint256 => Round) rounds; // Data of the different dispute rounds. rounds[roundId].
}
struct Round {
Party sideFunded; // Stores the side that successfully paid the appeal fees in the latest round. Note that if both sides have paid a new round is created.
uint256 feeRewards; // Sum of reimbursable fees and stake rewards available to the parties that made contributions to the side that ultimately wins a dispute.
uint256[3] amountPaid; // Tracks the sum paid for each Party in this round.
mapping(address => uint256[3]) contributions; // Maps contributors to their contributions for each side in the form contributions[address][party].
}
struct ArbitrationParams {
IArbitrator arbitrator; // The arbitrator trusted to solve disputes for this request.
bytes arbitratorExtraData; // The extra data for the trusted arbitrator of this request.
}
/* Constants */
uint256 public constant RULING_OPTIONS = 2; // The amount of non 0 choices the arbitrator can give.
uint256 private constant RESERVED_ROUND_ID = 0; // For compatibility with GeneralizedTCR consider the request/challenge cycle the first round (index 0).
/* Storage */
bool private initialized;
address public relayerContract; // The contract that is used to add or remove items directly to speed up the interchain communication.
address public governor; // The address that can make changes to the parameters of the contract.
uint256 public submissionBaseDeposit; // The base deposit to submit an item.
uint256 public removalBaseDeposit; // The base deposit to remove an item.
uint256 public submissionChallengeBaseDeposit; // The base deposit to challenge a submission.
uint256 public removalChallengeBaseDeposit; // The base deposit to challenge a removal request.
uint256 public challengePeriodDuration; // The time after which a request becomes executable if not challenged.
// Multipliers are in basis points.
uint256 public winnerStakeMultiplier; // Multiplier for calculating the fee stake paid by the party that won the previous round.
uint256 public loserStakeMultiplier; // Multiplier for calculating the fee stake paid by the party that lost the previous round.
uint256 public sharedStakeMultiplier; // Multiplier for calculating the fee stake that must be paid in the case where arbitrator refused to arbitrate.
uint256 public constant MULTIPLIER_DIVISOR = 10000; // Divisor parameter for multipliers.
mapping(bytes32 => Item) public items; // Maps the item ID to its data in the form items[_itemID].
mapping(address => mapping(uint256 => bytes32)) public arbitratorDisputeIDToItemID; // Maps a dispute ID to the ID of the item with the disputed request in the form arbitratorDisputeIDToItemID[arbitrator][disputeID].
mapping(bytes32 => mapping(uint256 => DisputeData)) public requestsDisputeData; // Maps an item and a request to the data of the dispute related to them. requestsDisputeData[itemID][requestIndex]
ArbitrationParams[] public arbitrationParamsChanges;
/* Modifiers */
modifier onlyGovernor() {
require(msg.sender == governor, "The caller must be the governor.");
_;
}
modifier onlyRelayer() {
require(msg.sender == relayerContract, "The caller must be the relay.");
_;
}
/* 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 _updatedDirectly Whether this was emitted in either `addItemDirectly` or `removeItemDirectly`. This is used in the subgraph.
*/
event ItemStatusChange(bytes32 indexed _itemID, bool _updatedDirectly);
/**
* @dev Emitted when someone submits an item for the first time.
* @param _itemID The ID of the new item.
* @param _data The item data URI.
* @param _addedDirectly Whether the item was added via `addItemDirectly`.
*/
event NewItem(bytes32 indexed _itemID, string _data, bool _addedDirectly);
/**
* @dev Emitted when someone submits a request.
* @param _itemID The ID of the affected item.
* @param _evidenceGroupID Unique identifier of the evidence group the evidence belongs to.
*/
event RequestSubmitted(bytes32 indexed _itemID, uint256 _evidenceGroupID);
/**
* @dev Emitted when a party contributes to an appeal. The roundID assumes the initial request and challenge deposits are the first round. This is done so indexers can know more information about the contribution without using call handlers.
* @param _itemID The ID of the item.
* @param _requestID The index of the request that received the contribution.
* @param _roundID The index of the round that received the contribution.
* @param _contributor The address making the contribution.
* @param _contribution How much of the contribution was accepted.
* @param _side The party receiving the contribution.
*/
event Contribution(
bytes32 indexed _itemID,
uint256 _requestID,
uint256 _roundID,
address indexed _contributor,
uint256 _contribution,
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 Emitted when someone withdraws more than 0 rewards.
* @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.
* @param _round The round from which to withdraw.
* @param _reward The amount withdrawn.
*/
event RewardWithdrawn(
address indexed _beneficiary,
bytes32 indexed _itemID,
uint256 _request,
uint256 _round,
uint256 _reward
);
/**
* @dev Initialize 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 _baseDeposits The base deposits for requests/challenges as follows:
* - The base deposit to submit an item.
* - The base deposit to remove an item.
* - The base deposit to challenge a submission.
* - 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.
* @param _relayerContract The address of the relay contract to add/remove items directly.
*/
function initialize(
IArbitrator _arbitrator,
bytes calldata _arbitratorExtraData,
address _connectedTCR,
string calldata _registrationMetaEvidence,
string calldata _clearingMetaEvidence,
address _governor,
uint256[4] calldata _baseDeposits,
uint256 _challengePeriodDuration,
uint256[3] calldata _stakeMultipliers,
address _relayerContract
) external {
require(!initialized, "Already initialized.");
emit ConnectedTCRSet(_connectedTCR);
governor = _governor;
submissionBaseDeposit = _baseDeposits[0];
removalBaseDeposit = _baseDeposits[1];
submissionChallengeBaseDeposit = _baseDeposits[2];
removalChallengeBaseDeposit = _baseDeposits[3];
challengePeriodDuration = _challengePeriodDuration;
sharedStakeMultiplier = _stakeMultipliers[0];
winnerStakeMultiplier = _stakeMultipliers[1];
loserStakeMultiplier = _stakeMultipliers[2];
relayerContract = _relayerContract;
_doChangeArbitrationParams(_arbitrator, _arbitratorExtraData, _registrationMetaEvidence, _clearingMetaEvidence);
initialized = true;
}
/* External and Public */
// ************************ //
// * Requests * //
// ************************ //
/**
* @dev Directly add an item to the list bypassing request-challenge. Can only be used by the relay contract.
* @param _item The URI to the item data.
*/
function addItemDirectly(string calldata _item) external onlyRelayer {
bytes32 itemID = keccak256(abi.encodePacked(_item));
Item storage item = items[itemID];
require(item.status == Status.Absent, "Item must be absent to be added.");
// Note that if the item is added directly once, the next time it is added it will emit this event again.
if (item.requestCount == 0) {
emit NewItem(itemID, _item, true);
}
item.status = Status.Registered;
emit ItemStatusChange(itemID, true);
}
/**
* @dev Directly remove an item from the list bypassing request-challenge. Can only be used by the relay contract.
* @param _itemID The ID of the item to remove.
*/
function removeItemDirectly(bytes32 _itemID) external onlyRelayer {
Item storage item = items[_itemID];
require(item.status == Status.Registered, "Item must be registered to be removed.");
item.status = Status.Absent;
emit ItemStatusChange(_itemID, true);
}
/**
* @dev Submit a request to register an item. Accepts enough ETH to cover the deposit, reimburses the rest.
* @param _item The URI to the item data.
*/
function addItem(string calldata _item) external payable {
bytes32 itemID = keccak256(abi.encodePacked(_item));
Item storage item = items[itemID];
// Extremely unlikely, but we check that for correctness sake.
require(item.requestCount < uint120(-1), "Too many requests for item.");
require(item.status == Status.Absent, "Item must be absent to be added.");
// Note that if the item was added previously using `addItemDirectly`, the event will be emitted again here.
if (item.requestCount == 0) {
emit NewItem(itemID, _item, false);
}
Request storage request = item.requests[item.requestCount++];
uint256 arbitrationParamsIndex = arbitrationParamsChanges.length - 1;
IArbitrator arbitrator = arbitrationParamsChanges[arbitrationParamsIndex].arbitrator;
bytes storage arbitratorExtraData = arbitrationParamsChanges[arbitrationParamsIndex].arbitratorExtraData;
uint256 arbitrationCost = arbitrator.arbitrationCost(arbitratorExtraData);
uint256 totalCost = arbitrationCost.addCap(submissionBaseDeposit);
require(msg.value >= totalCost, "You must fully fund the request.");
// Casting is safe here because this line will never be executed in case
// totalCost > type(uint128).max, since it would be an unpayable value.
item.sumDeposit = uint128(totalCost);
item.status = Status.RegistrationRequested;
request.requestType = RequestType.Registration;
request.submissionTime = uint64(block.timestamp);
request.arbitrationParamsIndex = uint24(arbitrationParamsIndex);
request.requester = msg.sender;
emit RequestSubmitted(itemID, getEvidenceGroupID(itemID, item.requestCount - 1));
emit Contribution(itemID, item.requestCount - 1, RESERVED_ROUND_ID, msg.sender, totalCost, Party.Requester);
if (msg.value > totalCost) {
msg.sender.send(msg.value - totalCost);
}
}
/**
* @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 {
Item storage item = items[_itemID];
// Extremely unlikely, but we check that for correctness sake.
require(item.requestCount < uint120(-1), "Too many requests for item.");
require(item.status == Status.Registered, "Item must be registered to be removed.");
Request storage request = item.requests[item.requestCount++];
uint256 arbitrationParamsIndex = arbitrationParamsChanges.length - 1;
IArbitrator arbitrator = arbitrationParamsChanges[arbitrationParamsIndex].arbitrator;
bytes storage arbitratorExtraData = arbitrationParamsChanges[arbitrationParamsIndex].arbitratorExtraData;
uint256 arbitrationCost = arbitrator.arbitrationCost(arbitratorExtraData);
uint256 totalCost = arbitrationCost.addCap(removalBaseDeposit);
require(msg.value >= totalCost, "You must fully fund the request.");
// Casting is safe here because this line will never be executed in case
// totalCost > type(uint128).max, since it would be an unpayable value.
item.sumDeposit = uint128(totalCost);
item.status = Status.ClearingRequested;
request.submissionTime = uint64(block.timestamp);
request.arbitrationParamsIndex = uint24(arbitrationParamsIndex);
request.requester = msg.sender;
request.requestType = RequestType.Clearing;
uint256 evidenceGroupID = getEvidenceGroupID(_itemID, item.requestCount - 1);
emit RequestSubmitted(_itemID, evidenceGroupID);
emit Contribution(_itemID, item.requestCount - 1, RESERVED_ROUND_ID, msg.sender, totalCost, Party.Requester);
// Emit evidence if it was provided.
if (bytes(_evidence).length > 0) {
emit Evidence(arbitrator, evidenceGroupID, msg.sender, _evidence);
}
if (msg.value > totalCost) {
msg.sender.send(msg.value - totalCost);
}
}
/**
* @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.Registered, "The item must have a pending request.");
uint256 lastRequestIndex = item.requestCount - 1;
Request storage request = item.requests[lastRequestIndex];
require(
block.timestamp - request.submissionTime <= challengePeriodDuration,
"Challenges must occur during the challenge period."
);
DisputeData storage disputeData = requestsDisputeData[_itemID][lastRequestIndex];
require(disputeData.status == DisputeStatus.None, "The request should not have already been disputed.");
ArbitrationParams storage arbitrationParams = arbitrationParamsChanges[request.arbitrationParamsIndex];
IArbitrator arbitrator = arbitrationParams.arbitrator;
uint256 arbitrationCost = arbitrator.arbitrationCost(arbitrationParams.arbitratorExtraData);
uint256 totalCost;
{
uint256 challengerBaseDeposit = item.status == Status.RegistrationRequested
? submissionChallengeBaseDeposit
: removalChallengeBaseDeposit;
totalCost = arbitrationCost.addCap(challengerBaseDeposit);
}
require(msg.value >= totalCost, "You must fully fund the challenge.");
emit Contribution(_itemID, lastRequestIndex, RESERVED_ROUND_ID, msg.sender, totalCost, Party.Challenger);
// Casting is safe here because this line will never be executed in case
// totalCost > type(uint128).max, since it would be an unpayable value.
item.sumDeposit = item.sumDeposit.addCap(uint128(totalCost)).subCap(uint128(arbitrationCost));
request.challenger = msg.sender;
// Raise a dispute.
disputeData.disputeID = arbitrator.createDispute.value(arbitrationCost)(
RULING_OPTIONS,
arbitrationParams.arbitratorExtraData
);
disputeData.status = DisputeStatus.AwaitingRuling;
// For compatibility with GeneralizedTCR consider the request/challenge cycle
// the first round (index 0), so we need to make the next round index 1.
disputeData.roundCount = 2;
arbitratorDisputeIDToItemID[address(arbitrator)][disputeData.disputeID] = _itemID;
uint256 metaEvidenceID = 2 * request.arbitrationParamsIndex + uint256(request.requestType);
uint256 evidenceGroupID = getEvidenceGroupID(_itemID, lastRequestIndex);
emit Dispute(arbitrator, disputeData.disputeID, metaEvidenceID, evidenceGroupID);
if (bytes(_evidence).length > 0) {
emit Evidence(arbitrator, evidenceGroupID, msg.sender, _evidence);
}
if (msg.value > totalCost) {
msg.sender.send(msg.value - totalCost);
}
}
/**
* @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.None, "Invalid side.");
Item storage item = items[_itemID];
require(item.status > Status.Registered, "The item must have a pending request.");
uint256 lastRequestIndex = item.requestCount - 1;
Request storage request = item.requests[lastRequestIndex];
DisputeData storage disputeData = requestsDisputeData[_itemID][lastRequestIndex];
require(
disputeData.status == DisputeStatus.AwaitingRuling,
"A dispute must have been raised to fund an appeal."
);
ArbitrationParams storage arbitrationParams = arbitrationParamsChanges[request.arbitrationParamsIndex];
IArbitrator arbitrator = arbitrationParams.arbitrator;
uint256 lastRoundIndex = disputeData.roundCount - 1;
Round storage round = disputeData.rounds[lastRoundIndex];
require(round.sideFunded != _side, "Side already fully funded.");
uint256 multiplier;
{
(uint256 appealPeriodStart, uint256 appealPeriodEnd) = arbitrator.appealPeriod(disputeData.disputeID);
require(
block.timestamp >= appealPeriodStart && block.timestamp < appealPeriodEnd,
"Contributions must be made within the appeal period."
);
Party winner = Party(arbitrator.currentRuling(disputeData.disputeID));
if (winner == Party.None) {
multiplier = sharedStakeMultiplier;
} else if (_side == winner) {
multiplier = winnerStakeMultiplier;
} else {
multiplier = loserStakeMultiplier;
require(
block.timestamp < (appealPeriodStart + appealPeriodEnd) / 2,
"The loser must contribute during the first half of the appeal period."
);
}
}
uint256 appealCost = arbitrator.appealCost(disputeData.disputeID, arbitrationParams.arbitratorExtraData);
uint256 totalCost = appealCost.addCap(appealCost.mulCap(multiplier) / MULTIPLIER_DIVISOR);
contribute(_itemID, lastRequestIndex, lastRoundIndex, uint256(_side), msg.sender, msg.value, totalCost);
if (round.amountPaid[uint256(_side)] >= totalCost) {
if (round.sideFunded == Party.None) {
round.sideFunded = _side;
} else {
// Resets the value because both sides are funded.
round.sideFunded = Party.None;
// Raise appeal if both sides are fully funded.
arbitrator.appeal.value(appealCost)(disputeData.disputeID, arbitrationParams.arbitratorExtraData);
disputeData.roundCount++;
round.feeRewards = round.feeRewards.subCap(appealCost);
}
}
}
/**
* @dev 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 _requestID The request from which to withdraw from.
* @param _roundID The round from which to withdraw from.
*/
function withdrawFeesAndRewards(
address payable _beneficiary,
bytes32 _itemID,
uint256 _requestID,
uint256 _roundID
) external {
DisputeData storage disputeData = requestsDisputeData[_itemID][_requestID];
require(disputeData.status == DisputeStatus.Resolved, "Request must be resolved.");
Round storage round = disputeData.rounds[_roundID];
uint256 reward;
if (_roundID == disputeData.roundCount - 1) {
// Reimburse if not enough fees were raised to appeal the ruling.
reward =
round.contributions[_beneficiary][uint256(Party.Requester)] +
round.contributions[_beneficiary][uint256(Party.Challenger)];
} else if (disputeData.ruling == Party.None) {
uint256 totalFeesInRound = round.amountPaid[uint256(Party.Challenger)] +
round.amountPaid[uint256(Party.Requester)];
uint256 claimableFees = round.contributions[_beneficiary][uint256(Party.Challenger)] +
round.contributions[_beneficiary][uint256(Party.Requester)];
reward = totalFeesInRound > 0 ? (claimableFees * round.feeRewards) / totalFeesInRound : 0;
} else {
// Reward the winner.
reward = round.amountPaid[uint256(disputeData.ruling)] > 0
? (round.contributions[_beneficiary][uint256(disputeData.ruling)] * round.feeRewards) /
round.amountPaid[uint256(disputeData.ruling)]
: 0;
}
round.contributions[_beneficiary][uint256(Party.Requester)] = 0;
round.contributions[_beneficiary][uint256(Party.Challenger)] = 0;
if (reward > 0) {
_beneficiary.send(reward);
emit RewardWithdrawn(_beneficiary, _itemID, _requestID, _roundID, 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];
uint256 lastRequestIndex = items[_itemID].requestCount - 1;
Request storage request = item.requests[lastRequestIndex];
require(
block.timestamp - request.submissionTime > challengePeriodDuration,
"Time to challenge the request must pass."
);
DisputeData storage disputeData = requestsDisputeData[_itemID][lastRequestIndex];
require(disputeData.status == DisputeStatus.None, "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.");
}
emit ItemStatusChange(_itemID, false);
uint256 sumDeposit = item.sumDeposit;
item.sumDeposit = 0;
if (sumDeposit > 0) {
// reimburse the requester
request.requester.send(sumDeposit);
}
}
/**
* @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(uint256 _disputeID, uint256 _ruling) external {
require(_ruling <= RULING_OPTIONS, "Invalid ruling option");
bytes32 itemID = arbitratorDisputeIDToItemID[msg.sender][_disputeID];
Item storage item = items[itemID];
uint256 lastRequestIndex = items[itemID].requestCount - 1;
Request storage request = item.requests[lastRequestIndex];
DisputeData storage disputeData = requestsDisputeData[itemID][lastRequestIndex];
require(disputeData.status == DisputeStatus.AwaitingRuling, "The request must not be resolved.");
ArbitrationParams storage arbitrationParams = arbitrationParamsChanges[request.arbitrationParamsIndex];
require(address(arbitrationParams.arbitrator) == msg.sender, "Only the arbitrator can give a ruling");
uint256 finalRuling;
Round storage round = disputeData.rounds[disputeData.roundCount - 1];
// If one side paid its fees, the ruling is in its favor.
// Note that if the other side had also paid, sideFudned would have been reset
// and an appeal would have been created.
if (round.sideFunded == Party.Requester) {
finalRuling = uint256(Party.Requester);
} else if (round.sideFunded == Party.Challenger) {
finalRuling = uint256(Party.Challenger);
} else {
finalRuling = _ruling;
}
emit Ruling(IArbitrator(msg.sender), _disputeID, finalRuling);
Party winner = Party(finalRuling);
disputeData.status = DisputeStatus.Resolved;
disputeData.ruling = winner;
uint256 sumDeposit = item.sumDeposit;
item.sumDeposit = 0;
if (winner == Party.None) {
// If the arbitrator refuse to rule, then the item status should be the same it was before the request.
// Regarding item.status this is equivalent to the challenger winning the dispute.
item.status = item.status == Status.RegistrationRequested ? Status.Absent : Status.Registered;
// Since nobody has won, then we reimburse both parties equally.
// If item.sumDeposit is odd, 1 wei will remain in the contract balance.
uint256 halfSumDeposit = sumDeposit / 2;
request.requester.send(halfSumDeposit);
request.challenger.send(halfSumDeposit);
} else if (winner == Party.Requester) {
item.status = item.status == Status.RegistrationRequested ? Status.Registered : Status.Absent;
request.requester.send(sumDeposit);
} else {
item.status = item.status == Status.RegistrationRequested ? Status.Absent : Status.Registered;
request.challenger.send(sumDeposit);
}
emit ItemStatusChange(itemID, false);
}
/**
* @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];
uint256 lastRequestIndex = item.requestCount - 1;
Request storage request = item.requests[lastRequestIndex];
ArbitrationParams storage arbitrationParams = arbitrationParamsChanges[request.arbitrationParamsIndex];
emit Evidence(
arbitrationParams.arbitrator,
getEvidenceGroupID(_itemID, lastRequestIndex),
msg.sender,
_evidence
);
}
// ************************ //
// * Governance * //
// ************************ //
/**
* @dev Change the duration of the challenge period.
* @param _challengePeriodDuration The new duration of the challenge period.
*/
function changeChallengePeriodDuration(uint256 _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(uint256 _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(uint256 _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(uint256 _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(uint256 _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(uint256 _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(uint256 _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(uint256 _loserStakeMultiplier) external onlyGovernor {
loserStakeMultiplier = _loserStakeMultiplier;
}
/**
* @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 Change the address of the relay contract.
* @param _relayerContract The new address of the relay contract.
*/
function changeRelayerContract(address _relayerContract) external onlyGovernor {
relayerContract = _relayerContract;
}
/**
* @notice Changes the params related to arbitration.
* @dev Effectively makes all new items use the new set of params.
* @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 _registrationMetaEvidence The URI of the meta evidence object for registration requests.
* @param _clearingMetaEvidence The URI of the meta evidence object for clearing requests.
*/
function changeArbitrationParams(
IArbitrator _arbitrator,
bytes calldata _arbitratorExtraData,
string calldata _registrationMetaEvidence,
string calldata _clearingMetaEvidence
) external onlyGovernor {
_doChangeArbitrationParams(_arbitrator, _arbitratorExtraData, _registrationMetaEvidence, _clearingMetaEvidence);
}
/* Internal */
/**
* @dev Effectively makes all new items use the new set of params.
* @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 _registrationMetaEvidence The URI of the meta evidence object for registration requests.
* @param _clearingMetaEvidence The URI of the meta evidence object for clearing requests.
*/
function _doChangeArbitrationParams(
IArbitrator _arbitrator,
bytes memory _arbitratorExtraData,
string memory _registrationMetaEvidence,
string memory _clearingMetaEvidence
) internal {
emit MetaEvidence(2 * arbitrationParamsChanges.length, _registrationMetaEvidence);
emit MetaEvidence(2 * arbitrationParamsChanges.length + 1, _clearingMetaEvidence);
arbitrationParamsChanges.push(
ArbitrationParams({arbitrator: _arbitrator, arbitratorExtraData: _arbitratorExtraData})
);
}
/**
* @notice Make a fee contribution.
* @dev It cannot be inlined in fundAppeal because of the stack limit.
* @param _itemID The item receiving the contribution.
* @param _requestID The request to contribute.
* @param _roundID 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(
bytes32 _itemID,
uint256 _requestID,
uint256 _roundID,
uint256 _side,
address payable _contributor,
uint256 _amount,
uint256 _totalRequired
) internal {
Round storage round = requestsDisputeData[_itemID][_requestID].rounds[_roundID];
uint256 pendingAmount = _totalRequired.subCap(round.amountPaid[_side]);
// Take up to the amount necessary to fund the current round at the current costs.
uint256 contribution; // Amount contributed.
uint256 remainingETH; // Remaining ETH to send back.
if (pendingAmount > _amount) {
contribution = _amount;
} else {
contribution = pendingAmount;
remainingETH = _amount - pendingAmount;
}
round.contributions[_contributor][_side] += contribution;
round.amountPaid[_side] += contribution;
round.feeRewards += contribution;
// Reimburse leftover ETH.
if (remainingETH > 0) {
// Deliberate use of send in order to not block the contract in case of reverting fallback.
_contributor.send(remainingETH);
}
if (contribution > 0) {
emit Contribution(_itemID, _requestID, _roundID, msg.sender, contribution, Party(_side));
}
}
// ************************ //
// * Getters * //
// ************************ //
/**
* @dev Gets the evidengeGroupID for a given item and request.
* @param _itemID The ID of the item.
* @param _requestID The ID of the request.
* @return The evidenceGroupID
*/
function getEvidenceGroupID(bytes32 _itemID, uint256 _requestID) public pure returns (uint256) {
return uint256(keccak256(abi.encodePacked(_itemID, _requestID)));
}
/**
* @notice Gets the arbitrator for new requests.
* @dev Gets the latest value in arbitrationParamsChanges.
* @return The arbitrator address.
*/
function arbitrator() external view returns (IArbitrator) {
return arbitrationParamsChanges[arbitrationParamsChanges.length - 1].arbitrator;
}
/**
* @notice Gets the arbitratorExtraData for new requests.
* @dev Gets the latest value in arbitrationParamsChanges.
* @return The arbitrator extra data.
*/
function arbitratorExtraData() external view returns (bytes memory) {
return arbitrationParamsChanges[arbitrationParamsChanges.length - 1].arbitratorExtraData;
}
/**
* @dev Gets the number of times MetaEvidence was updated.
* @return The number of times MetaEvidence was updated.
*/
function metaEvidenceUpdates() external view returns (uint256) {
return arbitrationParamsChanges.length;
}
/**
* @dev Gets the contributions made by a party for a given round of a request.
* @param _itemID The ID of the item.
* @param _requestID The request to query.
* @param _roundID The round to query.
* @param _contributor The address of the contributor.
* @return contributions The contributions.
*/
function getContributions(
bytes32 _itemID,
uint256 _requestID,
uint256 _roundID,
address _contributor
) external view returns (uint256[3] memory contributions) {
DisputeData storage disputeData = requestsDisputeData[_itemID][_requestID];
Round storage round = disputeData.rounds[_roundID];
contributions = round.contributions[_contributor];
}
/**
* @dev Returns item's information. Includes the total number of requests for the item
* @param _itemID The ID of the queried item.
* @return status The current status of the item.
* @return numberOfRequests Total number of requests for the item.
* @return sumDeposit The total deposit made by the requester and the challenger (if any)
*/
function getItemInfo(bytes32 _itemID)
external
view
returns (
Status status,
uint256 numberOfRequests,
uint256 sumDeposit
)
{
Item storage item = items[_itemID];
return (item.status, item.requestCount, item.sumDeposit);
}
/**
* @dev Gets information on a request made for the item.
* @param _itemID The ID of the queried item.
* @param _requestID 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, uint256 _requestID)
external
view
returns (
bool disputed,
uint256 disputeID,
uint256 submissionTime,
bool resolved,
address payable[3] memory parties,
uint256 numberOfRounds,
Party ruling,
IArbitrator requestArbitrator,
bytes memory requestArbitratorExtraData,
uint256 metaEvidenceID
)
{
Item storage item = items[_itemID];
require(item.requestCount > _requestID, "Request does not exist.");
Request storage request = items[_itemID].requests[_requestID];
submissionTime = request.submissionTime;
parties[uint256(Party.Requester)] = request.requester;
parties[uint256(Party.Challenger)] = request.challenger;
(disputed, disputeID, numberOfRounds, ruling) = getRequestDisputeData(_itemID, _requestID);
(requestArbitrator, requestArbitratorExtraData, metaEvidenceID) = getRequestArbitrationParams(
_itemID,
_requestID
);
resolved = getRequestResolvedStatus(_itemID, _requestID);
}
/**
* @dev Gets the dispute data relative to a given item request.
* @param _itemID The ID of the queried item.