@@ -870,6 +870,51 @@ contract TallySlashingProposerTest is TestBase {
870870 }
871871 }
872872
873+ function test_executeRoundWithEmptyCommittee () public {
874+ // Round FIRST_SLASH_ROUND targets epochs 0 and 1, which have no committees
875+ // because they precede the validator set sampling lag.
876+ //
877+ // Before the fix, casting votes that reach quorum for validator slots in these
878+ // committee-less epochs would cause executeRound (and getTally) to revert with
879+ // an array out-of-bounds access when indexing _committees[epochIndex][validatorIndex].
880+ _jumpToSlashRound (FIRST_SLASH_ROUND);
881+ SlashRound targetRound = slashingProposer.getCurrentRound ();
882+
883+ // Cast QUORUM votes with max slash for ALL validator slots, including those
884+ // corresponding to epochs without valid committees.
885+ uint8 [] memory slashAmounts = new uint8 [](COMMITTEE_SIZE * ROUND_SIZE_IN_EPOCHS);
886+ for (uint256 i = 0 ; i < slashAmounts.length ; i++ ) {
887+ slashAmounts[i] = 3 ;
888+ }
889+
890+ for (uint256 i = 0 ; i < QUORUM; i++ ) {
891+ _castVote (slashAmounts);
892+ if (i < QUORUM - 1 ) {
893+ timeCheater.cheat__progressSlot ();
894+ }
895+ }
896+
897+ // Jump past execution delay
898+ uint256 targetSlot = (SlashRound.unwrap (targetRound) + EXECUTION_DELAY_IN_ROUNDS + 1 ) * ROUND_SIZE;
899+ timeCheater.cheat__jumpToSlot (targetSlot);
900+
901+ // Verify that both targeted epochs have empty committees
902+ address [][] memory committees = slashingProposer.getSlashTargetCommittees (targetRound);
903+ assertEq (committees[0 ].length , 0 , "Epoch 0 should have empty committee " );
904+ assertEq (committees[1 ].length , 0 , "Epoch 1 should have empty committee " );
905+
906+ // getTally should not revert and should return 0 actions
907+ TallySlashingProposer.SlashAction[] memory actions = slashingProposer.getTally (targetRound, committees);
908+ assertEq (actions.length , 0 , "Should have no slash actions for empty committees " );
909+
910+ // executeRound should also succeed
911+ slashingProposer.executeRound (targetRound, committees);
912+
913+ // Verify round is marked as executed
914+ (bool isExecuted ,) = slashingProposer.getRound (targetRound);
915+ assertTrue (isExecuted, "Round should be marked as executed " );
916+ }
917+
873918 function test_getSlashTargetCommitteesEarlyEpochs () public {
874919 // Test that getSlashTargetCommittees handles epochs 0 and 1 without throwing
875920 // when ValidatorSelection__InsufficientValidatorSetSize is thrown
0 commit comments