Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

StakeTableV2

Git Source

Inherits: StakeTable, PausableUpgradeable, AccessControlUpgradeable

Title: Ethereum L1 component of the Espresso Global Confirmation Layer (GCL) stake table.

This contract is an upgrade to the original StakeTable contract. On Espresso mainnet we will only use the V2 contract. On decaf the V2 is used to upgrade the V1 that was first deployed with the original proof of stake release.

The V2 contract contains the following changes:

  1. The functions to register validators and update consensus keys are updated to require both a BLS signature and a Schnorr signature and emit the signatures via events so that the GCL can verify them. The new functions and events have a V2 postfix. After the upgrade components that support registration and key updates must use the V2 functions and listen to the V2 events. The original functions revert with a DeprecatedFunction error in V2.
  2. The exit escrow period can be updated by the owner of the contract within valid bounds (15 blocks to 14 days).
  3. The following functions can be paused by the PAUSER_ROLE:
  • claimWithdrawal(...)
  • claimValidatorExit(...)
  • delegate(...)
  • undelegate(...)
  • deregisterValidator(...)
  • registerValidatorV2(...)
  • updateConsensusKeysV2(...)
  • updateCommission(...)
  • updateMetadataUri(...) When paused, these functions revert with a standard pausable error, EnforcedPause(). Only the PAUSER_ROLE can pause/unpause the contract. Note: updateExitEscrowPeriod is NOT pausable for emergency governance access.
  1. The claimValidatorExit function is overridden to ensure that the validator’s delegatedAmount is updated during this method The update is deferred until the funds are actually withdrawn.
  2. The deregisterValidator function is overridden to ensure that the validator’s delegatedAmount is not updated during this method as it was in v1.
  3. The updateExitEscrowPeriod function is added to allow governance to update the exit escrow period within valid bounds (15 blocks to 14 days).
  4. The pause and unpause functions are added for emergency control.
  5. The commission rate for validators can be updated with the updateCommission function.
  6. The activeStake variable is added to allow governance to track the total stake in the contract. The activeStake is the total stake that is not awaiting exit or in exited state.
  7. Unique undelegation IDs are assigned to each undelegation via an auto-incrementing counter for better event tracking. The undelegationIds public mapping stores IDs alongside the base undelegations mapping. Events UndelegatedV2, WithdrawalClaimed, and ValidatorExitClaimed include the undelegation ID as an indexed parameter for efficient querying and tracking.
  8. Validators must provide a metadata URI during registration and can update it via updateMetadataUri. The metadata URI is event-sourced only (not stored on-chain for gas efficiency). The ValidatorRegisteredV2 event includes the metadata URI, and a new MetadataUriUpdated event is emitted when validators update their URI. Metadata URIs can be empty and cannot exceed 2048 bytes.
  9. A minimum delegation amount (minDelegateAmount) is enforced to prevent dust delegations and reduce state bloat. The minimum is initialized to 1 ESP token (1 ether) in initializeV2 and can be updated by governance via the setMinDelegateAmount function. The delegate function reverts with DelegateAmountTooSmall if the delegation amount is below the minimum.

The StakeTableV2 contract ABI is a superset of the original ABI. Consumers of the contract can use the V2 ABI, even if they would like to maintain backwards compatibility. Governance: This contract enforces a single-admin model. owner() and DEFAULT_ADMIN_ROLE always reference the same address and can only be changed via transferOwnership() (directly or through grantRole(DEFAULT_ADMIN_ROLE, ...), which delegates to the transfer). Any attempt to revoke or renounce the default admin role, or to renounce ownership, reverts. This removes governance drift.

All functions are marked as virtual so that future upgrades can override them.

State Variables

PAUSER_ROLE

bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE")

MAX_METADATA_URI_LENGTH

Maximum length for metadata URIs (in bytes)

uint256 public constant MAX_METADATA_URI_LENGTH = 2048

MAX_COMMISSION_BPS

Maximum commission in basis points (100% = 10000 bps)

uint16 public constant MAX_COMMISSION_BPS = 10000

MIN_EXIT_ESCROW_PERIOD

Minimum exit escrow period (2 days)

This is a technical minimum bound enforced by the contract. Setting the exit escrow period to this minimum does not guarantee safety. The actual exit escrow period must be set such that the contract holds funds long enough until they are no longer staked in Espresso, allowing sufficient time for validators to exit the active validator set and for slashing evidence to be submitted. Governance should set a value appropriate for Espresso network parameters (e.g., blocksPerEpoch, blockTime, and epoch duration) to ensure security.

uint64 public constant MIN_EXIT_ESCROW_PERIOD = 2 days

MAX_EXIT_ESCROW_PERIOD

Maximum exit escrow period (14 days)

Reasonable upper bound to prevent excessive lockup periods

uint64 public constant MAX_EXIT_ESCROW_PERIOD = 14 days

minCommissionIncreaseInterval

Minimum time interval between commission increases (in seconds)

uint256 public minCommissionIncreaseInterval

maxCommissionIncrease

Maximum commission increase allowed per increase (in basis points)

uint16 public maxCommissionIncrease

activeStake

Total stake in active (not marked for exit) validators in the contract

uint256 public activeStake

minDelegateAmount

min delegate amount

uint256 public minDelegateAmount

commissionTracking

Commission tracking for each validator

mapping(address validator => CommissionTracking tracking) public commissionTracking

schnorrKeys

Schnorr keys that have been seen by the contract

ensures a bijective mapping between schnorr key and ethereum account and prevents some errors due to misconfigurations of validators the contract currently marks keys as used and only allow them to be used once. This for example prevents callers from accidentally registering the same Schnorr key twice.

mapping(bytes32 schnorrKey => bool used) public schnorrKeys

nextUndelegationId

Auto-incrementing counter for unique undelegation IDs

Initialized to 1 in initializeV2 so that 0 can be used to identify V1 undelegations

uint64 private nextUndelegationId

undelegationIds

Mapping from (validator, delegator) to undelegation ID

Separate from base Undelegation struct since base contract is immutable

mapping(address validator => mapping(address delegator => uint64 id)) private undelegationIds

Functions

constructor

Constructor

This function is overridden to disable initializers

constructor() ;

initializeV2

Reinitialize the contract

initialCommissions must be an empty array if the contract we’re upgrading has not been used before (e.g. on mainnet). On decaf (sepolia), this must be called with the current commissions of pre-existing validators read from L1 events.

Sets up roles and transfers ownership to admin. The deployer picks the admin address (timelock, multisig, etc.) based on config.

function initializeV2(
    address pauser,
    address admin,
    uint256 initialActiveStake,
    InitialCommission[] calldata initialCommissions
) public onlyOwner reinitializer(2);

Parameters

NameTypeDescription
pauseraddressThe address to be granted the pauser role
adminaddressThe address to be granted the default admin role and ownership. This should be a timelock contract address, multisig, or another governance address.
initialActiveStakeuint256The initial active stake in the contract
initialCommissionsInitialCommission[]commissions of validators

getVersion

Get the version of the contract

This function is overridden to return the version of the contract

function getVersion()
    public
    pure
    virtual
    override
    returns (uint8 majorVersion, uint8 minorVersion, uint8 patchVersion);

pause

Pause the contract

This function is only callable by the PAUSER_ROLE

function pause() external onlyRole(PAUSER_ROLE);

unpause

Unpause the contract

This function is only callable by the PAUSER_ROLE

function unpause() external onlyRole(PAUSER_ROLE);

transferOwnership

Transfers ownership and keeps DEFAULT_ADMIN_ROLE in sync Grants the role to new owner and revokes from old owner. Access control is enforced by both onlyRole(DEFAULT_ADMIN_ROLE) and super.transferOwnership() which requires onlyOwner. This ensures that only the current admin (who holds both ownership and DEFAULT_ADMIN_ROLE) can transfer ownership. This is intentionally not pausable for emergency governance access.

Transfers ownership of the contract to a new account (newOwner). Can only be called by the current owner.

function transferOwnership(address newOwner)
    public
    virtual
    override
    onlyRole(DEFAULT_ADMIN_ROLE);

grantRole

Grants a role. Granting DEFAULT_ADMIN_ROLE transfers ownership first, which handles both role grant and ownership transfer atomically. This is intentionally not pausable for emergency governance access.

Grants role to account. If account had not been already granted role, emits a {RoleGranted} event. Requirements:

  • the caller must have role’s admin role. May emit a {RoleGranted} event.
function grantRole(bytes32 role, address account) public virtual override;

revokeRole

Prevent revoking DEFAULT_ADMIN_ROLE to preserve the single-admin invariant.

Revokes role from account. If account had been granted role, emits a {RoleRevoked} event. Requirements:

  • the caller must have role’s admin role. May emit a {RoleRevoked} event.
function revokeRole(bytes32 role, address account) public virtual override;

renounceRole

Prevent renouncing DEFAULT_ADMIN_ROLE to preserve the single-admin invariant.

Revokes role from the calling account. Roles are often managed via {grantRole} and {revokeRole}: this function’s purpose is to provide a mechanism for accounts to lose their privileges if they are compromised (such as when a trusted device is misplaced). If the calling account had been revoked role, emits a {RoleRevoked} event. Requirements:

  • the caller must be callerConfirmation. May emit a {RoleRevoked} event.
function renounceRole(bytes32 role, address callerConfirmation) public virtual override;

claimValidatorExit

Withdraw previously delegated funds after a validator has exited

This function is overridden to deduct the amount from the validator’s delegatedAmount

and to add pausable functionality

since the delegated Amount is no longer updated during validator exit

function claimValidatorExit(address validator) public virtual override whenNotPaused;

Parameters

NameTypeDescription
validatoraddressThe validator to withdraw from

claimWithdrawal

Withdraw previously delegated funds after an undelegation

This function is overridden to add pausable functionality and emit ID in event

function claimWithdrawal(address validator) public virtual override whenNotPaused;

Parameters

NameTypeDescription
validatoraddressThe validator to withdraw from

delegate

Delegate funds to a validator

This function is overridden to add pausable functionality

The function body is copied from V1 to maintain checks-effects-interactions pattern.

function delegate(address validator, uint256 amount) public virtual override whenNotPaused;

Parameters

NameTypeDescription
validatoraddressThe validator to delegate to
amountuint256The amount to delegate

undelegate

Undelegate funds from a validator

This function is overridden to add pausable functionality and emit UndelegatedV2

The undelegation ID can be retrieved from the UndelegatedV2 event or via getUndelegation()

function undelegate(address validator, uint256 amount) public virtual override whenNotPaused;

Parameters

NameTypeDescription
validatoraddressThe validator to undelegate from
amountuint256The amount to undelegate

deregisterValidator

Deregister a validator

This function is overridden to add pausable functionality

and to ensure that the validator’s delegatedAmount is not updated until withdrawal

delegatedAmount represents the no. of tokens that have been delegated to a validator, even if it’s not participating in consensus

emits ValidatorExitV2 instead of ValidatorExit

function deregisterValidator() public virtual override whenNotPaused;

registerValidatorV2

Register a validator in the stake table

This function is overridden to add pausable functionality

and to add schnorrSig validation

function registerValidatorV2(
    BN254.G2Point memory blsVK,
    EdOnBN254.EdOnBN254Point memory schnorrVK,
    BN254.G1Point memory blsSig,
    bytes memory schnorrSig,
    uint16 commission,
    string memory metadataUri
) external virtual whenNotPaused;

Parameters

NameTypeDescription
blsVKBN254.G2PointThe BLS verification key
schnorrVKEdOnBN254.EdOnBN254PointThe Schnorr verification key
blsSigBN254.G1PointThe BLS signature that authenticates the BLS VK
schnorrSigbytesThe Schnorr signature that authenticates the Schnorr VK
commissionuint16in % with 2 decimals, from 0.00% (value 0) to 100% (value 10_000)
metadataUristringThe metadata URI for the validator

updateConsensusKeysV2

Update the consensus keys of a validator

This function is overridden to add pausable functionality

and to add schnorrSig validation

function updateConsensusKeysV2(
    BN254.G2Point memory blsVK,
    EdOnBN254.EdOnBN254Point memory schnorrVK,
    BN254.G1Point memory blsSig,
    bytes memory schnorrSig
) public virtual whenNotPaused;

Parameters

NameTypeDescription
blsVKBN254.G2PointThe new BLS verification key
schnorrVKEdOnBN254.EdOnBN254PointThe new Schnorr verification key
blsSigBN254.G1PointThe BLS signature that authenticates the blsVK
schnorrSigbytesThe Schnorr signature that authenticates the schnorrVK

updateCommission

Update the commission rate for a validator

  1. Only one commission increase per minCommissionIncreaseInterval is allowed.
  2. The commission increase cannot exceed maxCommissionIncrease. These limits protect stakers from sudden large commission increases, particularly by exiting validators.
function updateCommission(uint16 newCommission) external virtual whenNotPaused;

Parameters

NameTypeDescription
newCommissionuint16The new commission rate in % with 2 decimals (0 to 10_000)

updateMetadataUri

Update the metadata URI for a validator

The metadata URI is NOT stored on-chain. Off-chain indexers must listen to the MetadataUriUpdated event to track the current URI. URIs should be kept reasonably short for gas efficiency (max 2048 bytes). Only the validator (msg.sender) can update their own metadata URI.

No format validation is performed on the URI - any string within length limits (including empty string) is accepted. Consumers should validate URI format and accessibility off-chain.

function updateMetadataUri(string memory metadataUri) external virtual whenNotPaused;

Parameters

NameTypeDescription
metadataUristringThe new metadata URI

setMinCommissionUpdateInterval

Set the minimum interval between commission updates

function setMinCommissionUpdateInterval(uint256 newInterval)
    external
    virtual
    onlyRole(DEFAULT_ADMIN_ROLE);

Parameters

NameTypeDescription
newIntervaluint256The new minimum interval in seconds

setMaxCommissionIncrease

Set the maximum commission increase allowed per update

function setMaxCommissionIncrease(uint16 newMaxIncrease)
    external
    virtual
    onlyRole(DEFAULT_ADMIN_ROLE);

Parameters

NameTypeDescription
newMaxIncreaseuint16The new maximum increase in basis points (e.g., 500 = 5%)

_initializeCommissions

Initialize validator commissions during V2 migration

This function is used to retroactively initialize commission storage for validators that were registered before the V2 upgrade. On decaf, this will be called with current commission values read from L1 events. On mainnet, this will be called with an empty array since there are no pre-existing validators.

function _initializeCommissions(InitialCommission[] calldata initialCommissions) private;

Parameters

NameTypeDescription
initialCommissionsInitialCommission[]Array of InitialCommission structs containing validator addresses and their commissions

_initializeActiveStake

Initialize the active stake in the contract

function _initializeActiveStake(uint256 initialActiveStake) private;

Parameters

NameTypeDescription
initialActiveStakeuint256The initial active stake in the contract

validateMetadataUri

Validate metadata URI length

Public view function to allow external validation before transaction submission

function validateMetadataUri(string memory metadataUri) public pure;

Parameters

NameTypeDescription
metadataUristringThe metadata URI to validate

updateExitEscrowPeriod

Update the exit escrow period

This function ensures that the exit escrow period is within the valid range (MIN_EXIT_ESCROW_PERIOD to MAX_EXIT_ESCROW_PERIOD). However, governance MUST set a value that ensures funds are held until they are no longer staked in Espresso, accounting for validator exit time and slashing evidence submission windows. This function is not pausable so that governance can perform emergency updates in the presence of system upgrades.

function updateExitEscrowPeriod(uint64 newExitEscrowPeriod)
    external
    virtual
    onlyRole(DEFAULT_ADMIN_ROLE);

Parameters

NameTypeDescription
newExitEscrowPerioduint64The new exit escrow period

getUndelegation

Get details of an undelegation for a (validator, delegator) pair

function getUndelegation(address validator, address delegator)
    external
    view
    returns (uint64 id, uint256 amount, uint256 unlocksAt);

Parameters

NameTypeDescription
validatoraddressAddress of the validator
delegatoraddressAddress of the delegator

Returns

NameTypeDescription
iduint64Unique ID of the undelegation
amountuint256Amount of tokens undelegated
unlocksAtuint256Timestamp when tokens can be claimed

_hashSchnorrKey

function _hashSchnorrKey(EdOnBN254.EdOnBN254Point memory schnorrVK)
    internal
    pure
    returns (bytes32);

ensureNewKeys

Ensure that the BLS and Schnorr keys are not already used

function ensureNewKeys(BN254.G2Point memory blsVK, EdOnBN254.EdOnBN254Point memory schnorrVK)
    internal
    view;

Parameters

NameTypeDescription
blsVKBN254.G2PointThe BLS verification key
schnorrVKEdOnBN254.EdOnBN254PointThe Schnorr verification key

registerValidator

Deprecate previous registration function

This function is overridden to revert with a DeprecatedFunction error

users must call registerValidatorV2 instead

function registerValidator(
    BN254.G2Point memory,
    EdOnBN254.EdOnBN254Point memory,
    BN254.G1Point memory,
    uint16
) external pure override;

updateConsensusKeys

Deprecate previous updateConsensusKeys function

This function is overridden to revert with a DeprecatedFunction error

users must call updateConsensusKeysV2 instead

function updateConsensusKeys(
    BN254.G2Point memory,
    EdOnBN254.EdOnBN254Point memory,
    BN254.G1Point memory
) external pure override;

setMinDelegateAmount

Set the minimum delegate amount

function setMinDelegateAmount(uint256 newMinDelegateAmount)
    external
    virtual
    onlyRole(DEFAULT_ADMIN_ROLE);

Parameters

NameTypeDescription
newMinDelegateAmountuint256The new minimum delegate amount in wei

_authorizeUpgrade

Authorize an upgrade to a new implementation

This function is overridden to use AccessControl instead of Ownable Only addresses with DEFAULT_ADMIN_ROLE can authorize upgrades

function _authorizeUpgrade(address newImplementation)
    internal
    virtual
    override
    onlyRole(DEFAULT_ADMIN_ROLE);

Parameters

NameTypeDescription
newImplementationaddressThe address of the new implementation

Events

ValidatorRegisteredV2

A validator is registered in the stake table

the blsSig and schnorrSig are validated by the Espresso Network

event ValidatorRegisteredV2(
    address indexed account,
    BN254.G2Point blsVK,
    EdOnBN254.EdOnBN254Point schnorrVK,
    uint16 commission,
    BN254.G1Point blsSig,
    bytes schnorrSig,
    string metadataUri
);

ConsensusKeysUpdatedV2

A validator updates their consensus keys

the blsSig and schnorrSig are validated by the Espresso Network

event ConsensusKeysUpdatedV2(
    address indexed account,
    BN254.G2Point blsVK,
    EdOnBN254.EdOnBN254Point schnorrVK,
    BN254.G1Point blsSig,
    bytes schnorrSig
);

ExitEscrowPeriodUpdated

The exit escrow period is updated

event ExitEscrowPeriodUpdated(uint64 newExitEscrowPeriod);

CommissionUpdated

A validator updates their commission rate

the timestamp is emitted to simplify processing in the GCL

event CommissionUpdated(
    address indexed validator, uint256 timestamp, uint16 oldCommission, uint16 newCommission
);

Parameters

NameTypeDescription
validatoraddressThe address of the validator
timestampuint256The timestamp of the update
oldCommissionuint16
newCommissionuint16The new commission rate

MinCommissionUpdateIntervalUpdated

The minimum commission update interval is updated

event MinCommissionUpdateIntervalUpdated(uint256 newInterval);

Parameters

NameTypeDescription
newIntervaluint256The new minimum update interval in seconds

MaxCommissionIncreaseUpdated

The maximum commission increase is updated

event MaxCommissionIncreaseUpdated(uint16 newMaxIncrease);

Parameters

NameTypeDescription
newMaxIncreaseuint16The new maximum commission increase in basis points

UndelegatedV2

A delegator undelegated funds from a validator (V2 with unlocksAt and undelegationId)

event UndelegatedV2(
    address indexed delegator,
    address indexed validator,
    uint64 indexed undelegationId,
    uint256 amount,
    uint256 unlocksAt
);

Parameters

NameTypeDescription
delegatoraddressThe address of the delegator
validatoraddressThe address of the validator
undelegationIduint64Unique identifier for this undelegation
amountuint256The amount undelegated
unlocksAtuint256The timestamp when the funds can be claimed

WithdrawalClaimed

A delegator claimed an undelegation (V2 with undelegationId)

event WithdrawalClaimed(
    address indexed delegator,
    address indexed validator,
    uint64 indexed undelegationId,
    uint256 amount
);

Parameters

NameTypeDescription
delegatoraddressThe address of the delegator
validatoraddressThe address of the validator
undelegationIduint64Unique identifier for this undelegation
amountuint256The amount claimed

ValidatorExitClaimed

A delegator claimed funds after validator exit

event ValidatorExitClaimed(
    address indexed delegator, address indexed validator, uint256 amount
);

Parameters

NameTypeDescription
delegatoraddressThe address of the delegator
validatoraddressThe address of the validator
amountuint256The amount claimed

ValidatorExitV2

A validator initiated an exit (V2 with unlocksAt)

event ValidatorExitV2(address indexed validator, uint256 unlocksAt);

Parameters

NameTypeDescription
validatoraddressThe address of the validator
unlocksAtuint256The timestamp when delegators can claim their funds

MetadataUriUpdated

A validator updated their metadata URI

event MetadataUriUpdated(address indexed validator, string metadataUri);

Parameters

NameTypeDescription
validatoraddressThe address of the validator
metadataUristringThe new metadata URI

MinDelegateAmountUpdated

The minimum delegate amount is updated

event MinDelegateAmountUpdated(uint256 newMinDelegateAmount);

Parameters

NameTypeDescription
newMinDelegateAmountuint256The new minimum delegate amount in wei

Errors

InvalidSchnorrSig

The Schnorr signature is invalid (either the wrong length or the wrong key)

error InvalidSchnorrSig();

DeprecatedFunction

The function is deprecated as it was replaced by a new function

error DeprecatedFunction();

CommissionUpdateTooSoon

The commission update is too soon after the last update

error CommissionUpdateTooSoon();

CommissionIncreaseExceedsMax

The commission increase exceeds the maximum allowed increase

error CommissionIncreaseExceedsMax();

CommissionUnchanged

The commission value is unchanged

error CommissionUnchanged();

InvalidRateLimitParameters

The rate limit parameters are invalid

error InvalidRateLimitParameters();

CommissionAlreadyInitialized

The validator commission has already been initialized

error CommissionAlreadyInitialized(address validator);

InitialActiveStakeExceedsBalance

The initial active stake exceeds the balance of the contract

error InitialActiveStakeExceedsBalance();

SchnorrKeyAlreadyUsed

The Schnorr key has been previously registered in the contract.

error SchnorrKeyAlreadyUsed();

DefaultAdminCannotBeRevoked

Attempted to revoke DEFAULT_ADMIN_ROLE which would break single-admin governance

error DefaultAdminCannotBeRevoked();

DefaultAdminCannotBeRenounced

Attempted to renounce DEFAULT_ADMIN_ROLE which would break single-admin governance

error DefaultAdminCannotBeRenounced();

NoUndelegationFound

No undelegation exists for the given validator and delegator

error NoUndelegationFound();

InvalidMetadataUriLength

The metadata URI exceeds maximum allowed length

error InvalidMetadataUriLength();

DelegateAmountTooSmall

The delegate amount is too small

error DelegateAmountTooSmall();

MinDelegateAmountTooSmall

The minimum delegate amount is too small

error MinDelegateAmountTooSmall();

Structs

CommissionTracking

Struct for tracking validator commission and last increase time

struct CommissionTracking {
    uint16 commission;
    uint256 lastIncreaseTime;
}

InitialCommission

Struct for initializing validator commissions during migration

struct InitialCommission {
    address validator;
    uint16 commission;
}