Skip to content

Commit cf3afa8

Browse files
committed
feat: Allow vetoer to disable slasher
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.
1 parent 3a391de commit cf3afa8

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)