Skip to content

Commit 96e48f8

Browse files
authored
Merge pull request #2090 from kleros/feat/multi-dimensional-coherence
Contracts: multi-dimensional coherence
2 parents bd4e169 + 1dbfedf commit 96e48f8

File tree

8 files changed

+142
-55
lines changed

8 files changed

+142
-55
lines changed

contracts/src/arbitration/KlerosCoreBase.sol

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,6 @@ abstract contract KlerosCoreBase is IArbitratorV2, Initializable, UUPSProxiable
8888
// * Storage * //
8989
// ************************************* //
9090

91-
uint256 private constant ALPHA_DIVISOR = 1e4; // The number to divide `Court.alpha` by.
9291
uint256 private constant NON_PAYABLE_AMOUNT = (2 ** 256 - 2) / 2; // An amount higher than the supply of ETH.
9392

9493
address public governor; // The governor of the contract.
@@ -766,20 +765,21 @@ abstract contract KlerosCoreBase is IArbitratorV2, Initializable, UUPSProxiable
766765
IDisputeKit disputeKit = disputeKits[round.disputeKitID];
767766

768767
// [0, 1] value that determines how coherent the juror was in this round, in basis points.
769-
uint256 degreeOfCoherence = disputeKit.getDegreeOfCoherence(
768+
uint256 coherence = disputeKit.getDegreeOfCoherencePenalty(
770769
_params.disputeID,
771770
_params.round,
772771
_params.repartition,
773772
_params.feePerJurorInRound,
774773
_params.pnkAtStakePerJurorInRound
775774
);
776-
if (degreeOfCoherence > ALPHA_DIVISOR) {
777-
// Make sure the degree doesn't exceed 1, though it should be ensured by the dispute kit.
778-
degreeOfCoherence = ALPHA_DIVISOR;
775+
776+
// Guard against degree exceeding 1, though it should be ensured by the dispute kit.
777+
if (coherence > ONE_BASIS_POINT) {
778+
coherence = ONE_BASIS_POINT;
779779
}
780780

781781
// Fully coherent jurors won't be penalized.
782-
uint256 penalty = (round.pnkAtStakePerJuror * (ALPHA_DIVISOR - degreeOfCoherence)) / ALPHA_DIVISOR;
782+
uint256 penalty = (round.pnkAtStakePerJuror * (ONE_BASIS_POINT - coherence)) / ONE_BASIS_POINT;
783783

784784
// Unlock the PNKs affected by the penalty
785785
address account = round.drawnJurors[_params.repartition];
@@ -792,7 +792,7 @@ abstract contract KlerosCoreBase is IArbitratorV2, Initializable, UUPSProxiable
792792
account,
793793
_params.disputeID,
794794
_params.round,
795-
degreeOfCoherence,
795+
coherence,
796796
-int256(availablePenalty),
797797
0,
798798
round.feeToken
@@ -824,37 +824,40 @@ abstract contract KlerosCoreBase is IArbitratorV2, Initializable, UUPSProxiable
824824
IDisputeKit disputeKit = disputeKits[round.disputeKitID];
825825

826826
// [0, 1] value that determines how coherent the juror was in this round, in basis points.
827-
uint256 degreeOfCoherence = disputeKit.getDegreeOfCoherence(
827+
(uint256 pnkCoherence, uint256 feeCoherence) = disputeKit.getDegreeOfCoherenceReward(
828828
_params.disputeID,
829829
_params.round,
830830
_params.repartition % _params.numberOfVotesInRound,
831831
_params.feePerJurorInRound,
832832
_params.pnkAtStakePerJurorInRound
833833
);
834834

835-
// Make sure the degree doesn't exceed 1, though it should be ensured by the dispute kit.
836-
if (degreeOfCoherence > ALPHA_DIVISOR) {
837-
degreeOfCoherence = ALPHA_DIVISOR;
835+
// Guard against degree exceeding 1, though it should be ensured by the dispute kit.
836+
if (pnkCoherence > ONE_BASIS_POINT) {
837+
pnkCoherence = ONE_BASIS_POINT;
838+
}
839+
if (feeCoherence > ONE_BASIS_POINT) {
840+
feeCoherence = ONE_BASIS_POINT;
838841
}
839842

840843
address account = round.drawnJurors[_params.repartition % _params.numberOfVotesInRound];
841-
uint256 pnkLocked = _applyCoherence(round.pnkAtStakePerJuror, degreeOfCoherence);
844+
uint256 pnkLocked = _applyCoherence(round.pnkAtStakePerJuror, pnkCoherence);
842845

843846
// Release the rest of the PNKs of the juror for this round.
844847
sortitionModule.unlockStake(account, pnkLocked);
845848

846849
// Transfer the rewards
847-
uint256 pnkReward = _applyCoherence(_params.pnkPenaltiesInRound / _params.coherentCount, degreeOfCoherence);
850+
uint256 pnkReward = _applyCoherence(_params.pnkPenaltiesInRound / _params.coherentCount, pnkCoherence);
848851
round.sumPnkRewardPaid += pnkReward;
849-
uint256 feeReward = _applyCoherence(round.totalFeesForJurors / _params.coherentCount, degreeOfCoherence);
852+
uint256 feeReward = _applyCoherence(round.totalFeesForJurors / _params.coherentCount, feeCoherence);
850853
round.sumFeeRewardPaid += feeReward;
851854
pinakion.safeTransfer(account, pnkReward);
852855
_transferFeeToken(round.feeToken, payable(account), feeReward);
853856
emit TokenAndETHShift(
854857
account,
855858
_params.disputeID,
856859
_params.round,
857-
degreeOfCoherence,
860+
pnkCoherence,
858861
int256(pnkReward),
859862
int256(feeReward),
860863
round.feeToken
@@ -1057,18 +1060,18 @@ abstract contract KlerosCoreBase is IArbitratorV2, Initializable, UUPSProxiable
10571060

10581061
/// @dev Applies degree of coherence to an amount
10591062
/// @param _amount The base amount to apply coherence to.
1060-
/// @param _degreeOfCoherence The degree of coherence in basis points.
1063+
/// @param _coherence The degree of coherence in basis points.
10611064
/// @return The amount after applying the degree of coherence.
1062-
function _applyCoherence(uint256 _amount, uint256 _degreeOfCoherence) internal pure returns (uint256) {
1063-
return (_amount * _degreeOfCoherence) / ALPHA_DIVISOR;
1065+
function _applyCoherence(uint256 _amount, uint256 _coherence) internal pure returns (uint256) {
1066+
return (_amount * _coherence) / ONE_BASIS_POINT;
10641067
}
10651068

10661069
/// @dev Calculates PNK at stake per juror based on court parameters
10671070
/// @param _minStake The minimum stake for the court.
10681071
/// @param _alpha The alpha parameter for the court in basis points.
10691072
/// @return The amount of PNK at stake per juror.
10701073
function _calculatePnkAtStake(uint256 _minStake, uint256 _alpha) internal pure returns (uint256) {
1071-
return (_minStake * _alpha) / ALPHA_DIVISOR;
1074+
return (_minStake * _alpha) / ONE_BASIS_POINT;
10721075
}
10731076

10741077
/// @dev Toggles the dispute kit support for a given court.

contracts/src/arbitration/SortitionModuleBase.sol

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -344,14 +344,14 @@ abstract contract SortitionModuleBase is ISortitionModule, Initializable, UUPSPr
344344
// Update the sortition sum tree.
345345
bytes32 stakePathID = _accountAndCourtIDToStakePathID(_account, _courtID);
346346
bool finished = false;
347-
uint96 currenCourtID = _courtID;
347+
uint96 currentCourtID = _courtID;
348348
while (!finished) {
349349
// Tokens are also implicitly staked in parent courts through sortition module to increase the chance of being drawn.
350-
_set(bytes32(uint256(currenCourtID)), _newStake, stakePathID);
351-
if (currenCourtID == GENERAL_COURT) {
350+
_set(bytes32(uint256(currentCourtID)), _newStake, stakePathID);
351+
if (currentCourtID == GENERAL_COURT) {
352352
finished = true;
353353
} else {
354-
(currenCourtID, , , , , , ) = core.courts(currenCourtID); // Get the parent court.
354+
(currentCourtID, , , , , , ) = core.courts(currentCourtID); // Get the parent court.
355355
}
356356
}
357357
emit StakeSet(_account, _courtID, _newStake, juror.stakedPnk);

contracts/src/arbitration/dispute-kits/DisputeKitClassicBase.sol

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {KlerosCore, KlerosCoreBase, IDisputeKit, ISortitionModule} from "../Kler
66
import {Initializable} from "../../proxy/Initializable.sol";
77
import {UUPSProxiable} from "../../proxy/UUPSProxiable.sol";
88
import {SafeSend} from "../../libraries/SafeSend.sol";
9+
import {ONE_BASIS_POINT} from "../../libraries/Constants.sol";
910

1011
/// @title DisputeKitClassicBase
1112
/// Abstract Dispute kit classic implementation of the Kleros v1 features including:
@@ -57,7 +58,6 @@ abstract contract DisputeKitClassicBase is IDisputeKit, Initializable, UUPSProxi
5758
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.
5859
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.
5960
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.
60-
uint256 public constant ONE_BASIS_POINT = 10000; // One basis point, for scaling.
6161

6262
address public governor; // The governor of the contract.
6363
KlerosCore public core; // The Kleros Core arbitrator
@@ -526,14 +526,39 @@ abstract contract DisputeKitClassicBase is IDisputeKit, Initializable, UUPSProxi
526526
/// @param _coreDisputeID The ID of the dispute in Kleros Core, not in the Dispute Kit.
527527
/// @param _coreRoundID The ID of the round in Kleros Core, not in the Dispute Kit.
528528
/// @param _voteID The ID of the vote.
529-
/// @return The degree of coherence in basis points.
530-
function getDegreeOfCoherence(
529+
/// @return pnkCoherence The degree of coherence in basis points for the dispute PNK reward.
530+
/// @return feeCoherence The degree of coherence in basis points for the dispute fee reward.
531+
function getDegreeOfCoherenceReward(
531532
uint256 _coreDisputeID,
532533
uint256 _coreRoundID,
533534
uint256 _voteID,
534535
uint256 /* _feePerJuror */,
535536
uint256 /* _pnkAtStakePerJuror */
536-
) external view override returns (uint256) {
537+
) external view override returns (uint256 pnkCoherence, uint256 feeCoherence) {
538+
uint256 coherence = _getDegreeOfCoherence(_coreDisputeID, _coreRoundID, _voteID);
539+
return (coherence, coherence);
540+
}
541+
542+
/// @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.
543+
/// @param _coreDisputeID The ID of the dispute in Kleros Core, not in the Dispute Kit.
544+
/// @param _coreRoundID The ID of the round in Kleros Core, not in the Dispute Kit.
545+
/// @param _voteID The ID of the vote.
546+
/// @return pnkCoherence The degree of coherence in basis points for the dispute PNK reward.
547+
function getDegreeOfCoherencePenalty(
548+
uint256 _coreDisputeID,
549+
uint256 _coreRoundID,
550+
uint256 _voteID,
551+
uint256 /* _feePerJuror */,
552+
uint256 /* _pnkAtStakePerJuror */
553+
) external view override returns (uint256 pnkCoherence) {
554+
return _getDegreeOfCoherence(_coreDisputeID, _coreRoundID, _voteID);
555+
}
556+
557+
function _getDegreeOfCoherence(
558+
uint256 _coreDisputeID,
559+
uint256 _coreRoundID,
560+
uint256 _voteID
561+
) internal view returns (uint256 coherence) {
537562
// In this contract this degree can be either 0 or 1, but in other dispute kits this value can be something in between.
538563
Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]];
539564
Vote storage vote = dispute.rounds[dispute.coreRoundIDToLocal[_coreRoundID]].votes[_voteID];

contracts/src/arbitration/interfaces/IDisputeKit.sol

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,14 +67,30 @@ interface IDisputeKit {
6767
/// @param _voteID The ID of the vote.
6868
/// @param _feePerJuror The fee per juror.
6969
/// @param _pnkAtStakePerJuror The PNK at stake per juror.
70-
/// @return The degree of coherence in basis points.
71-
function getDegreeOfCoherence(
70+
/// @return pnkCoherence The degree of coherence in basis points for the dispute PNK reward.
71+
/// @return feeCoherence The degree of coherence in basis points for the dispute fee reward.
72+
function getDegreeOfCoherenceReward(
7273
uint256 _coreDisputeID,
7374
uint256 _coreRoundID,
7475
uint256 _voteID,
7576
uint256 _feePerJuror,
7677
uint256 _pnkAtStakePerJuror
77-
) external view returns (uint256);
78+
) external view returns (uint256 pnkCoherence, uint256 feeCoherence);
79+
80+
/// @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.
81+
/// @param _coreDisputeID The ID of the dispute in Kleros Core, not in the Dispute Kit.
82+
/// @param _coreRoundID The ID of the round in Kleros Core, not in the Dispute Kit.
83+
/// @param _voteID The ID of the vote.
84+
/// @param _feePerJuror The fee per juror.
85+
/// @param _pnkAtStakePerJuror The PNK at stake per juror.
86+
/// @return pnkCoherence The degree of coherence in basis points for the dispute PNK reward.
87+
function getDegreeOfCoherencePenalty(
88+
uint256 _coreDisputeID,
89+
uint256 _coreRoundID,
90+
uint256 _voteID,
91+
uint256 _feePerJuror,
92+
uint256 _pnkAtStakePerJuror
93+
) external view returns (uint256 pnkCoherence);
7894

7995
/// @dev Gets the number of jurors who are eligible to a reward in this round.
8096
/// @param _coreDisputeID The ID of the dispute in Kleros Core, not in the Dispute Kit.

contracts/src/arbitration/university/KlerosCoreUniversity.sol

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ import {IArbitrableV2, IArbitratorV2} from "../interfaces/IArbitratorV2.sol";
66
import {IDisputeKit} from "../interfaces/IDisputeKit.sol";
77
import {ISortitionModuleUniversity} from "./ISortitionModuleUniversity.sol";
88
import {SafeERC20, IERC20} from "../../libraries/SafeERC20.sol";
9-
import "../../libraries/Constants.sol";
109
import {UUPSProxiable} from "../../proxy/UUPSProxiable.sol";
1110
import {Initializable} from "../../proxy/Initializable.sol";
11+
import "../../libraries/Constants.sol";
1212

1313
/// @title KlerosCoreUniversity
1414
/// Core arbitrator contract for educational purposes.
@@ -87,7 +87,6 @@ contract KlerosCoreUniversity is IArbitratorV2, UUPSProxiable, Initializable {
8787
// * Storage * //
8888
// ************************************* //
8989

90-
uint256 private constant ALPHA_DIVISOR = 1e4; // The number to divide `Court.alpha` by.
9190
uint256 private constant NON_PAYABLE_AMOUNT = (2 ** 256 - 2) / 2; // An amount higher than the supply of ETH.
9291

9392
address public governor; // The governor of the contract.
@@ -526,7 +525,7 @@ contract KlerosCoreUniversity is IArbitratorV2, UUPSProxiable, Initializable {
526525
: convertEthToTokenAmount(_feeToken, court.feeForJuror);
527526
round.nbVotes = _feeAmount / feeForJuror;
528527
round.disputeKitID = disputeKitID;
529-
round.pnkAtStakePerJuror = (court.minStake * court.alpha) / ALPHA_DIVISOR;
528+
round.pnkAtStakePerJuror = (court.minStake * court.alpha) / ONE_BASIS_POINT;
530529
round.totalFeesForJurors = _feeAmount;
531530
round.feeToken = IERC20(_feeToken);
532531

@@ -655,7 +654,7 @@ contract KlerosCoreUniversity is IArbitratorV2, UUPSProxiable, Initializable {
655654

656655
Court storage court = courts[newCourtID];
657656
extraRound.nbVotes = msg.value / court.feeForJuror; // As many votes that can be afforded by the provided funds.
658-
extraRound.pnkAtStakePerJuror = (court.minStake * court.alpha) / ALPHA_DIVISOR;
657+
extraRound.pnkAtStakePerJuror = (court.minStake * court.alpha) / ONE_BASIS_POINT;
659658
extraRound.totalFeesForJurors = msg.value;
660659
extraRound.disputeKitID = newDisputeKitID;
661660

@@ -754,20 +753,21 @@ contract KlerosCoreUniversity is IArbitratorV2, UUPSProxiable, Initializable {
754753
IDisputeKit disputeKit = disputeKits[round.disputeKitID];
755754

756755
// [0, 1] value that determines how coherent the juror was in this round, in basis points.
757-
uint256 degreeOfCoherence = disputeKit.getDegreeOfCoherence(
756+
uint256 coherence = disputeKit.getDegreeOfCoherencePenalty(
758757
_params.disputeID,
759758
_params.round,
760759
_params.repartition,
761760
_params.feePerJurorInRound,
762761
_params.pnkAtStakePerJurorInRound
763762
);
764-
if (degreeOfCoherence > ALPHA_DIVISOR) {
765-
// Make sure the degree doesn't exceed 1, though it should be ensured by the dispute kit.
766-
degreeOfCoherence = ALPHA_DIVISOR;
763+
764+
// Guard against degree exceeding 1, though it should be ensured by the dispute kit.
765+
if (coherence > ONE_BASIS_POINT) {
766+
coherence = ONE_BASIS_POINT;
767767
}
768768

769769
// Fully coherent jurors won't be penalized.
770-
uint256 penalty = (round.pnkAtStakePerJuror * (ALPHA_DIVISOR - degreeOfCoherence)) / ALPHA_DIVISOR;
770+
uint256 penalty = (round.pnkAtStakePerJuror * (ONE_BASIS_POINT - coherence)) / ONE_BASIS_POINT;
771771

772772
// Unlock the PNKs affected by the penalty
773773
address account = round.drawnJurors[_params.repartition];
@@ -780,7 +780,7 @@ contract KlerosCoreUniversity is IArbitratorV2, UUPSProxiable, Initializable {
780780
account,
781781
_params.disputeID,
782782
_params.round,
783-
degreeOfCoherence,
783+
coherence,
784784
-int256(availablePenalty),
785785
0,
786786
round.feeToken
@@ -818,29 +818,32 @@ contract KlerosCoreUniversity is IArbitratorV2, UUPSProxiable, Initializable {
818818
IDisputeKit disputeKit = disputeKits[round.disputeKitID];
819819

820820
// [0, 1] value that determines how coherent the juror was in this round, in basis points.
821-
uint256 degreeOfCoherence = disputeKit.getDegreeOfCoherence(
821+
(uint256 pnkCoherence, uint256 feeCoherence) = disputeKit.getDegreeOfCoherenceReward(
822822
_params.disputeID,
823823
_params.round,
824824
_params.repartition % _params.numberOfVotesInRound,
825825
_params.feePerJurorInRound,
826826
_params.pnkAtStakePerJurorInRound
827827
);
828828

829-
// Make sure the degree doesn't exceed 1, though it should be ensured by the dispute kit.
830-
if (degreeOfCoherence > ALPHA_DIVISOR) {
831-
degreeOfCoherence = ALPHA_DIVISOR;
829+
// Guard against degree exceeding 1, though it should be ensured by the dispute kit.
830+
if (pnkCoherence > ONE_BASIS_POINT) {
831+
pnkCoherence = ONE_BASIS_POINT;
832+
}
833+
if (feeCoherence > ONE_BASIS_POINT) {
834+
feeCoherence = ONE_BASIS_POINT;
832835
}
833836

834837
address account = round.drawnJurors[_params.repartition % _params.numberOfVotesInRound];
835-
uint256 pnkLocked = (round.pnkAtStakePerJuror * degreeOfCoherence) / ALPHA_DIVISOR;
838+
uint256 pnkLocked = (round.pnkAtStakePerJuror * pnkCoherence) / ONE_BASIS_POINT;
836839

837840
// Release the rest of the PNKs of the juror for this round.
838841
sortitionModule.unlockStake(account, pnkLocked);
839842

840843
// Transfer the rewards
841-
uint256 pnkReward = ((_params.pnkPenaltiesInRound / _params.coherentCount) * degreeOfCoherence) / ALPHA_DIVISOR;
844+
uint256 pnkReward = ((_params.pnkPenaltiesInRound / _params.coherentCount) * pnkCoherence) / ONE_BASIS_POINT;
842845
round.sumPnkRewardPaid += pnkReward;
843-
uint256 feeReward = ((round.totalFeesForJurors / _params.coherentCount) * degreeOfCoherence) / ALPHA_DIVISOR;
846+
uint256 feeReward = ((round.totalFeesForJurors / _params.coherentCount) * feeCoherence) / ONE_BASIS_POINT;
844847
round.sumFeeRewardPaid += feeReward;
845848
pinakion.safeTransfer(account, pnkReward);
846849
if (round.feeToken == NATIVE_CURRENCY) {
@@ -854,7 +857,7 @@ contract KlerosCoreUniversity is IArbitratorV2, UUPSProxiable, Initializable {
854857
account,
855858
_params.disputeID,
856859
_params.round,
857-
degreeOfCoherence,
860+
pnkCoherence,
858861
int256(pnkReward),
859862
int256(feeReward),
860863
round.feeToken

contracts/src/kleros-v1/kleros-liquid-xdai/xKlerosLiquidV2.sol

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,6 @@ contract xKlerosLiquidV2 is Initializable, ITokenController, IArbitratorV2 {
141141
uint256 public constant MAX_STAKE_PATHS = 4; // The maximum number of stake paths a juror can have.
142142
uint256 public constant DEFAULT_NB_OF_JURORS = 3; // The default number of jurors in a dispute.
143143
uint256 public constant NON_PAYABLE_AMOUNT = (2 ** 256 - 2) / 2; // An amount higher than the supply of ETH.
144-
uint256 public constant ALPHA_DIVISOR = 1e4; // The number to divide `Court.alpha` by.
145144
// General Contracts
146145
address public governor; // The governor of the contract.
147146
WrappedPinakion public pinakion; // The Pinakion token contract.

contracts/src/libraries/Constants.sol

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ uint256 constant DEFAULT_K = 6; // Default number of children per node.
2020
uint256 constant DEFAULT_NB_OF_JURORS = 3; // The default number of jurors in a dispute.
2121
IERC20 constant NATIVE_CURRENCY = IERC20(address(0)); // The native currency, such as ETH on Arbitrum, Optimism and Ethereum L1.
2222

23+
// Units
24+
uint256 constant ONE_BASIS_POINT = 10000;
25+
2326
enum OnError {
2427
Revert,
2528
Return

0 commit comments

Comments
 (0)