diff --git a/contracts/CHANGELOG.md b/contracts/CHANGELOG.md index bea677f34..751bd5473 100644 --- a/contracts/CHANGELOG.md +++ b/contracts/CHANGELOG.md @@ -23,6 +23,10 @@ The format is based on [Common Changelog](https://common-changelog.org/). - Bump `hardhat` to v2.26.2 ([#2069](https://github.com/kleros/kleros-v2/issues/2069)) - Bump `@kleros/vea-contracts` to v0.7.0 ([#2073](https://github.com/kleros/kleros-v2/issues/2073)) +### Added + +- Allow the dispute kits to force an early court jump and to override the number of votes after an appeal (future-proofing) ([#2110](https://github.com/kleros/kleros-v2/issues/2110)) + ### Fixed - Do not pass to Voting period if all the commits are cast because it breaks the current Shutter auto-reveal process. ([#2085](https://github.com/kleros/kleros-v2/issues/2085)) diff --git a/contracts/hardhat.config.ts b/contracts/hardhat.config.ts index dc1cce1c6..3bb2787ca 100644 --- a/contracts/hardhat.config.ts +++ b/contracts/hardhat.config.ts @@ -31,7 +31,7 @@ const config: HardhatUserConfig = { viaIR: process.env.VIA_IR !== "false", // Defaults to true optimizer: { enabled: true, - runs: 10000, + runs: 2000, }, outputSelection: { "*": { diff --git a/contracts/src/arbitration/KlerosCoreBase.sol b/contracts/src/arbitration/KlerosCoreBase.sol index 5816f872a..eb595096e 100644 --- a/contracts/src/arbitration/KlerosCoreBase.sol +++ b/contracts/src/arbitration/KlerosCoreBase.sol @@ -639,24 +639,17 @@ abstract contract KlerosCoreBase is IArbitratorV2, Initializable, UUPSProxiable Round storage round = dispute.rounds[dispute.rounds.length - 1]; if (msg.sender != address(disputeKits[round.disputeKitID])) revert DisputeKitOnly(); - uint96 newCourtID = dispute.courtID; - uint256 newDisputeKitID = round.disputeKitID; - // Warning: the extra round must be created before calling disputeKit.createDispute() Round storage extraRound = dispute.rounds.push(); - if (round.nbVotes >= courts[newCourtID].jurorsForCourtJump) { - // Jump to parent court. - newCourtID = courts[newCourtID].parent; - - if (!courts[newCourtID].supportedDisputeKits[newDisputeKitID]) { - // Switch to classic dispute kit if parent court doesn't support the current one. - newDisputeKitID = DISPUTE_KIT_CLASSIC; - } - - if (newCourtID != dispute.courtID) { - emit CourtJump(_disputeID, dispute.rounds.length - 1, dispute.courtID, newCourtID); - } + (uint96 newCourtID, uint256 newDisputeKitID, bool courtJump, ) = _getCourtAndDisputeKitJumps( + dispute, + round, + courts[dispute.courtID], + _disputeID + ); + if (courtJump) { + emit CourtJump(_disputeID, dispute.rounds.length - 1, dispute.courtID, newCourtID); } dispute.courtID = newCourtID; @@ -934,17 +927,25 @@ abstract contract KlerosCoreBase is IArbitratorV2, Initializable, UUPSProxiable Dispute storage dispute = disputes[_disputeID]; Round storage round = dispute.rounds[dispute.rounds.length - 1]; Court storage court = courts[dispute.courtID]; - if (round.nbVotes >= court.jurorsForCourtJump) { + + (, uint256 newDisputeKitID, bool courtJump, ) = _getCourtAndDisputeKitJumps(dispute, round, court, _disputeID); + + uint256 nbVotesAfterAppeal = disputeKits[newDisputeKitID].getNbVotesAfterAppeal( + disputeKits[round.disputeKitID], + round.nbVotes + ); + + if (courtJump) { // Jump to parent court. if (dispute.courtID == GENERAL_COURT) { // TODO: Handle the forking when appealed in General court. cost = NON_PAYABLE_AMOUNT; // Get the cost of the parent court. } else { - cost = courts[court.parent].feeForJuror * ((round.nbVotes * 2) + 1); + cost = courts[court.parent].feeForJuror * nbVotesAfterAppeal; } } else { // Stay in current court. - cost = court.feeForJuror * ((round.nbVotes * 2) + 1); + cost = court.feeForJuror * nbVotesAfterAppeal; } } @@ -1033,7 +1034,7 @@ abstract contract KlerosCoreBase is IArbitratorV2, Initializable, UUPSProxiable Round storage round = dispute.rounds[dispute.rounds.length - 1]; Court storage court = courts[dispute.courtID]; - if (round.nbVotes < court.jurorsForCourtJump) { + if (!_isCourtJumping(round, court, _disputeID)) { return false; } @@ -1053,6 +1054,41 @@ abstract contract KlerosCoreBase is IArbitratorV2, Initializable, UUPSProxiable // * Internal * // // ************************************* // + /// @dev Returns true if the round is jumping to a parent court. + /// @param _round The round to check. + /// @param _court The court to check. + /// @return Whether the round is jumping to a parent court or not. + function _isCourtJumping( + Round storage _round, + Court storage _court, + uint256 _disputeID + ) internal view returns (bool) { + return + disputeKits[_round.disputeKitID].earlyCourtJump(_disputeID) || _round.nbVotes >= _court.jurorsForCourtJump; + } + + function _getCourtAndDisputeKitJumps( + Dispute storage _dispute, + Round storage _round, + Court storage _court, + uint256 _disputeID + ) internal view returns (uint96 newCourtID, uint256 newDisputeKitID, bool courtJump, bool disputeKitJump) { + newCourtID = _dispute.courtID; + newDisputeKitID = _round.disputeKitID; + + if (!_isCourtJumping(_round, _court, _disputeID)) return (newCourtID, newDisputeKitID, false, false); + + // Jump to parent court. + newCourtID = courts[newCourtID].parent; + courtJump = true; + + if (!courts[newCourtID].supportedDisputeKits[newDisputeKitID]) { + // Switch to classic dispute kit if parent court doesn't support the current one. + newDisputeKitID = DISPUTE_KIT_CLASSIC; + disputeKitJump = true; + } + } + /// @dev Internal function to transfer fee tokens (ETH or ERC20) /// @param _feeToken The token to transfer (NATIVE_CURRENCY for ETH). /// @param _recipient The recipient address. diff --git a/contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol b/contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol index 0922f047b..a9e96f7eb 100644 --- a/contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol +++ b/contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol @@ -627,6 +627,22 @@ abstract contract DisputeKitClassicBase is IDisputeKit, Initializable, UUPSProxi ((appealPeriodEnd - appealPeriodStart) * LOSER_APPEAL_PERIOD_MULTIPLIER) / ONE_BASIS_POINT); } + /// @dev Returns true if the dispute is jumping to a parent court. + /// @return Whether the dispute is jumping to a parent court or not. + function earlyCourtJump(uint256 /* _coreDisputeID */) external pure override returns (bool) { + return false; + } + + /// @dev Returns the number of votes after the appeal. + /// @param _currentNbVotes The number of votes before the appeal. + /// @return The number of votes after the appeal. + function getNbVotesAfterAppeal( + IDisputeKit /* _previousDisputeKit */, + uint256 _currentNbVotes + ) external pure override returns (uint256) { + return (_currentNbVotes * 2) + 1; + } + /// @dev Returns true if the specified voter was active in this round. /// @param _coreDisputeID The ID of the dispute in Kleros Core, not in the Dispute Kit. /// @param _coreRoundID The ID of the round in Kleros Core, not in the Dispute Kit. diff --git a/contracts/src/arbitration/interfaces/IDisputeKit.sol b/contracts/src/arbitration/interfaces/IDisputeKit.sol index 6a72b35e4..0aab45b9c 100644 --- a/contracts/src/arbitration/interfaces/IDisputeKit.sol +++ b/contracts/src/arbitration/interfaces/IDisputeKit.sol @@ -113,6 +113,20 @@ interface IDisputeKit { /// @return Whether the appeal funding is finished. function isAppealFunded(uint256 _coreDisputeID) external view returns (bool); + /// @dev Returns true if the dispute is jumping to a parent court. + /// @param _coreDisputeID The ID of the dispute in Kleros Core, not in the Dispute Kit. + /// @return Whether the dispute is jumping to a parent court or not. + function earlyCourtJump(uint256 _coreDisputeID) external view returns (bool); + + /// @dev Returns the number of votes after the appeal. + /// @param _previousDisputeKit The previous Dispute Kit. + /// @param _currentNbVotes The number of votes before the appeal. + /// @return The number of votes after the appeal. + function getNbVotesAfterAppeal( + IDisputeKit _previousDisputeKit, + uint256 _currentNbVotes + ) external view returns (uint256); // TODO: remove previousDisputeKit + /// @dev Returns true if the specified voter was active in this round. /// @param _coreDisputeID The ID of the dispute in Kleros Core, not in the Dispute Kit. /// @param _coreRoundID The ID of the round in Kleros Core, not in the Dispute Kit.