From 95f2803fa60e1417ad900ec008d6f74e373d4f3c Mon Sep 17 00:00:00 2001 From: jaybuidl Date: Thu, 14 Aug 2025 00:55:06 +0100 Subject: [PATCH 1/4] refactor: consolidated ALPHA_DIVISOR and ONE_BASIS_POINTS --- contracts/src/arbitration/KlerosCoreBase.sol | 15 +++++++-------- .../dispute-kits/DisputeKitClassicBase.sol | 2 +- contracts/src/libraries/Constants.sol | 3 +++ 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/contracts/src/arbitration/KlerosCoreBase.sol b/contracts/src/arbitration/KlerosCoreBase.sol index 2b9998bda..387ff270f 100644 --- a/contracts/src/arbitration/KlerosCoreBase.sol +++ b/contracts/src/arbitration/KlerosCoreBase.sol @@ -88,7 +88,6 @@ abstract contract KlerosCoreBase is IArbitratorV2, Initializable, UUPSProxiable // * Storage * // // ************************************* // - uint256 private constant ALPHA_DIVISOR = 1e4; // The number to divide `Court.alpha` by. uint256 private constant NON_PAYABLE_AMOUNT = (2 ** 256 - 2) / 2; // An amount higher than the supply of ETH. address public governor; // The governor of the contract. @@ -775,13 +774,13 @@ abstract contract KlerosCoreBase is IArbitratorV2, Initializable, UUPSProxiable _params.feePerJurorInRound, _params.pnkAtStakePerJurorInRound ); - if (degreeOfCoherence > ALPHA_DIVISOR) { + if (degreeOfCoherence > ONE_BASIS_POINT) { // Make sure the degree doesn't exceed 1, though it should be ensured by the dispute kit. - degreeOfCoherence = ALPHA_DIVISOR; + degreeOfCoherence = ONE_BASIS_POINT; } // Fully coherent jurors won't be penalized. - uint256 penalty = (round.pnkAtStakePerJuror * (ALPHA_DIVISOR - degreeOfCoherence)) / ALPHA_DIVISOR; + uint256 penalty = (round.pnkAtStakePerJuror * (ONE_BASIS_POINT - degreeOfCoherence)) / ONE_BASIS_POINT; // Unlock the PNKs affected by the penalty address account = round.drawnJurors[_params.repartition]; @@ -835,8 +834,8 @@ abstract contract KlerosCoreBase is IArbitratorV2, Initializable, UUPSProxiable ); // Make sure the degree doesn't exceed 1, though it should be ensured by the dispute kit. - if (degreeOfCoherence > ALPHA_DIVISOR) { - degreeOfCoherence = ALPHA_DIVISOR; + if (degreeOfCoherence > ONE_BASIS_POINT) { + degreeOfCoherence = ONE_BASIS_POINT; } address account = round.drawnJurors[_params.repartition % _params.numberOfVotesInRound]; @@ -1062,7 +1061,7 @@ abstract contract KlerosCoreBase is IArbitratorV2, Initializable, UUPSProxiable /// @param _degreeOfCoherence The degree of coherence in basis points. /// @return The amount after applying the degree of coherence. function _applyCoherence(uint256 _amount, uint256 _degreeOfCoherence) internal pure returns (uint256) { - return (_amount * _degreeOfCoherence) / ALPHA_DIVISOR; + return (_amount * _degreeOfCoherence) / ONE_BASIS_POINT; } /// @dev Calculates PNK at stake per juror based on court parameters @@ -1070,7 +1069,7 @@ abstract contract KlerosCoreBase is IArbitratorV2, Initializable, UUPSProxiable /// @param _alpha The alpha parameter for the court in basis points. /// @return The amount of PNK at stake per juror. function _calculatePnkAtStake(uint256 _minStake, uint256 _alpha) internal pure returns (uint256) { - return (_minStake * _alpha) / ALPHA_DIVISOR; + return (_minStake * _alpha) / ONE_BASIS_POINT; } /// @dev Toggles the dispute kit support for a given court. diff --git a/contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol b/contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol index 2d804891a..59b51e52b 100644 --- a/contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol +++ b/contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol @@ -6,6 +6,7 @@ import {KlerosCore, KlerosCoreBase, IDisputeKit, ISortitionModule} from "../Kler import {Initializable} from "../../proxy/Initializable.sol"; import {UUPSProxiable} from "../../proxy/UUPSProxiable.sol"; import {SafeSend} from "../../libraries/SafeSend.sol"; +import {ONE_BASIS_POINT} from "../../libraries/Constants.sol"; /// @title DisputeKitClassicBase /// Abstract Dispute kit classic implementation of the Kleros v1 features including: @@ -57,7 +58,6 @@ abstract contract DisputeKitClassicBase is IDisputeKit, Initializable, UUPSProxi uint256 public constant WINNER_STAKE_MULTIPLIER = 10000; // Multiplier of the appeal cost that the winner has to pay as fee stake for a round in basis points. Default is 1x of appeal fee. uint256 public constant LOSER_STAKE_MULTIPLIER = 20000; // Multiplier of the appeal cost that the loser has to pay as fee stake for a round in basis points. Default is 2x of appeal fee. uint256 public constant LOSER_APPEAL_PERIOD_MULTIPLIER = 5000; // Multiplier of the appeal period for the choice that wasn't voted for in the previous round, in basis points. Default is 1/2 of original appeal period. - uint256 public constant ONE_BASIS_POINT = 10000; // One basis point, for scaling. address public governor; // The governor of the contract. KlerosCore public core; // The Kleros Core arbitrator diff --git a/contracts/src/libraries/Constants.sol b/contracts/src/libraries/Constants.sol index bed573fa6..10c42d8a9 100644 --- a/contracts/src/libraries/Constants.sol +++ b/contracts/src/libraries/Constants.sol @@ -20,6 +20,9 @@ uint256 constant DEFAULT_K = 6; // Default number of children per node. uint256 constant DEFAULT_NB_OF_JURORS = 3; // The default number of jurors in a dispute. IERC20 constant NATIVE_CURRENCY = IERC20(address(0)); // The native currency, such as ETH on Arbitrum, Optimism and Ethereum L1. +// Units +uint256 constant ONE_BASIS_POINT = 10000; + enum OnError { Revert, Return From c1bad1debd64610833b11b3da150c2e8275df0e2 Mon Sep 17 00:00:00 2001 From: jaybuidl Date: Thu, 14 Aug 2025 14:46:41 +0100 Subject: [PATCH 2/4] docs: comment --- contracts/src/arbitration/KlerosCoreBase.sol | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/contracts/src/arbitration/KlerosCoreBase.sol b/contracts/src/arbitration/KlerosCoreBase.sol index 387ff270f..fe1c53521 100644 --- a/contracts/src/arbitration/KlerosCoreBase.sol +++ b/contracts/src/arbitration/KlerosCoreBase.sol @@ -774,8 +774,9 @@ abstract contract KlerosCoreBase is IArbitratorV2, Initializable, UUPSProxiable _params.feePerJurorInRound, _params.pnkAtStakePerJurorInRound ); + + // Guard against degree exceeding 1, though it should be ensured by the dispute kit. if (degreeOfCoherence > ONE_BASIS_POINT) { - // Make sure the degree doesn't exceed 1, though it should be ensured by the dispute kit. degreeOfCoherence = ONE_BASIS_POINT; } @@ -833,7 +834,7 @@ abstract contract KlerosCoreBase is IArbitratorV2, Initializable, UUPSProxiable _params.pnkAtStakePerJurorInRound ); - // Make sure the degree doesn't exceed 1, though it should be ensured by the dispute kit. + // Guard against degree exceeding 1, though it should be ensured by the dispute kit. if (degreeOfCoherence > ONE_BASIS_POINT) { degreeOfCoherence = ONE_BASIS_POINT; } From be3384723a23c297260a3b6487eb335c9e0a3dd4 Mon Sep 17 00:00:00 2001 From: jaybuidl Date: Thu, 14 Aug 2025 14:54:45 +0100 Subject: [PATCH 3/4] fix: typo in local variable --- contracts/src/arbitration/SortitionModuleBase.sol | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/contracts/src/arbitration/SortitionModuleBase.sol b/contracts/src/arbitration/SortitionModuleBase.sol index e49fd2c6c..048fd3b40 100644 --- a/contracts/src/arbitration/SortitionModuleBase.sol +++ b/contracts/src/arbitration/SortitionModuleBase.sol @@ -344,14 +344,14 @@ abstract contract SortitionModuleBase is ISortitionModule, Initializable, UUPSPr // Update the sortition sum tree. bytes32 stakePathID = _accountAndCourtIDToStakePathID(_account, _courtID); bool finished = false; - uint96 currenCourtID = _courtID; + uint96 currentCourtID = _courtID; while (!finished) { // Tokens are also implicitly staked in parent courts through sortition module to increase the chance of being drawn. - _set(bytes32(uint256(currenCourtID)), _newStake, stakePathID); - if (currenCourtID == GENERAL_COURT) { + _set(bytes32(uint256(currentCourtID)), _newStake, stakePathID); + if (currentCourtID == GENERAL_COURT) { finished = true; } else { - (currenCourtID, , , , , , ) = core.courts(currenCourtID); // Get the parent court. + (currentCourtID, , , , , , ) = core.courts(currentCourtID); // Get the parent court. } } emit StakeSet(_account, _courtID, _newStake, juror.stakedPnk); From 1dbfedf4ef2efa7d431d83e8f1e3af49db500409 Mon Sep 17 00:00:00 2001 From: jaybuidl Date: Fri, 15 Aug 2025 00:46:30 +0100 Subject: [PATCH 4/4] feat: multi-dimensional degree of coherence for reward/penalties/pnk/eth --- contracts/src/arbitration/KlerosCoreBase.sol | 33 ++++++------ .../dispute-kits/DisputeKitClassicBase.sol | 31 ++++++++++-- .../arbitration/interfaces/IDisputeKit.sol | 22 ++++++-- .../university/KlerosCoreUniversity.sol | 39 ++++++++------- .../kleros-liquid-xdai/xKlerosLiquidV2.sol | 1 - contracts/test/foundry/KlerosCore.t.sol | 50 ++++++++++++++++--- 6 files changed, 130 insertions(+), 46 deletions(-) diff --git a/contracts/src/arbitration/KlerosCoreBase.sol b/contracts/src/arbitration/KlerosCoreBase.sol index fe1c53521..8b768d8aa 100644 --- a/contracts/src/arbitration/KlerosCoreBase.sol +++ b/contracts/src/arbitration/KlerosCoreBase.sol @@ -767,7 +767,7 @@ abstract contract KlerosCoreBase is IArbitratorV2, Initializable, UUPSProxiable IDisputeKit disputeKit = disputeKits[round.disputeKitID]; // [0, 1] value that determines how coherent the juror was in this round, in basis points. - uint256 degreeOfCoherence = disputeKit.getDegreeOfCoherence( + uint256 coherence = disputeKit.getDegreeOfCoherencePenalty( _params.disputeID, _params.round, _params.repartition, @@ -776,12 +776,12 @@ abstract contract KlerosCoreBase is IArbitratorV2, Initializable, UUPSProxiable ); // Guard against degree exceeding 1, though it should be ensured by the dispute kit. - if (degreeOfCoherence > ONE_BASIS_POINT) { - degreeOfCoherence = ONE_BASIS_POINT; + if (coherence > ONE_BASIS_POINT) { + coherence = ONE_BASIS_POINT; } // Fully coherent jurors won't be penalized. - uint256 penalty = (round.pnkAtStakePerJuror * (ONE_BASIS_POINT - degreeOfCoherence)) / ONE_BASIS_POINT; + uint256 penalty = (round.pnkAtStakePerJuror * (ONE_BASIS_POINT - coherence)) / ONE_BASIS_POINT; // Unlock the PNKs affected by the penalty address account = round.drawnJurors[_params.repartition]; @@ -794,7 +794,7 @@ abstract contract KlerosCoreBase is IArbitratorV2, Initializable, UUPSProxiable account, _params.disputeID, _params.round, - degreeOfCoherence, + coherence, -int256(availablePenalty), 0, round.feeToken @@ -826,7 +826,7 @@ abstract contract KlerosCoreBase is IArbitratorV2, Initializable, UUPSProxiable IDisputeKit disputeKit = disputeKits[round.disputeKitID]; // [0, 1] value that determines how coherent the juror was in this round, in basis points. - uint256 degreeOfCoherence = disputeKit.getDegreeOfCoherence( + (uint256 pnkCoherence, uint256 feeCoherence) = disputeKit.getDegreeOfCoherenceReward( _params.disputeID, _params.round, _params.repartition % _params.numberOfVotesInRound, @@ -835,20 +835,23 @@ abstract contract KlerosCoreBase is IArbitratorV2, Initializable, UUPSProxiable ); // Guard against degree exceeding 1, though it should be ensured by the dispute kit. - if (degreeOfCoherence > ONE_BASIS_POINT) { - degreeOfCoherence = ONE_BASIS_POINT; + if (pnkCoherence > ONE_BASIS_POINT) { + pnkCoherence = ONE_BASIS_POINT; + } + if (feeCoherence > ONE_BASIS_POINT) { + feeCoherence = ONE_BASIS_POINT; } address account = round.drawnJurors[_params.repartition % _params.numberOfVotesInRound]; - uint256 pnkLocked = _applyCoherence(round.pnkAtStakePerJuror, degreeOfCoherence); + uint256 pnkLocked = _applyCoherence(round.pnkAtStakePerJuror, pnkCoherence); // Release the rest of the PNKs of the juror for this round. sortitionModule.unlockStake(account, pnkLocked); // Transfer the rewards - uint256 pnkReward = _applyCoherence(_params.pnkPenaltiesInRound / _params.coherentCount, degreeOfCoherence); + uint256 pnkReward = _applyCoherence(_params.pnkPenaltiesInRound / _params.coherentCount, pnkCoherence); round.sumPnkRewardPaid += pnkReward; - uint256 feeReward = _applyCoherence(round.totalFeesForJurors / _params.coherentCount, degreeOfCoherence); + uint256 feeReward = _applyCoherence(round.totalFeesForJurors / _params.coherentCount, feeCoherence); round.sumFeeRewardPaid += feeReward; pinakion.safeTransfer(account, pnkReward); _transferFeeToken(round.feeToken, payable(account), feeReward); @@ -856,7 +859,7 @@ abstract contract KlerosCoreBase is IArbitratorV2, Initializable, UUPSProxiable account, _params.disputeID, _params.round, - degreeOfCoherence, + pnkCoherence, int256(pnkReward), int256(feeReward), round.feeToken @@ -1059,10 +1062,10 @@ abstract contract KlerosCoreBase is IArbitratorV2, Initializable, UUPSProxiable /// @dev Applies degree of coherence to an amount /// @param _amount The base amount to apply coherence to. - /// @param _degreeOfCoherence The degree of coherence in basis points. + /// @param _coherence The degree of coherence in basis points. /// @return The amount after applying the degree of coherence. - function _applyCoherence(uint256 _amount, uint256 _degreeOfCoherence) internal pure returns (uint256) { - return (_amount * _degreeOfCoherence) / ONE_BASIS_POINT; + function _applyCoherence(uint256 _amount, uint256 _coherence) internal pure returns (uint256) { + return (_amount * _coherence) / ONE_BASIS_POINT; } /// @dev Calculates PNK at stake per juror based on court parameters diff --git a/contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol b/contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol index 59b51e52b..0922f047b 100644 --- a/contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol +++ b/contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol @@ -526,14 +526,39 @@ abstract contract DisputeKitClassicBase is IDisputeKit, Initializable, UUPSProxi /// @param _coreDisputeID The ID of the dispute in Kleros Core, not in the Dispute Kit. /// @param _coreRoundID The ID of the round in Kleros Core, not in the Dispute Kit. /// @param _voteID The ID of the vote. - /// @return The degree of coherence in basis points. - function getDegreeOfCoherence( + /// @return pnkCoherence The degree of coherence in basis points for the dispute PNK reward. + /// @return feeCoherence The degree of coherence in basis points for the dispute fee reward. + function getDegreeOfCoherenceReward( uint256 _coreDisputeID, uint256 _coreRoundID, uint256 _voteID, uint256 /* _feePerJuror */, uint256 /* _pnkAtStakePerJuror */ - ) external view override returns (uint256) { + ) external view override returns (uint256 pnkCoherence, uint256 feeCoherence) { + uint256 coherence = _getDegreeOfCoherence(_coreDisputeID, _coreRoundID, _voteID); + return (coherence, coherence); + } + + /// @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 penalty. + /// @param _coreDisputeID The ID of the dispute in Kleros Core, not in the Dispute Kit. + /// @param _coreRoundID The ID of the round in Kleros Core, not in the Dispute Kit. + /// @param _voteID The ID of the vote. + /// @return pnkCoherence The degree of coherence in basis points for the dispute PNK reward. + function getDegreeOfCoherencePenalty( + uint256 _coreDisputeID, + uint256 _coreRoundID, + uint256 _voteID, + uint256 /* _feePerJuror */, + uint256 /* _pnkAtStakePerJuror */ + ) external view override returns (uint256 pnkCoherence) { + return _getDegreeOfCoherence(_coreDisputeID, _coreRoundID, _voteID); + } + + function _getDegreeOfCoherence( + uint256 _coreDisputeID, + uint256 _coreRoundID, + uint256 _voteID + ) internal view returns (uint256 coherence) { // 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[_coreDisputeID]]; Vote storage vote = dispute.rounds[dispute.coreRoundIDToLocal[_coreRoundID]].votes[_voteID]; diff --git a/contracts/src/arbitration/interfaces/IDisputeKit.sol b/contracts/src/arbitration/interfaces/IDisputeKit.sol index 423f38e3e..6a72b35e4 100644 --- a/contracts/src/arbitration/interfaces/IDisputeKit.sol +++ b/contracts/src/arbitration/interfaces/IDisputeKit.sol @@ -67,14 +67,30 @@ interface IDisputeKit { /// @param _voteID The ID of the vote. /// @param _feePerJuror The fee per juror. /// @param _pnkAtStakePerJuror The PNK at stake per juror. - /// @return The degree of coherence in basis points. - function getDegreeOfCoherence( + /// @return pnkCoherence The degree of coherence in basis points for the dispute PNK reward. + /// @return feeCoherence The degree of coherence in basis points for the dispute fee reward. + function getDegreeOfCoherenceReward( uint256 _coreDisputeID, uint256 _coreRoundID, uint256 _voteID, uint256 _feePerJuror, uint256 _pnkAtStakePerJuror - ) external view returns (uint256); + ) external view returns (uint256 pnkCoherence, uint256 feeCoherence); + + /// @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 penalty. + /// @param _coreDisputeID The ID of the dispute in Kleros Core, not in the Dispute Kit. + /// @param _coreRoundID The ID of the round in Kleros Core, not in the Dispute Kit. + /// @param _voteID The ID of the vote. + /// @param _feePerJuror The fee per juror. + /// @param _pnkAtStakePerJuror The PNK at stake per juror. + /// @return pnkCoherence The degree of coherence in basis points for the dispute PNK reward. + function getDegreeOfCoherencePenalty( + uint256 _coreDisputeID, + uint256 _coreRoundID, + uint256 _voteID, + uint256 _feePerJuror, + uint256 _pnkAtStakePerJuror + ) external view returns (uint256 pnkCoherence); /// @dev Gets the number of jurors who are eligible to a reward in this round. /// @param _coreDisputeID The ID of the dispute in Kleros Core, not in the Dispute Kit. diff --git a/contracts/src/arbitration/university/KlerosCoreUniversity.sol b/contracts/src/arbitration/university/KlerosCoreUniversity.sol index 35fd5262c..5744088b0 100644 --- a/contracts/src/arbitration/university/KlerosCoreUniversity.sol +++ b/contracts/src/arbitration/university/KlerosCoreUniversity.sol @@ -6,9 +6,9 @@ import {IArbitrableV2, IArbitratorV2} from "../interfaces/IArbitratorV2.sol"; import {IDisputeKit} from "../interfaces/IDisputeKit.sol"; import {ISortitionModuleUniversity} from "./ISortitionModuleUniversity.sol"; import {SafeERC20, IERC20} from "../../libraries/SafeERC20.sol"; -import "../../libraries/Constants.sol"; import {UUPSProxiable} from "../../proxy/UUPSProxiable.sol"; import {Initializable} from "../../proxy/Initializable.sol"; +import "../../libraries/Constants.sol"; /// @title KlerosCoreUniversity /// Core arbitrator contract for educational purposes. @@ -87,7 +87,6 @@ contract KlerosCoreUniversity is IArbitratorV2, UUPSProxiable, Initializable { // * Storage * // // ************************************* // - uint256 private constant ALPHA_DIVISOR = 1e4; // The number to divide `Court.alpha` by. uint256 private constant NON_PAYABLE_AMOUNT = (2 ** 256 - 2) / 2; // An amount higher than the supply of ETH. address public governor; // The governor of the contract. @@ -526,7 +525,7 @@ contract KlerosCoreUniversity is IArbitratorV2, UUPSProxiable, Initializable { : convertEthToTokenAmount(_feeToken, court.feeForJuror); round.nbVotes = _feeAmount / feeForJuror; round.disputeKitID = disputeKitID; - round.pnkAtStakePerJuror = (court.minStake * court.alpha) / ALPHA_DIVISOR; + round.pnkAtStakePerJuror = (court.minStake * court.alpha) / ONE_BASIS_POINT; round.totalFeesForJurors = _feeAmount; round.feeToken = IERC20(_feeToken); @@ -655,7 +654,7 @@ contract KlerosCoreUniversity is IArbitratorV2, UUPSProxiable, Initializable { Court storage court = courts[newCourtID]; extraRound.nbVotes = msg.value / court.feeForJuror; // As many votes that can be afforded by the provided funds. - extraRound.pnkAtStakePerJuror = (court.minStake * court.alpha) / ALPHA_DIVISOR; + extraRound.pnkAtStakePerJuror = (court.minStake * court.alpha) / ONE_BASIS_POINT; extraRound.totalFeesForJurors = msg.value; extraRound.disputeKitID = newDisputeKitID; @@ -754,20 +753,21 @@ contract KlerosCoreUniversity is IArbitratorV2, UUPSProxiable, Initializable { IDisputeKit disputeKit = disputeKits[round.disputeKitID]; // [0, 1] value that determines how coherent the juror was in this round, in basis points. - uint256 degreeOfCoherence = disputeKit.getDegreeOfCoherence( + uint256 coherence = disputeKit.getDegreeOfCoherencePenalty( _params.disputeID, _params.round, _params.repartition, _params.feePerJurorInRound, _params.pnkAtStakePerJurorInRound ); - if (degreeOfCoherence > ALPHA_DIVISOR) { - // Make sure the degree doesn't exceed 1, though it should be ensured by the dispute kit. - degreeOfCoherence = ALPHA_DIVISOR; + + // Guard against degree exceeding 1, though it should be ensured by the dispute kit. + if (coherence > ONE_BASIS_POINT) { + coherence = ONE_BASIS_POINT; } // Fully coherent jurors won't be penalized. - uint256 penalty = (round.pnkAtStakePerJuror * (ALPHA_DIVISOR - degreeOfCoherence)) / ALPHA_DIVISOR; + uint256 penalty = (round.pnkAtStakePerJuror * (ONE_BASIS_POINT - coherence)) / ONE_BASIS_POINT; // Unlock the PNKs affected by the penalty address account = round.drawnJurors[_params.repartition]; @@ -780,7 +780,7 @@ contract KlerosCoreUniversity is IArbitratorV2, UUPSProxiable, Initializable { account, _params.disputeID, _params.round, - degreeOfCoherence, + coherence, -int256(availablePenalty), 0, round.feeToken @@ -818,7 +818,7 @@ contract KlerosCoreUniversity is IArbitratorV2, UUPSProxiable, Initializable { IDisputeKit disputeKit = disputeKits[round.disputeKitID]; // [0, 1] value that determines how coherent the juror was in this round, in basis points. - uint256 degreeOfCoherence = disputeKit.getDegreeOfCoherence( + (uint256 pnkCoherence, uint256 feeCoherence) = disputeKit.getDegreeOfCoherenceReward( _params.disputeID, _params.round, _params.repartition % _params.numberOfVotesInRound, @@ -826,21 +826,24 @@ contract KlerosCoreUniversity is IArbitratorV2, UUPSProxiable, Initializable { _params.pnkAtStakePerJurorInRound ); - // Make sure the degree doesn't exceed 1, though it should be ensured by the dispute kit. - if (degreeOfCoherence > ALPHA_DIVISOR) { - degreeOfCoherence = ALPHA_DIVISOR; + // Guard against degree exceeding 1, though it should be ensured by the dispute kit. + if (pnkCoherence > ONE_BASIS_POINT) { + pnkCoherence = ONE_BASIS_POINT; + } + if (feeCoherence > ONE_BASIS_POINT) { + feeCoherence = ONE_BASIS_POINT; } address account = round.drawnJurors[_params.repartition % _params.numberOfVotesInRound]; - uint256 pnkLocked = (round.pnkAtStakePerJuror * degreeOfCoherence) / ALPHA_DIVISOR; + uint256 pnkLocked = (round.pnkAtStakePerJuror * pnkCoherence) / ONE_BASIS_POINT; // Release the rest of the PNKs of the juror for this round. sortitionModule.unlockStake(account, pnkLocked); // Transfer the rewards - uint256 pnkReward = ((_params.pnkPenaltiesInRound / _params.coherentCount) * degreeOfCoherence) / ALPHA_DIVISOR; + uint256 pnkReward = ((_params.pnkPenaltiesInRound / _params.coherentCount) * pnkCoherence) / ONE_BASIS_POINT; round.sumPnkRewardPaid += pnkReward; - uint256 feeReward = ((round.totalFeesForJurors / _params.coherentCount) * degreeOfCoherence) / ALPHA_DIVISOR; + uint256 feeReward = ((round.totalFeesForJurors / _params.coherentCount) * feeCoherence) / ONE_BASIS_POINT; round.sumFeeRewardPaid += feeReward; pinakion.safeTransfer(account, pnkReward); if (round.feeToken == NATIVE_CURRENCY) { @@ -854,7 +857,7 @@ contract KlerosCoreUniversity is IArbitratorV2, UUPSProxiable, Initializable { account, _params.disputeID, _params.round, - degreeOfCoherence, + pnkCoherence, int256(pnkReward), int256(feeReward), round.feeToken diff --git a/contracts/src/kleros-v1/kleros-liquid-xdai/xKlerosLiquidV2.sol b/contracts/src/kleros-v1/kleros-liquid-xdai/xKlerosLiquidV2.sol index d9c74946b..9a25909b7 100644 --- a/contracts/src/kleros-v1/kleros-liquid-xdai/xKlerosLiquidV2.sol +++ b/contracts/src/kleros-v1/kleros-liquid-xdai/xKlerosLiquidV2.sol @@ -141,7 +141,6 @@ contract xKlerosLiquidV2 is Initializable, ITokenController, IArbitratorV2 { uint256 public constant MAX_STAKE_PATHS = 4; // The maximum number of stake paths a juror can have. uint256 public constant DEFAULT_NB_OF_JURORS = 3; // The default number of jurors in a dispute. uint256 public constant NON_PAYABLE_AMOUNT = (2 ** 256 - 2) / 2; // An amount higher than the supply of ETH. - uint256 public constant ALPHA_DIVISOR = 1e4; // The number to divide `Court.alpha` by. // General Contracts address public governor; // The governor of the contract. WrappedPinakion public pinakion; // The Pinakion token contract. diff --git a/contracts/test/foundry/KlerosCore.t.sol b/contracts/test/foundry/KlerosCore.t.sol index 72a6a565c..a2508ad70 100644 --- a/contracts/test/foundry/KlerosCore.t.sol +++ b/contracts/test/foundry/KlerosCore.t.sol @@ -2311,10 +2311,33 @@ contract KlerosCoreTest is Test { core.unpause(); assertEq(disputeKit.getCoherentCount(disputeID, 0), 2, "Wrong coherent count"); + + uint256 pnkCoherence; + uint256 feeCoherence; // dispute, round, voteID, feeForJuror (not used in classic DK), pnkPerJuror (not used in classic DK) - assertEq(disputeKit.getDegreeOfCoherence(disputeID, 0, 0, 0, 0), 0, "Wrong degree of coherence 0 vote ID"); - assertEq(disputeKit.getDegreeOfCoherence(disputeID, 0, 1, 0, 0), 10000, "Wrong degree of coherence 1 vote ID"); - assertEq(disputeKit.getDegreeOfCoherence(disputeID, 0, 2, 0, 0), 10000, "Wrong degree of coherence 2 vote ID"); + (pnkCoherence, feeCoherence) = disputeKit.getDegreeOfCoherenceReward(disputeID, 0, 0, 0, 0); + assertEq(pnkCoherence, 0, "Wrong reward pnk coherence 0 vote ID"); + assertEq(feeCoherence, 0, "Wrong reward fee coherence 0 vote ID"); + + (pnkCoherence, feeCoherence) = disputeKit.getDegreeOfCoherenceReward(disputeID, 0, 1, 0, 0); + assertEq(pnkCoherence, 10000, "Wrong reward pnk coherence 1 vote ID"); + assertEq(feeCoherence, 10000, "Wrong reward fee coherence 1 vote ID"); + + (pnkCoherence, feeCoherence) = disputeKit.getDegreeOfCoherenceReward(disputeID, 0, 2, 0, 0); + assertEq(pnkCoherence, 10000, "Wrong reward pnk coherence 2 vote ID"); + assertEq(feeCoherence, 10000, "Wrong reward fee coherence 2 vote ID"); + + assertEq(disputeKit.getDegreeOfCoherencePenalty(disputeID, 0, 0, 0, 0), 0, "Wrong penalty coherence 0 vote ID"); + assertEq( + disputeKit.getDegreeOfCoherencePenalty(disputeID, 0, 1, 0, 0), + 10000, + "Wrong penalty coherence 1 vote ID" + ); + assertEq( + disputeKit.getDegreeOfCoherencePenalty(disputeID, 0, 2, 0, 0), + 10000, + "Wrong penalty coherence 2 vote ID" + ); vm.expectEmit(true, true, true, true); emit SortitionModuleBase.StakeLocked(staker1, 1000, true); @@ -2398,10 +2421,25 @@ contract KlerosCoreTest is Test { core.passPeriod(disputeID); // Execution assertEq(disputeKit.getCoherentCount(disputeID, 0), 0, "Wrong coherent count"); + + uint256 pnkCoherence; + uint256 feeCoherence; // dispute, round, voteID, feeForJuror (not used in classic DK), pnkPerJuror (not used in classic DK) - assertEq(disputeKit.getDegreeOfCoherence(disputeID, 0, 0, 0, 0), 0, "Wrong degree of coherence 0 vote ID"); - assertEq(disputeKit.getDegreeOfCoherence(disputeID, 0, 1, 0, 0), 0, "Wrong degree of coherence 1 vote ID"); - assertEq(disputeKit.getDegreeOfCoherence(disputeID, 0, 2, 0, 0), 0, "Wrong degree of coherence 2 vote ID"); + (pnkCoherence, feeCoherence) = disputeKit.getDegreeOfCoherenceReward(disputeID, 0, 0, 0, 0); + assertEq(pnkCoherence, 0, "Wrong reward pnk coherence 0 vote ID"); + assertEq(feeCoherence, 0, "Wrong reward fee coherence 0 vote ID"); + + (pnkCoherence, feeCoherence) = disputeKit.getDegreeOfCoherenceReward(disputeID, 0, 1, 0, 0); + assertEq(pnkCoherence, 0, "Wrong reward pnk coherence 1 vote ID"); + assertEq(feeCoherence, 0, "Wrong reward fee coherence 1 vote ID"); + + (pnkCoherence, feeCoherence) = disputeKit.getDegreeOfCoherenceReward(disputeID, 0, 2, 0, 0); + assertEq(pnkCoherence, 0, "Wrong reward pnk coherence 2 vote ID"); + assertEq(feeCoherence, 0, "Wrong reward fee coherence 2 vote ID"); + + assertEq(disputeKit.getDegreeOfCoherencePenalty(disputeID, 0, 0, 0, 0), 0, "Wrong penalty coherence 0 vote ID"); + assertEq(disputeKit.getDegreeOfCoherencePenalty(disputeID, 0, 1, 0, 0), 0, "Wrong penalty coherence 1 vote ID"); + assertEq(disputeKit.getDegreeOfCoherencePenalty(disputeID, 0, 2, 0, 0), 0, "Wrong penalty coherence 2 vote ID"); uint256 governorBalance = governor.balance; uint256 governorTokenBalance = pinakion.balanceOf(governor);