diff --git a/contracts/src/arbitration/IDisputeKit.sol b/contracts/src/arbitration/IDisputeKit.sol index 675043a76..8d12b5938 100644 --- a/contracts/src/arbitration/IDisputeKit.sol +++ b/contracts/src/arbitration/IDisputeKit.sol @@ -24,67 +24,86 @@ interface IDisputeKit { /** @dev Creates a local dispute and maps it to the dispute ID in the Core contract. * Note: Access restricted to Kleros Core only. - * @param _disputeID The ID of the dispute in Kleros Core. + * @param _coreDisputeID The ID of the dispute in Kleros Core, not in the Dispute Kit. * @param _numberOfChoices Number of choices of the dispute * @param _extraData Additional info about the dispute, for possible use in future dispute kits. */ function createDispute( - uint256 _disputeID, + uint256 _coreDisputeID, uint256 _numberOfChoices, bytes calldata _extraData ) external; /** @dev Draws the juror from the sortition tree. The drawn address is picked up by Kleros Core. * Note: Access restricted to Kleros Core only. - * @param _disputeID The ID of the dispute in Kleros Core. + * @param _coreDisputeID The ID of the dispute in Kleros Core, not in the Dispute Kit. * @return drawnAddress The drawn address. */ - function draw(uint256 _disputeID) external returns (address drawnAddress); + function draw(uint256 _coreDisputeID) external returns (address drawnAddress); // ************************************* // // * Public Views * // // ************************************* // /** @dev Gets the current ruling of a specified dispute. - * @param _disputeID The ID of the dispute in Kleros Core. + * @param _coreDisputeID The ID of the dispute in Kleros Core, not in the Dispute Kit. * @return ruling The current ruling. */ - function currentRuling(uint256 _disputeID) external view returns (uint256 ruling); + function currentRuling(uint256 _coreDisputeID) external view returns (uint256 ruling); + + /** @dev Returns the voting data from the most relevant round. + * @param _coreDisputeID The ID of the dispute in Kleros Core, not in the Dispute Kit. + * @return winningChoiece The winning choice of this round. + * @return tied Whether it's a tie or not. + */ + function getLastRoundResult(uint256 _coreDisputeID) external view returns (uint256 winningChoiece, bool tied); /** @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. - * @param _disputeID The ID of the dispute in Kleros Core. - * @param _round The ID of the round. + * @param _coreDisputeID The ID of the dispute in Kleros Core, not in the Dispute Kit. + * @param _coreRoundID The ID of the round in Kleros Core, not in the Dispute Kit. * @param _voteID The ID of the vote. * @return The degree of coherence in basis points. */ function getDegreeOfCoherence( - uint256 _disputeID, - uint256 _round, + uint256 _coreDisputeID, + uint256 _coreRoundID, uint256 _voteID ) external view returns (uint256); /** @dev Gets the number of jurors who are eligible to a reward in this round. - * @param _disputeID The ID of the dispute in Kleros Core. - * @param _round The ID of the round. + * @param _coreDisputeID The ID of the dispute in Kleros Core, not in the Dispute Kit. + * @param _coreRoundID The ID of the round in Kleros Core, not in the Dispute Kit. * @return The number of coherent jurors. */ - function getCoherentCount(uint256 _disputeID, uint256 _round) external view returns (uint256); + function getCoherentCount(uint256 _coreDisputeID, uint256 _coreRoundID) external view returns (uint256); + + /** @dev Returns true if all of the jurors have cast their commits for the last round. + * @param _coreDisputeID The ID of the dispute in Kleros Core, not in the Dispute Kit. + * @return Whether all of the jurors have cast their commits for the last round. + */ + function areCommitsAllCast(uint256 _coreDisputeID) external view returns (bool); + + /** @dev Returns true if all of the jurors have cast their votes for the last round. + * @param _coreDisputeID The ID of the dispute in Kleros Core, not in the Dispute Kit. + * @return Whether all of the jurors have cast their votes for the last round. + */ + function areVotesAllCast(uint256 _coreDisputeID) external view returns (bool); /** @dev Returns true if the specified voter was active in this round. - * @param _disputeID The ID of the dispute in Kleros Core. - * @param _round The ID of the round. + * @param _coreDisputeID The ID of the dispute in Kleros Core, not in the Dispute Kit. + * @param _coreRoundID The ID of the round in Kleros Core, not in the Dispute Kit. * @param _voteID The ID of the voter. * @return Whether the voter was active or not. */ function isVoteActive( - uint256 _disputeID, - uint256 _round, + uint256 _coreDisputeID, + uint256 _coreRoundID, uint256 _voteID ) external view returns (bool); function getRoundInfo( - uint256 _disputeID, - uint256 _round, + uint256 _coreDisputeID, + uint256 _coreRoundID, uint256 _choice ) external @@ -99,8 +118,8 @@ interface IDisputeKit { ); function getVoteInfo( - uint256 _disputeID, - uint256 _round, + uint256 _coreDisputeID, + uint256 _coreRoundID, uint256 _voteID ) external diff --git a/contracts/src/arbitration/KlerosCore.sol b/contracts/src/arbitration/KlerosCore.sol index a339ea9e6..f8b9fcf65 100644 --- a/contracts/src/arbitration/KlerosCore.sol +++ b/contracts/src/arbitration/KlerosCore.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT /** - * @authors: [@unknownunknown1] + * @authors: [@unknownunknown1, @jaybuidl] * @reviewers: [] * @auditors: [] * @bounties: [] @@ -43,23 +43,23 @@ contract KlerosCore is IArbitrator { uint256 feeForJuror; // Arbitration fee paid per juror. uint256 jurorsForCourtJump; // The appeal after the one that reaches this number of jurors will go to the parent court if any. uint256[4] timesPerPeriod; // The time allotted to each dispute period in the form `timesPerPeriod[period]`. - uint256 supportedDisputeKits; // The bitfield of dispute kits that the court supports. + mapping(uint256 => bool) supportedDisputeKits; // True if DK with this ID is supported by the court. } struct Dispute { uint96 subcourtID; // The ID of the subcourt the dispute is in. IArbitrable arbitrated; // The arbitrable contract. - IDisputeKit disputeKit; // ID of the dispute kit that this dispute was assigned to. Period period; // The current period of the dispute. bool ruled; // True if the ruling has been executed, false otherwise. uint256 lastPeriodChange; // The last time the period was changed. - uint256 nbVotes; // The total number of votes the dispute can possibly have in the current round. Former votes[_appeal].length. Round[] rounds; } struct Round { + uint256 disputeKitID; // Index of the dispute kit in the array. uint256 tokensAtStakePerJuror; // The amount of tokens at stake for each juror in this round. uint256 totalFeesForJurors; // The total juror fees paid in this round. + uint256 nbVotes; // The total number of votes the dispute can possibly have in the current round. Former votes[_round].length. uint256 repartitions; // A counter of reward repartitions made in this round. uint256 penalties; // The amount of tokens collected from penalties in this round. address[] drawnJurors; // Addresses of the jurors that were drawn in this round. @@ -71,10 +71,18 @@ contract KlerosCore is IArbitrator { mapping(uint96 => uint256) lockedTokens; // The number of tokens the juror has locked in the subcourt in the form `lockedTokens[subcourtID]`. } + struct DisputeKitNode { + uint256 parent; // Index of the parent dispute kit. If it's 0 then this DK is a root. + IDisputeKit disputeKit; // The dispute kit implementation. + } + // ************************************* // // * Storage * // // ************************************* // + uint256 public constant FORKING_COURT = 0; // Index of the default court. + uint256 public constant NULL_DISPUTE_KIT = 0; // Null pattern to indicate a top-level DK which has no parent. + uint256 public constant DISPUTE_KIT_CLASSIC_INDEX = 1; // Index of the default DK. 0 index is skipped. uint256 public constant MAX_STAKE_PATHS = 4; // The maximum number of stake paths a juror can have. uint256 public constant MIN_JURORS = 3; // The global default minimum number of jurors in a dispute. uint256 public constant ALPHA_DIVISOR = 1e4; // The number to divide `Court.alpha` by. @@ -84,12 +92,8 @@ contract KlerosCore is IArbitrator { IERC20 public pinakion; // The Pinakion token contract. // TODO: interactions with jurorProsecutionModule. address public jurorProsecutionModule; // The module for juror's prosecution. - Court[] public courts; // The subcourts. - - //TODO: disputeKits forest. - mapping(uint256 => IDisputeKit) public disputeKits; // All supported dispute kits. - + DisputeKitNode[] public disputeKitNodes; // The list of DisputeKitNode, indexed by DisputeKitID. Dispute[] public disputes; // The disputes. mapping(address => Juror) internal jurors; // The jurors. SortitionSumTreeFactory.SortitionSumTrees internal sortitionSumTrees; // The sortition sum trees. @@ -102,12 +106,24 @@ contract KlerosCore is IArbitrator { event NewPeriod(uint256 indexed _disputeID, Period _period); event AppealPossible(uint256 indexed _disputeID, IArbitrable indexed _arbitrable); event AppealDecision(uint256 indexed _disputeID, IArbitrable indexed _arbitrable); - event Draw(address indexed _address, uint256 indexed _disputeID, uint256 _appeal, uint256 _voteID); + event Draw(address indexed _address, uint256 indexed _disputeID, uint256 _roundID, uint256 _voteID); + event CourtJump( + uint256 indexed _disputeID, + uint256 indexed _roundID, + uint96 indexed _fromSubcourtID, + uint96 _toSubcourtID + ); + event DisputeKitJump( + uint256 indexed _disputeID, + uint256 indexed _roundID, + uint256 indexed _fromDisputeKitID, + uint256 _toDisputeKitID + ); event TokenAndETHShift( address indexed _account, uint256 indexed _disputeID, int256 _tokenAmount, - int256 _ETHAmount + int256 _ethAmount ); // ************************************* // @@ -148,22 +164,22 @@ contract KlerosCore is IArbitrator { governor = _governor; pinakion = _pinakion; jurorProsecutionModule = _jurorProsecutionModule; - disputeKits[0] = _disputeKit; + + disputeKitNodes.push(); // NULL_DISPUTE_KIT: an empty element at index 0 to indicate when a node has no parent. + disputeKitNodes.push(DisputeKitNode({parent: 0, disputeKit: _disputeKit})); // Create the Forking court. - courts.push( - Court({ - parent: 0, - children: new uint256[](0), - hiddenVotes: _hiddenVotes, - minStake: _minStake, - alpha: _alpha, - feeForJuror: _feeForJuror, - jurorsForCourtJump: _jurorsForCourtJump, - timesPerPeriod: _timesPerPeriod, - supportedDisputeKits: 1 // The first bit of the bit field is supported by default. - }) - ); + Court storage court = courts.push(); + court.parent = 0; + court.children = new uint256[](0); + court.hiddenVotes = _hiddenVotes; + court.minStake = _minStake; + court.alpha = _alpha; + court.feeForJuror = _feeForJuror; + court.jurorsForCourtJump = _jurorsForCourtJump; + court.timesPerPeriod = _timesPerPeriod; + court.supportedDisputeKits[DISPUTE_KIT_CLASSIC_INDEX] = true; + sortitionSumTrees.createTree(bytes32(0), _sortitionSumTreeK); } @@ -208,12 +224,34 @@ contract KlerosCore is IArbitrator { /** @dev Add a new supported dispute kit module to the court. * @param _disputeKitAddress The address of the dispute kit contract. - * @param _disputeKitID The ID assigned to the added dispute kit. + * @param _parent The ID of the parent dispute kit. It is left empty when root DK is created. + * Note that the root DK must be supported by the forking court. + */ + function addNewDisputeKit(IDisputeKit _disputeKitAddress, uint256 _parent) external onlyByGovernor { + require(_parent < disputeKitNodes.length, "Parent doesn't exist"); + + // Create new tree, which root should be supported by Forking court. + if (_parent == NULL_DISPUTE_KIT) { + courts[FORKING_COURT].supportedDisputeKits[disputeKitNodes.length] = true; + } + disputeKitNodes.push(DisputeKitNode({parent: _parent, disputeKit: _disputeKitAddress})); + } + + /** @dev Changes the parent of an existing dispute kit. + * @param _disputeKitID The ID of dispute kit. + * @param _newParent The ID of the new parent dispute kit. It is left empty when root DK is created. + * Note that the root DK must be supported by the forking court. */ - function addNewDisputeKit(IDisputeKit _disputeKitAddress, uint8 _disputeKitID) external onlyByGovernor { - // TODO: the dispute kit data structure. For now keep it a simple mapping. - // Also note that in current state this function doesn't take into account that the added address is actually new. - disputeKits[_disputeKitID] = _disputeKitAddress; + function changeDisputeKitParent(uint256 _disputeKitID, uint256 _newParent) external onlyByGovernor { + require(_disputeKitID < disputeKitNodes.length, "DisputeKitID doesn't exist"); + require(_newParent < disputeKitNodes.length, "NewParent doesn't exist"); + require(_newParent != _disputeKitID, "Invalid Parent"); + + // Create new tree, which root should be supported by Forking court. + if (_newParent == NULL_DISPUTE_KIT) { + courts[FORKING_COURT].supportedDisputeKits[_disputeKitID] = true; + } + disputeKitNodes[_disputeKitID].parent = _newParent; } /** @dev Creates a subcourt under a specified parent court. @@ -225,7 +263,7 @@ contract KlerosCore is IArbitrator { * @param _jurorsForCourtJump The `jurorsForCourtJump` property value of the subcourt. * @param _timesPerPeriod The `timesPerPeriod` property value of the subcourt. * @param _sortitionSumTreeK The number of children per node of the subcourt's sortition sum tree. - * @param _supportedDisputeKits Bitfield that contains the IDs of the dispute kits that this court will support. + * @param _supportedDisputeKits Indexes of dispute kits that this subcourt will support. */ function createSubcourt( uint96 _parent, @@ -236,28 +274,30 @@ contract KlerosCore is IArbitrator { uint256 _jurorsForCourtJump, uint256[4] memory _timesPerPeriod, uint256 _sortitionSumTreeK, - uint256 _supportedDisputeKits + uint256[] memory _supportedDisputeKits ) external onlyByGovernor { require( courts[_parent].minStake <= _minStake, "A subcourt cannot be a child of a subcourt with a higher minimum stake." ); + require(_supportedDisputeKits.length > 0, "Must support at least one DK"); uint256 subcourtID = courts.length; - // Create the subcourt. - courts.push( - Court({ - parent: _parent, - children: new uint256[](0), - hiddenVotes: _hiddenVotes, - minStake: _minStake, - alpha: _alpha, - feeForJuror: _feeForJuror, - jurorsForCourtJump: _jurorsForCourtJump, - timesPerPeriod: _timesPerPeriod, - supportedDisputeKits: _supportedDisputeKits - }) - ); + Court storage court = courts.push(); + + for (uint256 i = 0; i < _supportedDisputeKits.length; i++) { + require(_supportedDisputeKits[i] < disputeKitNodes.length, "DK doesn't exist"); + court.supportedDisputeKits[_supportedDisputeKits[i]] = true; + } + + court.parent = _parent; + court.children = new uint256[](0); + court.hiddenVotes = _hiddenVotes; + court.minStake = _minStake; + court.alpha = _alpha; + court.feeForJuror = _feeForJuror; + court.jurorsForCourtJump = _jurorsForCourtJump; + court.timesPerPeriod = _timesPerPeriod; sortitionSumTrees.createTree(bytes32(subcourtID), _sortitionSumTreeK); // Update the parent. @@ -269,7 +309,7 @@ contract KlerosCore is IArbitrator { * @param _minStake The new value for the `minStake` property value. */ function changeSubcourtMinStake(uint96 _subcourtID, uint256 _minStake) external onlyByGovernor { - require(_subcourtID == 0 || courts[courts[_subcourtID].parent].minStake <= _minStake); + require(_subcourtID == FORKING_COURT || courts[courts[_subcourtID].parent].minStake <= _minStake); for (uint256 i = 0; i < courts[_subcourtID].children.length; i++) { require( courts[courts[_subcourtID].children[i]].minStake >= _minStake, @@ -315,25 +355,27 @@ contract KlerosCore is IArbitrator { courts[_subcourtID].timesPerPeriod = _timesPerPeriod; } - /** @dev Adds/removes particular dispute kits to a subcourt's bitfield of supported dispute kits. + /** @dev Adds/removes court's support for specified dispute kits.. * @param _subcourtID The ID of the subcourt. - * @param _disputeKitIDs IDs of dispute kits which support should be added/removed. + * @param _disputeKitIDs The IDs of dispute kits which support should be added/removed. * @param _enable Whether add or remove the dispute kits from the subcourt. */ - function setDisputeKits( + function enableDisputeKits( uint96 _subcourtID, - uint8[] memory _disputeKitIDs, + uint256[] memory _disputeKitIDs, bool _enable ) external onlyByGovernor { Court storage subcourt = courts[_subcourtID]; for (uint256 i = 0; i < _disputeKitIDs.length; i++) { - uint256 bitToChange = 1 << _disputeKitIDs[i]; // Get the bit that corresponds with dispute kit's ID. - if (_enable) - require((bitToChange & ~subcourt.supportedDisputeKits) == bitToChange, "Dispute kit already supported"); - else require((bitToChange & subcourt.supportedDisputeKits) == bitToChange, "Dispute kit is not supported"); - - // Change the bit corresponding with the dispute kit's ID to an opposite value. - subcourt.supportedDisputeKits ^= bitToChange; + if (_enable) { + subcourt.supportedDisputeKits[_disputeKitIDs[i]] = true; + } else { + require( + !(_subcourtID == FORKING_COURT && disputeKitNodes[_disputeKitIDs[i]].parent == NULL_DISPUTE_KIT), + "Can't remove root DK support from the forking court" + ); + subcourt.supportedDisputeKits[_disputeKitIDs[i]] = false; + } } } @@ -362,11 +404,10 @@ contract KlerosCore is IArbitrator { returns (uint256 disputeID) { require(msg.value >= arbitrationCost(_extraData), "Not enough ETH to cover arbitration cost."); - (uint96 subcourtID, , uint8 disputeKitID) = extraDataToSubcourtIDMinJurorsDisputeKit(_extraData); + (uint96 subcourtID, , uint256 disputeKitID) = extraDataToSubcourtIDMinJurorsDisputeKit(_extraData); - uint256 bitToCheck = 1 << disputeKitID; // Get the bit that corresponds with dispute kit's ID. require( - (bitToCheck & courts[subcourtID].supportedDisputeKits) == bitToCheck, + courts[subcourtID].supportedDisputeKits[disputeKitID], "The dispute kit is not supported by this subcourt" ); @@ -374,17 +415,14 @@ contract KlerosCore is IArbitrator { Dispute storage dispute = disputes.push(); dispute.subcourtID = subcourtID; dispute.arbitrated = IArbitrable(msg.sender); - - IDisputeKit disputeKit = disputeKits[disputeKitID]; - dispute.disputeKit = disputeKit; - dispute.lastPeriodChange = block.timestamp; - dispute.nbVotes = msg.value / courts[dispute.subcourtID].feeForJuror; + IDisputeKit disputeKit = disputeKitNodes[disputeKitID].disputeKit; + Court storage court = courts[dispute.subcourtID]; Round storage round = dispute.rounds.push(); - round.tokensAtStakePerJuror = - (courts[dispute.subcourtID].minStake * courts[dispute.subcourtID].alpha) / - ALPHA_DIVISOR; + round.nbVotes = msg.value / court.feeForJuror; + round.disputeKitID = disputeKitID; + round.tokensAtStakePerJuror = (court.minStake * court.alpha) / ALPHA_DIVISOR; round.totalFeesForJurors = msg.value; disputeKit.createDispute(disputeID, _numberOfChoices, _extraData); @@ -396,41 +434,36 @@ contract KlerosCore is IArbitrator { */ function passPeriod(uint256 _disputeID) external { Dispute storage dispute = disputes[_disputeID]; + Court storage court = courts[dispute.subcourtID]; uint256 currentRound = dispute.rounds.length - 1; Round storage round = dispute.rounds[currentRound]; if (dispute.period == Period.evidence) { require( currentRound > 0 || - block.timestamp - dispute.lastPeriodChange >= - courts[dispute.subcourtID].timesPerPeriod[uint256(dispute.period)], + block.timestamp - dispute.lastPeriodChange >= court.timesPerPeriod[uint256(dispute.period)], "The evidence period time has not passed yet and it is not an appeal." ); - require(round.drawnJurors.length == dispute.nbVotes, "The dispute has not finished drawing yet."); - dispute.period = courts[dispute.subcourtID].hiddenVotes ? Period.commit : Period.vote; + require(round.drawnJurors.length == round.nbVotes, "The dispute has not finished drawing yet."); + dispute.period = court.hiddenVotes ? Period.commit : Period.vote; } else if (dispute.period == Period.commit) { - // In case the jurors finished casting commits beforehand the dispute kit should call passPeriod() by itself. require( - block.timestamp - dispute.lastPeriodChange >= - courts[dispute.subcourtID].timesPerPeriod[uint256(dispute.period)] || - msg.sender == address(dispute.disputeKit), + block.timestamp - dispute.lastPeriodChange >= court.timesPerPeriod[uint256(dispute.period)] || + disputeKitNodes[round.disputeKitID].disputeKit.areCommitsAllCast(_disputeID), "The commit period time has not passed yet." ); dispute.period = Period.vote; } else if (dispute.period == Period.vote) { - // In case the jurors finished casting votes beforehand the dispute kit should call passPeriod() by itself. require( - block.timestamp - dispute.lastPeriodChange >= - courts[dispute.subcourtID].timesPerPeriod[uint256(dispute.period)] || - msg.sender == address(dispute.disputeKit), + block.timestamp - dispute.lastPeriodChange >= court.timesPerPeriod[uint256(dispute.period)] || + disputeKitNodes[round.disputeKitID].disputeKit.areVotesAllCast(_disputeID), "The vote period time has not passed yet" ); dispute.period = Period.appeal; emit AppealPossible(_disputeID, dispute.arbitrated); } else if (dispute.period == Period.appeal) { require( - block.timestamp - dispute.lastPeriodChange >= - courts[dispute.subcourtID].timesPerPeriod[uint256(dispute.period)], + block.timestamp - dispute.lastPeriodChange >= court.timesPerPeriod[uint256(dispute.period)], "The appeal period time has not passed yet." ); dispute.period = Period.execution; @@ -452,9 +485,9 @@ contract KlerosCore is IArbitrator { Round storage round = dispute.rounds[currentRound]; require(dispute.period == Period.evidence, "Should be evidence period."); - IDisputeKit disputeKit = dispute.disputeKit; + IDisputeKit disputeKit = disputeKitNodes[round.disputeKitID].disputeKit; uint256 startIndex = round.drawnJurors.length; - uint256 endIndex = startIndex + _iterations <= dispute.nbVotes ? startIndex + _iterations : dispute.nbVotes; + uint256 endIndex = startIndex + _iterations <= round.nbVotes ? startIndex + _iterations : round.nbVotes; for (uint256 i = startIndex; i < endIndex; i++) { address drawnAddress = disputeKit.draw(_disputeID); @@ -475,28 +508,66 @@ contract KlerosCore is IArbitrator { /** @dev Appeals the ruling of a specified dispute. * Note: Access restricted to the Dispute Kit for this `disputeID`. * @param _disputeID The ID of the dispute. + * @param _numberOfChoices Number of choices for the dispute. Can be required during court jump. + * @param _extraData Extradata for the dispute. Can be required during court jump. */ - function appeal(uint256 _disputeID) external payable { + function appeal( + uint256 _disputeID, + uint256 _numberOfChoices, + bytes memory _extraData + ) external payable { require(msg.value >= appealCost(_disputeID), "Not enough ETH to cover appeal cost."); Dispute storage dispute = disputes[_disputeID]; require(dispute.period == Period.appeal, "Dispute is not appealable."); - require(msg.sender == address(dispute.disputeKit), "Access not allowed: Dispute Kit only."); - if (dispute.nbVotes >= courts[dispute.subcourtID].jurorsForCourtJump) - // Jump to parent subcourt. - // TODO: Handle court jump in the Forking court. Also make sure the new subcourt is compatible with the dispute kit. - dispute.subcourtID = courts[dispute.subcourtID].parent; + Round storage round = dispute.rounds[dispute.rounds.length - 1]; + require( + msg.sender == address(disputeKitNodes[round.disputeKitID].disputeKit), + "Access not allowed: Dispute Kit only." + ); - dispute.period = Period.evidence; - dispute.lastPeriodChange = block.timestamp; - // As many votes that can be afforded by the provided funds. - dispute.nbVotes = msg.value / courts[dispute.subcourtID].feeForJuror; + Court storage court = courts[dispute.subcourtID]; + uint256 newDisputeKitID = round.disputeKitID; + // Create a new round beforehand because dispute kit relies on the latest index. Round storage extraRound = dispute.rounds.push(); - extraRound.tokensAtStakePerJuror = - (courts[dispute.subcourtID].minStake * courts[dispute.subcourtID].alpha) / - ALPHA_DIVISOR; + + if (round.nbVotes >= court.jurorsForCourtJump) { + { + // Jump to parent subcourt. + emit CourtJump(_disputeID, dispute.rounds.length - 1, dispute.subcourtID, court.parent); + dispute.subcourtID = court.parent; + court = courts[court.parent]; + } + // TODO: Handle court jump in the Forking court. + while (!court.supportedDisputeKits[newDisputeKitID]) { + if (disputeKitNodes[newDisputeKitID].parent != 0) { + newDisputeKitID = disputeKitNodes[newDisputeKitID].parent; + } else { + // If the root still isn't supported by the parent court fallback on the Forking court instead. + // Note that root DK must be supported by Forking court by default but add this require as an extra check. + require( + courts[FORKING_COURT].supportedDisputeKits[newDisputeKitID] = true, + "DK isn't supported by Forking court" + ); + dispute.subcourtID = uint96(FORKING_COURT); + break; + } + } + + // Dispute kit was changed, so create a dispute in the new DK contract. + if (newDisputeKitID != round.disputeKitID) { + emit DisputeKitJump(_disputeID, dispute.rounds.length - 1, round.disputeKitID, newDisputeKitID); + disputeKitNodes[newDisputeKitID].disputeKit.createDispute(_disputeID, _numberOfChoices, _extraData); + } + } + + dispute.period = Period.evidence; + dispute.lastPeriodChange = block.timestamp; + extraRound.nbVotes = msg.value / court.feeForJuror; // As many votes that can be afforded by the provided funds. + extraRound.disputeKitID = newDisputeKitID; + extraRound.tokensAtStakePerJuror = (court.minStake * court.alpha) / ALPHA_DIVISOR; extraRound.totalFeesForJurors = msg.value; emit AppealDecision(_disputeID, dispute.arbitrated); @@ -505,22 +576,24 @@ contract KlerosCore is IArbitrator { /** @dev Distribute tokens and ETH for the specific round of the dispute. Can be called in parts. * @param _disputeID The ID of the dispute. - * @param _appeal The appeal round. + * @param _round The appeal round. * @param _iterations The number of iterations to run. */ function execute( uint256 _disputeID, - uint256 _appeal, + uint256 _round, uint256 _iterations ) external { Dispute storage dispute = disputes[_disputeID]; require(dispute.period == Period.execution, "Should be execution period."); - uint256 end = dispute.rounds[_appeal].repartitions + _iterations; - uint256 penaltiesInRoundCache = dispute.rounds[_appeal].penalties; // For saving gas. + Round storage round = dispute.rounds[_round]; + IDisputeKit disputeKit = disputeKitNodes[round.disputeKitID].disputeKit; - uint256 numberOfVotesInRound = dispute.rounds[_appeal].drawnJurors.length; - uint256 coherentCount = dispute.disputeKit.getCoherentCount(_disputeID, _appeal); // Total number of jurors that are eligible to a reward in this round. + uint256 end = round.repartitions + _iterations; + uint256 penaltiesInRoundCache = round.penalties; // For saving gas. + uint256 numberOfVotesInRound = round.drawnJurors.length; + uint256 coherentCount = disputeKit.getCoherentCount(_disputeID, _round); // Total number of jurors that are eligible to a reward in this round. address account; // Address of the juror. uint256 degreeOfCoherence; // [0, 1] value that determines how coherent the juror was in this round, in basis points. @@ -533,80 +606,80 @@ contract KlerosCore is IArbitrator { if (end > numberOfVotesInRound * 2) end = numberOfVotesInRound * 2; } - for (uint256 i = dispute.rounds[_appeal].repartitions; i < end; i++) { - // Penalty. + for (uint256 i = round.repartitions; i < end; i++) { if (i < numberOfVotesInRound) { - degreeOfCoherence = dispute.disputeKit.getDegreeOfCoherence(_disputeID, _appeal, i); - if (degreeOfCoherence > ALPHA_DIVISOR) degreeOfCoherence = ALPHA_DIVISOR; // Make sure the degree doesn't exceed 1, though it should be ensured by the dispute kit. + // Penalty. + degreeOfCoherence = disputeKit.getDegreeOfCoherence(_disputeID, _round, i); - uint256 penalty = (dispute.rounds[_appeal].tokensAtStakePerJuror * - (ALPHA_DIVISOR - degreeOfCoherence)) / ALPHA_DIVISOR; // Fully coherent jurors won't be penalized. + // Make sure the degree doesn't exceed 1, though it should be ensured by the dispute kit. + if (degreeOfCoherence > ALPHA_DIVISOR) { + degreeOfCoherence = ALPHA_DIVISOR; + } + + // Fully coherent jurors won't be penalized. + uint256 penalty = (round.tokensAtStakePerJuror * (ALPHA_DIVISOR - degreeOfCoherence)) / ALPHA_DIVISOR; penaltiesInRoundCache += penalty; - account = dispute.rounds[_appeal].drawnJurors[i]; + account = round.drawnJurors[i]; jurors[account].lockedTokens[dispute.subcourtID] -= penalty; // Release this part of locked tokens. // Can only update the stake if it is able to cover the minStake and penalty, otherwise unstake from the court. if (jurors[account].stakedTokens[dispute.subcourtID] >= courts[dispute.subcourtID].minStake + penalty) { - setStakeForAccount( - account, - dispute.subcourtID, - jurors[account].stakedTokens[dispute.subcourtID] - penalty, - penalty - ); + uint256 newStake = jurors[account].stakedTokens[dispute.subcourtID] - penalty; + setStakeForAccount(account, dispute.subcourtID, newStake, penalty); } else if (jurors[account].stakedTokens[dispute.subcourtID] != 0) { setStakeForAccount(account, dispute.subcourtID, 0, penalty); } // Unstake the juror if he lost due to inactivity. - if (!dispute.disputeKit.isVoteActive(_disputeID, _appeal, i)) { - for (uint256 j = 0; j < jurors[account].subcourtIDs.length; j++) + if (!disputeKit.isVoteActive(_disputeID, _round, i)) { + for (uint256 j = 0; j < jurors[account].subcourtIDs.length; j++) { setStakeForAccount(account, jurors[account].subcourtIDs[j], 0, 0); + } } emit TokenAndETHShift(account, _disputeID, -int256(penalty), 0); if (i == numberOfVotesInRound - 1) { if (coherentCount == 0) { // No one was coherent. Send the rewards to governor. - payable(governor).send(dispute.rounds[_appeal].totalFeesForJurors); + payable(governor).send(round.totalFeesForJurors); pinakion.transfer(governor, penaltiesInRoundCache); } } - // Reward. } else { - degreeOfCoherence = dispute.disputeKit.getDegreeOfCoherence( - _disputeID, - _appeal, - i % numberOfVotesInRound - ); - if (degreeOfCoherence > ALPHA_DIVISOR) degreeOfCoherence = ALPHA_DIVISOR; - account = dispute.rounds[_appeal].drawnJurors[i % numberOfVotesInRound]; + // Reward. + degreeOfCoherence = disputeKit.getDegreeOfCoherence(_disputeID, _round, i % numberOfVotesInRound); + + // Make sure the degree doesn't exceed 1, though it should be ensured by the dispute kit. + if (degreeOfCoherence > ALPHA_DIVISOR) { + degreeOfCoherence = ALPHA_DIVISOR; + } + + account = round.drawnJurors[i % numberOfVotesInRound]; + // Release the rest of the tokens of the juror for this round. jurors[account].lockedTokens[dispute.subcourtID] -= - (dispute.rounds[_appeal].tokensAtStakePerJuror * degreeOfCoherence) / + (round.tokensAtStakePerJuror * degreeOfCoherence) / ALPHA_DIVISOR; + // Give back the locked tokens in case the juror fully unstaked earlier. if (jurors[account].stakedTokens[dispute.subcourtID] == 0) { - // Give back the locked tokens in case the juror fully unstaked earlier. - pinakion.transfer( - account, - (dispute.rounds[_appeal].tokensAtStakePerJuror * degreeOfCoherence) / ALPHA_DIVISOR - ); + uint256 tokenLocked = (round.tokensAtStakePerJuror * degreeOfCoherence) / ALPHA_DIVISOR; + pinakion.transfer(account, tokenLocked); } uint256 tokenReward = ((penaltiesInRoundCache / coherentCount) * degreeOfCoherence) / ALPHA_DIVISOR; - uint256 ETHReward = ((dispute.rounds[_appeal].totalFeesForJurors / coherentCount) * degreeOfCoherence) / - ALPHA_DIVISOR; - + uint256 ethReward = ((round.totalFeesForJurors / coherentCount) * degreeOfCoherence) / ALPHA_DIVISOR; pinakion.transfer(account, tokenReward); - payable(account).send(ETHReward); - emit TokenAndETHShift(account, _disputeID, int256(tokenReward), int256(ETHReward)); + payable(account).send(ethReward); + emit TokenAndETHShift(account, _disputeID, int256(tokenReward), int256(ethReward)); } } - if (dispute.rounds[_appeal].penalties != penaltiesInRoundCache) - dispute.rounds[_appeal].penalties = penaltiesInRoundCache; - dispute.rounds[_appeal].repartitions = end; + if (round.penalties != penaltiesInRoundCache) { + round.penalties = penaltiesInRoundCache; + } + round.repartitions = end; } /** @dev Executes a specified dispute's ruling. UNTRUSTED. @@ -642,15 +715,20 @@ contract KlerosCore is IArbitrator { */ function appealCost(uint256 _disputeID) public view returns (uint256 cost) { Dispute storage dispute = disputes[_disputeID]; - if (dispute.nbVotes >= courts[dispute.subcourtID].jurorsForCourtJump) { + Round storage round = dispute.rounds[dispute.rounds.length - 1]; + Court storage court = courts[dispute.subcourtID]; + if (round.nbVotes >= court.jurorsForCourtJump) { // Jump to parent subcourt. - if (dispute.subcourtID == 0) + if (dispute.subcourtID == FORKING_COURT) { // Already in the forking court. cost = NON_PAYABLE_AMOUNT; // Get the cost of the parent subcourt. - else cost = courts[courts[dispute.subcourtID].parent].feeForJuror * ((dispute.nbVotes * 2) + 1); + } else { + cost = courts[court.parent].feeForJuror * ((round.nbVotes * 2) + 1); + } + } else { + // Stay in current subcourt. + cost = court.feeForJuror * ((round.nbVotes * 2) + 1); } - // Stay in current subcourt. - else cost = courts[dispute.subcourtID].feeForJuror * ((dispute.nbVotes * 2) + 1); } /** @dev Gets the start and the end of a specified dispute's current appeal period. @@ -674,7 +752,9 @@ contract KlerosCore is IArbitrator { * @return ruling The current ruling. */ function currentRuling(uint256 _disputeID) public view returns (uint256 ruling) { - IDisputeKit disputeKit = disputes[_disputeID].disputeKit; + Dispute storage dispute = disputes[_disputeID]; + Round storage round = dispute.rounds[dispute.rounds.length - 1]; + IDisputeKit disputeKit = disputeKitNodes[round.disputeKitID].disputeKit; return disputeKit.currentRuling(_disputeID); } @@ -686,7 +766,8 @@ contract KlerosCore is IArbitrator { uint256 totalFeesForJurors, uint256 repartitions, uint256 penalties, - address[] memory drawnJurors + address[] memory drawnJurors, + uint256 disputeKitID ) { Dispute storage dispute = disputes[_disputeID]; @@ -696,7 +777,8 @@ contract KlerosCore is IArbitrator { round.totalFeesForJurors, round.repartitions, round.penalties, - round.drawnJurors + round.drawnJurors, + round.disputeKitID ); } @@ -715,6 +797,10 @@ contract KlerosCore is IArbitrator { locked = juror.lockedTokens[_subcourtID]; } + function isSupported(uint96 _subcourtID, uint256 _disputeKitID) external view returns (bool) { + return courts[_subcourtID].supportedDisputeKits[_disputeKitID]; + } + /** @dev Gets the timesPerPeriod array for a given court. * @param _subcourtID The ID of the court to get the times from. * @return timesPerPeriod The timesPerPeriod array for the given court. @@ -763,6 +849,29 @@ contract KlerosCore is IArbitrator { return disputes[_disputeID].ruled; } + /** @dev Returns true if the dispute kit will be switched to a parent DK. + * @param _disputeID The ID of the dispute. + * @return Whether DK will be switched or not. + */ + function isDisputeKitJumping(uint256 _disputeID) public view returns (bool) { + Dispute storage dispute = disputes[_disputeID]; + Round storage round = dispute.rounds[dispute.rounds.length - 1]; + Court storage court = courts[dispute.subcourtID]; + + if (round.nbVotes < court.jurorsForCourtJump) { + return false; + } + + // Jump if the parent court doesn't support the current DK. + return !courts[court.parent].supportedDisputeKits[round.disputeKitID]; + } + + function getLastRoundResult(uint256 _disputeID) external view returns (uint256 winningChoice, bool tied) { + Dispute storage dispute = disputes[_disputeID]; + Round storage round = dispute.rounds[dispute.rounds.length - 1]; + (winningChoice, tied) = disputeKitNodes[round.disputeKitID].disputeKit.getLastRoundResult(_disputeID); + } + // ************************************* // // * Internal * // // ************************************* // @@ -815,7 +924,7 @@ contract KlerosCore is IArbitrator { uint256 currentSubcourtID = _subcourtID; while (!finished) { sortitionSumTrees.set(bytes32(currentSubcourtID), _stake, stakePathID); - if (currentSubcourtID == 0) finished = true; + if (currentSubcourtID == FORKING_COURT) finished = true; else currentSubcourtID = courts[currentSubcourtID].parent; } @@ -856,7 +965,7 @@ contract KlerosCore is IArbitrator { returns ( uint96 subcourtID, uint256 minJurors, - uint8 disputeKitID + uint256 disputeKitID ) { // Note that if the extradata doesn't contain 32 bytes for the dispute kit ID it'll return the default 0 index. @@ -867,13 +976,19 @@ contract KlerosCore is IArbitrator { minJurors := mload(add(_extraData, 0x40)) disputeKitID := mload(add(_extraData, 0x60)) } - if (subcourtID >= courts.length) subcourtID = 0; - if (minJurors == 0) minJurors = MIN_JURORS; - if (disputeKits[disputeKitID] == IDisputeKit(address(0))) disputeKitID = 0; + if (subcourtID >= courts.length) { + subcourtID = uint96(FORKING_COURT); + } + if (minJurors == 0) { + minJurors = MIN_JURORS; + } + if (disputeKitID == NULL_DISPUTE_KIT || disputeKitID >= disputeKitNodes.length) { + disputeKitID = DISPUTE_KIT_CLASSIC_INDEX; // 0 index is not used. + } } else { - subcourtID = 0; + subcourtID = uint96(FORKING_COURT); minJurors = MIN_JURORS; - disputeKitID = 0; + disputeKitID = DISPUTE_KIT_CLASSIC_INDEX; } } diff --git a/contracts/src/arbitration/dispute-kits/DisputeKitClassic.sol b/contracts/src/arbitration/dispute-kits/DisputeKitClassic.sol index 43e666c04..a345c6ee5 100644 --- a/contracts/src/arbitration/dispute-kits/DisputeKitClassic.sol +++ b/contracts/src/arbitration/dispute-kits/DisputeKitClassic.sol @@ -32,6 +32,9 @@ contract DisputeKitClassic is BaseDisputeKit, IEvidence { struct Dispute { Round[] rounds; // Rounds of the dispute. 0 is the default round, and [1, ..n] are the appeal rounds. uint256 numberOfChoices; // The number of choices jurors have when voting. This does not include choice `0` which is reserved for "refuse to arbitrate". + bool jumped; // True if dispute jumped to a parent dispute kit and won't be handled by this DK anymore. + mapping(uint256 => uint256) coreRoundIDToLocal; // Maps id of the round in the core contract to the index of the round of related local dispute. + bytes extraData; // Extradata for the dispute. } struct Round { @@ -90,6 +93,15 @@ contract DisputeKitClassic is BaseDisputeKit, IEvidence { event ChoiceFunded(uint256 indexed _disputeID, uint256 indexed _round, uint256 indexed _choice); + // ************************************* // + // * Modifiers * // + // ************************************* // + + modifier notJumped(uint256 _coreDisputeID) { + require(!disputes[coreDisputeIDToLocal[_coreDisputeID]].jumped, "Dispute jumped to a parent DK!"); + _; + } + /** @dev Constructor. * @param _governor The governor's address. * @param _core The KlerosCore arbitrator. @@ -135,39 +147,48 @@ contract DisputeKitClassic is BaseDisputeKit, IEvidence { /** @dev Creates a local dispute and maps it to the dispute ID in the Core contract. * Note: Access restricted to Kleros Core only. - * @param _disputeID The ID of the dispute in Kleros Core. + * @param _coreDisputeID The ID of the dispute in Kleros Core. * @param _numberOfChoices Number of choices of the dispute * @param _extraData Additional info about the dispute, for possible use in future dispute kits. */ function createDispute( - uint256 _disputeID, + uint256 _coreDisputeID, uint256 _numberOfChoices, bytes calldata _extraData ) external override onlyByCore { uint256 localDisputeID = disputes.length; Dispute storage dispute = disputes.push(); dispute.numberOfChoices = _numberOfChoices; + dispute.extraData = _extraData; + // New round in the Core should be created before the dispute creation in DK. + dispute.coreRoundIDToLocal[core.getNumberOfRounds(_coreDisputeID) - 1] = dispute.rounds.length; Round storage round = dispute.rounds.push(); round.tied = true; - coreDisputeIDToLocal[_disputeID] = localDisputeID; + coreDisputeIDToLocal[_coreDisputeID] = localDisputeID; } /** @dev Draws the juror from the sortition tree. The drawn address is picked up by Kleros Core. * Note: Access restricted to Kleros Core only. - * @param _disputeID The ID of the dispute in Kleros Core. + * @param _coreDisputeID The ID of the dispute in Kleros Core. * @return drawnAddress The drawn address. */ - function draw(uint256 _disputeID) external override onlyByCore returns (address drawnAddress) { - bytes32 key = bytes32(core.getSubcourtID(_disputeID)); // Get the ID of the tree. + function draw(uint256 _coreDisputeID) + external + override + onlyByCore + notJumped(_coreDisputeID) + returns (address drawnAddress) + { + bytes32 key = bytes32(core.getSubcourtID(_coreDisputeID)); // Get the ID of the tree. uint256 drawnNumber = getRandomNumber(); (uint256 K, , uint256[] memory nodes) = core.getSortitionSumTree(key); uint256 treeIndex = 0; uint256 currentDrawnNumber = drawnNumber % nodes[0]; - Dispute storage dispute = disputes[coreDisputeIDToLocal[_disputeID]]; + Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; Round storage round = dispute.rounds[dispute.rounds.length - 1]; // TODO: Handle the situation when no one has staked yet. @@ -199,22 +220,22 @@ contract DisputeKitClassic is BaseDisputeKit, IEvidence { /** @dev Sets the caller's commit for the specified votes. * `O(n)` where * `n` is the number of votes. - * @param _disputeID The ID of the dispute. + * @param _coreDisputeID The ID of the dispute in Kleros Core. * @param _voteIDs The IDs of the votes. * @param _commit The commit. */ function castCommit( - uint256 _disputeID, + uint256 _coreDisputeID, uint256[] calldata _voteIDs, bytes32 _commit - ) external { + ) external notJumped(_coreDisputeID) { require( - core.getCurrentPeriod(_disputeID) == KlerosCore.Period.commit, + core.getCurrentPeriod(_coreDisputeID) == KlerosCore.Period.commit, "The dispute should be in Commit period." ); require(_commit != bytes32(0), "Empty commit."); - Dispute storage dispute = disputes[coreDisputeIDToLocal[_disputeID]]; + Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; Round storage round = dispute.rounds[dispute.rounds.length - 1]; for (uint256 i = 0; i < _voteIDs.length; i++) { require(round.votes[_voteIDs[i]].account == msg.sender, "The caller has to own the vote."); @@ -222,32 +243,33 @@ contract DisputeKitClassic is BaseDisputeKit, IEvidence { round.votes[_voteIDs[i]].commit = _commit; } round.totalCommitted += _voteIDs.length; - - if (round.totalCommitted == round.votes.length) core.passPeriod(_disputeID); } /** @dev Sets the caller's choices for the specified votes. * `O(n)` where * `n` is the number of votes. - * @param _disputeID The ID of the dispute. + * @param _coreDisputeID The ID of the dispute in Kleros Core. * @param _voteIDs The IDs of the votes. * @param _choice The choice. * @param _salt The salt for the commit if the votes were hidden. */ function castVote( - uint256 _disputeID, + uint256 _coreDisputeID, uint256[] calldata _voteIDs, uint256 _choice, uint256 _salt - ) external { - require(core.getCurrentPeriod(_disputeID) == KlerosCore.Period.vote, "The dispute should be in Vote period."); + ) external notJumped(_coreDisputeID) { + require( + core.getCurrentPeriod(_coreDisputeID) == KlerosCore.Period.vote, + "The dispute should be in Vote period." + ); require(_voteIDs.length > 0, "No voteID provided"); - Dispute storage dispute = disputes[coreDisputeIDToLocal[_disputeID]]; + Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; require(_choice <= dispute.numberOfChoices, "Choice out of bounds"); Round storage round = dispute.rounds[dispute.rounds.length - 1]; - bool hiddenVotes = core.areVotesHidden(core.getSubcourtID(_disputeID)); + bool hiddenVotes = core.areVotesHidden(core.getSubcourtID(_coreDisputeID)); // Save the votes. for (uint256 i = 0; i < _voteIDs.length; i++) { @@ -277,25 +299,22 @@ contract DisputeKitClassic is BaseDisputeKit, IEvidence { round.tied = false; } } - - // Automatically switch period when voting is finished. - if (round.totalVoted == round.votes.length) core.passPeriod(_disputeID); } /** @dev Manages contributions, and appeals a dispute if at least two choices are fully funded. * Note that the surplus deposit will be reimbursed. - * @param _disputeID Index of the dispute in Kleros Core contract. + * @param _coreDisputeID Index of the dispute in Kleros Core. * @param _choice A choice that receives funding. */ - function fundAppeal(uint256 _disputeID, uint256 _choice) external payable { - Dispute storage dispute = disputes[coreDisputeIDToLocal[_disputeID]]; + function fundAppeal(uint256 _coreDisputeID, uint256 _choice) external payable notJumped(_coreDisputeID) { + Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; require(_choice <= dispute.numberOfChoices, "There is no such ruling to fund."); - (uint256 appealPeriodStart, uint256 appealPeriodEnd) = core.appealPeriod(_disputeID); + (uint256 appealPeriodStart, uint256 appealPeriodEnd) = core.appealPeriod(_coreDisputeID); require(block.timestamp >= appealPeriodStart && block.timestamp < appealPeriodEnd, "Appeal period is over."); uint256 multiplier; - if (this.currentRuling(_disputeID) == _choice) { + if (this.currentRuling(_coreDisputeID) == _choice) { multiplier = WINNER_STAKE_MULTIPLIER; } else { require( @@ -308,7 +327,7 @@ contract DisputeKitClassic is BaseDisputeKit, IEvidence { Round storage round = dispute.rounds[dispute.rounds.length - 1]; require(!round.hasPaid[_choice], "Appeal fee is already paid."); - uint256 appealCost = core.appealCost(_disputeID); + uint256 appealCost = core.appealCost(_coreDisputeID); uint256 totalCost = appealCost + (appealCost * multiplier) / ONE_BASIS_POINT; // Take up to the amount necessary to fund the current round at the current costs. @@ -317,7 +336,7 @@ contract DisputeKitClassic is BaseDisputeKit, IEvidence { contribution = totalCost - round.paidFees[_choice] > msg.value // Overflows and underflows will be managed on the compiler level. ? msg.value : totalCost - round.paidFees[_choice]; - emit Contribution(_disputeID, dispute.rounds.length - 1, _choice, msg.sender, contribution); + emit Contribution(_coreDisputeID, dispute.rounds.length - 1, _choice, msg.sender, contribution); } round.contributions[msg.sender][_choice] += contribution; @@ -326,39 +345,47 @@ contract DisputeKitClassic is BaseDisputeKit, IEvidence { round.feeRewards += round.paidFees[_choice]; round.fundedChoices.push(_choice); round.hasPaid[_choice] = true; - emit ChoiceFunded(_disputeID, dispute.rounds.length - 1, _choice); + emit ChoiceFunded(_coreDisputeID, dispute.rounds.length - 1, _choice); } if (round.fundedChoices.length > 1) { // At least two sides are fully funded. round.feeRewards = round.feeRewards - appealCost; - Round storage newRound = dispute.rounds.push(); - newRound.tied = true; - core.appeal{value: appealCost}(_disputeID); + // Don't create a new round in case of a jump, and remove local dispute from the flow. + if (core.isDisputeKitJumping(_coreDisputeID)) { + dispute.jumped = true; + } else { + // Don't subtract 1 from length since both round arrays haven't been updated yet. + dispute.coreRoundIDToLocal[core.getNumberOfRounds(_coreDisputeID)] = dispute.rounds.length; + + Round storage newRound = dispute.rounds.push(); + newRound.tied = true; + } + core.appeal{value: appealCost}(_coreDisputeID, dispute.numberOfChoices, dispute.extraData); } if (msg.value > contribution) payable(msg.sender).send(msg.value - contribution); } /** @dev Allows those contributors who attempted to fund an appeal round to withdraw any reimbursable fees or rewards after the dispute gets resolved. - * @param _disputeID Index of the dispute in Kleros Core contract. + * @param _coreDisputeID Index of the dispute in Kleros Core contract. * @param _beneficiary The address whose rewards to withdraw. * @param _round The round the caller wants to withdraw from. * @param _choice The ruling option that the caller wants to withdraw from. * @return amount The withdrawn amount. */ function withdrawFeesAndRewards( - uint256 _disputeID, + uint256 _coreDisputeID, address payable _beneficiary, uint256 _round, uint256 _choice ) external returns (uint256 amount) { - require(core.isRuled(_disputeID), "Dispute should be resolved."); + require(core.isRuled(_coreDisputeID), "Dispute should be resolved."); - Dispute storage dispute = disputes[coreDisputeIDToLocal[_disputeID]]; + Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; Round storage round = dispute.rounds[_round]; - uint256 finalRuling = this.currentRuling(_disputeID); + uint256 finalRuling = core.currentRuling(_coreDisputeID); if (!round.hasPaid[_choice]) { // Allow to reimburse if funding was unsuccessful for this ruling option. @@ -381,7 +408,7 @@ contract DisputeKitClassic is BaseDisputeKit, IEvidence { if (amount != 0) { _beneficiary.send(amount); // Deliberate use of send to prevent reverting fallback. It's the user's responsibility to accept ETH. - emit Withdrawal(_disputeID, _round, _choice, _beneficiary, amount); + emit Withdrawal(_coreDisputeID, _round, _choice, _beneficiary, amount); } } @@ -398,32 +425,43 @@ contract DisputeKitClassic is BaseDisputeKit, IEvidence { // ************************************* // /** @dev Gets the current ruling of a specified dispute. - * @param _disputeID The ID of the dispute in Kleros Core. + * @param _coreDisputeID The ID of the dispute in Kleros Core. * @return ruling The current ruling. */ - function currentRuling(uint256 _disputeID) external view override returns (uint256 ruling) { - Dispute storage dispute = disputes[coreDisputeIDToLocal[_disputeID]]; + function currentRuling(uint256 _coreDisputeID) external view override returns (uint256 ruling) { + Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; Round storage round = dispute.rounds[dispute.rounds.length - 1]; ruling = round.tied ? 0 : round.winningChoice; } + function getLastRoundResult(uint256 _coreDisputeID) + external + view + override + returns (uint256 winningChoiece, bool tied) + { + Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; + Round storage lastRound = dispute.rounds[dispute.rounds.length - 1]; + return (lastRound.winningChoice, lastRound.tied); + } + /** @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. - * @param _disputeID The ID of the dispute in Kleros Core. - * @param _round The ID of the round. + * @param _coreDisputeID The ID of the dispute in Kleros Core, not in the Dispute Kit. + * @param _coreRoundID The ID of the round in Kleros Core, not in the Dispute Kit. * @param _voteID The ID of the vote. * @return The degree of coherence in basis points. */ function getDegreeOfCoherence( - uint256 _disputeID, - uint256 _round, + uint256 _coreDisputeID, + uint256 _coreRoundID, uint256 _voteID ) external view override returns (uint256) { // In this contract this degree can be either 0 or 1, but in other dispute kits this value can be something in between. - Dispute storage dispute = disputes[coreDisputeIDToLocal[_disputeID]]; - Round storage lastRound = dispute.rounds[dispute.rounds.length - 1]; - Vote storage vote = dispute.rounds[_round].votes[_voteID]; + Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; + Vote storage vote = dispute.rounds[dispute.coreRoundIDToLocal[_coreRoundID]].votes[_voteID]; + (uint256 winningChoice, bool tied) = core.getLastRoundResult(_coreDisputeID); - if (vote.voted && (vote.choice == lastRound.winningChoice || lastRound.tied)) { + if (vote.voted && (vote.choice == winningChoice || tied)) { return ONE_BASIS_POINT; } else { return 0; @@ -431,44 +469,63 @@ contract DisputeKitClassic is BaseDisputeKit, IEvidence { } /** @dev Gets the number of jurors who are eligible to a reward in this round. - * @param _disputeID The ID of the dispute in Kleros Core. - * @param _round The ID of the round. + * @param _coreDisputeID The ID of the dispute in Kleros Core, not in the Dispute Kit. + * @param _coreRoundID The ID of the round in Kleros Core, not in the Dispute Kit. * @return The number of coherent jurors. */ - function getCoherentCount(uint256 _disputeID, uint256 _round) external view override returns (uint256) { - Dispute storage dispute = disputes[coreDisputeIDToLocal[_disputeID]]; - Round storage lastRound = dispute.rounds[dispute.rounds.length - 1]; - Round storage currentRound = dispute.rounds[_round]; - uint256 winningChoice = lastRound.winningChoice; + function getCoherentCount(uint256 _coreDisputeID, uint256 _coreRoundID) external view override returns (uint256) { + Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; + Round storage currentRound = dispute.rounds[dispute.coreRoundIDToLocal[_coreRoundID]]; + (uint256 winningChoice, bool tied) = core.getLastRoundResult(_coreDisputeID); - if (currentRound.totalVoted == 0 || (!lastRound.tied && currentRound.counts[winningChoice] == 0)) { + if (currentRound.totalVoted == 0 || (!tied && currentRound.counts[winningChoice] == 0)) { return 0; - } else if (lastRound.tied) { + } else if (tied) { return currentRound.totalVoted; } else { return currentRound.counts[winningChoice]; } } + /** @dev Returns true if all of the jurors have cast their commits for the last round. + * @param _coreDisputeID The ID of the dispute in Kleros Core. + * @return Whether all of the jurors have cast their commits for the last round. + */ + function areCommitsAllCast(uint256 _coreDisputeID) external view override returns (bool) { + Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; + Round storage round = dispute.rounds[dispute.rounds.length - 1]; + return round.totalCommitted == round.votes.length; + } + + /** @dev Returns true if all of the jurors have cast their votes for the last round. + * @param _coreDisputeID The ID of the dispute in Kleros Core. + * @return Whether all of the jurors have cast their votes for the last round. + */ + function areVotesAllCast(uint256 _coreDisputeID) external view override returns (bool) { + Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; + Round storage round = dispute.rounds[dispute.rounds.length - 1]; + return round.totalVoted == round.votes.length; + } + /** @dev Returns true if the specified voter was active in this round. - * @param _disputeID The ID of the dispute in Kleros Core. - * @param _round The ID of the round. + * @param _coreDisputeID The ID of the dispute in Kleros Core, not in the Dispute Kit. + * @param _coreRoundID The ID of the round in Kleros Core, not in the Dispute Kit. * @param _voteID The ID of the voter. * @return Whether the voter was active or not. */ function isVoteActive( - uint256 _disputeID, - uint256 _round, + uint256 _coreDisputeID, + uint256 _coreRoundID, uint256 _voteID ) external view override returns (bool) { - Dispute storage dispute = disputes[coreDisputeIDToLocal[_disputeID]]; - Vote storage vote = dispute.rounds[_round].votes[_voteID]; + Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; + Vote storage vote = dispute.rounds[dispute.coreRoundIDToLocal[_coreRoundID]].votes[_voteID]; return vote.voted; } function getRoundInfo( - uint256 _disputeID, - uint256 _round, + uint256 _coreDisputeID, + uint256 _coreRoundID, uint256 _choice ) external @@ -483,8 +540,8 @@ contract DisputeKitClassic is BaseDisputeKit, IEvidence { uint256 choiceCount ) { - Dispute storage dispute = disputes[coreDisputeIDToLocal[_disputeID]]; - Round storage round = dispute.rounds[_round]; + Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; + Round storage round = dispute.rounds[dispute.coreRoundIDToLocal[_coreRoundID]]; return ( round.winningChoice, round.tied, @@ -496,8 +553,8 @@ contract DisputeKitClassic is BaseDisputeKit, IEvidence { } function getVoteInfo( - uint256 _disputeID, - uint256 _round, + uint256 _coreDisputeID, + uint256 _coreRoundID, uint256 _voteID ) external @@ -510,8 +567,8 @@ contract DisputeKitClassic is BaseDisputeKit, IEvidence { bool voted ) { - Dispute storage dispute = disputes[coreDisputeIDToLocal[_disputeID]]; - Vote storage vote = dispute.rounds[_round].votes[_voteID]; + Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; + Vote storage vote = dispute.rounds[dispute.coreRoundIDToLocal[_coreRoundID]].votes[_voteID]; return (vote.account, vote.commit, vote.choice, vote.voted); } diff --git a/contracts/src/arbitration/dispute-kits/DisputeKitSybilResistant.sol b/contracts/src/arbitration/dispute-kits/DisputeKitSybilResistant.sol index e4907fb82..b912b637f 100644 --- a/contracts/src/arbitration/dispute-kits/DisputeKitSybilResistant.sol +++ b/contracts/src/arbitration/dispute-kits/DisputeKitSybilResistant.sol @@ -38,6 +38,9 @@ contract DisputeKitSybilResistant is BaseDisputeKit, IEvidence { struct Dispute { Round[] rounds; // Rounds of the dispute. 0 is the default round, and [1, ..n] are the appeal rounds. uint256 numberOfChoices; // The number of choices jurors have when voting. This does not include choice `0` which is reserved for "refuse to arbitrate". + bool jumped; // True if dispute jumped to a parent dispute kit and won't be handled by this DK anymore. + mapping(uint256 => uint256) coreRoundIDToLocal; // Maps id of the round in the core contract to the index of the round of related local dispute. + bytes extraData; // Extradata for the dispute. } struct Round { @@ -97,6 +100,15 @@ contract DisputeKitSybilResistant is BaseDisputeKit, IEvidence { event ChoiceFunded(uint256 indexed _disputeID, uint256 indexed _round, uint256 indexed _choice); + // ************************************* // + // * Modifiers * // + // ************************************* // + + modifier notJumped(uint256 _coreDisputeID) { + require(!disputes[coreDisputeIDToLocal[_coreDisputeID]].jumped, "Dispute jumped to a parent DK!"); + _; + } + /** @dev Constructor. * @param _governor The governor's address. * @param _core The KlerosCore arbitrator. @@ -130,6 +142,14 @@ contract DisputeKitSybilResistant is BaseDisputeKit, IEvidence { core = KlerosCore(_core); } + /** @dev Changes the `_rng` storage variable. + * @param _rng The new value for the `RNGenerator` storage variable. + */ + function changeRandomNumberGenerator(RNG _rng) external onlyByGovernor { + rng = _rng; + // TODO: if current phase is generating, call rng.requestRN() for the next block + } + /** @dev Changes the `poh` storage variable. * @param _poh The new value for the `poh` storage variable. */ @@ -143,39 +163,48 @@ contract DisputeKitSybilResistant is BaseDisputeKit, IEvidence { /** @dev Creates a local dispute and maps it to the dispute ID in the Core contract. * Note: Access restricted to Kleros Core only. - * @param _disputeID The ID of the dispute in Kleros Core. + * @param _coreDisputeID The ID of the dispute in Kleros Core. * @param _numberOfChoices Number of choices of the dispute * @param _extraData Additional info about the dispute, for possible use in future dispute kits. */ function createDispute( - uint256 _disputeID, + uint256 _coreDisputeID, uint256 _numberOfChoices, bytes calldata _extraData ) external override onlyByCore { uint256 localDisputeID = disputes.length; Dispute storage dispute = disputes.push(); dispute.numberOfChoices = _numberOfChoices; + dispute.extraData = _extraData; + // New round in the Core should be created before the dispute creation in DK. + dispute.coreRoundIDToLocal[core.getNumberOfRounds(_coreDisputeID) - 1] = dispute.rounds.length; Round storage round = dispute.rounds.push(); round.tied = true; - coreDisputeIDToLocal[_disputeID] = localDisputeID; + coreDisputeIDToLocal[_coreDisputeID] = localDisputeID; } /** @dev Draws the juror from the sortition tree. The drawn address is picked up by Kleros Core. * Note: Access restricted to Kleros Core only. - * @param _disputeID The ID of the dispute in Kleros Core. + * @param _coreDisputeID The ID of the dispute in Kleros Core. * @return drawnAddress The drawn address. */ - function draw(uint256 _disputeID) external override onlyByCore returns (address drawnAddress) { - bytes32 key = bytes32(core.getSubcourtID(_disputeID)); // Get the ID of the tree. + function draw(uint256 _coreDisputeID) + external + override + onlyByCore + notJumped(_coreDisputeID) + returns (address drawnAddress) + { + bytes32 key = bytes32(core.getSubcourtID(_coreDisputeID)); // Get the ID of the tree. uint256 drawnNumber = getRandomNumber(); (uint256 K, , uint256[] memory nodes) = core.getSortitionSumTree(key); uint256 treeIndex = 0; uint256 currentDrawnNumber = drawnNumber % nodes[0]; - Dispute storage dispute = disputes[coreDisputeIDToLocal[_disputeID]]; + Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; Round storage round = dispute.rounds[dispute.rounds.length - 1]; // TODO: Handle the situation when no one has staked yet. @@ -210,22 +239,22 @@ contract DisputeKitSybilResistant is BaseDisputeKit, IEvidence { /** @dev Sets the caller's commit for the specified votes. * `O(n)` where * `n` is the number of votes. - * @param _disputeID The ID of the dispute. + * @param _coreDisputeID The ID of the dispute in Kleros Core. * @param _voteIDs The IDs of the votes. * @param _commit The commit. */ function castCommit( - uint256 _disputeID, + uint256 _coreDisputeID, uint256[] calldata _voteIDs, bytes32 _commit - ) external { + ) external notJumped(_coreDisputeID) { require( - core.getCurrentPeriod(_disputeID) == KlerosCore.Period.commit, + core.getCurrentPeriod(_coreDisputeID) == KlerosCore.Period.commit, "The dispute should be in Commit period." ); require(_commit != bytes32(0), "Empty commit."); - Dispute storage dispute = disputes[coreDisputeIDToLocal[_disputeID]]; + Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; Round storage round = dispute.rounds[dispute.rounds.length - 1]; for (uint256 i = 0; i < _voteIDs.length; i++) { require(round.votes[_voteIDs[i]].account == msg.sender, "The caller has to own the vote."); @@ -233,32 +262,33 @@ contract DisputeKitSybilResistant is BaseDisputeKit, IEvidence { round.votes[_voteIDs[i]].commit = _commit; } round.totalCommitted += _voteIDs.length; - - if (round.totalCommitted == round.votes.length) core.passPeriod(_disputeID); } /** @dev Sets the caller's choices for the specified votes. * `O(n)` where * `n` is the number of votes. - * @param _disputeID The ID of the dispute. + * @param _coreDisputeID The ID of the dispute in Kleros Core. * @param _voteIDs The IDs of the votes. * @param _choice The choice. * @param _salt The salt for the commit if the votes were hidden. */ function castVote( - uint256 _disputeID, + uint256 _coreDisputeID, uint256[] calldata _voteIDs, uint256 _choice, uint256 _salt - ) external { - require(core.getCurrentPeriod(_disputeID) == KlerosCore.Period.vote, "The dispute should be in Vote period."); + ) external notJumped(_coreDisputeID) { + require( + core.getCurrentPeriod(_coreDisputeID) == KlerosCore.Period.vote, + "The dispute should be in Vote period." + ); require(_voteIDs.length > 0, "No voteID provided"); - Dispute storage dispute = disputes[coreDisputeIDToLocal[_disputeID]]; + Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; require(_choice <= dispute.numberOfChoices, "Choice out of bounds"); Round storage round = dispute.rounds[dispute.rounds.length - 1]; - bool hiddenVotes = core.areVotesHidden(core.getSubcourtID(_disputeID)); + bool hiddenVotes = core.areVotesHidden(core.getSubcourtID(_coreDisputeID)); // Save the votes. for (uint256 i = 0; i < _voteIDs.length; i++) { @@ -288,25 +318,22 @@ contract DisputeKitSybilResistant is BaseDisputeKit, IEvidence { round.tied = false; } } - - // Automatically switch period when voting is finished. - if (round.totalVoted == round.votes.length) core.passPeriod(_disputeID); } /** @dev Manages contributions, and appeals a dispute if at least two choices are fully funded. * Note that the surplus deposit will be reimbursed. - * @param _disputeID Index of the dispute in Kleros Core contract. + * @param _coreDisputeID Index of the dispute in Kleros Core. * @param _choice A choice that receives funding. */ - function fundAppeal(uint256 _disputeID, uint256 _choice) external payable { - Dispute storage dispute = disputes[coreDisputeIDToLocal[_disputeID]]; + function fundAppeal(uint256 _coreDisputeID, uint256 _choice) external payable notJumped(_coreDisputeID) { + Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; require(_choice <= dispute.numberOfChoices, "There is no such ruling to fund."); - (uint256 appealPeriodStart, uint256 appealPeriodEnd) = core.appealPeriod(_disputeID); + (uint256 appealPeriodStart, uint256 appealPeriodEnd) = core.appealPeriod(_coreDisputeID); require(block.timestamp >= appealPeriodStart && block.timestamp < appealPeriodEnd, "Appeal period is over."); uint256 multiplier; - if (this.currentRuling(_disputeID) == _choice) { + if (this.currentRuling(_coreDisputeID) == _choice) { multiplier = WINNER_STAKE_MULTIPLIER; } else { require( @@ -319,7 +346,7 @@ contract DisputeKitSybilResistant is BaseDisputeKit, IEvidence { Round storage round = dispute.rounds[dispute.rounds.length - 1]; require(!round.hasPaid[_choice], "Appeal fee is already paid."); - uint256 appealCost = core.appealCost(_disputeID); + uint256 appealCost = core.appealCost(_coreDisputeID); uint256 totalCost = appealCost + (appealCost * multiplier) / ONE_BASIS_POINT; // Take up to the amount necessary to fund the current round at the current costs. @@ -328,7 +355,7 @@ contract DisputeKitSybilResistant is BaseDisputeKit, IEvidence { contribution = totalCost - round.paidFees[_choice] > msg.value // Overflows and underflows will be managed on the compiler level. ? msg.value : totalCost - round.paidFees[_choice]; - emit Contribution(_disputeID, dispute.rounds.length - 1, _choice, msg.sender, contribution); + emit Contribution(_coreDisputeID, dispute.rounds.length - 1, _choice, msg.sender, contribution); } round.contributions[msg.sender][_choice] += contribution; @@ -337,39 +364,47 @@ contract DisputeKitSybilResistant is BaseDisputeKit, IEvidence { round.feeRewards += round.paidFees[_choice]; round.fundedChoices.push(_choice); round.hasPaid[_choice] = true; - emit ChoiceFunded(_disputeID, dispute.rounds.length - 1, _choice); + emit ChoiceFunded(_coreDisputeID, dispute.rounds.length - 1, _choice); } if (round.fundedChoices.length > 1) { // At least two sides are fully funded. round.feeRewards = round.feeRewards - appealCost; - Round storage newRound = dispute.rounds.push(); - newRound.tied = true; - core.appeal{value: appealCost}(_disputeID); + // Don't create a new round in case of a jump, and remove local dispute from the flow. + if (core.isDisputeKitJumping(_coreDisputeID)) { + dispute.jumped = true; + } else { + // Don't subtract 1 from length since both round arrays haven't been updated yet. + dispute.coreRoundIDToLocal[core.getNumberOfRounds(_coreDisputeID)] = dispute.rounds.length; + + Round storage newRound = dispute.rounds.push(); + newRound.tied = true; + } + core.appeal{value: appealCost}(_coreDisputeID, dispute.numberOfChoices, dispute.extraData); } if (msg.value > contribution) payable(msg.sender).send(msg.value - contribution); } /** @dev Allows those contributors who attempted to fund an appeal round to withdraw any reimbursable fees or rewards after the dispute gets resolved. - * @param _disputeID Index of the dispute in Kleros Core contract. + * @param _coreDisputeID Index of the dispute in Kleros Core contract. * @param _beneficiary The address whose rewards to withdraw. * @param _round The round the caller wants to withdraw from. * @param _choice The ruling option that the caller wants to withdraw from. * @return amount The withdrawn amount. */ function withdrawFeesAndRewards( - uint256 _disputeID, + uint256 _coreDisputeID, address payable _beneficiary, uint256 _round, uint256 _choice ) external returns (uint256 amount) { - require(core.isRuled(_disputeID), "Dispute should be resolved."); + require(core.isRuled(_coreDisputeID), "Dispute should be resolved."); - Dispute storage dispute = disputes[coreDisputeIDToLocal[_disputeID]]; + Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; Round storage round = dispute.rounds[_round]; - uint256 finalRuling = this.currentRuling(_disputeID); + uint256 finalRuling = core.currentRuling(_coreDisputeID); if (!round.hasPaid[_choice]) { // Allow to reimburse if funding was unsuccessful for this ruling option. @@ -392,7 +427,7 @@ contract DisputeKitSybilResistant is BaseDisputeKit, IEvidence { if (amount != 0) { _beneficiary.send(amount); // Deliberate use of send to prevent reverting fallback. It's the user's responsibility to accept ETH. - emit Withdrawal(_disputeID, _round, _choice, _beneficiary, amount); + emit Withdrawal(_coreDisputeID, _round, _choice, _beneficiary, amount); } } @@ -409,32 +444,43 @@ contract DisputeKitSybilResistant is BaseDisputeKit, IEvidence { // ************************************* // /** @dev Gets the current ruling of a specified dispute. - * @param _disputeID The ID of the dispute in Kleros Core. + * @param _coreDisputeID The ID of the dispute in Kleros Core. * @return ruling The current ruling. */ - function currentRuling(uint256 _disputeID) external view override returns (uint256 ruling) { - Dispute storage dispute = disputes[coreDisputeIDToLocal[_disputeID]]; + function currentRuling(uint256 _coreDisputeID) external view override returns (uint256 ruling) { + Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; Round storage round = dispute.rounds[dispute.rounds.length - 1]; ruling = round.tied ? 0 : round.winningChoice; } + function getLastRoundResult(uint256 _coreDisputeID) + external + view + override + returns (uint256 winningChoiece, bool tied) + { + Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; + Round storage lastRound = dispute.rounds[dispute.rounds.length - 1]; + return (lastRound.winningChoice, lastRound.tied); + } + /** @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. - * @param _disputeID The ID of the dispute in Kleros Core. - * @param _round The ID of the round. + * @param _coreDisputeID The ID of the dispute in Kleros Core, not in the Dispute Kit. + * @param _coreRoundID The ID of the round in Kleros Core, not in the Dispute Kit. * @param _voteID The ID of the vote. * @return The degree of coherence in basis points. */ function getDegreeOfCoherence( - uint256 _disputeID, - uint256 _round, + uint256 _coreDisputeID, + uint256 _coreRoundID, uint256 _voteID ) external view override returns (uint256) { // In this contract this degree can be either 0 or 1, but in other dispute kits this value can be something in between. - Dispute storage dispute = disputes[coreDisputeIDToLocal[_disputeID]]; - Round storage lastRound = dispute.rounds[dispute.rounds.length - 1]; - Vote storage vote = dispute.rounds[_round].votes[_voteID]; + Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; + Vote storage vote = dispute.rounds[dispute.coreRoundIDToLocal[_coreRoundID]].votes[_voteID]; + (uint256 winningChoice, bool tied) = core.getLastRoundResult(_coreDisputeID); - if (vote.voted && (vote.choice == lastRound.winningChoice || lastRound.tied)) { + if (vote.voted && (vote.choice == winningChoice || tied)) { return ONE_BASIS_POINT; } else { return 0; @@ -442,44 +488,63 @@ contract DisputeKitSybilResistant is BaseDisputeKit, IEvidence { } /** @dev Gets the number of jurors who are eligible to a reward in this round. - * @param _disputeID The ID of the dispute in Kleros Core. - * @param _round The ID of the round. + * @param _coreDisputeID The ID of the dispute in Kleros Core, not in the Dispute Kit. + * @param _coreRoundID The ID of the round in Kleros Core, not in the Dispute Kit. * @return The number of coherent jurors. */ - function getCoherentCount(uint256 _disputeID, uint256 _round) external view override returns (uint256) { - Dispute storage dispute = disputes[coreDisputeIDToLocal[_disputeID]]; - Round storage lastRound = dispute.rounds[dispute.rounds.length - 1]; - Round storage currentRound = dispute.rounds[_round]; - uint256 winningChoice = lastRound.winningChoice; + function getCoherentCount(uint256 _coreDisputeID, uint256 _coreRoundID) external view override returns (uint256) { + Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; + Round storage currentRound = dispute.rounds[dispute.coreRoundIDToLocal[_coreRoundID]]; + (uint256 winningChoice, bool tied) = core.getLastRoundResult(_coreDisputeID); - if (currentRound.totalVoted == 0 || (!lastRound.tied && currentRound.counts[winningChoice] == 0)) { + if (currentRound.totalVoted == 0 || (!tied && currentRound.counts[winningChoice] == 0)) { return 0; - } else if (lastRound.tied) { + } else if (tied) { return currentRound.totalVoted; } else { return currentRound.counts[winningChoice]; } } + /** @dev Returns true if all of the jurors have cast their commits for the last round. + * @param _coreDisputeID The ID of the dispute in Kleros Core. + * @return Whether all of the jurors have cast their commits for the last round. + */ + function areCommitsAllCast(uint256 _coreDisputeID) external view override returns (bool) { + Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; + Round storage round = dispute.rounds[dispute.rounds.length - 1]; + return round.totalCommitted == round.votes.length; + } + + /** @dev Returns true if all of the jurors have cast their votes for the last round. + * @param _coreDisputeID The ID of the dispute in Kleros Core. + * @return Whether all of the jurors have cast their votes for the last round. + */ + function areVotesAllCast(uint256 _coreDisputeID) external view override returns (bool) { + Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; + Round storage round = dispute.rounds[dispute.rounds.length - 1]; + return round.totalVoted == round.votes.length; + } + /** @dev Returns true if the specified voter was active in this round. - * @param _disputeID The ID of the dispute in Kleros Core. - * @param _round The ID of the round. + * @param _coreDisputeID The ID of the dispute in Kleros Core, not in the Dispute Kit. + * @param _coreRoundID The ID of the round in Kleros Core, not in the Dispute Kit. * @param _voteID The ID of the voter. * @return Whether the voter was active or not. */ function isVoteActive( - uint256 _disputeID, - uint256 _round, + uint256 _coreDisputeID, + uint256 _coreRoundID, uint256 _voteID ) external view override returns (bool) { - Dispute storage dispute = disputes[coreDisputeIDToLocal[_disputeID]]; - Vote storage vote = dispute.rounds[_round].votes[_voteID]; + Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; + Vote storage vote = dispute.rounds[dispute.coreRoundIDToLocal[_coreRoundID]].votes[_voteID]; return vote.voted; } function getRoundInfo( - uint256 _disputeID, - uint256 _round, + uint256 _coreDisputeID, + uint256 _coreRoundID, uint256 _choice ) external @@ -494,8 +559,8 @@ contract DisputeKitSybilResistant is BaseDisputeKit, IEvidence { uint256 choiceCount ) { - Dispute storage dispute = disputes[coreDisputeIDToLocal[_disputeID]]; - Round storage round = dispute.rounds[_round]; + Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; + Round storage round = dispute.rounds[dispute.coreRoundIDToLocal[_coreRoundID]]; return ( round.winningChoice, round.tied, @@ -507,8 +572,8 @@ contract DisputeKitSybilResistant is BaseDisputeKit, IEvidence { } function getVoteInfo( - uint256 _disputeID, - uint256 _round, + uint256 _coreDisputeID, + uint256 _coreRoundID, uint256 _voteID ) external @@ -521,8 +586,8 @@ contract DisputeKitSybilResistant is BaseDisputeKit, IEvidence { bool voted ) { - Dispute storage dispute = disputes[coreDisputeIDToLocal[_disputeID]]; - Vote storage vote = dispute.rounds[_round].votes[_voteID]; + Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; + Vote storage vote = dispute.rounds[dispute.coreRoundIDToLocal[_coreRoundID]].votes[_voteID]; return (vote.account, vote.commit, vote.choice, vote.voted); }