Skip to content

Commit 9618a6f

Browse files
authored
Merge pull request #2099 from kleros/feat/autostake-pnk-rewards
Automatically stake PNK rewards instead of transferring them
2 parents f02d718 + 524116c commit 9618a6f

File tree

7 files changed

+81
-6
lines changed

7 files changed

+81
-6
lines changed

contracts/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ The format is based on [Common Changelog](https://common-changelog.org/).
88

99
### Changed
1010

11+
- **Breaking:** Stake the juror's PNK rewards instead of transferring them out ([#2099](https://github.com/kleros/kleros-v2/issues/2099))
1112
- **Breaking:** Replace `require()` with `revert()` and custom errors outside KlerosCore for consistency and smaller bytecode ([#2084](https://github.com/kleros/kleros-v2/issues/2084))
1213
- **Breaking:** Rename the interface from `RNG` to `IRNG` ([#2054](https://github.com/kleros/kleros-v2/issues/2054))
1314
- **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))

contracts/src/arbitration/KlerosCoreBase.sol

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -846,13 +846,20 @@ abstract contract KlerosCoreBase is IArbitratorV2, Initializable, UUPSProxiable
846846
// Release the rest of the PNKs of the juror for this round.
847847
sortitionModule.unlockStake(account, pnkLocked);
848848

849-
// Transfer the rewards
849+
// Compute the rewards
850850
uint256 pnkReward = _applyCoherence(_params.pnkPenaltiesInRound / _params.coherentCount, pnkCoherence);
851851
round.sumPnkRewardPaid += pnkReward;
852852
uint256 feeReward = _applyCoherence(round.totalFeesForJurors / _params.coherentCount, feeCoherence);
853853
round.sumFeeRewardPaid += feeReward;
854-
pinakion.safeTransfer(account, pnkReward);
854+
855+
// Transfer the fee reward
855856
_transferFeeToken(round.feeToken, payable(account), feeReward);
857+
858+
// Stake the PNK reward if possible, by-passes delayed stakes and other checks usually done by validateStake()
859+
if (!sortitionModule.setStakeReward(account, dispute.courtID, pnkReward)) {
860+
pinakion.safeTransfer(account, pnkReward);
861+
}
862+
856863
emit TokenAndETHShift(
857864
account,
858865
_params.disputeID,

contracts/src/arbitration/SortitionModuleBase.sol

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,30 @@ abstract contract SortitionModuleBase is ISortitionModule, Initializable, UUPSPr
306306
_setStake(_account, _courtID, _pnkDeposit, _pnkWithdrawal, _newStake);
307307
}
308308

309+
/// @dev Update the state of the stakes with a PNK reward deposit, called by KC during rewards execution.
310+
/// `O(n + p * log_k(j))` where
311+
/// `n` is the number of courts the juror has staked in,
312+
/// `p` is the depth of the court tree,
313+
/// `k` is the minimum number of children per node of one of these courts' sortition sum tree,
314+
/// and `j` is the maximum number of jurors that ever staked in one of these courts simultaneously.
315+
/// @param _account The address of the juror.
316+
/// @param _courtID The ID of the court.
317+
/// @param _reward The amount of PNK to be deposited as a reward.
318+
function setStakeReward(
319+
address _account,
320+
uint96 _courtID,
321+
uint256 _reward
322+
) external override onlyByCore returns (bool success) {
323+
if (_reward == 0) return true; // No reward to add.
324+
325+
uint256 currentStake = stakeOf(_account, _courtID);
326+
if (currentStake == 0) return false; // Juror has been unstaked, don't increase their stake.
327+
328+
uint256 newStake = currentStake + _reward;
329+
_setStake(_account, _courtID, _reward, 0, newStake);
330+
return true;
331+
}
332+
309333
function _setStake(
310334
address _account,
311335
uint96 _courtID,

contracts/src/arbitration/interfaces/ISortitionModule.sol

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ interface ISortitionModule {
2929
uint256 _newStake
3030
) external;
3131

32+
function setStakeReward(address _account, uint96 _courtID, uint256 _reward) external returns (bool success);
33+
3234
function setJurorInactive(address _account) external;
3335

3436
function lockStake(address _account, uint256 _relativeAmount) external;

contracts/src/arbitration/university/KlerosCoreUniversity.sol

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -840,19 +840,26 @@ contract KlerosCoreUniversity is IArbitratorV2, UUPSProxiable, Initializable {
840840
// Release the rest of the PNKs of the juror for this round.
841841
sortitionModule.unlockStake(account, pnkLocked);
842842

843-
// Transfer the rewards
843+
// Compute the rewards
844844
uint256 pnkReward = ((_params.pnkPenaltiesInRound / _params.coherentCount) * pnkCoherence) / ONE_BASIS_POINT;
845845
round.sumPnkRewardPaid += pnkReward;
846846
uint256 feeReward = ((round.totalFeesForJurors / _params.coherentCount) * feeCoherence) / ONE_BASIS_POINT;
847847
round.sumFeeRewardPaid += feeReward;
848-
pinakion.safeTransfer(account, pnkReward);
848+
849+
// Transfer the fee reward
849850
if (round.feeToken == NATIVE_CURRENCY) {
850851
// The dispute fees were paid in ETH
851852
payable(account).send(feeReward);
852853
} else {
853854
// The dispute fees were paid in ERC20
854855
round.feeToken.safeTransfer(account, feeReward);
855856
}
857+
858+
// Stake the PNK reward if possible, by-passes delayed stakes and other checks usually done by validateStake()
859+
if (!sortitionModule.setStakeReward(account, dispute.courtID, pnkReward)) {
860+
pinakion.safeTransfer(account, pnkReward);
861+
}
862+
856863
emit TokenAndETHShift(
857864
account,
858865
_params.disputeID,

contracts/src/arbitration/university/SortitionModuleUniversity.sol

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,40 @@ contract SortitionModuleUniversity is ISortitionModuleUniversity, UUPSProxiable,
190190
uint256 _pnkWithdrawal,
191191
uint256 _newStake
192192
) external override onlyByCore {
193+
_setStake(_account, _courtID, _pnkDeposit, _pnkWithdrawal, _newStake);
194+
}
195+
196+
/// @dev Update the state of the stakes with a PNK reward deposit, called by KC during rewards execution.
197+
/// `O(n + p * log_k(j))` where
198+
/// `n` is the number of courts the juror has staked in,
199+
/// `p` is the depth of the court tree,
200+
/// `k` is the minimum number of children per node of one of these courts' sortition sum tree,
201+
/// and `j` is the maximum number of jurors that ever staked in one of these courts simultaneously.
202+
/// @param _account The address of the juror.
203+
/// @param _courtID The ID of the court.
204+
/// @param _reward The amount of PNK to be deposited as a reward.
205+
function setStakeReward(
206+
address _account,
207+
uint96 _courtID,
208+
uint256 _reward
209+
) external override onlyByCore returns (bool success) {
210+
if (_reward == 0) return true; // No reward to add.
211+
212+
uint256 currentStake = _stakeOf(_account, _courtID);
213+
if (currentStake == 0) return false; // Juror has been unstaked, don't increase their stake.
214+
215+
uint256 newStake = currentStake + _reward;
216+
_setStake(_account, _courtID, _reward, 0, newStake);
217+
return true;
218+
}
219+
220+
function _setStake(
221+
address _account,
222+
uint96 _courtID,
223+
uint256 _pnkDeposit,
224+
uint256 _pnkWithdrawal,
225+
uint256 _newStake
226+
) internal {
193227
Juror storage juror = jurors[_account];
194228
uint256 currentStake = _stakeOf(_account, _courtID);
195229
if (_pnkDeposit > 0) {

contracts/test/foundry/KlerosCore.t.sol

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2397,9 +2397,9 @@ contract KlerosCoreTest is Test {
23972397
assertEq(staker1.balance, 0, "Wrong balance of the staker1");
23982398
assertEq(staker2.balance, 0.09 ether, "Wrong balance of the staker2");
23992399

2400-
assertEq(pinakion.balanceOf(address(core)), 20500, "Wrong token balance of the core"); // Was 21500. 1000 was transferred to staker2
2400+
assertEq(pinakion.balanceOf(address(core)), 21500, "Wrong token balance of the core"); // Was 21500. 1000 was transferred to staker2
24012401
assertEq(pinakion.balanceOf(staker1), 999999999999998500, "Wrong token balance of staker1");
2402-
assertEq(pinakion.balanceOf(staker2), 999999999999981000, "Wrong token balance of staker2"); // 20k stake and 1k added as a reward, thus -19k from the default
2402+
assertEq(pinakion.balanceOf(staker2), 999999999999980000, "Wrong token balance of staker2"); // 20k stake and 1k added as a reward, thus -19k from the default
24032403
}
24042404

24052405
function test_execute_NoCoherence() public {

0 commit comments

Comments
 (0)