Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions l1-contracts/src/core/RollupCore.sol
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,8 @@ contract RollupCore is EIP712("Aztec Rollup", "1"), Ownable, IStakingCore, IVali
_config.slashAmounts,
_config.targetCommitteeSize,
_config.aztecEpochDuration,
_config.slashingOffsetInRounds
_config.slashingOffsetInRounds,
_config.slashingDisableDuration
);
} else {
slasher = EmpireSlasherDeploymentExtLib.deployEmpireSlasher(
Expand All @@ -265,7 +266,8 @@ contract RollupCore is EIP712("Aztec Rollup", "1"), Ownable, IStakingCore, IVali
_config.slashingQuorum,
_config.slashingRoundSize,
_config.slashingLifetimeInRounds,
_config.slashingExecutionDelayInRounds
_config.slashingExecutionDelayInRounds,
_config.slashingDisableDuration
);
}

Expand Down
1 change: 1 addition & 0 deletions l1-contracts/src/core/interfaces/IRollup.sol
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ struct RollupConfigInput {
uint256 slashingOffsetInRounds;
SlasherFlavor slasherFlavor;
address slashingVetoer;
uint256 slashingDisableDuration;
uint256 manaTarget;
uint256 exitDelaySeconds;
uint32 version;
Expand Down
3 changes: 3 additions & 0 deletions l1-contracts/src/core/interfaces/ISlasher.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ enum SlasherFlavor {

interface ISlasher {
event VetoedPayload(address indexed payload);
event SlashingDisabled(uint256 disabledUntil);

function slash(IPayload _payload) external returns (bool);
function vetoPayload(IPayload _payload) external returns (bool);
function setSlashingEnabled(bool _enabled) external;
function isSlashingEnabled() external view returns (bool);
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,11 @@ library EmpireSlasherDeploymentExtLib {
uint256 _quorumSize,
uint256 _roundSize,
uint256 _lifetimeInRounds,
uint256 _executionDelayInRounds
uint256 _executionDelayInRounds,
uint256 _slashingDisableDuration
) external returns (ISlasher) {
// Deploy slasher first
Slasher slasher = new Slasher(_vetoer, _governance);
Slasher slasher = new Slasher(_vetoer, _governance, _slashingDisableDuration);

// Deploy proposer with slasher address
EmpireSlashingProposer proposer = new EmpireSlashingProposer(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,11 @@ library TallySlasherDeploymentExtLib {
uint256[3] calldata _slashAmounts,
uint256 _committeeSize,
uint256 _epochDuration,
uint256 _slashOffsetInRounds
uint256 _slashOffsetInRounds,
uint256 _slashingDisableDuration
) external returns (ISlasher) {
// Deploy slasher first
Slasher slasher = new Slasher(_vetoer, _governance);
Slasher slasher = new Slasher(_vetoer, _governance, _slashingDisableDuration);

// Deploy proposer with slasher address
TallySlashingProposer proposer = new TallySlashingProposer(
Expand Down
22 changes: 21 additions & 1 deletion l1-contracts/src/core/slashing/Slasher.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,12 @@ import {IPayload} from "@aztec/governance/interfaces/IPayload.sol";
contract Slasher is ISlasher {
address public immutable GOVERNANCE;
address public immutable VETOER;
uint256 public immutable SLASHING_DISABLE_DURATION;
// solhint-disable-next-line var-name-mixedcase
address public PROPOSER;

uint256 public slashingDisabledUntil = 0;

mapping(address payload => bool vetoed) public vetoedPayloads;

error Slasher__SlashFailed(address target, bytes data, bytes returnData);
Expand All @@ -19,10 +22,12 @@ contract Slasher is ISlasher {
error Slasher__PayloadVetoed(address payload);
error Slasher__AlreadyInitialized();
error Slasher__ProposerZeroAddress();
error Slasher__SlashingDisabled();

constructor(address _vetoer, address _governance) {
constructor(address _vetoer, address _governance, uint256 _slashingDisableDuration) {
GOVERNANCE = _governance;
VETOER = _vetoer;
SLASHING_DISABLE_DURATION = _slashingDisableDuration;
}

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

function setSlashingEnabled(bool _enabled) external override(ISlasher) {
require(msg.sender == VETOER, Slasher__CallerNotVetoer(msg.sender, VETOER));
if (!_enabled) {
slashingDisabledUntil = block.timestamp + SLASHING_DISABLE_DURATION;
} else {
slashingDisabledUntil = 0;
}
emit SlashingDisabled(slashingDisabledUntil);
}

function slash(IPayload _payload) external override(ISlasher) returns (bool) {
require(msg.sender == PROPOSER || msg.sender == GOVERNANCE, Slasher__CallerNotAuthorizedToSlash(msg.sender));
require(block.timestamp >= slashingDisabledUntil, Slasher__SlashingDisabled());
require(!vetoedPayloads[address(_payload)], Slasher__PayloadVetoed(address(_payload)));

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

return true;
}

function isSlashingEnabled() external view override(ISlasher) returns (bool) {
return block.timestamp >= slashingDisabledUntil;
}
}
81 changes: 78 additions & 3 deletions l1-contracts/test/governance/scenario/slashing/TallySlashing.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ contract SlashingTest is TestBase {

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

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

_setupCommitteeForSlashing(_lifetimeInRounds, _executionDelayInRounds);
uint96 slashAmount = 20e18;
uint96 slashAmount = uint96(slashingProposer.SLASH_AMOUNT_SMALL());
SlashRound firstSlashingRound = _createSlashingVotes(slashAmount, COMMITTEE_SIZE);

uint256 firstExecutableSlot =
Expand Down Expand Up @@ -275,7 +275,7 @@ contract SlashingTest is TestBase {

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

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

function test_SlashingDisableTimestamp() public {
_setupCommitteeForSlashing();
address[] memory attesters = rollup.getEpochCommittee(Epoch.wrap(INITIAL_EPOCH));
uint96 slashAmount = uint96(slashingProposer.SLASH_AMOUNT_SMALL());
SlashRound firstSlashingRound = _createSlashingVotes(slashAmount, attesters.length);

// Initially slashing should be enabled
assertEq(slasher.isSlashingEnabled(), true, "Slashing should be enabled initially");

// Disable slashing temporarily
vm.prank(address(slasher.VETOER()));
slasher.setSlashingEnabled(false);

// Should be disabled now
assertEq(slasher.isSlashingEnabled(), false, "Slashing should be disabled after setting to false");
uint256 disableDuration = slasher.SLASHING_DISABLE_DURATION();

// Fast forward time but not past the disable duration (still disabled)
vm.warp(block.timestamp + disableDuration - 10 minutes);
assertEq(slasher.isSlashingEnabled(), false, "Slashing should still be disabled after 30 minutes");

// Fast forward time beyond the disable duration (should be enabled again)
vm.warp(block.timestamp + disableDuration + 1 minutes);
assertEq(slasher.isSlashingEnabled(), true, "Slashing should be enabled again after disable duration expires");

// Re-enable manually by calling setSlashingEnabled(true)
vm.prank(address(slasher.VETOER()));
slasher.setSlashingEnabled(false);
assertEq(slasher.isSlashingEnabled(), false, "Slashing should be disabled again");

vm.prank(address(slasher.VETOER()));
slasher.setSlashingEnabled(true);
assertEq(slasher.isSlashingEnabled(), true, "Slashing should be enabled after manual re-enable");
}

function test_CannotSlashIfDisabled() public {
// Use fixed values for a deterministic test
uint256 _lifetimeInRounds = 5;
uint256 _executionDelayInRounds = 1;

_setupCommitteeForSlashing(_lifetimeInRounds, _executionDelayInRounds);
address[] memory attesters = rollup.getEpochCommittee(Epoch.wrap(INITIAL_EPOCH));
uint96 slashAmount = uint96(slashingProposer.SLASH_AMOUNT_SMALL());
SlashRound firstSlashingRound = _createSlashingVotes(slashAmount, attesters.length);

// Calculate executable slot
uint256 firstExecutableSlot =
(SlashRound.unwrap(firstSlashingRound) + _executionDelayInRounds + 1) * slashingProposer.ROUND_SIZE();

// Setup committees
address[][] memory committees = new address[][](slashingProposer.ROUND_SIZE_IN_EPOCHS());
for (uint256 i = 0; i < committees.length; i++) {
Epoch epochSlashed = slashingProposer.getSlashTargetEpoch(firstSlashingRound, i);
committees[i] = rollup.getEpochCommittee(epochSlashed);
}

// Jump to executable slot
timeCheater.cheat__jumpToSlot(firstExecutableSlot);

// Disable slashing - this should prevent execution for 1 hour
vm.prank(address(slasher.VETOER()));
slasher.setSlashingEnabled(false);

// Should fail while slashing is disabled
vm.expectRevert(abi.encodeWithSelector(Slasher.Slasher__SlashingDisabled.selector));
slashingProposer.executeRound(firstSlashingRound, committees);

// Re-enable manually by calling setSlashingEnabled(true)
vm.prank(address(slasher.VETOER()));
slasher.setSlashingEnabled(true);

// Should now work since slashing was re-enabled
slashingProposer.executeRound(firstSlashingRound, committees);
}

function test_SlashingSmallAmount() public {
_setupCommitteeForSlashing();
uint256 howManyToSlash = HOW_MANY_SLASHED;
Expand Down
2 changes: 2 additions & 0 deletions l1-contracts/test/harnesses/TestConstants.sol
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ library TestConstants {
uint256 internal constant AZTEC_SLASHING_EXECUTION_DELAY_IN_ROUNDS = 0;
uint256 internal constant AZTEC_SLASHING_OFFSET_IN_ROUNDS = 2;
address internal constant AZTEC_SLASHING_VETOER = address(0);
uint256 internal constant AZTEC_SLASHING_DISABLE_DURATION = 5 days;
uint256 internal constant AZTEC_SLASH_AMOUNT_SMALL = 20e18;
uint256 internal constant AZTEC_SLASH_AMOUNT_MEDIUM = 40e18;
uint256 internal constant AZTEC_SLASH_AMOUNT_LARGE = 60e18;
Expand Down Expand Up @@ -103,6 +104,7 @@ library TestConstants {
slashingExecutionDelayInRounds: AZTEC_SLASHING_EXECUTION_DELAY_IN_ROUNDS,
slashingOffsetInRounds: AZTEC_SLASHING_OFFSET_IN_ROUNDS,
slashingVetoer: AZTEC_SLASHING_VETOER,
slashingDisableDuration: AZTEC_SLASHING_DISABLE_DURATION,
manaTarget: AZTEC_MANA_TARGET,
exitDelaySeconds: AZTEC_EXIT_DELAY_SECONDS,
provingCostPerMana: AZTEC_PROVING_COST_PER_MANA,
Expand Down
2 changes: 2 additions & 0 deletions yarn-project/cli/src/config/chain_l2_config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ const DefaultSlashConfig = {
slashingOffsetInRounds: 2,
/** No slash vetoer */
slashingVetoer: EthAddress.ZERO,
/** Use default disable duration */
slashingDisableDuration: DefaultL1ContractsConfig.slashingDisableDuration,
/** Use default slash amounts */
slashAmountSmall: DefaultL1ContractsConfig.slashAmountSmall,
slashAmountMedium: DefaultL1ContractsConfig.slashAmountMedium,
Expand Down
1 change: 1 addition & 0 deletions yarn-project/end-to-end/src/e2e_p2p/add_rollup.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ describe('e2e_p2p_add_rollup', () => {
slashingLifetimeInRounds: t.ctx.aztecNodeConfig.slashingLifetimeInRounds,
slashingExecutionDelayInRounds: t.ctx.aztecNodeConfig.slashingExecutionDelayInRounds,
slashingVetoer: t.ctx.aztecNodeConfig.slashingVetoer,
slashingDisableDuration: t.ctx.aztecNodeConfig.slashingDisableDuration,
manaTarget: t.ctx.aztecNodeConfig.manaTarget,
provingCostPerMana: t.ctx.aztecNodeConfig.provingCostPerMana,
feeJuicePortalInitialBalance: fundingNeeded,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ describe('veto slash', () => {
const vetoer = deployerClient.account.address;
const governance = EthAddress.random().toString(); // We don't need a real governance address for this test
debugLogger.info(`\n\ndeploying slasher with vetoer: ${vetoer}\n\n`);
const slasher = await deployer.deploy(SlasherArtifact, [vetoer, governance]);
const slasher = await deployer.deploy(SlasherArtifact, [vetoer, governance, 3600n]);
await deployer.waitForDeployments();

let proposer: EthAddress;
Expand Down
8 changes: 8 additions & 0 deletions yarn-project/ethereum/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ export type L1ContractsConfig = {
slashingVetoer: EthAddress;
/** How many slashing rounds back we slash (ie when slashing in round N, we slash for offenses committed during epochs of round N-offset) */
slashingOffsetInRounds: number;
/** How long slashing can be disabled for in seconds when vetoer disables it */
slashingDisableDuration: number;
/** Type of slasher proposer */
slasherFlavor: 'empire' | 'tally' | 'none';
/** Minimum amount that can be slashed in tally slashing */
Expand Down Expand Up @@ -93,6 +95,7 @@ export const DefaultL1ContractsConfig = {
exitDelaySeconds: 2 * 24 * 60 * 60,
slasherFlavor: 'tally' as const,
slashingOffsetInRounds: 2,
slashingDisableDuration: 5 * 24 * 60 * 60, // 5 days in seconds
} satisfies L1ContractsConfig;

const LocalGovernanceConfiguration = {
Expand Down Expand Up @@ -384,6 +387,11 @@ export const l1ContractsConfigMappings: ConfigMappingsType<L1ContractsConfig> =
parseEnv: (val: string) => EthAddress.fromString(val),
defaultValue: DefaultL1ContractsConfig.slashingVetoer,
},
slashingDisableDuration: {
env: 'AZTEC_SLASHING_DISABLE_DURATION',
description: 'How long slashing can be disabled for in seconds when vetoer disables it',
...numberConfigHelper(DefaultL1ContractsConfig.slashingDisableDuration),
},
governanceProposerQuorum: {
env: 'AZTEC_GOVERNANCE_PROPOSER_QUORUM',
description: 'The governance proposing quorum',
Expand Down
22 changes: 22 additions & 0 deletions yarn-project/ethereum/src/contracts/slasher_contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,19 @@ export class SlasherContract {
}
}

/**
* Checks if slashing is currently enabled. Slashing can be disabled by the vetoer.
* @returns True if slashing is enabled, false otherwise
*/
public async isSlashingEnabled(): Promise<boolean> {
try {
return await this.contract.read.isSlashingEnabled();
} catch (error) {
this.log.error(`Error checking if slashing is enabled`, error);
throw error;
}
}

/**
* Gets the current vetoer address.
* @returns The vetoer address
Expand All @@ -47,6 +60,15 @@ export class SlasherContract {
return EthAddress.fromString(vetoer);
}

/**
* Gets the disable duration by the vetoer.
* @returns The disable duration in seconds
*/
public async getSlashingDisableDuration(): Promise<number> {
const duration = await this.contract.read.SLASHING_DISABLE_DURATION();
return Number(duration);
}

/**
* Gets the current governance address.
* @returns The governance address
Expand Down
1 change: 1 addition & 0 deletions yarn-project/ethereum/src/deploy_l1_contracts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -548,6 +548,7 @@ export const deployRollup = async (
slashingOffsetInRounds: BigInt(args.slashingOffsetInRounds),
slashAmounts: [args.slashAmountSmall, args.slashAmountMedium, args.slashAmountLarge],
localEjectionThreshold: args.localEjectionThreshold,
slashingDisableDuration: BigInt(args.slashingDisableDuration ?? 0n),
};

const genesisStateArgs = {
Expand Down
5 changes: 4 additions & 1 deletion yarn-project/ethereum/src/queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ export async function getL1ContractsConfig(
slashingOffsetInRounds,
slashingAmounts,
slashingVetoer,
slashingDisableDuration,
manaTarget,
provingCostPerMana,
rollupVersion,
Expand All @@ -71,6 +72,7 @@ export async function getL1ContractsConfig(
slasherProposer?.type === 'tally' ? slasherProposer.getSlashOffsetInRounds() : 0n,
slasherProposer?.type === 'tally' ? slasherProposer.getSlashingAmounts() : [0n, 0n, 0n],
slasher?.getVetoer() ?? EthAddress.ZERO,
slasher?.getSlashingDisableDuration() ?? 0,
rollup.getManaTarget(),
rollup.getProvingCostPerMana(),
rollup.getVersion(),
Expand All @@ -96,7 +98,8 @@ export async function getL1ContractsConfig(
slashingLifetimeInRounds: Number(slashingLifetimeInRounds),
slashingExecutionDelayInRounds: Number(slashingExecutionDelayInRounds),
slashingVetoer,
manaTarget: manaTarget,
slashingDisableDuration,
manaTarget,
provingCostPerMana: provingCostPerMana,
rollupVersion: Number(rollupVersion),
genesisArchiveTreeRoot,
Expand Down
1 change: 1 addition & 0 deletions yarn-project/foundation/src/config/env_var.ts
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,7 @@ export type EnvVar =
| 'AZTEC_SLASHING_EXECUTION_DELAY_IN_ROUNDS'
| 'AZTEC_SLASHING_VETOER'
| 'AZTEC_SLASHING_OFFSET_IN_ROUNDS'
| 'AZTEC_SLASHING_DISABLE_DURATION'
| 'AZTEC_SLASH_AMOUNT_SMALL'
| 'AZTEC_SLASH_AMOUNT_MEDIUM'
| 'AZTEC_SLASH_AMOUNT_LARGE'
Expand Down
1 change: 1 addition & 0 deletions yarn-project/slasher/src/empire_slasher_client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ describe('EmpireSlasherClient', () => {
// Setup rollup and slasher contract mocks
rollupContract.getSlasherContract.mockResolvedValue(slasherContract);
slasherContract.isPayloadVetoed.mockResolvedValue(false);
slasherContract.isSlashingEnabled.mockResolvedValue(true);

// Create watcher
dummyWatcher = new DummyWatcher();
Expand Down
Loading
Loading