diff --git a/l1-contracts/src/core/slashing/TallySlashingProposer.sol b/l1-contracts/src/core/slashing/TallySlashingProposer.sol index 01e59de9a38e..36faaca31dab 100644 --- a/l1-contracts/src/core/slashing/TallySlashingProposer.sol +++ b/l1-contracts/src/core/slashing/TallySlashingProposer.sol @@ -487,8 +487,9 @@ contract TallySlashingProposer is EIP712 { /** * @notice Load committees for all epochs to be potentially slashed in a round from the rollup instance * @dev This is an expensive call. It is not marked as view since `getEpochCommittee` may modify rollup state. + * If `getEpochCommittee` throws (eg committee not yet formed), an empty committee is returned for that epoch. * @param _round The round number to load committees for - * @return committees Array of committees, one for each epoch in the round + * @return committees Array of committees, one for each epoch in the round (may contain empty arrays for early epochs) */ function getSlashTargetCommittees(SlashRound _round) external returns (address[][] memory committees) { committees = new address[][](ROUND_SIZE_IN_EPOCHS); @@ -497,7 +498,11 @@ contract TallySlashingProposer is EIP712 { unchecked { for (uint256 epochIndex; epochIndex < ROUND_SIZE_IN_EPOCHS; ++epochIndex) { Epoch epoch = getSlashTargetEpoch(_round, epochIndex); - committees[epochIndex] = rollup.getEpochCommittee(epoch); + try rollup.getEpochCommittee(epoch) returns (address[] memory committee) { + committees[epochIndex] = committee; + } catch { + committees[epochIndex] = new address[](0); + } } } diff --git a/l1-contracts/test/slashing/TallySlashingProposer.t.sol b/l1-contracts/test/slashing/TallySlashingProposer.t.sol index 8f29ff11021e..9d12c51f3128 100644 --- a/l1-contracts/test/slashing/TallySlashingProposer.t.sol +++ b/l1-contracts/test/slashing/TallySlashingProposer.t.sol @@ -864,4 +864,29 @@ contract TallySlashingProposerTest is TestBase { } } } + + function test_getSlashTargetCommitteesEarlyEpochs() public { + // Test that getSlashTargetCommittees handles epochs 0 and 1 without throwing + // when ValidatorSelection__InsufficientValidatorSetSize is thrown + + // Use a very early slash round that would target epochs 0 and 1 + // With SLASH_OFFSET_IN_ROUNDS = 2, round 2 targets epochs starting from (2-2)*ROUND_SIZE_IN_EPOCHS = 0 + SlashRound earlyRound = SlashRound.wrap(SLASH_OFFSET_IN_ROUNDS); + + // This should not revert and should return empty committees for early epochs + address[][] memory committees = slashingProposer.getSlashTargetCommittees(earlyRound); + + // Verify we get the expected number of committees + assertEq(committees.length, ROUND_SIZE_IN_EPOCHS, "Should return correct number of committees"); + + // For very early rounds, we expect empty committees for epochs 0 and 1 + // Since ROUND_SIZE_IN_EPOCHS = 2, both epochs should be empty + for (uint256 i = 0; i < ROUND_SIZE_IN_EPOCHS; i++) { + Epoch targetEpoch = slashingProposer.getSlashTargetEpoch(earlyRound, i); + if (Epoch.unwrap(targetEpoch) <= 1) { + assertEq(committees[i].length, 0, "Committee for early epochs should be empty"); + } + // For epochs > 1, we might get actual committees, but that depends on the test setup + } + } }