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
1 change: 1 addition & 0 deletions l1-contracts/l1-artifacts/scripts/generate-artifacts.sh
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ contracts=(
"RollupOperationsExtLib"
"EpochProofExtLib"
"ValidatorOperationsExtLib"
"AttesterExitExtLib"
"RewardExtLib"
"SlasherDeploymentExtLib"
"FeeJuicePortal"
Expand Down
23 changes: 19 additions & 4 deletions l1-contracts/src/core/Rollup.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,20 @@ import {
RegistryRewardOverride,
MAX_REGISTRY_REWARD_OVERRIDES
} from "@aztec/core/interfaces/IRollup.sol";
import {IStaking, AttesterConfig, Exit, AttesterView, Status} from "@aztec/core/interfaces/IStaking.sol";
import {
IStaking,
AttesterConfig,
Exit,
AttesterView,
Status,
AttesterExitLimitState
} from "@aztec/core/interfaces/IStaking.sol";
import {IValidatorSelection, IEmperor} from "@aztec/core/interfaces/IValidatorSelection.sol";
import {IVerifier} from "@aztec/core/interfaces/IVerifier.sol";
import {TempCheckpointLog, CheckpointLog} from "@aztec/core/libraries/compressed-data/CheckpointLog.sol";
import {FeeAssetValue, PriceLib} from "@aztec/core/libraries/compressed-data/fees/FeeConfig.sol";
import {FeeAssetValue} from "@aztec/core/libraries/compressed-data/fees/FeeConfig.sol";
import {FeeHeaderLib} from "@aztec/core/libraries/compressed-data/fees/FeeStructs.sol";
import {AttesterExitExtLib} from "@aztec/core/libraries/rollup/AttesterExitExtLib.sol";
import {ProposedHeader} from "@aztec/core/libraries/rollup/ProposedHeaderLib.sol";
import {StakingLib} from "@aztec/core/libraries/rollup/StakingLib.sol";
import {GSE} from "@aztec/governance/GSE.sol";
Expand Down Expand Up @@ -66,7 +74,6 @@ contract Rollup is IStaking, IValidatorSelection, IRollup, RollupCore {
using TimeLib for Timestamp;
using TimeLib for Slot;
using TimeLib for Epoch;
using PriceLib for EthValue;
using CompressedTimeMath for CompressedSlot;
using CompressedTimeMath for CompressedTimestamp;
using ChainTipsLib for CompressedChainTips;
Expand Down Expand Up @@ -250,6 +257,14 @@ contract Rollup is IStaking, IValidatorSelection, IRollup, RollupCore {
return StakingLib.getStorage().gse.ACTIVATION_THRESHOLD();
}

function getAttesterExitWindow() external view override(IStaking) returns (Timestamp) {
return AttesterExitExtLib.getAttesterExitWindow();
}

function getAttesterExitLimitState() external view override(IStaking) returns (AttesterExitLimitState memory) {
return AttesterExitExtLib.getAttesterExitLimitState();
}

function getExitDelay() external view override(IStaking) returns (Timestamp) {
return StakingLib.getStorage().exitDelay.decompress();
}
Expand Down Expand Up @@ -565,7 +580,7 @@ contract Rollup is IStaking, IValidatorSelection, IRollup, RollupCore {
}

function getProvingCostPerManaInFeeAsset() external view override(IRollup) returns (FeeAssetValue) {
return RewardExtLib.getProvingCostPerMana().toFeeAsset(getEthPerFeeAsset());
return RewardExtLib.getProvingCostPerManaInFeeAsset(getEthPerFeeAsset());
}

// The config getters below go through {_getRollupConfig} rather than reading their immutable
Expand Down
50 changes: 49 additions & 1 deletion l1-contracts/src/core/RollupCore.sol
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,11 @@ import {RollupOperationsExtLib} from "@aztec/core/libraries/rollup/RollupOperati
import {ValidatorOperationsExtLib} from "@aztec/core/libraries/rollup/ValidatorOperationsExtLib.sol";
import {SlasherDeploymentExtLib} from "@aztec/core/libraries/rollup/SlasherDeploymentExtLib.sol";
import {EthValue} from "@aztec/core/libraries/compressed-data/fees/FeeConfig.sol";
import {AttesterExitExtLib} from "@aztec/core/libraries/rollup/AttesterExitExtLib.sol";
import {FeeLib} from "@aztec/core/libraries/rollup/FeeLib.sol";
import {ProposeArgs} from "@aztec/core/libraries/rollup/ProposeLib.sol";
import {STFLib, GenesisState} from "@aztec/core/libraries/rollup/STFLib.sol";
import {StakingLib} from "@aztec/core/libraries/rollup/StakingLib.sol";
import {StakingLib, AttesterExitAuthorization} from "@aztec/core/libraries/rollup/StakingLib.sol";
import {Timestamp, Slot, Epoch, TimeLib} from "@aztec/core/libraries/TimeLib.sol";
import {Inbox, INBOX_BUCKET_RING_SIZE} from "@aztec/core/messagebridge/Inbox.sol";
import {Outbox} from "@aztec/core/messagebridge/Outbox.sol";
Expand Down Expand Up @@ -489,6 +490,53 @@ contract RollupCore is EIP712("Aztec Rollup", "1"), Ownable, IStakingCore, IVali
return ValidatorOperationsExtLib.initiateWithdraw(_attester, _recipient);
}

/**
* @notice Initiates withdrawal of a validator's stake by the attester
* @dev The registered withdrawer retains control of the payout recipient.
* @param _attester The validator position to exit.
*/
function initiateWithdrawByAttester(address _attester) external override(IStakingCore) {
AttesterExitExtLib.initiateWithdrawByAttester(_attester);
}

/**
* @notice Initiates an attester withdrawal using an EIP-712 authorization.
* @dev Anyone may relay the authorization. The registered withdrawer retains control of the payout recipient.
* @param _authorization The attester, deadline, and signature authorizing the exit.
*/
function initiateWithdrawByAttesterWithSignature(AttesterExitAuthorization calldata _authorization)
external
override(IStakingCore)
{
AttesterExitExtLib.initiateWithdrawByAttesterWithSignature(_authorization);
}

/**
* @notice Initiates multiple attester withdrawals using EIP-712 authorizations.
* @dev Shared instance and exit-limit checks are performed once for the whole batch.
* @param _authorizations The signed exit authorizations to execute atomically.
*/
function initiateWithdrawByAttesterBatch(AttesterExitAuthorization[] calldata _authorizations)
external
override(IStakingCore)
{
AttesterExitExtLib.initiateWithdrawByAttesterBatch(_authorizations);
}

/**
* @notice Initiates as many authorized attester withdrawals as the current exit limit permits.
* @dev Processes a prefix of the array and returns zero when no capacity remains.
* @param _authorizations The ordered signed exit authorizations.
* @return exitedCount The number of authorizations processed from the start of the array.
*/
function initiateWithdrawByAttesterBatchUpToLimit(AttesterExitAuthorization[] calldata _authorizations)
external
override(IStakingCore)
returns (uint256 exitedCount)
{
return AttesterExitExtLib.initiateWithdrawByAttesterBatchUpToLimit(_authorizations);
}

/**
* @notice Completes a withdrawal after the exit delay has passed
* @dev Can be called by anyone. Transfers the stake to the designated recipient.
Expand Down
20 changes: 18 additions & 2 deletions l1-contracts/src/core/interfaces/IStaking.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,13 @@
pragma solidity >=0.8.27;

import {StakingQueueConfig} from "@aztec/core/libraries/compressed-data/StakingQueueConfig.sol";
import {Exit, Status, AttesterView} from "@aztec/core/libraries/rollup/StakingLib.sol";
import {
Exit,
Status,
AttesterView,
AttesterExitLimitState,
AttesterExitAuthorization
} from "@aztec/core/libraries/rollup/StakingLib.sol";
import {DepositArgs} from "@aztec/core/libraries/StakingQueue.sol";
import {AttesterConfig, GSE} from "@aztec/governance/GSE.sol";
import {G1Point, G2Point} from "@aztec/shared/libraries/BN254Lib.sol";
Expand Down Expand Up @@ -35,6 +41,9 @@ interface IStakingCore {
event WithdrawFinalized(address indexed attester, address indexed recipient, uint256 amount);
event Slashed(address indexed attester, uint256 amount);
event StakingQueueConfigUpdated(StakingQueueConfig config);
event WithdrawInitiatedByAttester(
address indexed attester, address indexed withdrawer, uint256 amount, uint256 withdrawalId
);

function queueSetSlasher(address _slasher) external;
function cancelSetSlasher() external;
Expand All @@ -54,7 +63,12 @@ interface IStakingCore {
function slash(address _attester, uint256 _amount) external returns (bool);
function vote(uint256 _proposalId) external;
function updateStakingQueueConfig(StakingQueueConfig memory _config) external;

function initiateWithdrawByAttester(address _attester) external;
function initiateWithdrawByAttesterWithSignature(AttesterExitAuthorization calldata _authorization) external;
function initiateWithdrawByAttesterBatch(AttesterExitAuthorization[] calldata _authorizations) external;
function initiateWithdrawByAttesterBatchUpToLimit(AttesterExitAuthorization[] calldata _authorizations)
external
returns (uint256);
function getEntryQueueFlushSize() external view returns (uint256);
function getActiveAttesterCount() external view returns (uint256);
}
Expand All @@ -73,6 +87,8 @@ interface IStaking is IStakingCore {
function getActivationThreshold() external view returns (uint256);
function getEjectionThreshold() external view returns (uint256);
function getExitDelay() external view returns (Timestamp);
function getAttesterExitWindow() external view returns (Timestamp);
function getAttesterExitLimitState() external view returns (AttesterExitLimitState memory);
function getGSE() external view returns (GSE);
function getAttesterView(address _attester) external view returns (AttesterView memory);
function getStatus(address _attester) external view returns (Status);
Expand Down
6 changes: 6 additions & 0 deletions l1-contracts/src/core/libraries/Errors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,12 @@ library Errors {
error Staking__SlasherProposerNotInitialized(address slasher);
error Staking__NoPendingSlasher();
error Staking__SlasherNotReady(Timestamp readyAt);
error Staking__AttesterExitPoolTooSmall(uint256 validatorCount, uint256 committeeSize);
error Staking__AttesterExitLimitExceeded(uint256 used, uint256 allowance);
error Staking__EmptyAttesterExitBatch();
error Staking__AttesterExitAuthorizationExpired(uint256 deadline, uint256 currentTime);
error Staking__NotAttester(address attester, address caller);
error Staking__NotLatestRollup(address rollup, address latestRollup);

// Fee Juice Portal
error FeeJuicePortal__AlreadyInitialized(); // 0xc7a172fe
Expand Down
93 changes: 93 additions & 0 deletions l1-contracts/src/core/libraries/rollup/AttesterExitExtLib.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2026 Aztec Labs.
pragma solidity >=0.8.27;

import {Errors} from "@aztec/core/libraries/Errors.sol";
import {Signature, SignatureLib} from "@aztec/shared/libraries/SignatureLib.sol";
import {Timestamp} from "@aztec/shared/libraries/TimeMath.sol";
import {CoordinationSignatureLib} from "./CoordinationSignatureLib.sol";
import {StakingLib, AttesterExitLimitState, AttesterExitAuthorization} from "./StakingLib.sol";

/// @notice External attester-exit functions separated to keep rollup libraries within the contract size limit.
/// @dev Library calls execute against the calling rollup's staking storage.
library AttesterExitExtLib {
using SignatureLib for Signature;

bytes32 internal constant ATTESTER_EXIT_TYPEHASH = keccak256("AttesterExit(address attester,uint256 deadline)");

/// @notice Initiates an exit for a position controlled by the calling attester.
function initiateWithdrawByAttester(address _attester) external {
StakingLib.initiateWithdrawByAttester(_attester);
}

/// @notice Initiates an attester exit authorized by an EIP-712 signature.
/// @param _authorization The signed exit authorization.
function initiateWithdrawByAttesterWithSignature(AttesterExitAuthorization calldata _authorization) external {
_authorize(_authorization);
StakingLib.initiateWithdrawByAttesterWithSignature(_authorization.attester);
}

/// @notice Initiates multiple attester exits authorized by EIP-712 signatures.
/// @param _authorizations The signed exit authorizations to execute atomically.
function initiateWithdrawByAttesterBatch(AttesterExitAuthorization[] calldata _authorizations) external {
uint256 exitCount = _authorizations.length;
require(exitCount > 0, Errors.Staking__EmptyAttesterExitBatch());

StakingLib.checkAttesterExitInstance();
StakingLib.consumeAttesterExitAllowance(exitCount);

for (uint256 i = 0; i < exitCount;) {
AttesterExitAuthorization calldata authorization = _authorizations[i];
_authorize(authorization);
StakingLib.initiateWithdrawByAttesterBatchItem(authorization.attester);
unchecked {
++i;
}
Comment on lines +43 to +45

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, the gas savings that matter

}
}

/// @notice Initiates as many authorized attester exits as the current limit permits.
/// @param _authorizations The ordered signed exit authorizations.
/// @return exitCount The number of authorizations processed from the start of the array.
function initiateWithdrawByAttesterBatchUpToLimit(AttesterExitAuthorization[] calldata _authorizations)
external
returns (uint256 exitCount)
{
uint256 requestedExitCount = _authorizations.length;
require(requestedExitCount > 0, Errors.Staking__EmptyAttesterExitBatch());

StakingLib.checkAttesterExitInstance();
exitCount = StakingLib.consumeAttesterExitAllowanceUpTo(requestedExitCount);

for (uint256 i = 0; i < exitCount;) {
AttesterExitAuthorization calldata authorization = _authorizations[i];
_authorize(authorization);
StakingLib.initiateWithdrawByAttesterBatchItem(authorization.attester);
unchecked {
++i;
}
}
}

/// @notice Returns the current window used to count attester exits.
function getAttesterExitWindow() external view returns (Timestamp) {
return StakingLib.getAttesterExitWindow();
}

/// @notice Returns attester-exit capacity for the calling rollup.
/// @dev canExit does not establish eligibility of an individual position.
function getAttesterExitLimitState() external view returns (AttesterExitLimitState memory) {
return StakingLib.getAttesterExitLimitState();
}

function _authorize(AttesterExitAuthorization calldata _authorization) private view {
require(
block.timestamp <= _authorization.deadline,
Errors.Staking__AttesterExitAuthorizationExpired(_authorization.deadline, block.timestamp)
);

bytes32 structHash = keccak256(abi.encode(ATTESTER_EXIT_TYPEHASH, _authorization.attester, _authorization.deadline));
bytes32 digest = CoordinationSignatureLib.toTypedDataHash(structHash);
_authorization.signature.verify(_authorization.attester, digest);
}
}
12 changes: 11 additions & 1 deletion l1-contracts/src/core/libraries/rollup/RewardExtLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@
pragma solidity >=0.8.27;

import {IRollupCore} from "@aztec/core/interfaces/IRollup.sol";
import {FeeConfigLib, CompressedFeeConfig} from "@aztec/core/libraries/compressed-data/fees/FeeConfig.sol";
import {
FeeConfigLib,
CompressedFeeConfig,
FeeAssetValue,
PriceLib
} from "@aztec/core/libraries/compressed-data/fees/FeeConfig.sol";
import {Errors} from "@aztec/core/libraries/Errors.sol";
import {
FeeLib,
Expand Down Expand Up @@ -33,6 +38,7 @@ import {IERC20} from "@oz/token/ERC20/IERC20.sol";

library RewardExtLib {
using FeeConfigLib for CompressedFeeConfig;
using PriceLib for EthValue;

function initializeConfig(
RewardConfig memory _config,
Expand Down Expand Up @@ -168,6 +174,10 @@ library RewardExtLib {
return FeeLib.getProvingCostPerMana();
}

function getProvingCostPerManaInFeeAsset(EthPerFeeAssetE12 _ethPerFeeAsset) external view returns (FeeAssetValue) {
return FeeLib.getProvingCostPerMana().toFeeAsset(_ethPerFeeAsset);
}

function summedMinFee(ManaMinFeeComponents memory _components) external pure returns (uint256) {
return FeeLib.summedMinFee(_components);
}
Expand Down
Loading
Loading