@@ -17,20 +17,13 @@ abstract contract SortitionModuleBase is ISortitionModule, Initializable, UUPSPr
1717 // * Enums / Structs * //
1818 // ************************************* //
1919
20- struct SubCourtStakes {
21- uint256 totalStakedInSubCourts;
22- uint96 [MAX_STAKE_PATHS] subCourtIDs;
23- uint256 [MAX_STAKE_PATHS] stakedInSubCourts;
24- }
25-
2620 struct SortitionSumTree {
2721 uint256 K; // The maximum number of children per node.
2822 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.
2923 uint256 [] nodes; // The tree nodes.
3024 // 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.
3125 mapping (bytes32 stakePathID = > uint256 nodeIndex ) IDsToNodeIndexes;
3226 mapping (uint256 nodeIndex = > bytes32 stakePathID ) nodeIndexesToIDs;
33- mapping (bytes32 stakePathID = > SubCourtStakes subcourtStakes ) IDsToSubCourtStakes;
3427 }
3528
3629 struct DelayedStake {
@@ -322,27 +315,30 @@ abstract contract SortitionModuleBase is ISortitionModule, Initializable, UUPSPr
322315 /// @param _courtID The ID of the court.
323316 /// @param _penalty The amount of PNK to be deducted.
324317 /// @return pnkBalance The updated total PNK balance of the juror, including the penalty.
318+ /// @return newCourtStake The updated stake of the juror in the court.
325319 /// @return availablePenalty The amount of PNK that was actually deducted.
326320 function setStakePenalty (
327321 address _account ,
328322 uint96 _courtID ,
329323 uint256 _penalty
330- ) external override onlyByCore returns (uint256 pnkBalance , uint256 availablePenalty ) {
324+ ) external override onlyByCore returns (uint256 pnkBalance , uint256 newCourtStake , uint256 availablePenalty ) {
331325 Juror storage juror = jurors[_account];
332326 availablePenalty = _penalty;
327+ newCourtStake = stakeOf (_account, _courtID);
333328 if (juror.stakedPnk < _penalty) {
334329 availablePenalty = juror.stakedPnk;
335330 }
336331
337- if (availablePenalty == 0 ) return (juror.stakedPnk, 0 ); // No penalty to apply.
332+ if (availablePenalty == 0 ) return (juror.stakedPnk, newCourtStake, 0 ); // No penalty to apply.
338333
339334 uint256 currentStake = stakeOf (_account, _courtID);
340335 uint256 newStake = 0 ;
341336 if (currentStake >= availablePenalty) {
342337 newStake = currentStake - availablePenalty;
343338 }
344339 _setStake (_account, _courtID, 0 , availablePenalty, newStake);
345- pnkBalance = juror.stakedPnk; // Updated by _setStake().
340+ pnkBalance = juror.stakedPnk; // updated by _setStake()
341+ newCourtStake = stakeOf (_account, _courtID); // updated by _setStake()
346342 }
347343
348344 function _setStake (
@@ -378,14 +374,12 @@ abstract contract SortitionModuleBase is ISortitionModule, Initializable, UUPSPr
378374 bytes32 stakePathID = _accountAndCourtIDToStakePathID (_account, _courtID);
379375 bool finished = false ;
380376 uint96 currentCourtID = _courtID;
381- uint96 fromSubCourtID = 0 ; // 0 means it is not coming from a subcourt.
382377 while (! finished) {
383378 // Tokens are also implicitly staked in parent courts through sortition module to increase the chance of being drawn.
384- _set (bytes32 (uint256 (currentCourtID)), _newStake, stakePathID, fromSubCourtID );
379+ _set (bytes32 (uint256 (currentCourtID)), _newStake, stakePathID);
385380 if (currentCourtID == GENERAL_COURT) {
386381 finished = true ;
387382 } else {
388- fromSubCourtID = currentCourtID;
389383 (currentCourtID, , , , , , ) = core.courts (currentCourtID); // Get the parent court.
390384 }
391385 }
@@ -486,31 +480,7 @@ abstract contract SortitionModuleBase is ISortitionModule, Initializable, UUPSPr
486480 }
487481
488482 bytes32 stakePathID = tree.nodeIndexesToIDs[treeIndex];
489- drawnAddress = _stakePathIDToAccount (stakePathID);
490-
491- // Identify which subcourt was selected based on currentDrawnNumber
492- SubCourtStakes storage subcourtStakes = tree.IDsToSubCourtStakes[stakePathID];
493-
494- // The current court stake is the node value minus all subcourt stakes
495- uint256 currentCourtStake = 0 ;
496- if (tree.nodes[treeIndex] > subcourtStakes.totalStakedInSubCourts) {
497- currentCourtStake = tree.nodes[treeIndex] - subcourtStakes.totalStakedInSubCourts;
498- }
499-
500- // Check if the drawn number falls within current court range
501- if (currentDrawnNumber >= currentCourtStake) {
502- // Find which subcourt range contains the drawn number
503- uint256 accumulatedStake = currentCourtStake;
504- for (uint256 i = 0 ; i < MAX_STAKE_PATHS; i++ ) {
505- if (subcourtStakes.stakedInSubCourts[i] > 0 ) {
506- if (currentDrawnNumber < accumulatedStake + subcourtStakes.stakedInSubCourts[i]) {
507- fromSubcourtID = subcourtStakes.subCourtIDs[i];
508- break ;
509- }
510- accumulatedStake += subcourtStakes.stakedInSubCourts[i];
511- }
512- }
513- }
483+ (drawnAddress, fromSubcourtID) = _stakePathIDToAccountAndCourtID (stakePathID);
514484 }
515485
516486 /// @dev Get the stake of a juror in a court.
@@ -524,11 +494,11 @@ abstract contract SortitionModuleBase is ISortitionModule, Initializable, UUPSPr
524494
525495 /// @dev Get the stake of a juror in a court.
526496 /// @param _key The key of the tree, corresponding to a court.
527- /// @param _ID The stake path ID, corresponding to a juror.
497+ /// @param _stakePathID The stake path ID, corresponding to a juror.
528498 /// @return The stake of the juror in the court.
529- function stakeOf (bytes32 _key , bytes32 _ID ) public view returns (uint256 ) {
499+ function stakeOf (bytes32 _key , bytes32 _stakePathID ) public view returns (uint256 ) {
530500 SortitionSumTree storage tree = sortitionSumTrees[_key];
531- uint treeIndex = tree.IDsToNodeIndexes[_ID ];
501+ uint treeIndex = tree.IDsToNodeIndexes[_stakePathID ];
532502 if (treeIndex == 0 ) {
533503 return 0 ;
534504 }
@@ -601,59 +571,6 @@ abstract contract SortitionModuleBase is ISortitionModule, Initializable, UUPSPr
601571 }
602572 }
603573
604- /// @dev Update the subcourt stakes.
605- /// @param _subcourtStakes The subcourt stakes.
606- /// @param _value The value to update.
607- /// @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.
608- /// `O(1)` complexity with `MAX_STAKE_PATHS` a small constant.
609- function _updateSubCourtStakes (
610- SubCourtStakes storage _subcourtStakes ,
611- uint256 _value ,
612- uint96 _fromSubCourtID
613- ) internal {
614- // Update existing stake item if found
615- for (uint256 i = 0 ; i < MAX_STAKE_PATHS; i++ ) {
616- if (_subcourtStakes.subCourtIDs[i] == _fromSubCourtID) {
617- if (_value == 0 ) {
618- delete _subcourtStakes.subCourtIDs[i];
619- delete _subcourtStakes.stakedInSubCourts[i];
620- } else {
621- _subcourtStakes.totalStakedInSubCourts += _value;
622- _subcourtStakes.totalStakedInSubCourts -= _subcourtStakes.stakedInSubCourts[i];
623- _subcourtStakes.stakedInSubCourts[i] = _value;
624- }
625- return ;
626- }
627- }
628- // Not found so add a new stake item
629- for (uint256 i = 0 ; i < MAX_STAKE_PATHS; i++ ) {
630- if (_subcourtStakes.subCourtIDs[i] == 0 ) {
631- _subcourtStakes.subCourtIDs[i] = _fromSubCourtID;
632- _subcourtStakes.totalStakedInSubCourts += _value;
633- _subcourtStakes.stakedInSubCourts[i] = _value;
634- return ;
635- }
636- }
637- }
638-
639- /// @dev Retrieves a juror's address from the stake path ID.
640- /// @param _stakePathID The stake path ID to unpack.
641- /// @return account The account.
642- function _stakePathIDToAccount (bytes32 _stakePathID ) internal pure returns (address account ) {
643- assembly {
644- // solium-disable-line security/no-inline-assembly
645- let ptr := mload (0x40 )
646- for {
647- let i := 0x00
648- } lt (i, 0x14 ) {
649- i := add (i, 0x01 )
650- } {
651- mstore8 (add (add (ptr, 0x0c ), i), byte (i, _stakePathID))
652- }
653- account := mload (ptr)
654- }
655- }
656-
657574 function _extraDataToTreeK (bytes memory _extraData ) internal pure returns (uint256 K ) {
658575 if (_extraData.length >= 32 ) {
659576 assembly {
@@ -668,14 +585,13 @@ abstract contract SortitionModuleBase is ISortitionModule, Initializable, UUPSPr
668585 /// @dev Set a value in a tree.
669586 /// @param _key The key of the tree.
670587 /// @param _value The new value.
671- /// @param _ID The ID of the value.
672- /// @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.
588+ /// @param _stakePathID The ID of the value.
673589 /// `O(log_k(n))` where
674590 /// `k` is the maximum number of children per node in the tree,
675591 /// and `n` is the maximum number of nodes ever appended.
676- function _set (bytes32 _key , uint256 _value , bytes32 _ID , uint96 _fromSubCourtID ) internal {
592+ function _set (bytes32 _key , uint256 _value , bytes32 _stakePathID ) internal {
677593 SortitionSumTree storage tree = sortitionSumTrees[_key];
678- uint256 treeIndex = tree.IDsToNodeIndexes[_ID ];
594+ uint256 treeIndex = tree.IDsToNodeIndexes[_stakePathID ];
679595
680596 if (treeIndex == 0 ) {
681597 // No existing node.
@@ -709,8 +625,8 @@ abstract contract SortitionModuleBase is ISortitionModule, Initializable, UUPSPr
709625 }
710626
711627 // Add label.
712- tree.IDsToNodeIndexes[_ID ] = treeIndex;
713- tree.nodeIndexesToIDs[treeIndex] = _ID ;
628+ tree.IDsToNodeIndexes[_stakePathID ] = treeIndex;
629+ tree.nodeIndexesToIDs[treeIndex] = _stakePathID ;
714630
715631 _updateParents (_key, treeIndex, true , _value);
716632 }
@@ -727,7 +643,7 @@ abstract contract SortitionModuleBase is ISortitionModule, Initializable, UUPSPr
727643 tree.stack.push (treeIndex);
728644
729645 // Clear label.
730- delete tree.IDsToNodeIndexes[_ID ];
646+ delete tree.IDsToNodeIndexes[_stakePathID ];
731647 delete tree.nodeIndexesToIDs[treeIndex];
732648
733649 _updateParents (_key, treeIndex, false , value);
@@ -743,11 +659,9 @@ abstract contract SortitionModuleBase is ISortitionModule, Initializable, UUPSPr
743659 _updateParents (_key, treeIndex, plusOrMinus, plusOrMinusValue);
744660 }
745661 }
746-
747- _updateSubCourtStakes (tree.IDsToSubCourtStakes[_ID], _value, _fromSubCourtID);
748662 }
749663
750- /// @dev Packs an account and a court ID into a stake path ID.
664+ /// @dev Packs an account and a court ID into a stake path ID: [20 bytes of address][12 bytes of courtID] = 32 bytes total .
751665 /// @param _account The address of the juror to pack.
752666 /// @param _courtID The court ID to pack.
753667 /// @return stakePathID The stake path ID.
@@ -776,6 +690,39 @@ abstract contract SortitionModuleBase is ISortitionModule, Initializable, UUPSPr
776690 }
777691 }
778692
693+ /// @dev Retrieves both juror's address and court ID from the stake path ID.
694+ /// @param _stakePathID The stake path ID to unpack.
695+ /// @return account The account.
696+ /// @return courtID The court ID.
697+ function _stakePathIDToAccountAndCourtID (
698+ bytes32 _stakePathID
699+ ) internal pure returns (address account , uint96 courtID ) {
700+ assembly {
701+ // solium-disable-line security/no-inline-assembly
702+ let ptr := mload (0x40 )
703+
704+ // Extract account address (first 20 bytes)
705+ for {
706+ let i := 0x00
707+ } lt (i, 0x14 ) {
708+ i := add (i, 0x01 )
709+ } {
710+ mstore8 (add (add (ptr, 0x0c ), i), byte (i, _stakePathID))
711+ }
712+ account := mload (ptr)
713+
714+ // Extract court ID (last 12 bytes)
715+ for {
716+ let i := 0x00
717+ } lt (i, 0x0c ) {
718+ i := add (i, 0x01 )
719+ } {
720+ mstore8 (add (add (ptr, 0x14 ), i), byte (add (i, 0x14 ), _stakePathID))
721+ }
722+ courtID := mload (ptr)
723+ }
724+ }
725+
779726 // ************************************* //
780727 // * Errors * //
781728 // ************************************* //
0 commit comments