From 5893960aec275c75df782d6e97b2063315444d2a Mon Sep 17 00:00:00 2001 From: unknownunknown1 Date: Mon, 14 Mar 2022 16:52:00 +1000 Subject: [PATCH 1/5] feat(KlerosCore): dispute kit forest implementation --- contracts/src/arbitration/IDisputeKit.sol | 7 + contracts/src/arbitration/KlerosCore.sol | 224 ++++++++++++------ .../dispute-kits/DisputeKitClassic.sol | 72 ++++-- .../dispute-kits/DisputeKitSybilResistant.sol | 72 ++++-- 4 files changed, 261 insertions(+), 114 deletions(-) diff --git a/contracts/src/arbitration/IDisputeKit.sol b/contracts/src/arbitration/IDisputeKit.sol index 675043a76..332b0516f 100644 --- a/contracts/src/arbitration/IDisputeKit.sol +++ b/contracts/src/arbitration/IDisputeKit.sol @@ -51,6 +51,13 @@ interface IDisputeKit { */ function currentRuling(uint256 _disputeID) external view returns (uint256 ruling); + /** @dev Returns the voting data from the most relevant round. + * @param _disputeID The ID of the dispute in Kleros Core. + * @return winningChoiece The winning choice of this round. + * @return tied Whether it's a tie or not. + */ + function getLastRoundResult(uint256 _disputeID) 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. diff --git a/contracts/src/arbitration/KlerosCore.sol b/contracts/src/arbitration/KlerosCore.sol index a339ea9e6..f45fdf5b0 100644 --- a/contracts/src/arbitration/KlerosCore.sol +++ b/contracts/src/arbitration/KlerosCore.sol @@ -43,13 +43,12 @@ 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. @@ -58,6 +57,7 @@ contract KlerosCore is IArbitrator { } 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 repartitions; // A counter of reward repartitions made in this round. @@ -71,10 +71,17 @@ 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 DisputeKitStruct { + uint256 parent; // Index of the parent dispute kit. If it's 0 then this DK is a root. + IDisputeKit dkAddress; // Dispute kit's address. + } + // ************************************* // // * Storage * // // ************************************* // + uint256 public constant FORKING_COURT = 0; // Index of the default court. + 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. @@ -87,8 +94,7 @@ contract KlerosCore is IArbitrator { Court[] public courts; // The subcourts. - //TODO: disputeKits forest. - mapping(uint256 => IDisputeKit) public disputeKits; // All supported dispute kits. + DisputeKitStruct[] public disputeKits; Dispute[] public disputes; // The disputes. mapping(address => Juror) internal jurors; // The jurors. @@ -148,22 +154,24 @@ contract KlerosCore is IArbitrator { governor = _governor; pinakion = _pinakion; jurorProsecutionModule = _jurorProsecutionModule; - disputeKits[0] = _disputeKit; + + // Create an empty element so 0 index is not used. + disputeKits.push(); // 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; + + disputeKits.push(DisputeKitStruct({parent: 0, dkAddress: _disputeKit})); + + court.supportedDisputeKits[DISPUTE_KIT_CLASSIC_INDEX] = true; sortitionSumTrees.createTree(bytes32(0), _sortitionSumTreeK); } @@ -208,12 +216,17 @@ 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, 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 addNewDisputeKit(IDisputeKit _disputeKitAddress, uint256 _parent) external onlyByGovernor { + require(_parent < disputeKits.length, "Parent doesn't exist"); + // Create new tree, which root should be supported by Forking court. + if (_parent == 0) { + courts[FORKING_COURT].supportedDisputeKits[disputeKits.length] = true; + } + + disputeKits.push(DisputeKitStruct({parent: _parent, dkAddress: _disputeKitAddress})); } /** @dev Creates a subcourt under a specified parent court. @@ -225,7 +238,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 +249,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] < disputeKits.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 +284,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 +330,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 _enable Whether add or remove the dispute kits from the subcourt. */ function setDisputeKits( 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 && disputeKits[_disputeKitIDs[i]].parent == 0), + "Can't remove root DK support from the forking court" + ); + subcourt.supportedDisputeKits[_disputeKitIDs[i]] = false; + } } } @@ -362,11 +379,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" ); @@ -375,13 +391,13 @@ contract KlerosCore is IArbitrator { dispute.subcourtID = subcourtID; dispute.arbitrated = IArbitrable(msg.sender); - IDisputeKit disputeKit = disputeKits[disputeKitID]; - dispute.disputeKit = disputeKit; + IDisputeKit disputeKit = disputeKits[disputeKitID].dkAddress; dispute.lastPeriodChange = block.timestamp; dispute.nbVotes = msg.value / courts[dispute.subcourtID].feeForJuror; Round storage round = dispute.rounds.push(); + round.disputeKitID = disputeKitID; round.tokensAtStakePerJuror = (courts[dispute.subcourtID].minStake * courts[dispute.subcourtID].alpha) / ALPHA_DIVISOR; @@ -413,7 +429,7 @@ contract KlerosCore is IArbitrator { require( block.timestamp - dispute.lastPeriodChange >= courts[dispute.subcourtID].timesPerPeriod[uint256(dispute.period)] || - msg.sender == address(dispute.disputeKit), + msg.sender == address(disputeKits[round.disputeKitID].dkAddress), "The commit period time has not passed yet." ); dispute.period = Period.vote; @@ -422,7 +438,7 @@ contract KlerosCore is IArbitrator { require( block.timestamp - dispute.lastPeriodChange >= courts[dispute.subcourtID].timesPerPeriod[uint256(dispute.period)] || - msg.sender == address(dispute.disputeKit), + msg.sender == address(disputeKits[round.disputeKitID].dkAddress), "The vote period time has not passed yet" ); dispute.period = Period.appeal; @@ -452,7 +468,7 @@ 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 = disputeKits[round.disputeKitID].dkAddress; uint256 startIndex = round.drawnJurors.length; uint256 endIndex = startIndex + _iterations <= dispute.nbVotes ? startIndex + _iterations : dispute.nbVotes; @@ -475,25 +491,55 @@ 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]; + Round storage round = dispute.rounds[dispute.rounds.length - 1]; require(dispute.period == Period.appeal, "Dispute is not appealable."); - require(msg.sender == address(dispute.disputeKit), "Access not allowed: Dispute Kit only."); + require( + msg.sender == address(disputeKits[round.disputeKitID].dkAddress), + "Access not allowed: Dispute Kit only." + ); + + uint256 disputeKitID = round.disputeKitID; + // Create a new round beforehand because dispute kit relies on the latest index. + Round storage extraRound = dispute.rounds.push(); - if (dispute.nbVotes >= courts[dispute.subcourtID].jurorsForCourtJump) + if (isDisputeJumping(_disputeID)) { // Jump to parent subcourt. - // TODO: Handle court jump in the Forking court. Also make sure the new subcourt is compatible with the dispute kit. + // TODO: Handle court jump in the Forking court. dispute.subcourtID = courts[dispute.subcourtID].parent; + while (!courts[dispute.subcourtID].supportedDisputeKits[disputeKitID]) { + if (disputeKits[disputeKitID].parent != 0) { + disputeKitID = disputeKits[disputeKitID].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[disputeKitID] = true, + "DK isn't supported by Forking court" + ); + dispute.subcourtID = uint96(FORKING_COURT); + break; + } + } + disputeKits[disputeKitID].dkAddress.createDispute(_disputeID, _numberOfChoices, _extraData); + } 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; - Round storage extraRound = dispute.rounds.push(); + extraRound.disputeKitID = disputeKitID; extraRound.tokensAtStakePerJuror = (courts[dispute.subcourtID].minStake * courts[dispute.subcourtID].alpha) / ALPHA_DIVISOR; @@ -520,7 +566,10 @@ contract KlerosCore is IArbitrator { uint256 penaltiesInRoundCache = dispute.rounds[_appeal].penalties; // For saving gas. 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 coherentCount = disputeKits[dispute.rounds[_appeal].disputeKitID].dkAddress.getCoherentCount( + _disputeID, + _appeal + ); // 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. @@ -536,7 +585,11 @@ contract KlerosCore is IArbitrator { for (uint256 i = dispute.rounds[_appeal].repartitions; i < end; i++) { // Penalty. if (i < numberOfVotesInRound) { - degreeOfCoherence = dispute.disputeKit.getDegreeOfCoherence(_disputeID, _appeal, i); + degreeOfCoherence = disputeKits[dispute.rounds[_appeal].disputeKitID].dkAddress.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. uint256 penalty = (dispute.rounds[_appeal].tokensAtStakePerJuror * @@ -559,7 +612,7 @@ contract KlerosCore is IArbitrator { } // Unstake the juror if he lost due to inactivity. - if (!dispute.disputeKit.isVoteActive(_disputeID, _appeal, i)) { + if (!disputeKits[dispute.rounds[_appeal].disputeKitID].dkAddress.isVoteActive(_disputeID, _appeal, i)) { for (uint256 j = 0; j < jurors[account].subcourtIDs.length; j++) setStakeForAccount(account, jurors[account].subcourtIDs[j], 0, 0); } @@ -574,7 +627,7 @@ contract KlerosCore is IArbitrator { } // Reward. } else { - degreeOfCoherence = dispute.disputeKit.getDegreeOfCoherence( + degreeOfCoherence = disputeKits[dispute.rounds[_appeal].disputeKitID].dkAddress.getDegreeOfCoherence( _disputeID, _appeal, i % numberOfVotesInRound @@ -644,7 +697,7 @@ contract KlerosCore is IArbitrator { Dispute storage dispute = disputes[_disputeID]; if (dispute.nbVotes >= courts[dispute.subcourtID].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); @@ -674,7 +727,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 = disputeKits[round.disputeKitID].dkAddress; return disputeKit.currentRuling(_disputeID); } @@ -686,7 +741,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 +752,8 @@ contract KlerosCore is IArbitrator { round.totalFeesForJurors, round.repartitions, round.penalties, - round.drawnJurors + round.drawnJurors, + round.disputeKitID ); } @@ -715,6 +772,10 @@ contract KlerosCore is IArbitrator { locked = juror.lockedTokens[_subcourtID]; } + function chkSupportedDK(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 +824,17 @@ contract KlerosCore is IArbitrator { return disputes[_disputeID].ruled; } + function isDisputeJumping(uint256 _disputeID) public view returns (bool) { + Dispute storage dispute = disputes[_disputeID]; + return dispute.nbVotes >= courts[dispute.subcourtID].jurorsForCourtJump; + } + + 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) = disputeKits[round.disputeKitID].dkAddress.getLastRoundResult(_disputeID); + } + // ************************************* // // * Internal * // // ************************************* // @@ -815,7 +887,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 +928,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 +939,13 @@ contract KlerosCore is IArbitrator { minJurors := mload(add(_extraData, 0x40)) disputeKitID := mload(add(_extraData, 0x60)) } - if (subcourtID >= courts.length) subcourtID = 0; + if (subcourtID >= courts.length) subcourtID = uint96(FORKING_COURT); if (minJurors == 0) minJurors = MIN_JURORS; - if (disputeKits[disputeKitID] == IDisputeKit(address(0))) disputeKitID = 0; + if (disputeKitID == 0 || disputeKitID >= disputeKits.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..5deeb988f 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 _disputeID) { + require(!disputes[coreDisputeIDToLocal[_disputeID]].jumped, "Dispute jumped to a parent DK!"); + _; + } + /** @dev Constructor. * @param _governor The governor's address. * @param _core The KlerosCore arbitrator. @@ -147,6 +159,9 @@ contract DisputeKitClassic is BaseDisputeKit, IEvidence { uint256 localDisputeID = disputes.length; Dispute storage dispute = disputes.push(); dispute.numberOfChoices = _numberOfChoices; + dispute.extraData = _extraData; + // New round in the Core should be added before the dispute creation in DK. + dispute.coreRoundIDToLocal[core.getNumberOfRounds(_disputeID) - 1] = dispute.rounds.length; Round storage round = dispute.rounds.push(); round.tied = true; @@ -159,7 +174,13 @@ contract DisputeKitClassic is BaseDisputeKit, IEvidence { * @param _disputeID The ID of the dispute in Kleros Core. * @return drawnAddress The drawn address. */ - function draw(uint256 _disputeID) external override onlyByCore returns (address drawnAddress) { + function draw(uint256 _disputeID) + external + override + onlyByCore + notJumped(_disputeID) + returns (address drawnAddress) + { bytes32 key = bytes32(core.getSubcourtID(_disputeID)); // Get the ID of the tree. uint256 drawnNumber = getRandomNumber(); @@ -207,7 +228,7 @@ contract DisputeKitClassic is BaseDisputeKit, IEvidence { uint256 _disputeID, uint256[] calldata _voteIDs, bytes32 _commit - ) external { + ) external notJumped(_disputeID) { require( core.getCurrentPeriod(_disputeID) == KlerosCore.Period.commit, "The dispute should be in Commit period." @@ -239,7 +260,7 @@ contract DisputeKitClassic is BaseDisputeKit, IEvidence { uint256[] calldata _voteIDs, uint256 _choice, uint256 _salt - ) external { + ) external notJumped(_disputeID) { require(core.getCurrentPeriod(_disputeID) == KlerosCore.Period.vote, "The dispute should be in Vote period."); require(_voteIDs.length > 0, "No voteID provided"); @@ -287,7 +308,7 @@ contract DisputeKitClassic is BaseDisputeKit, IEvidence { * @param _disputeID Index of the dispute in Kleros Core contract. * @param _choice A choice that receives funding. */ - function fundAppeal(uint256 _disputeID, uint256 _choice) external payable { + function fundAppeal(uint256 _disputeID, uint256 _choice) external payable notJumped(_disputeID) { Dispute storage dispute = disputes[coreDisputeIDToLocal[_disputeID]]; require(_choice <= dispute.numberOfChoices, "There is no such ruling to fund."); @@ -333,9 +354,17 @@ contract DisputeKitClassic is BaseDisputeKit, IEvidence { // 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.isDisputeJumping(_disputeID)) { + dispute.jumped = true; + } else { + // Don't subtract 1 from length since both round arrays haven't been updated yet. + dispute.coreRoundIDToLocal[core.getNumberOfRounds(_disputeID)] = dispute.rounds.length; + + Round storage newRound = dispute.rounds.push(); + newRound.tied = true; + } + core.appeal{value: appealCost}(_disputeID, dispute.numberOfChoices, dispute.extraData); } if (msg.value > contribution) payable(msg.sender).send(msg.value - contribution); @@ -358,7 +387,7 @@ contract DisputeKitClassic is BaseDisputeKit, IEvidence { Dispute storage dispute = disputes[coreDisputeIDToLocal[_disputeID]]; Round storage round = dispute.rounds[_round]; - uint256 finalRuling = this.currentRuling(_disputeID); + uint256 finalRuling = core.currentRuling(_disputeID); if (!round.hasPaid[_choice]) { // Allow to reimburse if funding was unsuccessful for this ruling option. @@ -407,6 +436,12 @@ contract DisputeKitClassic is BaseDisputeKit, IEvidence { ruling = round.tied ? 0 : round.winningChoice; } + function getLastRoundResult(uint256 _disputeID) external view override returns (uint256 winningChoiece, bool tied) { + Dispute storage dispute = disputes[coreDisputeIDToLocal[_disputeID]]; + 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. @@ -420,10 +455,10 @@ contract DisputeKitClassic is BaseDisputeKit, IEvidence { ) 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]; + Vote storage vote = dispute.rounds[dispute.coreRoundIDToLocal[_round]].votes[_voteID]; + (uint256 winningChoice, bool tied) = core.getLastRoundResult(_disputeID); - if (vote.voted && (vote.choice == lastRound.winningChoice || lastRound.tied)) { + if (vote.voted && (vote.choice == winningChoice || tied)) { return ONE_BASIS_POINT; } else { return 0; @@ -437,13 +472,12 @@ contract DisputeKitClassic is BaseDisputeKit, IEvidence { */ 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; + Round storage currentRound = dispute.rounds[dispute.coreRoundIDToLocal[_round]]; + (uint256 winningChoice, bool tied) = core.getLastRoundResult(_disputeID); - 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]; @@ -462,7 +496,7 @@ contract DisputeKitClassic is BaseDisputeKit, IEvidence { uint256 _voteID ) external view override returns (bool) { Dispute storage dispute = disputes[coreDisputeIDToLocal[_disputeID]]; - Vote storage vote = dispute.rounds[_round].votes[_voteID]; + Vote storage vote = dispute.rounds[dispute.coreRoundIDToLocal[_round]].votes[_voteID]; return vote.voted; } @@ -484,7 +518,7 @@ contract DisputeKitClassic is BaseDisputeKit, IEvidence { ) { Dispute storage dispute = disputes[coreDisputeIDToLocal[_disputeID]]; - Round storage round = dispute.rounds[_round]; + Round storage round = dispute.rounds[dispute.coreRoundIDToLocal[_round]]; return ( round.winningChoice, round.tied, @@ -511,7 +545,7 @@ contract DisputeKitClassic is BaseDisputeKit, IEvidence { ) { Dispute storage dispute = disputes[coreDisputeIDToLocal[_disputeID]]; - Vote storage vote = dispute.rounds[_round].votes[_voteID]; + Vote storage vote = dispute.rounds[dispute.coreRoundIDToLocal[_round]].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..d04fd8f4c 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 _disputeID) { + require(!disputes[coreDisputeIDToLocal[_disputeID]].jumped, "Dispute jumped to a parent DK!"); + _; + } + /** @dev Constructor. * @param _governor The governor's address. * @param _core The KlerosCore arbitrator. @@ -155,6 +167,9 @@ contract DisputeKitSybilResistant is BaseDisputeKit, IEvidence { uint256 localDisputeID = disputes.length; Dispute storage dispute = disputes.push(); dispute.numberOfChoices = _numberOfChoices; + dispute.extraData = _extraData; + // New round in the Core should be added before the dispute creation in DK. + dispute.coreRoundIDToLocal[core.getNumberOfRounds(_disputeID) - 1] = dispute.rounds.length; Round storage round = dispute.rounds.push(); round.tied = true; @@ -167,7 +182,13 @@ contract DisputeKitSybilResistant is BaseDisputeKit, IEvidence { * @param _disputeID The ID of the dispute in Kleros Core. * @return drawnAddress The drawn address. */ - function draw(uint256 _disputeID) external override onlyByCore returns (address drawnAddress) { + function draw(uint256 _disputeID) + external + override + onlyByCore + notJumped(_disputeID) + returns (address drawnAddress) + { bytes32 key = bytes32(core.getSubcourtID(_disputeID)); // Get the ID of the tree. uint256 drawnNumber = getRandomNumber(); @@ -218,7 +239,7 @@ contract DisputeKitSybilResistant is BaseDisputeKit, IEvidence { uint256 _disputeID, uint256[] calldata _voteIDs, bytes32 _commit - ) external { + ) external notJumped(_disputeID) { require( core.getCurrentPeriod(_disputeID) == KlerosCore.Period.commit, "The dispute should be in Commit period." @@ -250,7 +271,7 @@ contract DisputeKitSybilResistant is BaseDisputeKit, IEvidence { uint256[] calldata _voteIDs, uint256 _choice, uint256 _salt - ) external { + ) external notJumped(_disputeID) { require(core.getCurrentPeriod(_disputeID) == KlerosCore.Period.vote, "The dispute should be in Vote period."); require(_voteIDs.length > 0, "No voteID provided"); @@ -298,7 +319,7 @@ contract DisputeKitSybilResistant is BaseDisputeKit, IEvidence { * @param _disputeID Index of the dispute in Kleros Core contract. * @param _choice A choice that receives funding. */ - function fundAppeal(uint256 _disputeID, uint256 _choice) external payable { + function fundAppeal(uint256 _disputeID, uint256 _choice) external payable notJumped(_disputeID) { Dispute storage dispute = disputes[coreDisputeIDToLocal[_disputeID]]; require(_choice <= dispute.numberOfChoices, "There is no such ruling to fund."); @@ -344,9 +365,17 @@ contract DisputeKitSybilResistant is BaseDisputeKit, IEvidence { // 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.isDisputeJumping(_disputeID)) { + dispute.jumped = true; + } else { + // Don't subtract 1 from length since both round arrays haven't been updated yet. + dispute.coreRoundIDToLocal[core.getNumberOfRounds(_disputeID)] = dispute.rounds.length; + + Round storage newRound = dispute.rounds.push(); + newRound.tied = true; + } + core.appeal{value: appealCost}(_disputeID, dispute.numberOfChoices, dispute.extraData); } if (msg.value > contribution) payable(msg.sender).send(msg.value - contribution); @@ -369,7 +398,7 @@ contract DisputeKitSybilResistant is BaseDisputeKit, IEvidence { Dispute storage dispute = disputes[coreDisputeIDToLocal[_disputeID]]; Round storage round = dispute.rounds[_round]; - uint256 finalRuling = this.currentRuling(_disputeID); + uint256 finalRuling = core.currentRuling(_disputeID); if (!round.hasPaid[_choice]) { // Allow to reimburse if funding was unsuccessful for this ruling option. @@ -418,6 +447,12 @@ contract DisputeKitSybilResistant is BaseDisputeKit, IEvidence { ruling = round.tied ? 0 : round.winningChoice; } + function getLastRoundResult(uint256 _disputeID) external view override returns (uint256 winningChoiece, bool tied) { + Dispute storage dispute = disputes[coreDisputeIDToLocal[_disputeID]]; + 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. @@ -431,10 +466,10 @@ contract DisputeKitSybilResistant is BaseDisputeKit, IEvidence { ) 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]; + Vote storage vote = dispute.rounds[dispute.coreRoundIDToLocal[_round]].votes[_voteID]; + (uint256 winningChoice, bool tied) = core.getLastRoundResult(_disputeID); - if (vote.voted && (vote.choice == lastRound.winningChoice || lastRound.tied)) { + if (vote.voted && (vote.choice == winningChoice || tied)) { return ONE_BASIS_POINT; } else { return 0; @@ -448,13 +483,12 @@ contract DisputeKitSybilResistant is BaseDisputeKit, IEvidence { */ 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; + Round storage currentRound = dispute.rounds[dispute.coreRoundIDToLocal[_round]]; + (uint256 winningChoice, bool tied) = core.getLastRoundResult(_disputeID); - 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]; @@ -473,7 +507,7 @@ contract DisputeKitSybilResistant is BaseDisputeKit, IEvidence { uint256 _voteID ) external view override returns (bool) { Dispute storage dispute = disputes[coreDisputeIDToLocal[_disputeID]]; - Vote storage vote = dispute.rounds[_round].votes[_voteID]; + Vote storage vote = dispute.rounds[dispute.coreRoundIDToLocal[_round]].votes[_voteID]; return vote.voted; } @@ -495,7 +529,7 @@ contract DisputeKitSybilResistant is BaseDisputeKit, IEvidence { ) { Dispute storage dispute = disputes[coreDisputeIDToLocal[_disputeID]]; - Round storage round = dispute.rounds[_round]; + Round storage round = dispute.rounds[dispute.coreRoundIDToLocal[_round]]; return ( round.winningChoice, round.tied, @@ -522,7 +556,7 @@ contract DisputeKitSybilResistant is BaseDisputeKit, IEvidence { ) { Dispute storage dispute = disputes[coreDisputeIDToLocal[_disputeID]]; - Vote storage vote = dispute.rounds[_round].votes[_voteID]; + Vote storage vote = dispute.rounds[dispute.coreRoundIDToLocal[_round]].votes[_voteID]; return (vote.account, vote.commit, vote.choice, vote.voted); } From c767d38940152d5df20309e25cb9ae51227507ac Mon Sep 17 00:00:00 2001 From: unknownunknown1 Date: Wed, 16 Mar 2022 23:36:25 +1000 Subject: [PATCH 2/5] fix(KlerosCore): don't create new dispute without DK switch --- contracts/src/arbitration/KlerosCore.sol | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/contracts/src/arbitration/KlerosCore.sol b/contracts/src/arbitration/KlerosCore.sol index f45fdf5b0..a4de0caa1 100644 --- a/contracts/src/arbitration/KlerosCore.sol +++ b/contracts/src/arbitration/KlerosCore.sol @@ -513,7 +513,7 @@ contract KlerosCore is IArbitrator { // Create a new round beforehand because dispute kit relies on the latest index. Round storage extraRound = dispute.rounds.push(); - if (isDisputeJumping(_disputeID)) { + if (dispute.nbVotes >= courts[dispute.subcourtID].jurorsForCourtJump) { // Jump to parent subcourt. // TODO: Handle court jump in the Forking court. dispute.subcourtID = courts[dispute.subcourtID].parent; @@ -531,7 +531,11 @@ contract KlerosCore is IArbitrator { break; } } - disputeKits[disputeKitID].dkAddress.createDispute(_disputeID, _numberOfChoices, _extraData); + + // Dispute kit was changed, so create a dispute in the new DK contract. + if (disputeKitID != round.disputeKitID) { + disputeKits[disputeKitID].dkAddress.createDispute(_disputeID, _numberOfChoices, _extraData); + } } dispute.period = Period.evidence; @@ -824,9 +828,21 @@ 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 isDisputeJumping(uint256 _disputeID) public view returns (bool) { Dispute storage dispute = disputes[_disputeID]; - return dispute.nbVotes >= courts[dispute.subcourtID].jurorsForCourtJump; + + if (dispute.nbVotes < courts[dispute.subcourtID].jurorsForCourtJump) { + return false; + } else { + uint256 disputeKitID = dispute.rounds[dispute.rounds.length - 1].disputeKitID; + uint96 parentCourt = courts[dispute.subcourtID].parent; + // Parent court doesn't support current DK so it'll be switched. + return !courts[parentCourt].supportedDisputeKits[disputeKitID]; + } } function getLastRoundResult(uint256 _disputeID) external view returns (uint256 winningChoice, bool tied) { From 4f44e0b4b30d7b01f1be97c63bcfdc25a9ea7e05 Mon Sep 17 00:00:00 2001 From: unknownunknown1 Date: Mon, 28 Mar 2022 22:09:53 +1000 Subject: [PATCH 3/5] fix(KlerosCore): add iterations for DK jump + hidden votes setter --- contracts/src/arbitration/KlerosCore.sol | 70 +++++++++++++++++++----- 1 file changed, 55 insertions(+), 15 deletions(-) diff --git a/contracts/src/arbitration/KlerosCore.sol b/contracts/src/arbitration/KlerosCore.sol index a4de0caa1..479d5c586 100644 --- a/contracts/src/arbitration/KlerosCore.sol +++ b/contracts/src/arbitration/KlerosCore.sol @@ -73,7 +73,9 @@ contract KlerosCore is IArbitrator { struct DisputeKitStruct { uint256 parent; // Index of the parent dispute kit. If it's 0 then this DK is a root. + uint256[] children; // List of child dispute kits. IDisputeKit dkAddress; // Dispute kit's address. + uint256 depthLevel; // How far this DK is from the root. 0 for root DK. } // ************************************* // @@ -86,6 +88,7 @@ contract KlerosCore is IArbitrator { 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. uint256 public constant NON_PAYABLE_AMOUNT = (2**256 - 2) / 2; // An amount higher than the supply of ETH. + uint256 public constant SEARCH_ITERATIONS = 10; // Number of iterations to search for suitable parent court before jumping to the top court. address public governor; // The governor of the contract. IERC20 public pinakion; // The Pinakion token contract. @@ -169,7 +172,9 @@ contract KlerosCore is IArbitrator { court.jurorsForCourtJump = _jurorsForCourtJump; court.timesPerPeriod = _timesPerPeriod; - disputeKits.push(DisputeKitStruct({parent: 0, dkAddress: _disputeKit})); + disputeKits.push( + DisputeKitStruct({parent: 0, children: new uint256[](0), dkAddress: _disputeKit, depthLevel: 0}) + ); court.supportedDisputeKits[DISPUTE_KIT_CLASSIC_INDEX] = true; sortitionSumTrees.createTree(bytes32(0), _sortitionSumTreeK); @@ -220,13 +225,27 @@ contract KlerosCore is IArbitrator { * Note that the root DK must be supported by the forking court. */ function addNewDisputeKit(IDisputeKit _disputeKitAddress, uint256 _parent) external onlyByGovernor { - require(_parent < disputeKits.length, "Parent doesn't exist"); + uint256 disputeKitID = disputeKits.length; + require(_parent < disputeKitID, "Parent doesn't exist"); + uint256 depthLevel; // Create new tree, which root should be supported by Forking court. if (_parent == 0) { - courts[FORKING_COURT].supportedDisputeKits[disputeKits.length] = true; + courts[FORKING_COURT].supportedDisputeKits[disputeKitID] = true; + } else { + depthLevel = disputeKits[_parent].depthLevel + 1; + // It should be always possible to reach the root from the leaf with the defined number of search iterations. + require(depthLevel < SEARCH_ITERATIONS, "Depth level is at max"); } - disputeKits.push(DisputeKitStruct({parent: _parent, dkAddress: _disputeKitAddress})); + disputeKits.push( + DisputeKitStruct({ + parent: _parent, + children: new uint256[](0), + dkAddress: _disputeKitAddress, + depthLevel: depthLevel + }) + ); + disputeKits[_parent].children.push(disputeKitID); } /** @dev Creates a subcourt under a specified parent court. @@ -261,7 +280,7 @@ contract KlerosCore is IArbitrator { Court storage court = courts.push(); for (uint256 i = 0; i < _supportedDisputeKits.length; i++) { - require(_supportedDisputeKits[i] < disputeKits.length, "DK doesn't exist"); + require(_supportedDisputeKits[i] > 0 && _supportedDisputeKits[i] < disputeKits.length, "Wrong DK index"); court.supportedDisputeKits[_supportedDisputeKits[i]] = true; } @@ -319,6 +338,14 @@ contract KlerosCore is IArbitrator { courts[_subcourtID].jurorsForCourtJump = _jurorsForCourtJump; } + /** @dev Changes the `hiddenVotes` property value of a specified subcourt. + * @param _subcourtID The ID of the subcourt. + * @param _hiddenVotes The new value for the `hiddenVotes` property value. + */ + function changeHiddenVotes(uint96 _subcourtID, bool _hiddenVotes) external onlyByGovernor { + courts[_subcourtID].hiddenVotes = _hiddenVotes; + } + /** @dev Changes the `timesPerPeriod` property value of a specified subcourt. * @param _subcourtID The ID of the subcourt. * @param _timesPerPeriod The new value for the `timesPerPeriod` property value. @@ -343,6 +370,7 @@ contract KlerosCore is IArbitrator { Court storage subcourt = courts[_subcourtID]; for (uint256 i = 0; i < _disputeKitIDs.length; i++) { if (_enable) { + require(_disputeKitIDs[i] > 0 && _disputeKitIDs[i] < disputeKits.length, "Wrong DK index"); subcourt.supportedDisputeKits[_disputeKitIDs[i]] = true; } else { require( @@ -517,20 +545,24 @@ contract KlerosCore is IArbitrator { // Jump to parent subcourt. // TODO: Handle court jump in the Forking court. dispute.subcourtID = courts[dispute.subcourtID].parent; - while (!courts[dispute.subcourtID].supportedDisputeKits[disputeKitID]) { - if (disputeKits[disputeKitID].parent != 0) { + + for (uint256 i = 0; i < SEARCH_ITERATIONS; i++) { + if (courts[dispute.subcourtID].supportedDisputeKits[disputeKitID]) { + break; + } else if (disputeKits[disputeKitID].parent != 0) { disputeKitID = disputeKits[disputeKitID].parent; + // If DK's parent has 0 index that means we reached the root DK (0 depth level). } 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[disputeKitID] = true, - "DK isn't supported by Forking court" - ); - dispute.subcourtID = uint96(FORKING_COURT); - break; + // Jump to the next parent court if the current court doesn't support any DK from this tree. + // Note that we don't reset disputeKitID in this case as, a precaution. + dispute.subcourtID = courts[dispute.subcourtID].parent; } } + // We didn't find a court that is compatible with DK from this tree, so we jump directly to the top court. + // Note that this can only happen when disputeKitID is at its root, and each root DK is supported by the top court by default. + if (!courts[dispute.subcourtID].supportedDisputeKits[disputeKitID]) { + dispute.subcourtID = uint96(FORKING_COURT); + } // Dispute kit was changed, so create a dispute in the new DK contract. if (disputeKitID != round.disputeKitID) { @@ -780,6 +812,14 @@ contract KlerosCore is IArbitrator { return courts[_subcourtID].supportedDisputeKits[_disputeKitID]; } + /** @dev Gets non-primitive properties of a specified dispute kit struct. + * @param _disputeKitID The ID of the dispute kit. + * @return children Indexes of children of this DK. + */ + function getDisputeKitStruct(uint256 _disputeKitID) external view returns (uint256[] memory) { + return disputeKits[_disputeKitID].children; + } + /** @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. From 61c7ae10ba7dcd6e919949341314c565de74b1cb Mon Sep 17 00:00:00 2001 From: jaybuidl Date: Sat, 16 Apr 2022 02:55:47 +0100 Subject: [PATCH 4/5] fix: minor fix, commented that changeDisputeKitParent() needs fixing --- contracts/src/arbitration/KlerosCore.sol | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/contracts/src/arbitration/KlerosCore.sol b/contracts/src/arbitration/KlerosCore.sol index 148551f61..01801909f 100644 --- a/contracts/src/arbitration/KlerosCore.sol +++ b/contracts/src/arbitration/KlerosCore.sol @@ -272,6 +272,8 @@ contract KlerosCore is IArbitrator { courts[FORKING_COURT].supportedDisputeKits[_disputeKitID] = true; } disputeKitNodes[_disputeKitID].parent = _newParent; + + // FIXME: update the children and depth } /** @dev Creates a subcourt under a specified parent court. @@ -572,7 +574,7 @@ contract KlerosCore is IArbitrator { for (uint256 i = 0; i < SEARCH_ITERATIONS; i++) { if (courts[newSubcourtID].supportedDisputeKits[newDisputeKitID]) { break; - } else if (disputeKitNodes[newDisputeKitID].parent != 0) { + } else if (disputeKitNodes[newDisputeKitID].parent != NULL_DISPUTE_KIT) { newDisputeKitID = disputeKitNodes[newDisputeKitID].parent; } else { // DK's parent has 0 index, that means we reached the root DK (0 depth level). From afa9248783ff1be8105138ebfb9bae7ad430238f Mon Sep 17 00:00:00 2001 From: jaybuidl Date: Thu, 19 May 2022 11:56:08 +0100 Subject: [PATCH 5/5] fix: removed work-in-progress for changeDisputeKitParent() to another branch --- contracts/src/arbitration/KlerosCore.sol | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/contracts/src/arbitration/KlerosCore.sol b/contracts/src/arbitration/KlerosCore.sol index 01801909f..037207e52 100644 --- a/contracts/src/arbitration/KlerosCore.sol +++ b/contracts/src/arbitration/KlerosCore.sol @@ -257,25 +257,6 @@ contract KlerosCore is IArbitrator { disputeKitNodes[_parent].children.push(disputeKitID); } - /** @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 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; - - // FIXME: update the children and depth - } - /** @dev Creates a subcourt under a specified parent court. * @param _parent The `parent` property value of the subcourt. * @param _hiddenVotes The `hiddenVotes` property value of the subcourt.