Skip to content

Commit 10ad99b

Browse files
authored
Merge pull request #90 from unknownunknown1/small-features
feat(KC): justification + small fixes
2 parents c31d20f + f66ab4b commit 10ad99b

File tree

6 files changed

+195
-114
lines changed

6 files changed

+195
-114
lines changed

contracts/src/arbitration/IDisputeKit.sol

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,24 @@ import "./IArbitrator.sol";
1818
* It does not intend to abstract the interactions with the user (such as voting or appeal funding) to allow for implementation-specific parameters.
1919
*/
2020
interface IDisputeKit {
21+
// ************************************ //
22+
// * Events * //
23+
// ************************************ //
24+
25+
/**
26+
* @dev Emitted when casting a vote to provide the justification of juror's choice.
27+
* @param _coreDisputeID ID of the dispute in the core contract.
28+
* @param _juror Address of the juror.
29+
* @param _choice The choice juror voted for.
30+
* @param _justification Justification of the choice.
31+
*/
32+
event Justification(
33+
uint256 indexed _coreDisputeID,
34+
address indexed _juror,
35+
uint256 indexed _choice,
36+
string _justification
37+
);
38+
2139
// ************************************* //
2240
// * State Modifiers * //
2341
// ************************************* //
@@ -58,10 +76,10 @@ interface IDisputeKit {
5876

5977
/** @dev Returns the voting data from the most relevant round.
6078
* @param _coreDisputeID The ID of the dispute in Kleros Core, not in the Dispute Kit.
61-
* @return winningChoiece The winning choice of this round.
79+
* @return winningChoice The winning choice of this round.
6280
* @return tied Whether it's a tie or not.
6381
*/
64-
function getLastRoundResult(uint256 _coreDisputeID) external view returns (uint256 winningChoiece, bool tied);
82+
function getLastRoundResult(uint256 _coreDisputeID) external view returns (uint256 winningChoice, bool tied);
6583

6684
/** @dev Gets the degree of coherence of a particular voter. This function is called by Kleros Core in order to determine the amount of the reward.
6785
* @param _coreDisputeID The ID of the dispute in Kleros Core, not in the Dispute Kit.

contracts/src/arbitration/KlerosCore.sol

Lines changed: 83 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {SortitionSumTreeFactory} from "../data-structures/SortitionSumTreeFactor
1818
/**
1919
* @title KlerosCore
2020
* Core arbitrator contract for Kleros v2.
21+
* Note that this contract trusts the token and the dispute kit contracts.
2122
*/
2223
contract KlerosCore is IArbitrator {
2324
using SortitionSumTreeFactory for SortitionSumTreeFactory.SortitionSumTrees; // Use library functions for sortition sum trees.
@@ -71,7 +72,7 @@ contract KlerosCore is IArbitrator {
7172
}
7273

7374
struct Juror {
74-
uint96[] subcourtIDs; // The IDs of subcourts where the juror's stake path ends. A stake path is a path from the forking court to a court the juror directly staked in using `_setStake`.
75+
uint96[] subcourtIDs; // The IDs of subcourts where the juror's stake path ends. A stake path is a path from the general court to a court the juror directly staked in using `_setStake`.
7576
mapping(uint96 => uint256) stakedTokens; // The number of tokens the juror has staked in the subcourt in the form `stakedTokens[subcourtID]`.
7677
mapping(uint96 => uint256) lockedTokens; // The number of tokens the juror has locked in the subcourt in the form `lockedTokens[subcourtID]`.
7778
}
@@ -95,7 +96,8 @@ contract KlerosCore is IArbitrator {
9596
// * Storage * //
9697
// ************************************* //
9798

98-
uint256 public constant FORKING_COURT = 0; // Index of the default court.
99+
uint256 public constant FORKING_COURT = 0; // Index of the forking court.
100+
uint256 public constant GENERAL_COURT = 1; // Index of the default (general) court.
99101
uint256 public constant NULL_DISPUTE_KIT = 0; // Null pattern to indicate a top-level DK which has no parent.
100102
uint256 public constant DISPUTE_KIT_CLASSIC_INDEX = 1; // Index of the default DK. 0 index is skipped.
101103
uint256 public constant MAX_STAKE_PATHS = 4; // The maximum number of stake paths a juror can have.
@@ -168,10 +170,10 @@ contract KlerosCore is IArbitrator {
168170
* @param _jurorProsecutionModule The address of the juror prosecution module.
169171
* @param _disputeKit The address of the default dispute kit.
170172
* @param _phaseTimeouts minStakingTime and maxFreezingTime respectively
171-
* @param _hiddenVotes The `hiddenVotes` property value of the forking court.
172-
* @param _courtParameters Numeric parameters of Forking court (minStake, alpha, feeForJuror and jurorsForCourtJump respectively).
173-
* @param _timesPerPeriod The `timesPerPeriod` property value of the forking court.
174-
* @param _sortitionSumTreeK The number of children per node of the forking court's sortition sum tree.
173+
* @param _hiddenVotes The `hiddenVotes` property value of the general court.
174+
* @param _courtParameters Numeric parameters of General court (minStake, alpha, feeForJuror and jurorsForCourtJump respectively).
175+
* @param _timesPerPeriod The `timesPerPeriod` property value of the general court.
176+
* @param _sortitionSumTreeK The number of children per node of the general court's sortition sum tree.
175177
*/
176178
constructor(
177179
address _governor,
@@ -204,8 +206,12 @@ contract KlerosCore is IArbitrator {
204206
lastPhaseChange = block.timestamp;
205207

206208
// Create the Forking court.
209+
courts.push();
210+
// TODO: fill the properties for Forking court.
211+
212+
// Create the General court.
207213
Court storage court = courts.push();
208-
court.parent = 0;
214+
court.parent = 1; // TODO: Should the parent for General court be 0 or 1? In the former case the Forking court will become the top court after jumping.
209215
court.children = new uint256[](0);
210216
court.hiddenVotes = _hiddenVotes;
211217
court.minStake = _courtParameters[0];
@@ -215,7 +221,9 @@ contract KlerosCore is IArbitrator {
215221
court.timesPerPeriod = _timesPerPeriod;
216222
court.supportedDisputeKits[DISPUTE_KIT_CLASSIC_INDEX] = true;
217223

218-
sortitionSumTrees.createTree(bytes32(0), _sortitionSumTreeK);
224+
// TODO: fill the properties for Forking court.
225+
sortitionSumTrees.createTree(bytes32(FORKING_COURT), _sortitionSumTreeK);
226+
sortitionSumTrees.createTree(bytes32(GENERAL_COURT), _sortitionSumTreeK);
219227
}
220228

221229
// ************************ //
@@ -274,16 +282,16 @@ contract KlerosCore is IArbitrator {
274282
/** @dev Add a new supported dispute kit module to the court.
275283
* @param _disputeKitAddress The address of the dispute kit contract.
276284
* @param _parent The ID of the parent dispute kit. It is left empty when root DK is created.
277-
* Note that the root DK must be supported by the forking court.
285+
* Note that the root DK must be supported by the general court.
278286
*/
279287
function addNewDisputeKit(IDisputeKit _disputeKitAddress, uint256 _parent) external onlyByGovernor {
280288
uint256 disputeKitID = disputeKitNodes.length;
281289
require(_parent < disputeKitID, "Parent doesn't exist");
282290
uint256 depthLevel;
283291

284-
// Create new tree, which root should be supported by Forking court.
292+
// Create new tree, which root should be supported by General court.
285293
if (_parent == NULL_DISPUTE_KIT) {
286-
courts[FORKING_COURT].supportedDisputeKits[disputeKitID] = true;
294+
courts[GENERAL_COURT].supportedDisputeKits[disputeKitID] = true;
287295
} else {
288296
depthLevel = disputeKitNodes[_parent].depthLevel + 1;
289297
// It should be always possible to reach the root from the leaf with the defined number of search iterations.
@@ -328,6 +336,7 @@ contract KlerosCore is IArbitrator {
328336
"A subcourt cannot be a child of a subcourt with a higher minimum stake."
329337
);
330338
require(_supportedDisputeKits.length > 0, "Must support at least one DK");
339+
require(_parent != FORKING_COURT, "Can't have Forking court as a parent");
331340

332341
uint256 subcourtID = courts.length;
333342
Court storage court = courts.push();
@@ -359,7 +368,7 @@ contract KlerosCore is IArbitrator {
359368
* @param _minStake The new value for the `minStake` property value.
360369
*/
361370
function changeSubcourtMinStake(uint96 _subcourtID, uint256 _minStake) external onlyByGovernor {
362-
require(_subcourtID == FORKING_COURT || courts[courts[_subcourtID].parent].minStake <= _minStake);
371+
require(_subcourtID == GENERAL_COURT || courts[courts[_subcourtID].parent].minStake <= _minStake);
363372
for (uint256 i = 0; i < courts[_subcourtID].children.length; i++) {
364373
require(
365374
courts[courts[_subcourtID].children[i]].minStake >= _minStake,
@@ -430,8 +439,8 @@ contract KlerosCore is IArbitrator {
430439
subcourt.supportedDisputeKits[_disputeKitIDs[i]] = true;
431440
} else {
432441
require(
433-
!(_subcourtID == FORKING_COURT && disputeKitNodes[_disputeKitIDs[i]].parent == NULL_DISPUTE_KIT),
434-
"Can't remove root DK support from the forking court"
442+
!(_subcourtID == GENERAL_COURT && disputeKitNodes[_disputeKitIDs[i]].parent == NULL_DISPUTE_KIT),
443+
"Can't remove root DK support from the general court"
435444
);
436445
subcourt.supportedDisputeKits[_disputeKitIDs[i]] = false;
437446
}
@@ -674,7 +683,7 @@ contract KlerosCore is IArbitrator {
674683
// We didn't find a court that is compatible with DK from this tree, so we jump directly to the top court.
675684
// Note that this can only happen when disputeKitID is at its root, and each root DK is supported by the top court by default.
676685
if (!courts[newSubcourtID].supportedDisputeKits[newDisputeKitID]) {
677-
newSubcourtID = uint96(FORKING_COURT);
686+
newSubcourtID = uint96(GENERAL_COURT);
678687
}
679688

680689
if (newSubcourtID != dispute.subcourtID) {
@@ -782,7 +791,7 @@ contract KlerosCore is IArbitrator {
782791
if (coherentCount == 0) {
783792
// No one was coherent. Send the rewards to governor.
784793
payable(governor).send(round.totalFeesForJurors);
785-
pinakion.transfer(governor, penaltiesInRoundCache);
794+
safeTransfer(governor, penaltiesInRoundCache);
786795
}
787796
}
788797
} else {
@@ -804,12 +813,12 @@ contract KlerosCore is IArbitrator {
804813
// Give back the locked tokens in case the juror fully unstaked earlier.
805814
if (jurors[account].stakedTokens[dispute.subcourtID] == 0) {
806815
uint256 tokenLocked = (round.tokensAtStakePerJuror * degreeOfCoherence) / ALPHA_DIVISOR;
807-
pinakion.transfer(account, tokenLocked);
816+
safeTransfer(account, tokenLocked);
808817
}
809818

810819
uint256 tokenReward = ((penaltiesInRoundCache / coherentCount) * degreeOfCoherence) / ALPHA_DIVISOR;
811820
uint256 ethReward = ((round.totalFeesForJurors / coherentCount) * degreeOfCoherence) / ALPHA_DIVISOR;
812-
pinakion.transfer(account, tokenReward);
821+
safeTransfer(account, tokenReward);
813822
payable(account).send(ethReward);
814823
emit TokenAndETHShift(account, _disputeID, int256(tokenReward), int256(ethReward));
815824
}
@@ -858,8 +867,8 @@ contract KlerosCore is IArbitrator {
858867
Court storage court = courts[dispute.subcourtID];
859868
if (round.nbVotes >= court.jurorsForCourtJump) {
860869
// Jump to parent subcourt.
861-
if (dispute.subcourtID == FORKING_COURT) {
862-
// Already in the forking court.
870+
if (dispute.subcourtID == GENERAL_COURT) {
871+
// TODO: Handle the forking when appealed in General court.
863872
cost = NON_PAYABLE_AMOUNT; // Get the cost of the parent subcourt.
864873
} else {
865874
cost = courts[court.parent].feeForJuror * ((round.nbVotes * 2) + 1);
@@ -1063,8 +1072,7 @@ contract KlerosCore is IArbitrator {
10631072
uint256 _stake,
10641073
uint256 _penalty
10651074
) internal returns (bool succeeded) {
1066-
// Input and transfer checks
1067-
if (_subcourtID > courts.length) return false;
1075+
if (_subcourtID == FORKING_COURT || _subcourtID > courts.length) return false;
10681076

10691077
Juror storage juror = jurors[_account];
10701078
bytes32 stakePathID = accountAndSubcourtIDToStakePathID(_account, _subcourtID);
@@ -1091,33 +1099,35 @@ contract KlerosCore is IArbitrator {
10911099
if (_stake >= currentStake) {
10921100
transferredAmount = _stake - currentStake;
10931101
if (transferredAmount > 0) {
1094-
// TODO: handle transfer reverts.
1095-
if (!pinakion.transferFrom(_account, address(this), transferredAmount)) return false;
1102+
if (safeTransferFrom(_account, address(this), transferredAmount)) {
1103+
if (currentStake == 0) {
1104+
juror.subcourtIDs.push(_subcourtID);
1105+
}
1106+
} else {
1107+
return false;
1108+
}
10961109
}
10971110
} else if (_stake == 0) {
10981111
// Keep locked tokens in the contract and release them after dispute is executed.
10991112
transferredAmount = currentStake - juror.lockedTokens[_subcourtID] - _penalty;
11001113
if (transferredAmount > 0) {
1101-
if (!pinakion.transfer(_account, transferredAmount)) return false;
1114+
if (safeTransfer(_account, transferredAmount)) {
1115+
for (uint256 i = 0; i < juror.subcourtIDs.length; i++) {
1116+
if (juror.subcourtIDs[i] == _subcourtID) {
1117+
juror.subcourtIDs[i] = juror.subcourtIDs[juror.subcourtIDs.length - 1];
1118+
juror.subcourtIDs.pop();
1119+
break;
1120+
}
1121+
}
1122+
} else {
1123+
return false;
1124+
}
11021125
}
11031126
} else {
11041127
transferredAmount = currentStake - _stake - _penalty;
11051128
if (transferredAmount > 0) {
1106-
if (!pinakion.transfer(_account, transferredAmount)) return false;
1107-
}
1108-
}
1109-
1110-
// State update
1111-
if (_stake != 0) {
1112-
if (currentStake == 0) {
1113-
juror.subcourtIDs.push(_subcourtID);
1114-
}
1115-
} else {
1116-
for (uint256 i = 0; i < juror.subcourtIDs.length; i++) {
1117-
if (juror.subcourtIDs[i] == _subcourtID) {
1118-
juror.subcourtIDs[i] = juror.subcourtIDs[juror.subcourtIDs.length - 1];
1119-
juror.subcourtIDs.pop();
1120-
break;
1129+
if (!safeTransfer(_account, transferredAmount)) {
1130+
return false;
11211131
}
11221132
}
11231133
}
@@ -1131,7 +1141,7 @@ contract KlerosCore is IArbitrator {
11311141
uint256 currentSubcourtID = _subcourtID;
11321142
while (!finished) {
11331143
sortitionSumTrees.set(bytes32(currentSubcourtID), _stake, stakePathID);
1134-
if (currentSubcourtID == FORKING_COURT) finished = true;
1144+
if (currentSubcourtID == GENERAL_COURT) finished = true;
11351145
else currentSubcourtID = courts[currentSubcourtID].parent;
11361146
}
11371147

@@ -1164,8 +1174,8 @@ contract KlerosCore is IArbitrator {
11641174
minJurors := mload(add(_extraData, 0x40))
11651175
disputeKitID := mload(add(_extraData, 0x60))
11661176
}
1167-
if (subcourtID >= courts.length) {
1168-
subcourtID = uint96(FORKING_COURT);
1177+
if (subcourtID == FORKING_COURT || subcourtID >= courts.length) {
1178+
subcourtID = uint96(GENERAL_COURT);
11691179
}
11701180
if (minJurors == 0) {
11711181
minJurors = MIN_JURORS;
@@ -1174,7 +1184,7 @@ contract KlerosCore is IArbitrator {
11741184
disputeKitID = DISPUTE_KIT_CLASSIC_INDEX; // 0 index is not used.
11751185
}
11761186
} else {
1177-
subcourtID = uint96(FORKING_COURT);
1187+
subcourtID = uint96(GENERAL_COURT);
11781188
minJurors = MIN_JURORS;
11791189
disputeKitID = DISPUTE_KIT_CLASSIC_INDEX;
11801190
}
@@ -1210,4 +1220,33 @@ contract KlerosCore is IArbitrator {
12101220
stakePathID := mload(ptr)
12111221
}
12121222
}
1223+
1224+
/** @dev Calls transfer() without reverting.
1225+
* @param _to Recepient address.
1226+
* @param _value Amount transferred.
1227+
* @return Whether transfer succeeded or not.
1228+
*/
1229+
function safeTransfer(address _to, uint256 _value) internal returns (bool) {
1230+
(bool success, bytes memory data) = address(pinakion).call(
1231+
abi.encodeWithSelector(IERC20.transfer.selector, _to, _value)
1232+
);
1233+
return (success && (data.length == 0 || abi.decode(data, (bool))));
1234+
}
1235+
1236+
/** @dev Calls transferFrom() without reverting.
1237+
* @param _from Sender address.
1238+
* @param _to Recepient address.
1239+
* @param _value Amount transferred.
1240+
* @return Whether transfer succeeded or not.
1241+
*/
1242+
function safeTransferFrom(
1243+
address _from,
1244+
address _to,
1245+
uint256 _value
1246+
) internal returns (bool) {
1247+
(bool success, bytes memory data) = address(pinakion).call(
1248+
abi.encodeWithSelector(IERC20.transferFrom.selector, _from, _to, _value)
1249+
);
1250+
return (success && (data.length == 0 || abi.decode(data, (bool))));
1251+
}
12131252
}

contracts/src/arbitration/dispute-kits/BaseDisputeKit.sol

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,9 @@ abstract contract BaseDisputeKit is IDisputeKit {
6767
}
6868

6969
/** @dev Checks that the chosen address satisfies certain conditions for being drawn.
70-
* @param _disputeID ID of the dispute in the core contract.
70+
* @param _coreDisputeID ID of the dispute in the core contract.
7171
* @param _juror Chosen address.
72+
* @return Whether the address can be drawn or not.
7273
*/
73-
function postDrawCheck(uint256 _disputeID, address _juror) internal virtual returns (bool);
74+
function postDrawCheck(uint256 _coreDisputeID, address _juror) internal virtual returns (bool);
7475
}

0 commit comments

Comments
 (0)