Skip to content

Commit ba13aa6

Browse files
authored
feat: Allow vetoer to disable slasher temporarily (#16971)
Allows the vetoer to disable slashing altogether temporarily. This allows the vetoer to stop all slashes if a bug is detected that continuously triggers slashes that are not valid, so the vetoer does not need to continuously vetoed every individual slash until the issue is solved. Slashing is disabled for `slashingDisableDuration` (immutable, set during deployment, defaults to 5 days) and then automatically resumed. Vetoer can extend the disabled period at any time, and can also resume the slashing.
2 parents 0fcfb76 + cf3afa8 commit ba13aa6

File tree

20 files changed

+205
-13
lines changed

20 files changed

+205
-13
lines changed

l1-contracts/src/core/RollupCore.sol

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,8 @@ contract RollupCore is EIP712("Aztec Rollup", "1"), Ownable, IStakingCore, IVali
255255
_config.slashAmounts,
256256
_config.targetCommitteeSize,
257257
_config.aztecEpochDuration,
258-
_config.slashingOffsetInRounds
258+
_config.slashingOffsetInRounds,
259+
_config.slashingDisableDuration
259260
);
260261
} else {
261262
slasher = EmpireSlasherDeploymentExtLib.deployEmpireSlasher(
@@ -265,7 +266,8 @@ contract RollupCore is EIP712("Aztec Rollup", "1"), Ownable, IStakingCore, IVali
265266
_config.slashingQuorum,
266267
_config.slashingRoundSize,
267268
_config.slashingLifetimeInRounds,
268-
_config.slashingExecutionDelayInRounds
269+
_config.slashingExecutionDelayInRounds,
270+
_config.slashingDisableDuration
269271
);
270272
}
271273

l1-contracts/src/core/interfaces/IRollup.sol

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ struct RollupConfigInput {
6767
uint256 slashingOffsetInRounds;
6868
SlasherFlavor slasherFlavor;
6969
address slashingVetoer;
70+
uint256 slashingDisableDuration;
7071
uint256 manaTarget;
7172
uint256 exitDelaySeconds;
7273
uint32 version;

l1-contracts/src/core/interfaces/ISlasher.sol

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@ enum SlasherFlavor {
1212

1313
interface ISlasher {
1414
event VetoedPayload(address indexed payload);
15+
event SlashingDisabled(uint256 disabledUntil);
1516

1617
function slash(IPayload _payload) external returns (bool);
1718
function vetoPayload(IPayload _payload) external returns (bool);
19+
function setSlashingEnabled(bool _enabled) external;
20+
function isSlashingEnabled() external view returns (bool);
1821
}

l1-contracts/src/core/libraries/rollup/EmpireSlasherDeploymentExtLib.sol

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,11 @@ library EmpireSlasherDeploymentExtLib {
2424
uint256 _quorumSize,
2525
uint256 _roundSize,
2626
uint256 _lifetimeInRounds,
27-
uint256 _executionDelayInRounds
27+
uint256 _executionDelayInRounds,
28+
uint256 _slashingDisableDuration
2829
) external returns (ISlasher) {
2930
// Deploy slasher first
30-
Slasher slasher = new Slasher(_vetoer, _governance);
31+
Slasher slasher = new Slasher(_vetoer, _governance, _slashingDisableDuration);
3132

3233
// Deploy proposer with slasher address
3334
EmpireSlashingProposer proposer = new EmpireSlashingProposer(

l1-contracts/src/core/libraries/rollup/TallySlasherDeploymentExtLib.sol

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,11 @@ library TallySlasherDeploymentExtLib {
2727
uint256[3] calldata _slashAmounts,
2828
uint256 _committeeSize,
2929
uint256 _epochDuration,
30-
uint256 _slashOffsetInRounds
30+
uint256 _slashOffsetInRounds,
31+
uint256 _slashingDisableDuration
3132
) external returns (ISlasher) {
3233
// Deploy slasher first
33-
Slasher slasher = new Slasher(_vetoer, _governance);
34+
Slasher slasher = new Slasher(_vetoer, _governance, _slashingDisableDuration);
3435

3536
// Deploy proposer with slasher address
3637
TallySlashingProposer proposer = new TallySlashingProposer(

l1-contracts/src/core/slashing/Slasher.sol

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,12 @@ import {IPayload} from "@aztec/governance/interfaces/IPayload.sol";
88
contract Slasher is ISlasher {
99
address public immutable GOVERNANCE;
1010
address public immutable VETOER;
11+
uint256 public immutable SLASHING_DISABLE_DURATION;
1112
// solhint-disable-next-line var-name-mixedcase
1213
address public PROPOSER;
1314

15+
uint256 public slashingDisabledUntil = 0;
16+
1417
mapping(address payload => bool vetoed) public vetoedPayloads;
1518

1619
error Slasher__SlashFailed(address target, bytes data, bytes returnData);
@@ -19,10 +22,12 @@ contract Slasher is ISlasher {
1922
error Slasher__PayloadVetoed(address payload);
2023
error Slasher__AlreadyInitialized();
2124
error Slasher__ProposerZeroAddress();
25+
error Slasher__SlashingDisabled();
2226

23-
constructor(address _vetoer, address _governance) {
27+
constructor(address _vetoer, address _governance, uint256 _slashingDisableDuration) {
2428
GOVERNANCE = _governance;
2529
VETOER = _vetoer;
30+
SLASHING_DISABLE_DURATION = _slashingDisableDuration;
2631
}
2732

2833
// solhint-disable-next-line comprehensive-interface
@@ -39,8 +44,19 @@ contract Slasher is ISlasher {
3944
return true;
4045
}
4146

47+
function setSlashingEnabled(bool _enabled) external override(ISlasher) {
48+
require(msg.sender == VETOER, Slasher__CallerNotVetoer(msg.sender, VETOER));
49+
if (!_enabled) {
50+
slashingDisabledUntil = block.timestamp + SLASHING_DISABLE_DURATION;
51+
} else {
52+
slashingDisabledUntil = 0;
53+
}
54+
emit SlashingDisabled(slashingDisabledUntil);
55+
}
56+
4257
function slash(IPayload _payload) external override(ISlasher) returns (bool) {
4358
require(msg.sender == PROPOSER || msg.sender == GOVERNANCE, Slasher__CallerNotAuthorizedToSlash(msg.sender));
59+
require(block.timestamp >= slashingDisabledUntil, Slasher__SlashingDisabled());
4460
require(!vetoedPayloads[address(_payload)], Slasher__PayloadVetoed(address(_payload)));
4561

4662
IPayload.Action[] memory actions = _payload.getActions();
@@ -51,4 +67,8 @@ contract Slasher is ISlasher {
5167

5268
return true;
5369
}
70+
71+
function isSlashingEnabled() external view override(ISlasher) returns (bool) {
72+
return block.timestamp >= slashingDisabledUntil;
73+
}
5474
}

l1-contracts/test/governance/scenario/slashing/TallySlashing.t.sol

Lines changed: 78 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,7 @@ contract SlashingTest is TestBase {
205205

206206
_setupCommitteeForSlashing(_lifetimeInRounds, _executionDelayInRounds);
207207
address[] memory attesters = rollup.getEpochCommittee(Epoch.wrap(INITIAL_EPOCH));
208-
uint96 slashAmount = 20e18;
208+
uint96 slashAmount = uint96(slashingProposer.SLASH_AMOUNT_SMALL());
209209
SlashRound firstSlashingRound = _createSlashingVotes(slashAmount, attesters.length);
210210

211211
uint256 firstExecutableSlot =
@@ -232,7 +232,7 @@ contract SlashingTest is TestBase {
232232
_lifetimeInRounds = bound(_lifetimeInRounds, _executionDelayInRounds + 1, 127); // Must be < ROUNDABOUT_SIZE
233233

234234
_setupCommitteeForSlashing(_lifetimeInRounds, _executionDelayInRounds);
235-
uint96 slashAmount = 20e18;
235+
uint96 slashAmount = uint96(slashingProposer.SLASH_AMOUNT_SMALL());
236236
SlashRound firstSlashingRound = _createSlashingVotes(slashAmount, COMMITTEE_SIZE);
237237

238238
uint256 firstExecutableSlot =
@@ -275,7 +275,7 @@ contract SlashingTest is TestBase {
275275

276276
_setupCommitteeForSlashing(_lifetimeInRounds, _executionDelayInRounds);
277277
address[] memory attesters = rollup.getEpochCommittee(Epoch.wrap(INITIAL_EPOCH));
278-
uint96 slashAmount = 20e18;
278+
uint96 slashAmount = uint96(slashingProposer.SLASH_AMOUNT_SMALL());
279279
SlashRound firstSlashingRound = _createSlashingVotes(slashAmount, attesters.length);
280280

281281
// For tally slashing, we need to predict the payload address and veto it
@@ -307,6 +307,81 @@ contract SlashingTest is TestBase {
307307
slashingProposer.executeRound(firstSlashingRound, committees);
308308
}
309309

310+
function test_SlashingDisableTimestamp() public {
311+
_setupCommitteeForSlashing();
312+
address[] memory attesters = rollup.getEpochCommittee(Epoch.wrap(INITIAL_EPOCH));
313+
uint96 slashAmount = uint96(slashingProposer.SLASH_AMOUNT_SMALL());
314+
SlashRound firstSlashingRound = _createSlashingVotes(slashAmount, attesters.length);
315+
316+
// Initially slashing should be enabled
317+
assertEq(slasher.isSlashingEnabled(), true, "Slashing should be enabled initially");
318+
319+
// Disable slashing temporarily
320+
vm.prank(address(slasher.VETOER()));
321+
slasher.setSlashingEnabled(false);
322+
323+
// Should be disabled now
324+
assertEq(slasher.isSlashingEnabled(), false, "Slashing should be disabled after setting to false");
325+
uint256 disableDuration = slasher.SLASHING_DISABLE_DURATION();
326+
327+
// Fast forward time but not past the disable duration (still disabled)
328+
vm.warp(block.timestamp + disableDuration - 10 minutes);
329+
assertEq(slasher.isSlashingEnabled(), false, "Slashing should still be disabled after 30 minutes");
330+
331+
// Fast forward time beyond the disable duration (should be enabled again)
332+
vm.warp(block.timestamp + disableDuration + 1 minutes);
333+
assertEq(slasher.isSlashingEnabled(), true, "Slashing should be enabled again after disable duration expires");
334+
335+
// Re-enable manually by calling setSlashingEnabled(true)
336+
vm.prank(address(slasher.VETOER()));
337+
slasher.setSlashingEnabled(false);
338+
assertEq(slasher.isSlashingEnabled(), false, "Slashing should be disabled again");
339+
340+
vm.prank(address(slasher.VETOER()));
341+
slasher.setSlashingEnabled(true);
342+
assertEq(slasher.isSlashingEnabled(), true, "Slashing should be enabled after manual re-enable");
343+
}
344+
345+
function test_CannotSlashIfDisabled() public {
346+
// Use fixed values for a deterministic test
347+
uint256 _lifetimeInRounds = 5;
348+
uint256 _executionDelayInRounds = 1;
349+
350+
_setupCommitteeForSlashing(_lifetimeInRounds, _executionDelayInRounds);
351+
address[] memory attesters = rollup.getEpochCommittee(Epoch.wrap(INITIAL_EPOCH));
352+
uint96 slashAmount = uint96(slashingProposer.SLASH_AMOUNT_SMALL());
353+
SlashRound firstSlashingRound = _createSlashingVotes(slashAmount, attesters.length);
354+
355+
// Calculate executable slot
356+
uint256 firstExecutableSlot =
357+
(SlashRound.unwrap(firstSlashingRound) + _executionDelayInRounds + 1) * slashingProposer.ROUND_SIZE();
358+
359+
// Setup committees
360+
address[][] memory committees = new address[][](slashingProposer.ROUND_SIZE_IN_EPOCHS());
361+
for (uint256 i = 0; i < committees.length; i++) {
362+
Epoch epochSlashed = slashingProposer.getSlashTargetEpoch(firstSlashingRound, i);
363+
committees[i] = rollup.getEpochCommittee(epochSlashed);
364+
}
365+
366+
// Jump to executable slot
367+
timeCheater.cheat__jumpToSlot(firstExecutableSlot);
368+
369+
// Disable slashing - this should prevent execution for 1 hour
370+
vm.prank(address(slasher.VETOER()));
371+
slasher.setSlashingEnabled(false);
372+
373+
// Should fail while slashing is disabled
374+
vm.expectRevert(abi.encodeWithSelector(Slasher.Slasher__SlashingDisabled.selector));
375+
slashingProposer.executeRound(firstSlashingRound, committees);
376+
377+
// Re-enable manually by calling setSlashingEnabled(true)
378+
vm.prank(address(slasher.VETOER()));
379+
slasher.setSlashingEnabled(true);
380+
381+
// Should now work since slashing was re-enabled
382+
slashingProposer.executeRound(firstSlashingRound, committees);
383+
}
384+
310385
function test_SlashingSmallAmount() public {
311386
_setupCommitteeForSlashing();
312387
uint256 howManyToSlash = HOW_MANY_SLASHED;

l1-contracts/test/harnesses/TestConstants.sol

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ library TestConstants {
2626
uint256 internal constant AZTEC_SLASHING_EXECUTION_DELAY_IN_ROUNDS = 0;
2727
uint256 internal constant AZTEC_SLASHING_OFFSET_IN_ROUNDS = 2;
2828
address internal constant AZTEC_SLASHING_VETOER = address(0);
29+
uint256 internal constant AZTEC_SLASHING_DISABLE_DURATION = 5 days;
2930
uint256 internal constant AZTEC_SLASH_AMOUNT_SMALL = 20e18;
3031
uint256 internal constant AZTEC_SLASH_AMOUNT_MEDIUM = 40e18;
3132
uint256 internal constant AZTEC_SLASH_AMOUNT_LARGE = 60e18;
@@ -103,6 +104,7 @@ library TestConstants {
103104
slashingExecutionDelayInRounds: AZTEC_SLASHING_EXECUTION_DELAY_IN_ROUNDS,
104105
slashingOffsetInRounds: AZTEC_SLASHING_OFFSET_IN_ROUNDS,
105106
slashingVetoer: AZTEC_SLASHING_VETOER,
107+
slashingDisableDuration: AZTEC_SLASHING_DISABLE_DURATION,
106108
manaTarget: AZTEC_MANA_TARGET,
107109
exitDelaySeconds: AZTEC_EXIT_DELAY_SECONDS,
108110
provingCostPerMana: AZTEC_PROVING_COST_PER_MANA,

yarn-project/cli/src/config/chain_l2_config.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ const DefaultSlashConfig = {
4545
slashingOffsetInRounds: 2,
4646
/** No slash vetoer */
4747
slashingVetoer: EthAddress.ZERO,
48+
/** Use default disable duration */
49+
slashingDisableDuration: DefaultL1ContractsConfig.slashingDisableDuration,
4850
/** Use default slash amounts */
4951
slashAmountSmall: DefaultL1ContractsConfig.slashAmountSmall,
5052
slashAmountMedium: DefaultL1ContractsConfig.slashAmountMedium,

yarn-project/end-to-end/src/e2e_p2p/add_rollup.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@ describe('e2e_p2p_add_rollup', () => {
162162
slashingLifetimeInRounds: t.ctx.aztecNodeConfig.slashingLifetimeInRounds,
163163
slashingExecutionDelayInRounds: t.ctx.aztecNodeConfig.slashingExecutionDelayInRounds,
164164
slashingVetoer: t.ctx.aztecNodeConfig.slashingVetoer,
165+
slashingDisableDuration: t.ctx.aztecNodeConfig.slashingDisableDuration,
165166
manaTarget: t.ctx.aztecNodeConfig.manaTarget,
166167
provingCostPerMana: t.ctx.aztecNodeConfig.provingCostPerMana,
167168
feeJuicePortalInitialBalance: fundingNeeded,

0 commit comments

Comments
 (0)