@@ -36,8 +36,8 @@ import {SafeCast} from "@oz/utils/math/SafeCast.sol";
3636 * 1. Time is divided into rounds (ROUND_SIZE slots each).
3737 * 2. During each round, block proposers can submit votes indicating which validators from the epochs that span
3838 * SLASH_OFFSET_IN_ROUNDS rounds ago should be slashed.
39- * 3. Votes are encoded as bytes where the i-th nibble (4 bits) represents the slash amount (0-15 slash units) for
40- * the i-th validator slashed in the round.
39+ * 3. Votes are encoded as bytes where each 2-bit pair represents the slash amount (0-3 slash units) for
40+ * the corresponding validator slashed in the round.
4141 * 4. After a round ends, there is an execution delay period for review so the VETOER in the Slasher can veto the
4242 * expected payload address if needed.
4343 * 5. Once the delay passes, anyone can call executeRound() to tally votes and execute slashing.
@@ -121,7 +121,7 @@ contract TallySlashingProposer is EIP712 {
121121 /**
122122 * @notice Contains all vote data for a single round
123123 * @dev Stores up to MAX_ROUND_SIZE votes as bytes arrays. Each vote encodes slash amounts
124- * for all validators in the round using 4-bit nibbles per validator.
124+ * for all validators in the round using 2 bits per validator.
125125 * @param votes Array of encoded vote data, one entry per proposer vote in the round
126126 */
127127 struct RoundVotes {
@@ -180,7 +180,7 @@ contract TallySlashingProposer is EIP712 {
180180
181181 /**
182182 * @notice Base amount of stake to slash per slashing unit (in wei)
183- * @dev Validators can be voted to be slashed by 1-15 units, multiplied by this base amount
183+ * @dev Validators can be voted to be slashed by 1-3 units, multiplied by this base amount
184184 */
185185 uint256 public immutable SLASHING_UNIT;
186186
@@ -323,12 +323,12 @@ contract TallySlashingProposer is EIP712 {
323323 /**
324324 * @notice Submit a vote for slashing validators from SLASH_OFFSET_IN_ROUNDS rounds ago
325325 * @dev Only the current block proposer can submit votes, enforced via EIP-712 signature verification.
326- * Each byte in the votes encodes slash amounts for 2 validators using 4-bit nibbles (0-15 units each).
326+ * Each byte in the votes encodes slash amounts for 4 validators using 2 bits each (0-3 units each).
327327 * The vote includes the current slot number to prevent replay attacks.
328328 *
329- * @param _votes Encoded voting data where each byte represents slash amounts for 2 validators.
330- * Lower 4 bits for first validator, upper 4 bits for second validator in each byte .
331- * Length must equal (COMMITTEE_SIZE * ROUND_SIZE_IN_EPOCHS) / 2 bytes.
329+ * @param _votes Encoded voting data where each byte represents slash amounts for 4 validators.
330+ * Bits 0-1 for first validator, bits 2-3 for second, bits 4-5 for third, bits 6-7 for fourth .
331+ * Length must equal (COMMITTEE_SIZE * ROUND_SIZE_IN_EPOCHS) / 4 bytes.
332332 * @param _sig EIP-712 signature from the current proposer proving authorization to vote.
333333 * Signature covers the vote data and current slot number.
334334 *
@@ -356,8 +356,8 @@ contract TallySlashingProposer is EIP712 {
356356 bytes32 digest = getVoteSignatureDigest (_votes, slot);
357357 require (_sig.verify (proposer, digest), Errors.TallySlashingProposer__InvalidSignature ());
358358
359- // Each byte encodes 2 validators (4 bits each), so each validator is represented as a nibble in the byte array.
360- uint256 expectedLength = COMMITTEE_SIZE * ROUND_SIZE_IN_EPOCHS / 2 ;
359+ // Each byte encodes 4 validators (2 bits each), so each validator is represented as 2 bits in the byte array.
360+ uint256 expectedLength = COMMITTEE_SIZE * ROUND_SIZE_IN_EPOCHS / 4 ;
361361 require (
362362 _votes.length == expectedLength, Errors.TallySlashingProposer__InvalidVoteLength (expectedLength, _votes.length )
363363 );
@@ -676,28 +676,41 @@ contract TallySlashingProposer is EIP712 {
676676
677677 // Create a 2D voting tally matrix: tallyMatrix[validatorIndex][slashAmountInUnits] = voteCount
678678 // - First dimension: validator index (0 to COMMITTEE_SIZE * ROUND_SIZE_IN_EPOCHS - 1)
679- // - Second dimension: slash amount in units (0-15 , where 0 means no slash)
680- // Each validator can be voted to be slashed by 1-15 units (0 represents no slashing)
681- uint256 [16 ][] memory tallyMatrix = new uint256 [16 ][](COMMITTEE_SIZE * ROUND_SIZE_IN_EPOCHS);
679+ // - Second dimension: slash amount in units (0-3 , where 0 means no slash)
680+ // Each validator can be voted to be slashed by 1-3 units (0 represents no slashing)
681+ uint256 [4 ][] memory tallyMatrix = new uint256 [4 ][](COMMITTEE_SIZE * ROUND_SIZE_IN_EPOCHS);
682682
683683 // Process all votes cast during this round to populate the tally matrix
684- // Vote encoding: each byte contains 2 validator votes using 4-bit nibbles
685- // - Lower 4 bits (0x0F): slash amount for validator at index (j * 2)
686- // - Upper 4 bits (0xF0): slash amount for validator at index (j * 2 + 1)
684+ // Vote encoding: each byte contains 4 validator votes using 2 bits each
685+ // - Bits 0-1 (0x03): slash amount for validator at index (j * 4)
686+ // - Bits 2-3 (0x0C): slash amount for validator at index (j * 4 + 1)
687+ // - Bits 4-5 (0x30): slash amount for validator at index (j * 4 + 2)
688+ // - Bits 6-7 (0xC0): slash amount for validator at index (j * 4 + 3)
687689 for (uint256 i = 0 ; i < voteCount; i++ ) {
688690 // Load the i-th votes from this round from storage into memory
689691 bytes memory currentVote = _getRoundVotes (roundNumber).votes[i];
690692 for (uint256 j = 0 ; j < currentVote.length ; j++ ) {
691- // Extract lower 4 bits for first validator in the byte
692- uint8 validatorSlash = uint8 (currentVote[j]) & 0x0F ;
693- if (validatorSlash > 0 ) {
694- tallyMatrix[j * 2 ][validatorSlash]++ ;
693+ uint8 currentByte = uint8 (currentVote[j]);
694+
695+ // Extract 2 bits for each of the 4 validators in this byte
696+ uint8 validatorSlash0 = currentByte & 0x03 ;
697+ if (validatorSlash0 > 0 ) {
698+ tallyMatrix[j * 4 ][validatorSlash0]++ ;
699+ }
700+
701+ uint8 validatorSlash1 = (currentByte >> 2 ) & 0x03 ;
702+ if (validatorSlash1 > 0 ) {
703+ tallyMatrix[j * 4 + 1 ][validatorSlash1]++ ;
704+ }
705+
706+ uint8 validatorSlash2 = (currentByte >> 4 ) & 0x03 ;
707+ if (validatorSlash2 > 0 ) {
708+ tallyMatrix[j * 4 + 2 ][validatorSlash2]++ ;
695709 }
696710
697- // Extract upper 4 bits for second validator in the byte
698- validatorSlash = (uint8 (currentVote[j]) >> 4 ) & 0x0F ;
699- if (validatorSlash > 0 ) {
700- tallyMatrix[j * 2 + 1 ][validatorSlash]++ ;
711+ uint8 validatorSlash3 = (currentByte >> 6 ) & 0x03 ;
712+ if (validatorSlash3 > 0 ) {
713+ tallyMatrix[j * 4 + 3 ][validatorSlash3]++ ;
701714 }
702715 }
703716 }
@@ -712,10 +725,10 @@ contract TallySlashingProposer is EIP712 {
712725 for (uint256 i = 0 ; i < tallyMatrix.length ; i++ ) {
713726 uint256 voteCountForValidator = 0 ;
714727
715- // Check slash amounts from highest (15 units) to lowest (1 unit)
728+ // Check slash amounts from highest (3 units) to lowest (1 unit)
716729 // Cumulative voting: a vote for N units counts as votes for N-1, N-2, ..., 1 units
717- // This means if someone votes to slash 5 units, it also counts as votes for 1-4 units
718- for (uint256 j = 15 ; j > 0 ; j-- ) {
730+ // This means if someone votes to slash 3 units, it also counts as votes for 1-2 units
731+ for (uint256 j = 3 ; j > 0 ; j-- ) {
719732 voteCountForValidator += tallyMatrix[i][j];
720733
721734 // Check if this slash amount has reached quorum
0 commit comments