From 732847efa0591a1f29efc9b497091d2fa504ca8f Mon Sep 17 00:00:00 2001 From: unknownunknown1 Date: Mon, 13 Jun 2022 16:26:04 +1000 Subject: [PATCH 1/6] feat(KC): justification + small fixes --- contracts/src/arbitration/IDisputeKit.sol | 22 ++- contracts/src/arbitration/KlerosCore.sol | 174 +++++++++++++----- .../dispute-kits/BaseDisputeKit.sol | 5 +- .../dispute-kits/DisputeKitClassic.sol | 38 ++-- .../dispute-kits/DisputeKitSybilResistant.sol | 61 ++++-- 5 files changed, 220 insertions(+), 80 deletions(-) diff --git a/contracts/src/arbitration/IDisputeKit.sol b/contracts/src/arbitration/IDisputeKit.sol index a1e5eb876..463c561af 100644 --- a/contracts/src/arbitration/IDisputeKit.sol +++ b/contracts/src/arbitration/IDisputeKit.sol @@ -18,6 +18,24 @@ import "./IArbitrator.sol"; * It does not intend to abstract the interactions with the user (such as voting or appeal funding) to allow for implementation-specific parameters. */ interface IDisputeKit { + // ************************************ // + // * Events * // + // ************************************ // + + /** + * @dev Emitted when casting a vote to provide the justification of juror's choice. + * @param _coreDisputeID ID of the dispute in the core contract. + * @param _juror Address of the juror. + * @param _choice The choice juror voted for. + * @param _justification Justification of the choice. + */ + event Justification( + uint256 indexed _coreDisputeID, + address indexed _juror, + uint256 indexed _choice, + string _justification + ); + // ************************************* // // * State Modifiers * // // ************************************* // @@ -58,10 +76,10 @@ interface IDisputeKit { /** @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 winningChoice 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); + function getLastRoundResult(uint256 _coreDisputeID) external view returns (uint256 winningChoice, 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 _coreDisputeID The ID of the dispute in Kleros Core, not in the Dispute Kit. diff --git a/contracts/src/arbitration/KlerosCore.sol b/contracts/src/arbitration/KlerosCore.sol index 349d2928e..d063dccd0 100644 --- a/contracts/src/arbitration/KlerosCore.sol +++ b/contracts/src/arbitration/KlerosCore.sol @@ -18,6 +18,7 @@ import {SortitionSumTreeFactory} from "../data-structures/SortitionSumTreeFactor /** * @title KlerosCore * Core arbitrator contract for Kleros v2. + * Note that this contract trusts the token and the dispute kit contracts. */ contract KlerosCore is IArbitrator { using SortitionSumTreeFactory for SortitionSumTreeFactory.SortitionSumTrees; // Use library functions for sortition sum trees. @@ -71,7 +72,7 @@ contract KlerosCore is IArbitrator { } struct Juror { - uint96[] subcourtIDs; // The IDs of subcourts where the juror's stake path ends. A stake path is a path from the forking court to a court the juror directly staked in using `_setStake`. + uint96[] subcourtIDs; // The IDs of subcourts where the juror's stake path ends. A stake path is a path from the general court to a court the juror directly staked in using `_setStake`. mapping(uint96 => uint256) stakedTokens; // The number of tokens the juror has staked in the subcourt in the form `stakedTokens[subcourtID]`. mapping(uint96 => uint256) lockedTokens; // The number of tokens the juror has locked in the subcourt in the form `lockedTokens[subcourtID]`. } @@ -95,7 +96,8 @@ contract KlerosCore is IArbitrator { // * Storage * // // ************************************* // - uint256 public constant FORKING_COURT = 0; // Index of the default court. + uint256 public constant FORKING_COURT = 0; // Index of the forking court. + uint256 public constant GENERAL_COURT = 1; // Index of the default (general) 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. @@ -168,10 +170,10 @@ contract KlerosCore is IArbitrator { * @param _jurorProsecutionModule The address of the juror prosecution module. * @param _disputeKit The address of the default dispute kit. * @param _phaseTimeouts minStakingTime and maxFreezingTime respectively - * @param _hiddenVotes The `hiddenVotes` property value of the forking court. - * @param _courtParameters Numeric parameters of Forking court (minStake, alpha, feeForJuror and jurorsForCourtJump respectively). - * @param _timesPerPeriod The `timesPerPeriod` property value of the forking court. - * @param _sortitionSumTreeK The number of children per node of the forking court's sortition sum tree. + * @param _hiddenVotes The `hiddenVotes` property value of the general court. + * @param _courtParameters Numeric parameters of General court (minStake, alpha, feeForJuror and jurorsForCourtJump respectively). + * @param _timesPerPeriod The `timesPerPeriod` property value of the general court. + * @param _sortitionSumTreeK The number of children per node of the general court's sortition sum tree. */ constructor( address _governor, @@ -204,8 +206,12 @@ contract KlerosCore is IArbitrator { lastPhaseChange = block.timestamp; // Create the Forking court. + courts.push(); + // TODO: fill the properties for Forking court. + + // Create the General court. Court storage court = courts.push(); - court.parent = 0; + court.parent = 1; // TODO: Should the parent for General court be 0 or 1? In the former case the Forking court will become the top court after jumping. court.children = new uint256[](0); court.hiddenVotes = _hiddenVotes; court.minStake = _courtParameters[0]; @@ -274,16 +280,16 @@ 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 _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. + * Note that the root DK must be supported by the general court. */ function addNewDisputeKit(IDisputeKit _disputeKitAddress, uint256 _parent) external onlyByGovernor { uint256 disputeKitID = disputeKitNodes.length; require(_parent < disputeKitID, "Parent doesn't exist"); uint256 depthLevel; - // Create new tree, which root should be supported by Forking court. + // Create new tree, which root should be supported by General court. if (_parent == NULL_DISPUTE_KIT) { - courts[FORKING_COURT].supportedDisputeKits[disputeKitID] = true; + courts[GENERAL_COURT].supportedDisputeKits[disputeKitID] = true; } else { depthLevel = disputeKitNodes[_parent].depthLevel + 1; // It should be always possible to reach the root from the leaf with the defined number of search iterations. @@ -328,6 +334,7 @@ contract KlerosCore is IArbitrator { "A subcourt cannot be a child of a subcourt with a higher minimum stake." ); require(_supportedDisputeKits.length > 0, "Must support at least one DK"); + require(_parent != FORKING_COURT, "Can't have Forking court as a parent"); uint256 subcourtID = courts.length; Court storage court = courts.push(); @@ -359,7 +366,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 == FORKING_COURT || courts[courts[_subcourtID].parent].minStake <= _minStake); + require(_subcourtID == GENERAL_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, @@ -430,8 +437,8 @@ contract KlerosCore is IArbitrator { 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" + !(_subcourtID == GENERAL_COURT && disputeKitNodes[_disputeKitIDs[i]].parent == NULL_DISPUTE_KIT), + "Can't remove root DK support from the general court" ); subcourt.supportedDisputeKits[_disputeKitIDs[i]] = false; } @@ -469,6 +476,57 @@ contract KlerosCore is IArbitrator { delayedStakeReadIndex = newDelayedStakeReadIndex; } + /** @dev Allows an active dispute kit contract to manually unstake the ineligible juror. The main purpose of this function is to remove + * jurors that might obstruct the drawing process. + * @param _disputeID The ID of the dispute. + * @param _account The address of the juror. + * @return True if unstaking was successful. + */ + function unstakeByDK(uint256 _disputeID, address _account) external returns (bool) { + Dispute storage dispute = disputes[_disputeID]; + Round storage round = dispute.rounds[dispute.rounds.length - 1]; + uint96 subcourtID = dispute.subcourtID; + + require(msg.sender == address(disputeKitNodes[round.disputeKitID].disputeKit), "Can only be called by DK"); + require(dispute.period == Period.evidence, "The dispute should be in Evidence period."); + + Juror storage juror = jurors[_account]; + bytes32 stakePathID = accountAndSubcourtIDToStakePathID(_account, subcourtID); + uint256 currentStake = sortitionSumTrees.stakeOf(bytes32(uint256(subcourtID)), stakePathID); + + uint256 transferredAmount; + transferredAmount = currentStake - juror.lockedTokens[subcourtID]; + if (transferredAmount > 0) { + if (safeTransfer(_account, transferredAmount)) { + for (uint256 i = 0; i < juror.subcourtIDs.length; i++) { + if (juror.subcourtIDs[i] == subcourtID) { + juror.subcourtIDs[i] = juror.subcourtIDs[juror.subcourtIDs.length - 1]; + juror.subcourtIDs.pop(); + break; + } + } + } else { + return false; + } + } + + // Update juror's records. + juror.stakedTokens[subcourtID] = 0; + + // Update subcourt parents. + bool finished = false; + uint256 currentSubcourtID = subcourtID; + while (!finished) { + sortitionSumTrees.set(bytes32(currentSubcourtID), 0, stakePathID); + if (currentSubcourtID == GENERAL_COURT) finished = true; + else currentSubcourtID = courts[currentSubcourtID].parent; + } + + emit StakeSet(_account, subcourtID, 0, 0); + + return true; + } + /** @dev Creates a dispute. Must be called by the arbitrable contract. * @param _numberOfChoices Number of choices for the jurors to choose from. * @param _extraData Additional info about the dispute. We use it to pass the ID of the dispute's subcourt (first 32 bytes), @@ -674,7 +732,7 @@ contract KlerosCore is IArbitrator { // 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[newSubcourtID].supportedDisputeKits[newDisputeKitID]) { - newSubcourtID = uint96(FORKING_COURT); + newSubcourtID = uint96(GENERAL_COURT); } if (newSubcourtID != dispute.subcourtID) { @@ -782,7 +840,7 @@ contract KlerosCore is IArbitrator { if (coherentCount == 0) { // No one was coherent. Send the rewards to governor. payable(governor).send(round.totalFeesForJurors); - pinakion.transfer(governor, penaltiesInRoundCache); + safeTransfer(governor, penaltiesInRoundCache); } } } else { @@ -804,12 +862,12 @@ contract KlerosCore is IArbitrator { // Give back the locked tokens in case the juror fully unstaked earlier. if (jurors[account].stakedTokens[dispute.subcourtID] == 0) { uint256 tokenLocked = (round.tokensAtStakePerJuror * degreeOfCoherence) / ALPHA_DIVISOR; - pinakion.transfer(account, tokenLocked); + safeTransfer(account, tokenLocked); } uint256 tokenReward = ((penaltiesInRoundCache / coherentCount) * degreeOfCoherence) / ALPHA_DIVISOR; uint256 ethReward = ((round.totalFeesForJurors / coherentCount) * degreeOfCoherence) / ALPHA_DIVISOR; - pinakion.transfer(account, tokenReward); + safeTransfer(account, tokenReward); payable(account).send(ethReward); emit TokenAndETHShift(account, _disputeID, int256(tokenReward), int256(ethReward)); } @@ -858,8 +916,8 @@ contract KlerosCore is IArbitrator { Court storage court = courts[dispute.subcourtID]; if (round.nbVotes >= court.jurorsForCourtJump) { // Jump to parent subcourt. - if (dispute.subcourtID == FORKING_COURT) { - // Already in the forking court. + if (dispute.subcourtID == GENERAL_COURT) { + // TODO: Handle the forking when appealed in General court. cost = NON_PAYABLE_AMOUNT; // Get the cost of the parent subcourt. } else { cost = courts[court.parent].feeForJuror * ((round.nbVotes * 2) + 1); @@ -1063,8 +1121,7 @@ contract KlerosCore is IArbitrator { uint256 _stake, uint256 _penalty ) internal returns (bool succeeded) { - // Input and transfer checks - if (_subcourtID > courts.length) return false; + if (_subcourtID == FORKING_COURT || _subcourtID > courts.length) return false; Juror storage juror = jurors[_account]; bytes32 stakePathID = accountAndSubcourtIDToStakePathID(_account, _subcourtID); @@ -1091,33 +1148,35 @@ contract KlerosCore is IArbitrator { if (_stake >= currentStake) { transferredAmount = _stake - currentStake; if (transferredAmount > 0) { - // TODO: handle transfer reverts. - if (!pinakion.transferFrom(_account, address(this), transferredAmount)) return false; + if (safeTransferFrom(_account, address(this), transferredAmount)) { + if (currentStake == 0) { + juror.subcourtIDs.push(_subcourtID); + } + } else { + return false; + } } } else if (_stake == 0) { // Keep locked tokens in the contract and release them after dispute is executed. transferredAmount = currentStake - juror.lockedTokens[_subcourtID] - _penalty; if (transferredAmount > 0) { - if (!pinakion.transfer(_account, transferredAmount)) return false; + if (safeTransfer(_account, transferredAmount)) { + for (uint256 i = 0; i < juror.subcourtIDs.length; i++) { + if (juror.subcourtIDs[i] == _subcourtID) { + juror.subcourtIDs[i] = juror.subcourtIDs[juror.subcourtIDs.length - 1]; + juror.subcourtIDs.pop(); + break; + } + } + } else { + return false; + } } } else { transferredAmount = currentStake - _stake - _penalty; if (transferredAmount > 0) { - if (!pinakion.transfer(_account, transferredAmount)) return false; - } - } - - // State update - if (_stake != 0) { - if (currentStake == 0) { - juror.subcourtIDs.push(_subcourtID); - } - } else { - for (uint256 i = 0; i < juror.subcourtIDs.length; i++) { - if (juror.subcourtIDs[i] == _subcourtID) { - juror.subcourtIDs[i] = juror.subcourtIDs[juror.subcourtIDs.length - 1]; - juror.subcourtIDs.pop(); - break; + if (!safeTransfer(_account, transferredAmount)) { + return false; } } } @@ -1131,7 +1190,7 @@ contract KlerosCore is IArbitrator { uint256 currentSubcourtID = _subcourtID; while (!finished) { sortitionSumTrees.set(bytes32(currentSubcourtID), _stake, stakePathID); - if (currentSubcourtID == FORKING_COURT) finished = true; + if (currentSubcourtID == GENERAL_COURT) finished = true; else currentSubcourtID = courts[currentSubcourtID].parent; } @@ -1164,8 +1223,8 @@ contract KlerosCore is IArbitrator { minJurors := mload(add(_extraData, 0x40)) disputeKitID := mload(add(_extraData, 0x60)) } - if (subcourtID >= courts.length) { - subcourtID = uint96(FORKING_COURT); + if (subcourtID == FORKING_COURT || subcourtID >= courts.length) { + subcourtID = uint96(GENERAL_COURT); } if (minJurors == 0) { minJurors = MIN_JURORS; @@ -1174,7 +1233,7 @@ contract KlerosCore is IArbitrator { disputeKitID = DISPUTE_KIT_CLASSIC_INDEX; // 0 index is not used. } } else { - subcourtID = uint96(FORKING_COURT); + subcourtID = uint96(GENERAL_COURT); minJurors = MIN_JURORS; disputeKitID = DISPUTE_KIT_CLASSIC_INDEX; } @@ -1210,4 +1269,33 @@ contract KlerosCore is IArbitrator { stakePathID := mload(ptr) } } + + /** @dev Calls transfer() without reverting. + * @param _to Recepient address. + * @param _value Amount transferred. + * @return Whether transfer succeeded or not. + */ + function safeTransfer(address _to, uint256 _value) internal returns (bool) { + (bool success, bytes memory data) = address(pinakion).call( + abi.encodeWithSelector(IERC20.transfer.selector, _to, _value) + ); + return (success && (data.length == 0 || abi.decode(data, (bool)))); + } + + /** @dev Calls transferFrom() without reverting. + * @param _from Sender address. + * @param _to Recepient address. + * @param _value Amount transferred. + * @return Whether transfer succeeded or not. + */ + function safeTransferFrom( + address _from, + address _to, + uint256 _value + ) internal returns (bool) { + (bool success, bytes memory data) = address(pinakion).call( + abi.encodeWithSelector(IERC20.transferFrom.selector, _from, _to, _value) + ); + return (success && (data.length == 0 || abi.decode(data, (bool)))); + } } diff --git a/contracts/src/arbitration/dispute-kits/BaseDisputeKit.sol b/contracts/src/arbitration/dispute-kits/BaseDisputeKit.sol index 9f56db46b..ce3190a65 100644 --- a/contracts/src/arbitration/dispute-kits/BaseDisputeKit.sol +++ b/contracts/src/arbitration/dispute-kits/BaseDisputeKit.sol @@ -67,8 +67,9 @@ abstract contract BaseDisputeKit is IDisputeKit { } /** @dev Checks that the chosen address satisfies certain conditions for being drawn. - * @param _disputeID ID of the dispute in the core contract. + * @param _coreDisputeID ID of the dispute in the core contract. * @param _juror Chosen address. + * @return Whether the address can be drawn or not. */ - function postDrawCheck(uint256 _disputeID, address _juror) internal virtual returns (bool); + function postDrawCheck(uint256 _coreDisputeID, address _juror) internal virtual returns (bool); } diff --git a/contracts/src/arbitration/dispute-kits/DisputeKitClassic.sol b/contracts/src/arbitration/dispute-kits/DisputeKitClassic.sol index 3929be284..1891e6282 100644 --- a/contracts/src/arbitration/dispute-kits/DisputeKitClassic.sol +++ b/contracts/src/arbitration/dispute-kits/DisputeKitClassic.sol @@ -85,22 +85,22 @@ contract DisputeKitClassic is BaseDisputeKit, IEvidence { // ************************************* // event Contribution( - uint256 indexed _disputeID, - uint256 indexed _round, + uint256 indexed _coreDisputeID, + uint256 indexed _coreRoundID, uint256 _choice, address indexed _contributor, uint256 _amount ); event Withdrawal( - uint256 indexed _disputeID, - uint256 indexed _round, + uint256 indexed _coreDisputeID, + uint256 indexed _coreRoundID, uint256 _choice, address indexed _contributor, uint256 _amount ); - event ChoiceFunded(uint256 indexed _disputeID, uint256 indexed _round, uint256 indexed _choice); + event ChoiceFunded(uint256 indexed _coreDisputeID, uint256 indexed _coreRoundID, uint256 indexed _choice); event NewPhaseDisputeKit(Phase _phase); // ************************************* // @@ -302,12 +302,14 @@ contract DisputeKitClassic is BaseDisputeKit, IEvidence { * @param _voteIDs The IDs of the votes. * @param _choice The choice. * @param _salt The salt for the commit if the votes were hidden. + * @param _justification Justification of the choice. */ function castVote( uint256 _coreDisputeID, uint256[] calldata _voteIDs, uint256 _choice, - uint256 _salt + uint256 _salt, + string memory _justification ) external notJumped(_coreDisputeID) { require( core.getCurrentPeriod(_coreDisputeID) == KlerosCore.Period.vote, @@ -349,6 +351,7 @@ contract DisputeKitClassic is BaseDisputeKit, IEvidence { round.tied = false; } } + emit Justification(_coreDisputeID, msg.sender, _choice, _justification); } /** @dev Manages contributions, and appeals a dispute if at least two choices are fully funded. @@ -376,6 +379,8 @@ contract DisputeKitClassic is BaseDisputeKit, IEvidence { } Round storage round = dispute.rounds[dispute.rounds.length - 1]; + uint256 coreRoundID = core.getNumberOfRounds(_coreDisputeID) - 1; + require(!round.hasPaid[_choice], "Appeal fee is already paid."); uint256 appealCost = core.appealCost(_coreDisputeID); uint256 totalCost = appealCost + (appealCost * multiplier) / ONE_BASIS_POINT; @@ -386,7 +391,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(_coreDisputeID, dispute.rounds.length - 1, _choice, msg.sender, contribution); + emit Contribution(_coreDisputeID, coreRoundID, _choice, msg.sender, contribution); } round.contributions[msg.sender][_choice] += contribution; @@ -395,7 +400,7 @@ contract DisputeKitClassic is BaseDisputeKit, IEvidence { round.feeRewards += round.paidFees[_choice]; round.fundedChoices.push(_choice); round.hasPaid[_choice] = true; - emit ChoiceFunded(_coreDisputeID, dispute.rounds.length - 1, _choice); + emit ChoiceFunded(_coreDisputeID, coreRoundID, _choice); } if (round.fundedChoices.length > 1) { @@ -407,7 +412,7 @@ contract DisputeKitClassic is BaseDisputeKit, IEvidence { 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; + dispute.coreRoundIDToLocal[coreRoundID + 1] = dispute.rounds.length; Round storage newRound = dispute.rounds.push(); newRound.nbVotes = core.getNumberOfVotes(_coreDisputeID); @@ -423,20 +428,20 @@ contract DisputeKitClassic is BaseDisputeKit, IEvidence { /** @dev Allows those contributors who attempted to fund an appeal round to withdraw any reimbursable fees or rewards after the dispute gets resolved. * @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 _coreRoundID The round in the Kleros Core contract 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 _coreDisputeID, address payable _beneficiary, - uint256 _round, + uint256 _coreRoundID, uint256 _choice ) external returns (uint256 amount) { require(core.isRuled(_coreDisputeID), "Dispute should be resolved."); Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; - Round storage round = dispute.rounds[_round]; + Round storage round = dispute.rounds[dispute.coreRoundIDToLocal[_coreRoundID]]; uint256 finalRuling = core.currentRuling(_coreDisputeID); if (!round.hasPaid[_choice]) { @@ -460,7 +465,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(_coreDisputeID, _round, _choice, _beneficiary, amount); + emit Withdrawal(_coreDisputeID, _coreRoundID, _choice, _beneficiary, amount); } } @@ -490,7 +495,7 @@ contract DisputeKitClassic is BaseDisputeKit, IEvidence { external view override - returns (uint256 winningChoiece, bool tied) + returns (uint256 winningChoice, bool tied) { Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; Round storage lastRound = dispute.rounds[dispute.rounds.length - 1]; @@ -632,6 +637,11 @@ contract DisputeKitClassic is BaseDisputeKit, IEvidence { // * Internal * // // ************************************* // + /** @dev Checks that the chosen address satisfies certain conditions for being drawn. + * @param _coreDisputeID ID of the dispute in the core contract. + * @param _juror Chosen address. + * @return Whether the address can be drawn or not. + */ function postDrawCheck(uint256 _coreDisputeID, address _juror) internal view override returns (bool) { uint256 subcourtID = core.getSubcourtID(_coreDisputeID); (uint256 lockedAmountPerJuror, , , , , ) = core.getRoundInfo( diff --git a/contracts/src/arbitration/dispute-kits/DisputeKitSybilResistant.sol b/contracts/src/arbitration/dispute-kits/DisputeKitSybilResistant.sol index 990b50029..94a55b8c8 100644 --- a/contracts/src/arbitration/dispute-kits/DisputeKitSybilResistant.sol +++ b/contracts/src/arbitration/dispute-kits/DisputeKitSybilResistant.sol @@ -62,6 +62,7 @@ contract DisputeKitSybilResistant is BaseDisputeKit, IEvidence { uint256 feeRewards; // Sum of reimbursable appeal fees available to the parties that made contributions to the ruling that ultimately wins a dispute. uint256[] fundedChoices; // Stores the choices that are fully funded. uint256 nbVotes; // Maximal number of votes this dispute can get. + mapping(address => bool) alreadyDrawn; // Set to 'true' if the address has already been drawn, so it can't be drawn more than once. } struct Vote { @@ -94,22 +95,22 @@ contract DisputeKitSybilResistant is BaseDisputeKit, IEvidence { // ************************************* // event Contribution( - uint256 indexed _disputeID, - uint256 indexed _round, + uint256 indexed _coreDisputeID, + uint256 indexed _coreRoundID, uint256 _choice, address indexed _contributor, uint256 _amount ); event Withdrawal( - uint256 indexed _disputeID, - uint256 indexed _round, + uint256 indexed _coreDisputeID, + uint256 indexed _coreRoundID, uint256 _choice, address indexed _contributor, uint256 _amount ); - event ChoiceFunded(uint256 indexed _disputeID, uint256 indexed _round, uint256 indexed _choice); + event ChoiceFunded(uint256 indexed _coreDisputeID, uint256 indexed _coreRoundID, uint256 indexed _choice); event NewPhaseDisputeKit(Phase _phase); // ************************************* // @@ -229,6 +230,16 @@ contract DisputeKitSybilResistant is BaseDisputeKit, IEvidence { emit NewPhaseDisputeKit(phase); } + /** @dev Allows to unstake the juror that obstructs drawing process. + * @param _coreDisputeID The ID of the dispute in Kleros Core which drawing process is obstructed. + * @param _juror The address of the juror to unstake. + */ + function unstakeJuror(uint256 _coreDisputeID, address _juror) external { + require(phase == Phase.drawing, "Should be in drawing phase"); + require(!postDrawCheck(_coreDisputeID, _juror), "The juror is eligible to drawing"); + core.unstakeByDK(_coreDisputeID, _juror); + } + /** @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 _coreDisputeID The ID of the dispute in Kleros Core. @@ -275,11 +286,9 @@ contract DisputeKitSybilResistant is BaseDisputeKit, IEvidence { bytes32 ID = core.getSortitionSumTreeID(key, treeIndex); drawnAddress = stakePathIDToAccount(ID); - // TODO: deduplicate the list of all the drawn humans before moving to the next period !! - if (!proofOfHumanity(drawnAddress)) drawnAddress = address(0); - - if (postDrawCheck(_coreDisputeID, drawnAddress)) { + if (postDrawCheck(_coreDisputeID, drawnAddress) && !round.alreadyDrawn[drawnAddress]) { round.votes.push(Vote({account: drawnAddress, commit: bytes32(0), choice: 0, voted: false})); + round.alreadyDrawn[drawnAddress] = true; if (round.votes.length == round.nbVotes) { disputesWithoutJurors--; } @@ -323,12 +332,14 @@ contract DisputeKitSybilResistant is BaseDisputeKit, IEvidence { * @param _voteIDs The IDs of the votes. * @param _choice The choice. * @param _salt The salt for the commit if the votes were hidden. + * @param _justification Justification of the choice. */ function castVote( uint256 _coreDisputeID, uint256[] calldata _voteIDs, uint256 _choice, - uint256 _salt + uint256 _salt, + string calldata _justification ) external notJumped(_coreDisputeID) { require( core.getCurrentPeriod(_coreDisputeID) == KlerosCore.Period.vote, @@ -370,6 +381,7 @@ contract DisputeKitSybilResistant is BaseDisputeKit, IEvidence { round.tied = false; } } + emit Justification(_coreDisputeID, msg.sender, _choice, _justification); } /** @dev Manages contributions, and appeals a dispute if at least two choices are fully funded. @@ -397,6 +409,8 @@ contract DisputeKitSybilResistant is BaseDisputeKit, IEvidence { } Round storage round = dispute.rounds[dispute.rounds.length - 1]; + uint256 coreRoundID = core.getNumberOfRounds(_coreDisputeID) - 1; + require(!round.hasPaid[_choice], "Appeal fee is already paid."); uint256 appealCost = core.appealCost(_coreDisputeID); uint256 totalCost = appealCost + (appealCost * multiplier) / ONE_BASIS_POINT; @@ -407,7 +421,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(_coreDisputeID, dispute.rounds.length - 1, _choice, msg.sender, contribution); + emit Contribution(_coreDisputeID, coreRoundID, _choice, msg.sender, contribution); } round.contributions[msg.sender][_choice] += contribution; @@ -416,7 +430,7 @@ contract DisputeKitSybilResistant is BaseDisputeKit, IEvidence { round.feeRewards += round.paidFees[_choice]; round.fundedChoices.push(_choice); round.hasPaid[_choice] = true; - emit ChoiceFunded(_coreDisputeID, dispute.rounds.length - 1, _choice); + emit ChoiceFunded(_coreDisputeID, coreRoundID, _choice); } if (round.fundedChoices.length > 1) { @@ -428,7 +442,7 @@ contract DisputeKitSybilResistant is BaseDisputeKit, IEvidence { 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; + dispute.coreRoundIDToLocal[coreRoundID + 1] = dispute.rounds.length; Round storage newRound = dispute.rounds.push(); newRound.nbVotes = core.getNumberOfVotes(_coreDisputeID); @@ -444,20 +458,20 @@ contract DisputeKitSybilResistant is BaseDisputeKit, IEvidence { /** @dev Allows those contributors who attempted to fund an appeal round to withdraw any reimbursable fees or rewards after the dispute gets resolved. * @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 _coreRoundID The round in the Kleros Core contract 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 _coreDisputeID, address payable _beneficiary, - uint256 _round, + uint256 _coreRoundID, uint256 _choice ) external returns (uint256 amount) { require(core.isRuled(_coreDisputeID), "Dispute should be resolved."); Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; - Round storage round = dispute.rounds[_round]; + Round storage round = dispute.rounds[dispute.coreRoundIDToLocal[_coreRoundID]]; uint256 finalRuling = core.currentRuling(_coreDisputeID); if (!round.hasPaid[_choice]) { @@ -481,7 +495,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(_coreDisputeID, _round, _choice, _beneficiary, amount); + emit Withdrawal(_coreDisputeID, _coreRoundID, _choice, _beneficiary, amount); } } @@ -511,7 +525,7 @@ contract DisputeKitSybilResistant is BaseDisputeKit, IEvidence { external view override - returns (uint256 winningChoiece, bool tied) + returns (uint256 winningChoice, bool tied) { Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; Round storage lastRound = dispute.rounds[dispute.rounds.length - 1]; @@ -653,6 +667,11 @@ contract DisputeKitSybilResistant is BaseDisputeKit, IEvidence { // * Internal * // // ************************************* // + /** @dev Checks that the chosen address satisfies certain conditions for being drawn. + * @param _coreDisputeID ID of the dispute in the core contract. + * @param _juror Chosen address. + * @return Whether the address can be drawn or not. + */ function postDrawCheck(uint256 _coreDisputeID, address _juror) internal view override returns (bool) { uint256 subcourtID = core.getSubcourtID(_coreDisputeID); (uint256 lockedAmountPerJuror, , , , , ) = core.getRoundInfo( @@ -660,7 +679,11 @@ contract DisputeKitSybilResistant is BaseDisputeKit, IEvidence { core.getNumberOfRounds(_coreDisputeID) - 1 ); (uint256 stakedTokens, uint256 lockedTokens) = core.getJurorBalance(_juror, uint96(subcourtID)); - return stakedTokens >= lockedTokens + lockedAmountPerJuror; + if (stakedTokens < lockedTokens + lockedAmountPerJuror) { + return false; + } else { + return proofOfHumanity(_juror); + } } /** @dev Checks if an address belongs to the Proof of Humanity registry. From 2947180052bf744bd084f786d311c6dbc0c9e106 Mon Sep 17 00:00:00 2001 From: unknownunknown1 Date: Tue, 14 Jun 2022 16:30:05 +1000 Subject: [PATCH 2/6] feat(DK): add justitication to commit --- contracts/src/arbitration/dispute-kits/DisputeKitClassic.sol | 5 +++-- .../arbitration/dispute-kits/DisputeKitSybilResistant.sol | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/contracts/src/arbitration/dispute-kits/DisputeKitClassic.sol b/contracts/src/arbitration/dispute-kits/DisputeKitClassic.sol index 1891e6282..185b6663b 100644 --- a/contracts/src/arbitration/dispute-kits/DisputeKitClassic.sol +++ b/contracts/src/arbitration/dispute-kits/DisputeKitClassic.sol @@ -272,7 +272,7 @@ contract DisputeKitClassic is BaseDisputeKit, IEvidence { * `n` is the number of votes. * @param _coreDisputeID The ID of the dispute in Kleros Core. * @param _voteIDs The IDs of the votes. - * @param _commit The commit. + * @param _commit The commit. Note that justification string is a part of the commit. */ function castCommit( uint256 _coreDisputeID, @@ -327,7 +327,8 @@ contract DisputeKitClassic is BaseDisputeKit, IEvidence { for (uint256 i = 0; i < _voteIDs.length; i++) { require(round.votes[_voteIDs[i]].account == msg.sender, "The caller has to own the vote."); require( - !hiddenVotes || round.votes[_voteIDs[i]].commit == keccak256(abi.encodePacked(_choice, _salt)), + !hiddenVotes || + round.votes[_voteIDs[i]].commit == keccak256(abi.encodePacked(_choice, _justification, _salt)), "The commit must match the choice in subcourts with hidden votes." ); require(!round.votes[_voteIDs[i]].voted, "Vote already cast."); diff --git a/contracts/src/arbitration/dispute-kits/DisputeKitSybilResistant.sol b/contracts/src/arbitration/dispute-kits/DisputeKitSybilResistant.sol index 94a55b8c8..ab1e85c15 100644 --- a/contracts/src/arbitration/dispute-kits/DisputeKitSybilResistant.sol +++ b/contracts/src/arbitration/dispute-kits/DisputeKitSybilResistant.sol @@ -302,7 +302,7 @@ contract DisputeKitSybilResistant is BaseDisputeKit, IEvidence { * `n` is the number of votes. * @param _coreDisputeID The ID of the dispute in Kleros Core. * @param _voteIDs The IDs of the votes. - * @param _commit The commit. + * @param _commit The commit. Note that justification string is a part of the commit. */ function castCommit( uint256 _coreDisputeID, @@ -357,7 +357,8 @@ contract DisputeKitSybilResistant is BaseDisputeKit, IEvidence { for (uint256 i = 0; i < _voteIDs.length; i++) { require(round.votes[_voteIDs[i]].account == msg.sender, "The caller has to own the vote."); require( - !hiddenVotes || round.votes[_voteIDs[i]].commit == keccak256(abi.encodePacked(_choice, _salt)), + !hiddenVotes || + round.votes[_voteIDs[i]].commit == keccak256(abi.encodePacked(_choice, _justification, _salt)), "The commit must match the choice in subcourts with hidden votes." ); require(!round.votes[_voteIDs[i]].voted, "Vote already cast."); From 75c95102089e67644c8598f6f6ee49f35d60c642 Mon Sep 17 00:00:00 2001 From: unknownunknown1 Date: Tue, 14 Jun 2022 17:30:49 +1000 Subject: [PATCH 3/6] fix(KC): createTree bug --- 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 d063dccd0..c43f41536 100644 --- a/contracts/src/arbitration/KlerosCore.sol +++ b/contracts/src/arbitration/KlerosCore.sol @@ -221,7 +221,9 @@ contract KlerosCore is IArbitrator { court.timesPerPeriod = _timesPerPeriod; court.supportedDisputeKits[DISPUTE_KIT_CLASSIC_INDEX] = true; - sortitionSumTrees.createTree(bytes32(0), _sortitionSumTreeK); + // TODO: fill the properties for Forking court. + sortitionSumTrees.createTree(bytes32(FORKING_COURT), _sortitionSumTreeK); + sortitionSumTrees.createTree(bytes32(GENERAL_COURT), _sortitionSumTreeK); } // ************************ // From 333169d17b9a4a36a9cb76069990026381791c1b Mon Sep 17 00:00:00 2001 From: unknownunknown1 Date: Tue, 14 Jun 2022 17:31:49 +1000 Subject: [PATCH 4/6] fix(KC): test file fix --- contracts/test/integration/index.ts | 60 ++++++++++++++--------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/contracts/test/integration/index.ts b/contracts/test/integration/index.ts index 4fbbc0bd9..c675fbd3a 100644 --- a/contracts/test/integration/index.ts +++ b/contracts/test/integration/index.ts @@ -87,33 +87,33 @@ describe("Demo pre-alpha1", function () { await pnk.approve(core.address, ONE_THOUSAND_PNK.mul(100)); - await core.setStake(0, ONE_THOUSAND_PNK); - await core.getJurorBalance(deployer, 0).then((result) => { + await core.setStake(1, ONE_THOUSAND_PNK); + await core.getJurorBalance(deployer, 1).then((result) => { expect(result.staked).to.equal(ONE_THOUSAND_PNK); expect(result.locked).to.equal(0); logJurorBalance(result); }); - await core.setStake(0, ONE_HUNDRED_PNK.mul(5)); - await core.getJurorBalance(deployer, 0).then((result) => { + await core.setStake(1, ONE_HUNDRED_PNK.mul(5)); + await core.getJurorBalance(deployer, 1).then((result) => { expect(result.staked).to.equal(ONE_HUNDRED_PNK.mul(5)); expect(result.locked).to.equal(0); logJurorBalance(result); }); - await core.setStake(0, 0); - await core.getJurorBalance(deployer, 0).then((result) => { + await core.setStake(1, 0); + await core.getJurorBalance(deployer, 1).then((result) => { expect(result.staked).to.equal(0); expect(result.locked).to.equal(0); logJurorBalance(result); }); - await core.setStake(0, ONE_THOUSAND_PNK.mul(4)); - await core.getJurorBalance(deployer, 0).then((result) => { + await core.setStake(1, ONE_THOUSAND_PNK.mul(4)); + await core.getJurorBalance(deployer, 1).then((result) => { expect(result.staked).to.equal(ONE_THOUSAND_PNK.mul(4)); expect(result.locked).to.equal(0); logJurorBalance(result); - }); + }); const tx = await arbitrable.createDispute(2, "0x00", 0, { value: arbitrationCost }); const trace = await network.provider.send("debug_traceTransaction", [tx.hash]); const [disputeId] = ethers.utils.defaultAbiCoder.decode(["uint"], `0x${trace.returnValue}`); @@ -141,7 +141,7 @@ describe("Demo pre-alpha1", function () { await network.provider.send("evm_increaseTime", [130]); // Wait for minStakingTime await network.provider.send("evm_mine"); - + expect(await core.phase()).to.equal(Phase.staking); expect(await disputeKit.phase()).to.equal(DisputeKitPhase.resolving); expect(await disputeKit.disputesWithoutJurors()).to.equal(1); @@ -176,14 +176,14 @@ describe("Demo pre-alpha1", function () { await core.passPeriod(0); expect((await core.disputes(0)).period).to.equal(Period.vote); - await disputeKit.connect(await ethers.getSigner(deployer)).castVote(0, [0, 1, 2], 0, 0); + await disputeKit.connect(await ethers.getSigner(deployer)).castVote(0, [0, 1, 2], 0, 0, ""); await core.passPeriod(0); await core.passPeriod(0); expect((await core.disputes(0)).period).to.equal(Period.execution); await core.execute(0, 0, 1000); const ticket1 = await fastBridgeSender.currentTicketID(); expect(ticket1).to.equal(1); - + const tx4 = await core.executeRuling(0); expect(tx4).to.emit(fastBridgeSender, "OutgoingMessage"); @@ -225,29 +225,29 @@ describe("Demo pre-alpha1", function () { console.log("KC phase: %d, DK phase: ", await core.phase(), await disputeKit.phase()); - await core.setStake(0, ONE_THOUSAND_PNK); - await core.getJurorBalance(deployer, 0).then((result) => { + await core.setStake(1, ONE_THOUSAND_PNK); + await core.getJurorBalance(deployer, 1).then((result) => { expect(result.staked).to.equal(ONE_THOUSAND_PNK); expect(result.locked).to.equal(0); logJurorBalance(result); }); - await core.setStake(0, ONE_HUNDRED_PNK.mul(5)); - await core.getJurorBalance(deployer, 0).then((result) => { + await core.setStake(1, ONE_HUNDRED_PNK.mul(5)); + await core.getJurorBalance(deployer, 1).then((result) => { expect(result.staked).to.equal(ONE_HUNDRED_PNK.mul(5)); expect(result.locked).to.equal(0); logJurorBalance(result); }); - await core.setStake(0, 0); - await core.getJurorBalance(deployer, 0).then((result) => { + await core.setStake(1, 0); + await core.getJurorBalance(deployer, 1).then((result) => { expect(result.staked).to.equal(0); expect(result.locked).to.equal(0); logJurorBalance(result); }); - await core.setStake(0, ONE_THOUSAND_PNK.mul(4)); - await core.getJurorBalance(deployer, 0).then((result) => { + await core.setStake(1, ONE_THOUSAND_PNK.mul(4)); + await core.getJurorBalance(deployer, 1).then((result) => { expect(result.staked).to.equal(ONE_THOUSAND_PNK.mul(4)); expect(result.locked).to.equal(0); logJurorBalance(result); @@ -342,7 +342,7 @@ describe("Demo pre-alpha1", function () { console.log("KC phase: %d, DK phase: ", await core.phase(), await disputeKit.phase()); - await disputeKit.connect(await ethers.getSigner(deployer)).castVote(0, [0, 1, 2], 0, 0); + await disputeKit.connect(await ethers.getSigner(deployer)).castVote(0, [0, 1, 2], 0, 0, ""); await core.passPeriod(0); await core.passPeriod(0); expect((await core.disputes(0)).period).to.equal(Period.execution); @@ -419,29 +419,29 @@ describe("Demo pre-alpha1", function () { await pnk.approve(core.address, ONE_THOUSAND_PNK.mul(100)); - await core.setStake(0, ONE_THOUSAND_PNK); - await core.getJurorBalance(deployer, 0).then((result) => { + await core.setStake(1, ONE_THOUSAND_PNK); + await core.getJurorBalance(deployer, 1).then((result) => { expect(result.staked).to.equal(ONE_THOUSAND_PNK); expect(result.locked).to.equal(0); logJurorBalance(result); }); - await core.setStake(0, ONE_HUNDRED_PNK.mul(5)); - await core.getJurorBalance(deployer, 0).then((result) => { + await core.setStake(1, ONE_HUNDRED_PNK.mul(5)); + await core.getJurorBalance(deployer, 1).then((result) => { expect(result.staked).to.equal(ONE_HUNDRED_PNK.mul(5)); expect(result.locked).to.equal(0); logJurorBalance(result); }); - await core.setStake(0, 0); - await core.getJurorBalance(deployer, 0).then((result) => { + await core.setStake(1, 0); + await core.getJurorBalance(deployer, 1).then((result) => { expect(result.staked).to.equal(0); expect(result.locked).to.equal(0); logJurorBalance(result); }); - await core.setStake(0, ONE_THOUSAND_PNK.mul(4)); - await core.getJurorBalance(deployer, 0).then((result) => { + await core.setStake(1, ONE_THOUSAND_PNK.mul(4)); + await core.getJurorBalance(deployer, 1).then((result) => { expect(result.staked).to.equal(ONE_THOUSAND_PNK.mul(4)); expect(result.locked).to.equal(0); logJurorBalance(result); @@ -507,7 +507,7 @@ describe("Demo pre-alpha1", function () { await core.passPeriod(coreId); expect((await core.disputes(coreId)).period).to.equal(Period.vote); - await disputeKit.connect(await ethers.getSigner(deployer)).castVote(coreId, [0, 1, 2], 0, 0); + await disputeKit.connect(await ethers.getSigner(deployer)).castVote(coreId, [0, 1, 2], 0, 0, ""); await core.passPeriod(coreId); await core.passPeriod(coreId); expect((await core.disputes(coreId)).period).to.equal(Period.execution); From 51ec1d2894560781371fa8ca97e2b5f08311bed3 Mon Sep 17 00:00:00 2001 From: unknownunknown1 Date: Tue, 14 Jun 2022 17:46:05 +1000 Subject: [PATCH 5/6] style(test): remove empty lines --- contracts/test/integration/index.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/contracts/test/integration/index.ts b/contracts/test/integration/index.ts index c675fbd3a..d71d1a383 100644 --- a/contracts/test/integration/index.ts +++ b/contracts/test/integration/index.ts @@ -113,7 +113,7 @@ describe("Demo pre-alpha1", function () { expect(result.staked).to.equal(ONE_THOUSAND_PNK.mul(4)); expect(result.locked).to.equal(0); logJurorBalance(result); - }); + }); const tx = await arbitrable.createDispute(2, "0x00", 0, { value: arbitrationCost }); const trace = await network.provider.send("debug_traceTransaction", [tx.hash]); const [disputeId] = ethers.utils.defaultAbiCoder.decode(["uint"], `0x${trace.returnValue}`); @@ -141,7 +141,6 @@ describe("Demo pre-alpha1", function () { await network.provider.send("evm_increaseTime", [130]); // Wait for minStakingTime await network.provider.send("evm_mine"); - expect(await core.phase()).to.equal(Phase.staking); expect(await disputeKit.phase()).to.equal(DisputeKitPhase.resolving); expect(await disputeKit.disputesWithoutJurors()).to.equal(1); @@ -183,7 +182,6 @@ describe("Demo pre-alpha1", function () { await core.execute(0, 0, 1000); const ticket1 = await fastBridgeSender.currentTicketID(); expect(ticket1).to.equal(1); - const tx4 = await core.executeRuling(0); expect(tx4).to.emit(fastBridgeSender, "OutgoingMessage"); From f66ab4bfb8171f2b43a8a699fc64dbd885b69f0a Mon Sep 17 00:00:00 2001 From: unknownunknown1 Date: Wed, 15 Jun 2022 18:53:29 +1000 Subject: [PATCH 6/6] feat(KC): remove unstaking feature --- contracts/src/arbitration/KlerosCore.sol | 51 ------------------- .../dispute-kits/DisputeKitSybilResistant.sol | 10 ---- 2 files changed, 61 deletions(-) diff --git a/contracts/src/arbitration/KlerosCore.sol b/contracts/src/arbitration/KlerosCore.sol index c43f41536..bf467b530 100644 --- a/contracts/src/arbitration/KlerosCore.sol +++ b/contracts/src/arbitration/KlerosCore.sol @@ -478,57 +478,6 @@ contract KlerosCore is IArbitrator { delayedStakeReadIndex = newDelayedStakeReadIndex; } - /** @dev Allows an active dispute kit contract to manually unstake the ineligible juror. The main purpose of this function is to remove - * jurors that might obstruct the drawing process. - * @param _disputeID The ID of the dispute. - * @param _account The address of the juror. - * @return True if unstaking was successful. - */ - function unstakeByDK(uint256 _disputeID, address _account) external returns (bool) { - Dispute storage dispute = disputes[_disputeID]; - Round storage round = dispute.rounds[dispute.rounds.length - 1]; - uint96 subcourtID = dispute.subcourtID; - - require(msg.sender == address(disputeKitNodes[round.disputeKitID].disputeKit), "Can only be called by DK"); - require(dispute.period == Period.evidence, "The dispute should be in Evidence period."); - - Juror storage juror = jurors[_account]; - bytes32 stakePathID = accountAndSubcourtIDToStakePathID(_account, subcourtID); - uint256 currentStake = sortitionSumTrees.stakeOf(bytes32(uint256(subcourtID)), stakePathID); - - uint256 transferredAmount; - transferredAmount = currentStake - juror.lockedTokens[subcourtID]; - if (transferredAmount > 0) { - if (safeTransfer(_account, transferredAmount)) { - for (uint256 i = 0; i < juror.subcourtIDs.length; i++) { - if (juror.subcourtIDs[i] == subcourtID) { - juror.subcourtIDs[i] = juror.subcourtIDs[juror.subcourtIDs.length - 1]; - juror.subcourtIDs.pop(); - break; - } - } - } else { - return false; - } - } - - // Update juror's records. - juror.stakedTokens[subcourtID] = 0; - - // Update subcourt parents. - bool finished = false; - uint256 currentSubcourtID = subcourtID; - while (!finished) { - sortitionSumTrees.set(bytes32(currentSubcourtID), 0, stakePathID); - if (currentSubcourtID == GENERAL_COURT) finished = true; - else currentSubcourtID = courts[currentSubcourtID].parent; - } - - emit StakeSet(_account, subcourtID, 0, 0); - - return true; - } - /** @dev Creates a dispute. Must be called by the arbitrable contract. * @param _numberOfChoices Number of choices for the jurors to choose from. * @param _extraData Additional info about the dispute. We use it to pass the ID of the dispute's subcourt (first 32 bytes), diff --git a/contracts/src/arbitration/dispute-kits/DisputeKitSybilResistant.sol b/contracts/src/arbitration/dispute-kits/DisputeKitSybilResistant.sol index ab1e85c15..a81418639 100644 --- a/contracts/src/arbitration/dispute-kits/DisputeKitSybilResistant.sol +++ b/contracts/src/arbitration/dispute-kits/DisputeKitSybilResistant.sol @@ -230,16 +230,6 @@ contract DisputeKitSybilResistant is BaseDisputeKit, IEvidence { emit NewPhaseDisputeKit(phase); } - /** @dev Allows to unstake the juror that obstructs drawing process. - * @param _coreDisputeID The ID of the dispute in Kleros Core which drawing process is obstructed. - * @param _juror The address of the juror to unstake. - */ - function unstakeJuror(uint256 _coreDisputeID, address _juror) external { - require(phase == Phase.drawing, "Should be in drawing phase"); - require(!postDrawCheck(_coreDisputeID, _juror), "The juror is eligible to drawing"); - core.unstakeByDK(_coreDisputeID, _juror); - } - /** @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 _coreDisputeID The ID of the dispute in Kleros Core.