@@ -17,14 +17,20 @@ abstract contract SortitionModuleBase is ISortitionModule, Initializable, UUPSPr
1717 // * Enums / Structs * //
1818 // ************************************* //
1919
20+ struct SubCourtStakes {
21+ uint256 totalStakedInCourts;
22+ uint96 [MAX_STAKE_PATHS] courtIDs;
23+ uint256 [MAX_STAKE_PATHS] stakedInCourts;
24+ }
25+
2026 struct SortitionSumTree {
2127 uint256 K; // The maximum number of children per node.
22- // We use this to keep track of vacant positions in the tree after removing a leaf. This is for keeping the tree as balanced as possible without spending gas on moving nodes around.
23- uint256 [] stack;
24- uint256 [] nodes;
28+ uint256 [] stack; // We use this to keep track of vacant positions in the tree after removing a leaf. This is for keeping the tree as balanced as possible without spending gas on moving nodes around.
29+ uint256 [] nodes; // The tree nodes.
2530 // Two-way mapping of IDs to node indexes. Note that node index 0 is reserved for the root node, and means the ID does not have a node.
26- mapping (bytes32 => uint256 ) IDsToNodeIndexes;
27- mapping (uint256 => bytes32 ) nodeIndexesToIDs;
31+ mapping (bytes32 stakePathID = > uint256 nodeIndex ) IDsToNodeIndexes;
32+ mapping (uint256 nodeIndex = > bytes32 stakePathID ) nodeIndexesToIDs;
33+ mapping (bytes32 stakePathID = > SubCourtStakes subcourtStakes ) IDsToSubCourtStakes;
2834 }
2935
3036 struct DelayedStake {
@@ -36,7 +42,7 @@ abstract contract SortitionModuleBase is ISortitionModule, Initializable, UUPSPr
3642
3743 struct Juror {
3844 uint96 [] courtIDs; // The IDs of courts where the juror's stake path ends. A stake path is a path from the general court to a court the juror directly staked in using `_setStake`.
39- uint256 stakedPnk; // The juror's total amount of tokens staked in subcourts. Reflects actual pnk balance .
45+ uint256 stakedPnk; // The juror's total amount of tokens staked in subcourts. PNK balance including locked PNK and penalty deductions .
4046 uint256 lockedPnk; // The juror's total amount of tokens locked in disputes.
4147 }
4248
@@ -306,6 +312,28 @@ abstract contract SortitionModuleBase is ISortitionModule, Initializable, UUPSPr
306312 _setStake (_account, _courtID, _pnkDeposit, _pnkWithdrawal, _newStake);
307313 }
308314
315+ function setStakePenalty (
316+ address _account ,
317+ uint96 _courtID ,
318+ uint256 _penalty
319+ ) external override onlyByCore returns (uint256 pnkBalance , uint256 availablePenalty ) {
320+ Juror storage juror = jurors[_account];
321+ availablePenalty = _penalty;
322+ if (juror.stakedPnk < _penalty) {
323+ availablePenalty = juror.stakedPnk;
324+ }
325+
326+ if (availablePenalty == 0 ) return (juror.stakedPnk, 0 ); // No penalty to apply.
327+
328+ uint256 currentStake = stakeOf (_account, _courtID);
329+ uint256 newStake = 0 ;
330+ if (currentStake >= availablePenalty) {
331+ newStake = currentStake - availablePenalty;
332+ }
333+ _setStake (_account, _courtID, 0 , availablePenalty, newStake);
334+ pnkBalance = juror.stakedPnk; // updated by _setStake()
335+ }
336+
309337 function _setStake (
310338 address _account ,
311339 uint96 _courtID ,
@@ -339,12 +367,14 @@ abstract contract SortitionModuleBase is ISortitionModule, Initializable, UUPSPr
339367 bytes32 stakePathID = _accountAndCourtIDToStakePathID (_account, _courtID);
340368 bool finished = false ;
341369 uint96 currentCourtID = _courtID;
370+ uint96 fromSubCourtID = 0 ; // 0 means it is not coming from a subcourt.
342371 while (! finished) {
343372 // Tokens are also implicitly staked in parent courts through sortition module to increase the chance of being drawn.
344- _set (bytes32 (uint256 (currentCourtID)), _newStake, stakePathID);
373+ _set (bytes32 (uint256 (currentCourtID)), _newStake, stakePathID, fromSubCourtID );
345374 if (currentCourtID == GENERAL_COURT) {
346375 finished = true ;
347376 } else {
377+ fromSubCourtID = currentCourtID;
348378 (currentCourtID, , , , , , ) = core.courts (currentCourtID); // Get the parent court.
349379 }
350380 }
@@ -367,25 +397,6 @@ abstract contract SortitionModuleBase is ISortitionModule, Initializable, UUPSPr
367397 }
368398 }
369399
370- function penalizeStake (
371- address _account ,
372- uint256 _relativeAmount
373- ) external override onlyByCore returns (uint256 pnkBalance , uint256 availablePenalty ) {
374- Juror storage juror = jurors[_account];
375- uint256 stakedPnk = juror.stakedPnk;
376-
377- if (stakedPnk >= _relativeAmount) {
378- availablePenalty = _relativeAmount;
379- juror.stakedPnk -= _relativeAmount;
380- } else {
381- availablePenalty = stakedPnk;
382- juror.stakedPnk = 0 ;
383- }
384-
385- pnkBalance = juror.stakedPnk;
386- return (pnkBalance, availablePenalty);
387- }
388-
389400 /// @dev Unstakes the inactive juror from all courts.
390401 /// `O(n * (p * log_k(j)) )` where
391402 /// `n` is the number of courts the juror has staked in,
@@ -433,12 +444,12 @@ abstract contract SortitionModuleBase is ISortitionModule, Initializable, UUPSPr
433444 bytes32 _key ,
434445 uint256 _coreDisputeID ,
435446 uint256 _nonce
436- ) public view override returns (address drawnAddress ) {
447+ ) public view override returns (address drawnAddress , uint96 fromSubcourtID ) {
437448 if (phase != Phase.drawing) revert NotDrawingPhase ();
438449 SortitionSumTree storage tree = sortitionSumTrees[_key];
439450
440451 if (tree.nodes[0 ] == 0 ) {
441- return address (0 ); // No jurors staked.
452+ return ( address (0 ), 0 ); // No jurors staked.
442453 }
443454
444455 uint256 currentDrawnNumber = uint256 (keccak256 (abi.encodePacked (randomNumber, _coreDisputeID, _nonce))) %
@@ -462,7 +473,33 @@ abstract contract SortitionModuleBase is ISortitionModule, Initializable, UUPSPr
462473 }
463474 }
464475 }
465- drawnAddress = _stakePathIDToAccount (tree.nodeIndexesToIDs[treeIndex]);
476+
477+ bytes32 stakePathID = tree.nodeIndexesToIDs[treeIndex];
478+ drawnAddress = _stakePathIDToAccount (stakePathID);
479+
480+ // Identify which subcourt was selected based on currentDrawnNumber
481+ SubCourtStakes storage subcourtStakes = tree.IDsToSubCourtStakes[stakePathID];
482+
483+ // The current court stake is the node value minus all subcourt stakes
484+ uint256 currentCourtStake = 0 ;
485+ if (tree.nodes[treeIndex] > subcourtStakes.totalStakedInCourts) {
486+ currentCourtStake = tree.nodes[treeIndex] - subcourtStakes.totalStakedInCourts;
487+ }
488+
489+ // Check if the drawn number falls within current court range
490+ if (currentDrawnNumber >= currentCourtStake) {
491+ // Find which subcourt range contains the drawn number
492+ uint256 accumulatedStake = currentCourtStake;
493+ for (uint256 i = 0 ; i < MAX_STAKE_PATHS; i++ ) {
494+ if (subcourtStakes.stakedInCourts[i] > 0 ) {
495+ if (currentDrawnNumber < accumulatedStake + subcourtStakes.stakedInCourts[i]) {
496+ fromSubcourtID = subcourtStakes.courtIDs[i];
497+ break ;
498+ }
499+ accumulatedStake += subcourtStakes.stakedInCourts[i];
500+ }
501+ }
502+ }
466503 }
467504
468505 /// @dev Get the stake of a juror in a court.
@@ -487,6 +524,13 @@ abstract contract SortitionModuleBase is ISortitionModule, Initializable, UUPSPr
487524 return tree.nodes[treeIndex];
488525 }
489526
527+ /// @dev Gets the balance of a juror in a court.
528+ /// @param _juror The address of the juror.
529+ /// @param _courtID The ID of the court.
530+ /// @return totalStaked The total amount of tokens staked including locked tokens and penalty deductions. Equivalent to the effective stake in the General court.
531+ /// @return totalLocked The total amount of tokens locked in disputes.
532+ /// @return stakedInCourt The amount of tokens staked in the specified court including locked tokens and penalty deductions.
533+ /// @return nbCourts The number of courts the juror has directly staked in.
490534 function getJurorBalance (
491535 address _juror ,
492536 uint96 _courtID
@@ -546,6 +590,41 @@ abstract contract SortitionModuleBase is ISortitionModule, Initializable, UUPSPr
546590 }
547591 }
548592
593+ /// @dev Update the subcourt stakes.
594+ /// @param _subcourtStakes The subcourt stakes.
595+ /// @param _value The value to update.
596+ /// @param _fromSubCourtID The ID of the subcourt from which the stake is coming from, or 0 if the stake does not come from a subcourt.
597+ /// `O(1)` complexity with `MAX_STAKE_PATHS` a small constant.
598+ function _updateSubCourtStakes (
599+ SubCourtStakes storage _subcourtStakes ,
600+ uint256 _value ,
601+ uint96 _fromSubCourtID
602+ ) internal {
603+ // Update existing stake item if found
604+ for (uint256 i = 0 ; i < MAX_STAKE_PATHS; i++ ) {
605+ if (_subcourtStakes.courtIDs[i] == _fromSubCourtID) {
606+ if (_value == 0 ) {
607+ delete _subcourtStakes.courtIDs[i];
608+ delete _subcourtStakes.stakedInCourts[i];
609+ } else {
610+ _subcourtStakes.totalStakedInCourts += _value;
611+ _subcourtStakes.totalStakedInCourts -= _subcourtStakes.stakedInCourts[i];
612+ _subcourtStakes.stakedInCourts[i] = _value;
613+ }
614+ return ;
615+ }
616+ }
617+ // Not found so add a new stake item
618+ for (uint256 i = 0 ; i < MAX_STAKE_PATHS; i++ ) {
619+ if (_subcourtStakes.courtIDs[i] == 0 ) {
620+ _subcourtStakes.courtIDs[i] = _fromSubCourtID;
621+ _subcourtStakes.totalStakedInCourts += _value;
622+ _subcourtStakes.stakedInCourts[i] = _value;
623+ return ;
624+ }
625+ }
626+ }
627+
549628 /// @dev Retrieves a juror's address from the stake path ID.
550629 /// @param _stakePathID The stake path ID to unpack.
551630 /// @return account The account.
@@ -579,10 +658,11 @@ abstract contract SortitionModuleBase is ISortitionModule, Initializable, UUPSPr
579658 /// @param _key The key of the tree.
580659 /// @param _value The new value.
581660 /// @param _ID The ID of the value.
661+ /// @param _fromSubCourtID The ID of the subcourt from which the stake is coming from, or 0 if the stake does not come from a subcourt.
582662 /// `O(log_k(n))` where
583663 /// `k` is the maximum number of children per node in the tree,
584664 /// and `n` is the maximum number of nodes ever appended.
585- function _set (bytes32 _key , uint256 _value , bytes32 _ID ) internal {
665+ function _set (bytes32 _key , uint256 _value , bytes32 _ID , uint96 _fromSubCourtID ) internal {
586666 SortitionSumTree storage tree = sortitionSumTrees[_key];
587667 uint256 treeIndex = tree.IDsToNodeIndexes[_ID];
588668
@@ -652,6 +732,8 @@ abstract contract SortitionModuleBase is ISortitionModule, Initializable, UUPSPr
652732 _updateParents (_key, treeIndex, plusOrMinus, plusOrMinusValue);
653733 }
654734 }
735+
736+ _updateSubCourtStakes (tree.IDsToSubCourtStakes[_ID], _value, _fromSubCourtID);
655737 }
656738
657739 /// @dev Packs an account and a court ID into a stake path ID.
0 commit comments