Skip to content

Commit 953bd95

Browse files
authored
Merge pull request #2107 from kleros/fix/penalties-reduce-drawing-odds
PNK penalties applied to the sortition tree stakes
2 parents 361fa55 + 1d1f9f2 commit 953bd95

File tree

12 files changed

+334
-123
lines changed

12 files changed

+334
-123
lines changed

contracts/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,12 @@ The format is based on [Common Changelog](https://common-changelog.org/).
1313
- **Breaking:** Rename the interface from `RNG` to `IRNG` ([#2054](https://github.com/kleros/kleros-v2/issues/2054))
1414
- **Breaking:** Remove the `_block` parameter from `IRNG.requestRandomness()` and `IRNG.receiveRandomness()`, not needed for the primary VRF-based RNG ([#2054](https://github.com/kleros/kleros-v2/issues/2054))
1515
- **Breaking:** Rename `governor` to `owner` in order to comply with the lightweight ownership standard [ERC-5313](https://eipsinsight.com/ercs/erc-5313) ([#2112](https://github.com/kleros/kleros-v2/issues/2112))
16+
- **Breaking:** Apply the penalties to the stakes in the Sortition Tree ([#2107](https://github.com/kleros/kleros-v2/issues/2107))
17+
- **Breaking:** Make `SortitionModule.getJurorBalance().stakedInCourt` include the penalties ([#2107](https://github.com/kleros/kleros-v2/issues/2107))
18+
- **Breaking:** Add a new field `drawnJurorFromCourtIDs` to the `Round` struct in `KlerosCoreBase` and `KlerosCoreUniversity` ([#2107](https://github.com/kleros/kleros-v2/issues/2107))
19+
- Make `IDisputeKit.draw()` and `ISortitionModule.draw()` return the court ID from which the juror was drawn ([#2107](https://github.com/kleros/kleros-v2/issues/2107))
20+
- Rename `SortitionModule.setJurorInactive()` to `SortitionModule.forcedUnstakeAllCourts()` ([#2107](https://github.com/kleros/kleros-v2/issues/2107))
21+
- Allow stake changes to by-pass delayed stakes when initiated by the SortitionModule by setting the `_noDelay` parameter to `true` in `SortitionModule.validateStake()` ([#2107](https://github.com/kleros/kleros-v2/issues/2107))
1622
- Make the primary VRF-based RNG fall back to `BlockhashRNG` if the VRF request is not fulfilled within a timeout ([#2054](https://github.com/kleros/kleros-v2/issues/2054))
1723
- Authenticate the calls to the RNGs to prevent 3rd parties from depleting the Chainlink VRF subscription funds ([#2054](https://github.com/kleros/kleros-v2/issues/2054))
1824
- Use `block.timestamp` rather than `block.number` for `BlockhashRNG` for better reliability on Arbitrum as block production is sporadic depending on network conditions. ([#2054](https://github.com/kleros/kleros-v2/issues/2054))

contracts/src/arbitration/KlerosCoreBase.sol

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ abstract contract KlerosCoreBase is IArbitratorV2, Initializable, UUPSProxiable
6060
uint256 repartitions; // A counter of reward repartitions made in this round.
6161
uint256 pnkPenalties; // The amount of PNKs collected from penalties in this round.
6262
address[] drawnJurors; // Addresses of the jurors that were drawn in this round.
63+
uint96[] drawnJurorFromCourtIDs; // The courtIDs where the juror was drawn from, possibly their stake in a subcourt.
6364
uint256 sumFeeRewardPaid; // Total sum of arbitration fees paid to coherent jurors as a reward in this round.
6465
uint256 sumPnkRewardPaid; // Total sum of PNK paid to coherent jurors as a reward in this round.
6566
IERC20 feeToken; // The token used for paying fees in this round.
@@ -463,16 +464,16 @@ abstract contract KlerosCoreBase is IArbitratorV2, Initializable, UUPSProxiable
463464
/// @param _newStake The new stake.
464465
/// Note that the existing delayed stake will be nullified as non-relevant.
465466
function setStake(uint96 _courtID, uint256 _newStake) external virtual whenNotPaused {
466-
_setStake(msg.sender, _courtID, _newStake, OnError.Revert);
467+
_setStake(msg.sender, _courtID, _newStake, false, OnError.Revert);
467468
}
468469

469-
/// @dev Sets the stake of a specified account in a court, typically to apply a delayed stake or unstake inactive jurors.
470+
/// @dev Sets the stake of a specified account in a court without delaying stake changes, typically to apply a delayed stake or unstake inactive jurors.
470471
/// @param _account The account whose stake is being set.
471472
/// @param _courtID The ID of the court.
472473
/// @param _newStake The new stake.
473474
function setStakeBySortitionModule(address _account, uint96 _courtID, uint256 _newStake) external {
474475
if (msg.sender != address(sortitionModule)) revert SortitionModuleOnly();
475-
_setStake(_account, _courtID, _newStake, OnError.Return);
476+
_setStake(_account, _courtID, _newStake, true, OnError.Return);
476477
}
477478

478479
/// @dev Transfers PNK to the juror by SortitionModule.
@@ -606,13 +607,14 @@ abstract contract KlerosCoreBase is IArbitratorV2, Initializable, UUPSProxiable
606607
uint256 startIndex = round.drawIterations; // for gas: less storage reads
607608
uint256 i;
608609
while (i < _iterations && round.drawnJurors.length < round.nbVotes) {
609-
address drawnAddress = disputeKit.draw(_disputeID, startIndex + i++);
610+
(address drawnAddress, uint96 fromSubcourtID) = disputeKit.draw(_disputeID, startIndex + i++);
610611
if (drawnAddress == address(0)) {
611612
continue;
612613
}
613614
sortitionModule.lockStake(drawnAddress, round.pnkAtStakePerJuror);
614615
emit Draw(drawnAddress, _disputeID, currentRound, round.drawnJurors.length);
615616
round.drawnJurors.push(drawnAddress);
617+
round.drawnJurorFromCourtIDs.push(fromSubcourtID != 0 ? fromSubcourtID : dispute.courtID);
616618
if (round.drawnJurors.length == round.nbVotes) {
617619
sortitionModule.postDrawHook(_disputeID, currentRound);
618620
}
@@ -775,7 +777,12 @@ abstract contract KlerosCoreBase is IArbitratorV2, Initializable, UUPSProxiable
775777
sortitionModule.unlockStake(account, penalty);
776778

777779
// Apply the penalty to the staked PNKs.
778-
(uint256 pnkBalance, uint256 availablePenalty) = sortitionModule.penalizeStake(account, penalty);
780+
uint96 penalizedInCourtID = round.drawnJurorFromCourtIDs[_params.repartition];
781+
(uint256 pnkBalance, uint256 newCourtStake, uint256 availablePenalty) = sortitionModule.setStakePenalty(
782+
account,
783+
penalizedInCourtID,
784+
penalty
785+
);
779786
_params.pnkPenaltiesInRound += availablePenalty;
780787
emit TokenAndETHShift(
781788
account,
@@ -786,10 +793,15 @@ abstract contract KlerosCoreBase is IArbitratorV2, Initializable, UUPSProxiable
786793
0,
787794
round.feeToken
788795
);
789-
// Unstake the juror from all courts if he was inactive or his balance can't cover penalties anymore.
796+
790797
if (pnkBalance == 0 || !disputeKit.isVoteActive(_params.disputeID, _params.round, _params.repartition)) {
791-
sortitionModule.setJurorInactive(account);
798+
// The juror is inactive or their balance is can't cover penalties anymore, unstake them from all courts.
799+
sortitionModule.forcedUnstakeAllCourts(account);
800+
} else if (newCourtStake < courts[penalizedInCourtID].minStake) {
801+
// The juror's balance fell below the court minStake, unstake them from the court.
802+
sortitionModule.forcedUnstake(account, penalizedInCourtID);
792803
}
804+
793805
if (_params.repartition == _params.numberOfVotesInRound - 1 && _params.coherentCount == 0) {
794806
// No one was coherent, send the rewards to the owner.
795807
_transferFeeToken(round.feeToken, payable(owner), round.totalFeesForJurors);
@@ -1126,9 +1138,16 @@ abstract contract KlerosCoreBase is IArbitratorV2, Initializable, UUPSProxiable
11261138
/// @param _account The account to set the stake for.
11271139
/// @param _courtID The ID of the court to set the stake for.
11281140
/// @param _newStake The new stake.
1141+
/// @param _noDelay True if the stake change should not be delayed.
11291142
/// @param _onError Whether to revert or return false on error.
11301143
/// @return Whether the stake was successfully set or not.
1131-
function _setStake(address _account, uint96 _courtID, uint256 _newStake, OnError _onError) internal returns (bool) {
1144+
function _setStake(
1145+
address _account,
1146+
uint96 _courtID,
1147+
uint256 _newStake,
1148+
bool _noDelay,
1149+
OnError _onError
1150+
) internal returns (bool) {
11321151
if (_courtID == FORKING_COURT || _courtID >= courts.length) {
11331152
_stakingFailed(_onError, StakingResult.CannotStakeInThisCourt); // Staking directly into the forking court is not allowed.
11341153
return false;
@@ -1140,7 +1159,8 @@ abstract contract KlerosCoreBase is IArbitratorV2, Initializable, UUPSProxiable
11401159
(uint256 pnkDeposit, uint256 pnkWithdrawal, StakingResult stakingResult) = sortitionModule.validateStake(
11411160
_account,
11421161
_courtID,
1143-
_newStake
1162+
_newStake,
1163+
_noDelay
11441164
);
11451165
if (stakingResult != StakingResult.Successful && stakingResult != StakingResult.Delayed) {
11461166
_stakingFailed(_onError, stakingResult);

contracts/src/arbitration/KlerosCoreNeo.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ contract KlerosCoreNeo is KlerosCoreBase {
108108
/// Note that the existing delayed stake will be nullified as non-relevant.
109109
function setStake(uint96 _courtID, uint256 _newStake) external override whenNotPaused {
110110
if (jurorNft.balanceOf(msg.sender) == 0) revert NotEligibleForStaking();
111-
super._setStake(msg.sender, _courtID, _newStake, OnError.Revert);
111+
super._setStake(msg.sender, _courtID, _newStake, false, OnError.Revert);
112112
}
113113

114114
// ************************************* //

0 commit comments

Comments
 (0)