Description:
Multi-signature wallet contract requiring multiple confirmations for transaction execution.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
{{
"language": "Solidity",
"sources": {
"src/CSEjector.sol": {
"content": "// SPDX-FileCopyrightText: 2025 Lido <info@lido.fi>
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.24;
import { AccessControlEnumerable } from "@openzeppelin/contracts/access/extensions/AccessControlEnumerable.sol";
import { AssetRecoverer } from "./abstract/AssetRecoverer.sol";
import { ExitTypes } from "./abstract/ExitTypes.sol";
import { PausableUntil } from "./lib/utils/PausableUntil.sol";
import { SigningKeys } from "./lib/SigningKeys.sol";
import { ICSEjector } from "./interfaces/ICSEjector.sol";
import { ICSModule } from "./interfaces/ICSModule.sol";
import { ITriggerableWithdrawalsGateway, ValidatorData } from "./interfaces/ITriggerableWithdrawalsGateway.sol";
contract CSEjector is
ICSEjector,
ExitTypes,
AccessControlEnumerable,
PausableUntil,
AssetRecoverer
{
bytes32 public constant PAUSE_ROLE = keccak256("PAUSE_ROLE");
bytes32 public constant RESUME_ROLE = keccak256("RESUME_ROLE");
bytes32 public constant RECOVERER_ROLE = keccak256("RECOVERER_ROLE");
uint256 public immutable STAKING_MODULE_ID;
ICSModule public immutable MODULE;
address public immutable STRIKES;
modifier onlyStrikes() {
if (msg.sender != STRIKES) {
revert SenderIsNotStrikes();
}
_;
}
constructor(
address module,
address strikes,
uint256 stakingModuleId,
address admin
) {
if (module == address(0)) {
revert ZeroModuleAddress();
}
if (strikes == address(0)) {
revert ZeroStrikesAddress();
}
if (admin == address(0)) {
revert ZeroAdminAddress();
}
STRIKES = strikes;
MODULE = ICSModule(module);
STAKING_MODULE_ID = stakingModuleId;
_grantRole(DEFAULT_ADMIN_ROLE, admin);
}
/// @inheritdoc ICSEjector
function resume() external onlyRole(RESUME_ROLE) {
_resume();
}
/// @inheritdoc ICSEjector
function pauseFor(uint256 duration) external onlyRole(PAUSE_ROLE) {
_pauseFor(duration);
}
/// @inheritdoc ICSEjector
function voluntaryEject(
uint256 nodeOperatorId,
uint256 startFrom,
uint256 keysCount,
address refundRecipient
) external payable whenResumed {
_onlyNodeOperatorOwner(nodeOperatorId);
if (keysCount == 0) {
revert NothingToEject();
}
{
// A key must be deposited to prevent ejecting unvetted keys that can intersect with
// other modules.
uint256 maxKeyIndex = startFrom + keysCount;
if (
maxKeyIndex >
MODULE.getNodeOperatorTotalDepositedKeys(nodeOperatorId)
) {
revert SigningKeysInvalidOffset();
}
// A key must be non-withdrawn to restrict unlimited exit requests consuming sanity
// checker limits, although a deposited key can be requested to exit multiple times.
// But, it will eventually be withdrawn, so potentially malicious behaviour stops when
// there are no active keys available
for (uint256 i = startFrom; i < maxKeyIndex; ++i) {
if (MODULE.isValidatorWithdrawn(nodeOperatorId, i)) {
revert AlreadyWithdrawn();
}
}
}
bytes memory pubkeys = MODULE.getSigningKeys(
nodeOperatorId,
startFrom,
keysCount
);
ValidatorData[] memory exitsData = new ValidatorData[](keysCount);
for (uint256 i; i < keysCount; ++i) {
bytes memory pubkey = new bytes(SigningKeys.PUBKEY_LENGTH);
assembly {
let keyLen := mload(pubkey) // PUBKEY_LENGTH
let offset := mul(keyLen, i) // PUBKEY_LENGTH * i
let keyPos := add(add(pubkeys, 0x20), offset) // pubkeys[offset]
mcopy(add(pubkey, 0x20), keyPos, keyLen) // pubkey = pubkeys[offset:offset+PUBKEY_LENGTH]
}
exitsData[i] = ValidatorData({
stakingModuleId: STAKING_MODULE_ID,
nodeOperatorId: nodeOperatorId,
pubkey: pubkey
});
}
// @dev This call might revert if the limits are exceeded on the protocol side.
triggerableWithdrawalsGateway().triggerFullWithdrawals{
value: msg.value
}(
exitsData,
refundRecipient == address(0) ? msg.sender : refundRecipient,
VOLUNTARY_EXIT_TYPE_ID
);
}
/// @dev Additional method for non-sequential keys to save gas and decrease fee amount compared
/// to separate transactions.
/// @inheritdoc ICSEjector
function voluntaryEjectByArray(
uint256 nodeOperatorId,
uint256[] calldata keyIndices,
address refundRecipient
) external payable whenResumed {
_onlyNodeOperatorOwner(nodeOperatorId);
if (keyIndices.length == 0) {
revert NothingToEject();
}
uint256 totalDepositedKeys = MODULE.getNodeOperatorTotalDepositedKeys(
nodeOperatorId
);
ValidatorData[] memory exitsData = new ValidatorData[](
keyIndices.length
);
for (uint256 i = 0; i < keyIndices.length; i++) {
// A key must be deposited to prevent ejecting unvetted keys that can intersect with
// other modules.
if (keyIndices[i] >= totalDepositedKeys) {
revert SigningKeysInvalidOffset();
}
// A key must be non-withdrawn to restrict unlimited exit requests consuming sanity
// checker limits, although a deposited key can be requested to exit multiple times.
// But, it will eventually be withdrawn, so potentially malicious behaviour stops when
// there are no active keys available
if (MODULE.isValidatorWithdrawn(nodeOperatorId, keyIndices[i])) {
revert AlreadyWithdrawn();
}
bytes memory pubkey = MODULE.getSigningKeys(
nodeOperatorId,
keyIndices[i],
1
);
exitsData[i] = ValidatorData({
stakingModuleId: STAKING_MODULE_ID,
nodeOperatorId: nodeOperatorId,
pubkey: pubkey
});
}
// @dev This call might revert if the limits are exceeded on the protocol side.
triggerableWithdrawalsGateway().triggerFullWithdrawals{
value: msg.value
}(
exitsData,
refundRecipient == address(0) ? msg.sender : refundRecipient,
VOLUNTARY_EXIT_TYPE_ID
);
}
/// @inheritdoc ICSEjector
function ejectBadPerformer(
uint256 nodeOperatorId,
uint256 keyIndex,
address refundRecipient
) external payable whenResumed onlyStrikes {
// A key must be deposited to prevent ejecting unvetted keys that can intersect with
// other modules.
if (
keyIndex >= MODULE.getNodeOperatorTotalDepositedKeys(nodeOperatorId)
) {
revert SigningKeysInvalidOffset();
}
// A key must be non-withdrawn to restrict unlimited exit requests consuming sanity checker
// limits, although a deposited key can be requested to exit multiple times. But, it will
// eventually be withdrawn, so potentially malicious behaviour stops when there are no
// active keys available
if (MODULE.isValidatorWithdrawn(nodeOperatorId, keyIndex)) {
revert AlreadyWithdrawn();
}
ValidatorData[] memory exitsData = new ValidatorData[](1);
bytes memory pubkey = MODULE.getSigningKeys(
nodeOperatorId,
keyIndex,
1
);
exitsData[0] = ValidatorData({
stakingModuleId: STAKING_MODULE_ID,
nodeOperatorId: nodeOperatorId,
pubkey: pubkey
});
// @dev This call might revert if the limits are exceeded on the protocol side.
triggerableWithdrawalsGateway().triggerFullWithdrawals{
value: msg.value
}(exitsData, refundRecipient, STRIKES_EXIT_TYPE_ID);
}
/// @inheritdoc ICSEjector
function triggerableWithdrawalsGateway()
public
view
returns (ITriggerableWithdrawalsGateway)
{
return
ITriggerableWithdrawalsGateway(
MODULE.LIDO_LOCATOR().triggerableWithdrawalsGateway()
);
}
/// @dev Verifies that the sender is the owner of the node operator
function _onlyNodeOperatorOwner(uint256 nodeOperatorId) internal view {
address owner = MODULE.getNodeOperatorOwner(nodeOperatorId);
if (owner == address(0)) {
revert NodeOperatorDoesNotExist();
}
if (owner != msg.sender) {
revert SenderIsNotEligible();
}
}
function _onlyRecoverer() internal view override {
_checkRole(RECOVERER_ROLE);
}
}
"
},
"node_modules/@openzeppelin/contracts/access/extensions/AccessControlEnumerable.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/extensions/AccessControlEnumerable.sol)
pragma solidity ^0.8.20;
import {IAccessControlEnumerable} from "./IAccessControlEnumerable.sol";
import {AccessControl} from "../AccessControl.sol";
import {EnumerableSet} from "../../utils/structs/EnumerableSet.sol";
/**
* @dev Extension of {AccessControl} that allows enumerating the members of each role.
*/
abstract contract AccessControlEnumerable is IAccessControlEnumerable, AccessControl {
using EnumerableSet for EnumerableSet.AddressSet;
mapping(bytes32 role => EnumerableSet.AddressSet) private _roleMembers;
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return interfaceId == type(IAccessControlEnumerable).interfaceId || super.supportsInterface(interfaceId);
}
/**
* @dev Returns one of the accounts that have `role`. `index` must be a
* value between 0 and {getRoleMemberCount}, non-inclusive.
*
* Role bearers are not sorted in any particular way, and their ordering may
* change at any point.
*
* WARNING: When using {getRoleMember} and {getRoleMemberCount}, make sure
* you perform all queries on the same block. See the following
* https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forum post]
* for more information.
*/
function getRoleMember(bytes32 role, uint256 index) public view virtual returns (address) {
return _roleMembers[role].at(index);
}
/**
* @dev Returns the number of accounts that have `role`. Can be used
* together with {getRoleMember} to enumerate all bearers of a role.
*/
function getRoleMemberCount(bytes32 role) public view virtual returns (uint256) {
return _roleMembers[role].length();
}
/**
* @dev Overload {AccessControl-_grantRole} to track enumerable memberships
*/
function _grantRole(bytes32 role, address account) internal virtual override returns (bool) {
bool granted = super._grantRole(role, account);
if (granted) {
_roleMembers[role].add(account);
}
return granted;
}
/**
* @dev Overload {AccessControl-_revokeRole} to track enumerable memberships
*/
function _revokeRole(bytes32 role, address account) internal virtual override returns (bool) {
bool revoked = super._revokeRole(role, account);
if (revoked) {
_roleMembers[role].remove(account);
}
return revoked;
}
}
"
},
"src/abstract/AssetRecoverer.sol": {
"content": "// SPDX-FileCopyrightText: 2025 Lido <info@lido.fi>
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.24;
import { AssetRecovererLib } from "../lib/AssetRecovererLib.sol";
/// @title AssetRecoverer
/// @dev Abstract contract providing mechanisms for recovering various asset types (ETH, ERC20, ERC721, ERC1155) from a contract.
/// This contract is designed to allow asset recovery by an authorized address by implementing the onlyRecovererRole guardian
/// @notice Assets can be sent only to the `msg.sender`
abstract contract AssetRecoverer {
/// @dev Allows sender to recover Ether held by the contract
/// Emits an EtherRecovered event upon success
function recoverEther() external {
_onlyRecoverer();
AssetRecovererLib.recoverEther();
}
/// @dev Allows sender to recover ERC20 tokens held by the contract
/// @param token The address of the ERC20 token to recover
/// @param amount The amount of the ERC20 token to recover
/// Emits an ERC20Recovered event upon success
/// Optionally, the inheriting contract can override this function to add additional restrictions
function recoverERC20(address token, uint256 amount) external virtual {
_onlyRecoverer();
AssetRecovererLib.recoverERC20(token, amount);
}
/// @dev Allows sender to recover ERC721 tokens held by the contract
/// @param token The address of the ERC721 token to recover
/// @param tokenId The token ID of the ERC721 token to recover
/// Emits an ERC721Recovered event upon success
function recoverERC721(address token, uint256 tokenId) external {
_onlyRecoverer();
AssetRecovererLib.recoverERC721(token, tokenId);
}
/// @dev Allows sender to recover ERC1155 tokens held by the contract.
/// @param token The address of the ERC1155 token to recover.
/// @param tokenId The token ID of the ERC1155 token to recover.
/// Emits an ERC1155Recovered event upon success.
function recoverERC1155(address token, uint256 tokenId) external {
_onlyRecoverer();
AssetRecovererLib.recoverERC1155(token, tokenId);
}
/// @dev Guardian to restrict access to the recover methods.
/// Should be implemented by the inheriting contract
function _onlyRecoverer() internal view virtual;
}
"
},
"src/abstract/ExitTypes.sol": {
"content": "// SPDX-FileCopyrightText: 2025 Lido <info@lido.fi>
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.24;
import { IExitTypes } from "../interfaces/IExitTypes.sol";
abstract contract ExitTypes is IExitTypes {
uint8 public constant VOLUNTARY_EXIT_TYPE_ID = 0;
uint8 public constant STRIKES_EXIT_TYPE_ID = 1;
}
"
},
"src/lib/utils/PausableUntil.sol": {
"content": "// SPDX-FileCopyrightText: 2025 Lido <info@lido.fi>
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.24;
import { UnstructuredStorage } from "../UnstructuredStorage.sol";
contract PausableUntil {
using UnstructuredStorage for bytes32;
/// Contract resume/pause control storage slot
bytes32 internal constant RESUME_SINCE_TIMESTAMP_POSITION =
keccak256("lido.PausableUntil.resumeSinceTimestamp");
/// Special value for the infinite pause
uint256 public constant PAUSE_INFINITELY = type(uint256).max;
/// @notice Emitted when paused by the `pauseFor` or `pauseUntil` call
event Paused(uint256 duration);
/// @notice Emitted when resumed by the `resume` call
event Resumed();
error ZeroPauseDuration();
error PausedExpected();
error ResumedExpected();
error PauseUntilMustBeInFuture();
/// @notice Reverts when resumed
modifier whenPaused() {
_checkPaused();
_;
}
/// @notice Reverts when paused
modifier whenResumed() {
_checkResumed();
_;
}
/// @notice Returns one of:
/// - PAUSE_INFINITELY if paused infinitely returns
/// - first second when get contract get resumed if paused for specific duration
/// - some timestamp in past if not paused
function getResumeSinceTimestamp() external view returns (uint256) {
return RESUME_SINCE_TIMESTAMP_POSITION.getStorageUint256();
}
/// @notice Returns whether the contract is paused
function isPaused() public view returns (bool) {
return
block.timestamp <
RESUME_SINCE_TIMESTAMP_POSITION.getStorageUint256();
}
function _resume() internal {
_checkPaused();
RESUME_SINCE_TIMESTAMP_POSITION.setStorageUint256(block.timestamp);
emit Resumed();
}
function _pauseFor(uint256 duration) internal {
_checkResumed();
if (duration == 0) {
revert ZeroPauseDuration();
}
uint256 resumeSince;
if (duration == PAUSE_INFINITELY) {
resumeSince = PAUSE_INFINITELY;
} else {
resumeSince = block.timestamp + duration;
}
_setPausedState(resumeSince);
}
function _pauseUntil(uint256 pauseUntilInclusive) internal {
_checkResumed();
if (pauseUntilInclusive < block.timestamp) {
revert PauseUntilMustBeInFuture();
}
uint256 resumeSince;
if (pauseUntilInclusive != PAUSE_INFINITELY) {
resumeSince = pauseUntilInclusive + 1;
} else {
resumeSince = PAUSE_INFINITELY;
}
_setPausedState(resumeSince);
}
function _setPausedState(uint256 resumeSince) internal {
RESUME_SINCE_TIMESTAMP_POSITION.setStorageUint256(resumeSince);
if (resumeSince == PAUSE_INFINITELY) {
emit Paused(PAUSE_INFINITELY);
} else {
emit Paused(resumeSince - block.timestamp);
}
}
function _checkPaused() internal view {
if (!isPaused()) {
revert PausedExpected();
}
}
function _checkResumed() internal view {
if (isPaused()) {
revert ResumedExpected();
}
}
}
"
},
"src/lib/SigningKeys.sol": {
"content": "// SPDX-FileCopyrightText: 2025 Lido <info@lido.fi>
// SPDX-License-Identifier: GPL-3.0
// See contracts/COMPILERS.md
pragma solidity 0.8.24;
import { IStakingModule } from "../interfaces/IStakingModule.sol";
/// @title Library for manage operator keys in storage
/// @author KRogLA
library SigningKeys {
using SigningKeys for bytes32;
bytes32 internal constant SIGNING_KEYS_POSITION =
keccak256("lido.CommunityStakingModule.signingKeysPosition");
uint64 internal constant PUBKEY_LENGTH = 48;
uint64 internal constant SIGNATURE_LENGTH = 96;
error InvalidKeysCount();
error InvalidLength();
error EmptyKey();
/// @dev store operator keys to storage
/// @param nodeOperatorId operator id
/// @param startIndex start index
/// @param keysCount keys count to load
/// @param pubkeys keys buffer to read from
/// @param signatures signatures buffer to read from
/// @return new total keys count
function saveKeysSigs(
uint256 nodeOperatorId,
uint256 startIndex,
uint256 keysCount,
bytes calldata pubkeys,
bytes calldata signatures
) internal returns (uint256) {
if (keysCount == 0 || startIndex + keysCount > type(uint32).max) {
revert InvalidKeysCount();
}
unchecked {
if (
pubkeys.length != keysCount * PUBKEY_LENGTH ||
signatures.length != keysCount * SIGNATURE_LENGTH
) {
revert InvalidLength();
}
}
uint256 curOffset;
bool isEmpty;
bytes memory tmpKey = new bytes(48);
for (uint256 i; i < keysCount; ) {
curOffset = SIGNING_KEYS_POSITION.getKeyOffset(
nodeOperatorId,
startIndex
);
assembly {
let _ofs := add(pubkeys.offset, mul(i, 48)) // PUBKEY_LENGTH = 48
let _part1 := calldataload(_ofs) // bytes 0..31
let _part2 := calldataload(add(_ofs, 0x10)) // bytes 16..47
isEmpty := iszero(or(_part1, _part2))
mstore(add(tmpKey, 0x30), _part2) // store 2nd part first
mstore(add(tmpKey, 0x20), _part1) // store 1st part with overwrite bytes 16-31
}
if (isEmpty) {
revert EmptyKey();
}
assembly {
// store key
sstore(curOffset, mload(add(tmpKey, 0x20))) // store bytes 0..31
sstore(add(curOffset, 1), shl(128, mload(add(tmpKey, 0x30)))) // store bytes 32..47
// store signature
let _ofs := add(signatures.offset, mul(i, 96)) // SIGNATURE_LENGTH = 96
sstore(add(curOffset, 2), calldataload(_ofs))
sstore(add(curOffset, 3), calldataload(add(_ofs, 0x20)))
sstore(add(curOffset, 4), calldataload(add(_ofs, 0x40)))
i := add(i, 1)
startIndex := add(startIndex, 1)
}
emit IStakingModule.SigningKeyAdded(nodeOperatorId, tmpKey);
}
return startIndex;
}
/// @dev remove operator keys from storage
/// @param nodeOperatorId operator id
/// @param startIndex start index
/// @param keysCount keys count to load
/// @param totalKeysCount current total keys count for operator
/// @return new total keys count
function removeKeysSigs(
uint256 nodeOperatorId,
uint256 startIndex,
uint256 keysCount,
uint256 totalKeysCount
) internal returns (uint256) {
if (
keysCount == 0 ||
startIndex + keysCount > totalKeysCount ||
totalKeysCount > type(uint32).max
) {
revert InvalidKeysCount();
}
uint256 curOffset;
uint256 lastOffset;
uint256 j;
bytes memory tmpKey = new bytes(48);
// removing from the last index
unchecked {
for (uint256 i = startIndex + keysCount; i > startIndex; ) {
curOffset = SIGNING_KEYS_POSITION.getKeyOffset(
nodeOperatorId,
i - 1
);
assembly {
// read key
mstore(
add(tmpKey, 0x30),
shr(128, sload(add(curOffset, 1)))
) // bytes 16..47
mstore(add(tmpKey, 0x20), sload(curOffset)) // bytes 0..31
}
if (i < totalKeysCount) {
lastOffset = SIGNING_KEYS_POSITION.getKeyOffset(
nodeOperatorId,
totalKeysCount - 1
);
// move last key to deleted key index
for (j = 0; j < 5; ) {
// load 160 bytes (5 slots) containing key and signature
assembly {
sstore(add(curOffset, j), sload(add(lastOffset, j)))
j := add(j, 1)
}
}
curOffset = lastOffset;
}
// clear storage
for (j = 0; j < 5; ) {
assembly {
sstore(add(curOffset, j), 0)
j := add(j, 1)
}
}
assembly {
totalKeysCount := sub(totalKeysCount, 1)
i := sub(i, 1)
}
emit IStakingModule.SigningKeyRemoved(nodeOperatorId, tmpKey);
}
}
return totalKeysCount;
}
/// @dev Load operator's keys and signatures from the storage to the given in-memory arrays.
/// @dev The function doesn't check for `pubkeys` and `signatures` out of boundaries access.
/// @param nodeOperatorId operator id
/// @param startIndex start index
/// @param keysCount keys count to load
/// @param pubkeys preallocated keys buffer to read in
/// @param signatures preallocated signatures buffer to read in
/// @param bufOffset start offset in `pubkeys`/`signatures` buffer to place values (in number of keys)
function loadKeysSigs(
uint256 nodeOperatorId,
uint256 startIndex,
uint256 keysCount,
bytes memory pubkeys,
bytes memory signatures,
uint256 bufOffset
) internal view {
uint256 curOffset;
for (uint256 i; i < keysCount; ) {
curOffset = SIGNING_KEYS_POSITION.getKeyOffset(
nodeOperatorId,
startIndex + i
);
assembly {
// read key
let _ofs := add(add(pubkeys, 0x20), mul(add(bufOffset, i), 48)) // PUBKEY_LENGTH = 48
mstore(add(_ofs, 0x10), shr(128, sload(add(curOffset, 1)))) // bytes 16..47
mstore(_ofs, sload(curOffset)) // bytes 0..31
// store signature
_ofs := add(add(signatures, 0x20), mul(add(bufOffset, i), 96)) // SIGNATURE_LENGTH = 96
mstore(_ofs, sload(add(curOffset, 2)))
mstore(add(_ofs, 0x20), sload(add(curOffset, 3)))
mstore(add(_ofs, 0x40), sload(add(curOffset, 4)))
i := add(i, 1)
}
}
}
function loadKeys(
uint256 nodeOperatorId,
uint256 startIndex,
uint256 keysCount
) internal view returns (bytes memory pubkeys) {
uint256 curOffset;
pubkeys = new bytes(keysCount * PUBKEY_LENGTH);
for (uint256 i; i < keysCount; ) {
curOffset = SIGNING_KEYS_POSITION.getKeyOffset(
nodeOperatorId,
startIndex + i
);
assembly {
// read key
let offset := add(add(pubkeys, 0x20), mul(i, 48)) // PUBKEY_LENGTH = 48
mstore(add(offset, 0x10), shr(128, sload(add(curOffset, 1)))) // bytes 16..47
mstore(offset, sload(curOffset)) // bytes 0..31
i := add(i, 1)
}
}
}
function initKeysSigsBuf(
uint256 count
) internal pure returns (bytes memory, bytes memory) {
return (
new bytes(count * PUBKEY_LENGTH),
new bytes(count * SIGNATURE_LENGTH)
);
}
function getKeyOffset(
bytes32 position,
uint256 nodeOperatorId,
uint256 keyIndex
) internal pure returns (uint256) {
return
uint256(
keccak256(abi.encodePacked(position, nodeOperatorId, keyIndex))
);
}
}
"
},
"src/interfaces/ICSEjector.sol": {
"content": "// SPDX-FileCopyrightText: 2025 Lido <info@lido.fi>
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.24;
import { ICSAccounting } from "./ICSAccounting.sol";
import { ICSModule } from "./ICSModule.sol";
import { ICSParametersRegistry } from "./ICSParametersRegistry.sol";
import { IExitTypes } from "./IExitTypes.sol";
import { ITriggerableWithdrawalsGateway } from "./ITriggerableWithdrawalsGateway.sol";
interface ICSEjector is IExitTypes {
error SigningKeysInvalidOffset();
error AlreadyWithdrawn();
error ZeroAdminAddress();
error ZeroModuleAddress();
error ZeroStrikesAddress();
error NodeOperatorDoesNotExist();
error SenderIsNotEligible();
error SenderIsNotStrikes();
error NothingToEject();
function PAUSE_ROLE() external view returns (bytes32);
function RESUME_ROLE() external view returns (bytes32);
function RECOVERER_ROLE() external view returns (bytes32);
function STAKING_MODULE_ID() external view returns (uint256);
function MODULE() external view returns (ICSModule);
function STRIKES() external view returns (address);
/// @notice Pause ejection methods calls
/// @param duration Duration of the pause in seconds
function pauseFor(uint256 duration) external;
/// @notice Resume ejection methods calls
function resume() external;
/// @notice Withdraw the validator key from the Node Operator
/// @notice Called by the node operator
/// @param nodeOperatorId ID of the Node Operator
/// @param startFrom Index of the first key to withdraw
/// @param keysCount Number of keys to withdraw
/// @param refundRecipient Address to send the refund to
function voluntaryEject(
uint256 nodeOperatorId,
uint256 startFrom,
uint256 keysCount,
address refundRecipient
) external payable;
/// @notice Withdraw the validator key from the Node Operator
/// @notice Called by the node operator
/// @param nodeOperatorId ID of the Node Operator
/// @param keyIndices Array of indices of the keys to withdraw
/// @param refundRecipient Address to send the refund to
function voluntaryEjectByArray(
uint256 nodeOperatorId,
uint256[] calldata keyIndices,
address refundRecipient
) external payable;
/// @notice Eject Node Operator's key as a bad performer
/// @notice Called by the `CSStrikes` contract.
/// See `CSStrikes.processBadPerformanceProof` to use this method permissionless
/// @param nodeOperatorId ID of the Node Operator
/// @param keyIndex index of deposited key to eject
/// @param refundRecipient Address to send the refund to
function ejectBadPerformer(
uint256 nodeOperatorId,
uint256 keyIndex,
address refundRecipient
) external payable;
/// @notice TriggerableWithdrawalsGateway implementation used by the contract.
function triggerableWithdrawalsGateway()
external
view
returns (ITriggerableWithdrawalsGateway);
}
"
},
"src/interfaces/ICSModule.sol": {
"content": "// SPDX-FileCopyrightText: 2025 Lido <info@lido.fi>
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.24;
import { IStakingModule } from "./IStakingModule.sol";
import { ICSAccounting } from "./ICSAccounting.sol";
import { IQueueLib } from "../lib/QueueLib.sol";
import { INOAddresses } from "../lib/NOAddresses.sol";
import { IAssetRecovererLib } from "../lib/AssetRecovererLib.sol";
import { Batch } from "../lib/QueueLib.sol";
import { ILidoLocator } from "./ILidoLocator.sol";
import { IStETH } from "./IStETH.sol";
import { ICSParametersRegistry } from "./ICSParametersRegistry.sol";
import { ICSExitPenalties } from "./ICSExitPenalties.sol";
struct NodeOperator {
// All the counters below are used together e.g. in the _updateDepositableValidatorsCount
/* 1 */ uint32 totalAddedKeys; // @dev increased and decreased when removed
/* 1 */ uint32 totalWithdrawnKeys; // @dev only increased
/* 1 */ uint32 totalDepositedKeys; // @dev only increased
/* 1 */ uint32 totalVettedKeys; // @dev both increased and decreased
/* 1 */ uint32 stuckValidatorsCount; // @dev both increased and decreased
/* 1 */ uint32 depositableValidatorsCount; // @dev any value
/* 1 */ uint32 targetLimit;
/* 1 */ uint8 targetLimitMode;
/* 2 */ uint32 totalExitedKeys; // @dev only increased except for the unsafe updates
/* 2 */ uint32 enqueuedCount; // Tracks how many places are occupied by the node operator's keys in the queue.
/* 2 */ address managerAddress;
/* 3 */ address proposedManagerAddress;
/* 4 */ address rewardAddress;
/* 5 */ address proposedRewardAddress;
/* 5 */ bool extendedManagerPermissions;
/* 5 */ bool usedPriorityQueue;
}
struct NodeOperatorManagementProperties {
address managerAddress;
address rewardAddress;
bool extendedManagerPermissions;
}
struct ValidatorWithdrawalInfo {
uint256 nodeOperatorId; // @dev ID of the Node Operator
uint256 keyIndex; // @dev Index of the withdrawn key in the Node Operator's keys storage
uint256 amount; // @dev Amount of withdrawn ETH in wei
}
/// @title Lido's Community Staking Module interface
interface ICSModule is
IQueueLib,
INOAddresses,
IAssetRecovererLib,
IStakingModule
{
error CannotAddKeys();
error NodeOperatorDoesNotExist();
error SenderIsNotEligible();
error InvalidVetKeysPointer();
error ExitedKeysHigherThanTotalDeposited();
error ExitedKeysDecrease();
error InvalidInput();
error NotEnoughKeys();
error PriorityQueueAlreadyUsed();
error NotEligibleForPriorityQueue();
error PriorityQueueMaxDepositsUsed();
error NoQueuedKeysToMigrate();
error KeysLimitExceeded();
error SigningKeysInvalidOffset();
error InvalidAmount();
error ZeroLocatorAddress();
error ZeroAccountingAddress();
error ZeroExitPenaltiesAddress();
error ZeroAdminAddress();
error ZeroSenderAddress();
error ZeroParametersRegistryAddress();
event NodeOperatorAdded(
uint256 indexed nodeOperatorId,
address indexed managerAddress,
address indexed rewardAddress,
bool extendedManagerPermissions
);
event ReferrerSet(uint256 indexed nodeOperatorId, address indexed referrer);
event DepositableSigningKeysCountChanged(
uint256 indexed nodeOperatorId,
uint256 depositableKeysCount
);
event VettedSigningKeysCountChanged(
uint256 indexed nodeOperatorId,
uint256 vettedKeysCount
);
event VettedSigningKeysCountDecreased(uint256 indexed nodeOperatorId);
event DepositedSigningKeysCountChanged(
uint256 indexed nodeOperatorId,
uint256 depositedKeysCount
);
event ExitedSigningKeysCountChanged(
uint256 indexed nodeOperatorId,
uint256 exitedKeysCount
);
event TotalSigningKeysCountChanged(
uint256 indexed nodeOperatorId,
uint256 totalKeysCount
);
event TargetValidatorsCountChanged(
uint256 indexed nodeOperatorId,
uint256 targetLimitMode,
uint256 targetValidatorsCount
);
event WithdrawalSubmitted(
uint256 indexed nodeOperatorId,
uint256 keyIndex,
uint256 amount,
bytes pubkey
);
event BatchEnqueued(
uint256 indexed queuePriority,
uint256 indexed nodeOperatorId,
uint256 count
);
event KeyRemovalChargeApplied(uint256 indexed nodeOperatorId);
event ELRewardsStealingPenaltyReported(
uint256 indexed nodeOperatorId,
bytes32 proposedBlockHash,
uint256 stolenAmount
);
event ELRewardsStealingPenaltyCancelled(
uint256 indexed nodeOperatorId,
uint256 amount
);
event ELRewardsStealingPenaltyCompensated(
uint256 indexed nodeOperatorId,
uint256 amount
);
event ELRewardsStealingPenaltySettled(uint256 indexed nodeOperatorId);
function PAUSE_ROLE() external view returns (bytes32);
function RESUME_ROLE() external view returns (bytes32);
function STAKING_ROUTER_ROLE() external view returns (bytes32);
function REPORT_EL_REWARDS_STEALING_PENALTY_ROLE()
external
view
returns (bytes32);
function SETTLE_EL_REWARDS_STEALING_PENALTY_ROLE()
external
view
returns (bytes32);
function VERIFIER_ROLE() external view returns (bytes32);
function RECOVERER_ROLE() external view returns (bytes32);
function CREATE_NODE_OPERATOR_ROLE() external view returns (bytes32);
function DEPOSIT_SIZE() external view returns (uint256);
function LIDO_LOCATOR() external view returns (ILidoLocator);
function STETH() external view returns (IStETH);
function PARAMETERS_REGISTRY()
external
view
returns (ICSParametersRegistry);
function ACCOUNTING() external view returns (ICSAccounting);
function EXIT_PENALTIES() external view returns (ICSExitPenalties);
function FEE_DISTRIBUTOR() external view returns (address);
function QUEUE_LOWEST_PRIORITY() external view returns (uint256);
function QUEUE_LEGACY_PRIORITY() external view returns (uint256);
/// @notice Returns the address of the accounting contract
function accounting() external view returns (ICSAccounting);
/// @notice Pause creation of the Node Operators and keys upload for `duration` seconds.
/// Existing NO management and reward claims are still available.
/// To pause reward claims use pause method on CSAccounting
/// @param duration Duration of the pause in seconds
function pauseFor(uint256 duration) external;
/// @notice Resume creation of the Node Operators and keys upload
function resume() external;
/// @notice Returns the initialized version of the contract
function getInitializedVersion() external view returns (uint64);
/// @notice Permissioned method to add a new Node Operator
/// Should be called by `*Gate.sol` contracts. See `PermissionlessGate.sol` and `VettedGate.sol` for examples
/// @param from Sender address. Initial sender address to be used as a default manager and reward addresses.
/// Gates must pass the correct address in order to specify which address should be the owner of the Node Operator.
/// @param managementProperties Optional. Management properties to be used for the Node Operator.
/// managerAddress: Used as `managerAddress` for the Node Operator. If not passed `from` will be used.
/// rewardAddress: Used as `rewardAddress` for the Node Operator. If not passed `from` will be used.
/// extendedManagerPermissions: Flag indicating that `managerAddress` will be able to change `rewardAddress`.
/// If set to true `resetNodeOperatorManagerAddress` method will be disabled
/// @param referrer Optional. Referrer address. Should be passed when Node Operator is created using partners integration
function createNodeOperator(
address from,
NodeOperatorManagementProperties memory managementProperties,
address referrer
) external returns (uint256 nodeOperatorId);
/// @notice Add new keys to the existing Node Operator using ETH as a bond
/// @param from Sender address. Commonly equals to `msg.sender` except for the case of Node Operator creation by `*Gate.sol` contracts
/// @param nodeOperatorId ID of the Node Operator
/// @param keysCount Signing keys count
/// @param publicKeys Public keys to submit
/// @param signatures Signatures of `(deposit_message_root, domain)` tuples
/// https://github.com/ethereum/consensus-specs/blob/v1.4.0/specs/phase0/beacon-chain.md#signingdata
function addValidatorKeysETH(
address from,
uint256 nodeOperatorId,
uint256 keysCount,
bytes memory publicKeys,
bytes memory signatures
) external payable;
/// @notice Add new keys to the existing Node Operator using stETH as a bond
/// @notice Due to the stETH rounding issue make sure to make approval or sign permit with extra 10 wei to avoid revert
/// @param from Sender address. Commonly equals to `msg.sender` except for the case of Node Operator creation by `*Gate.sol` contracts
/// @param nodeOperatorId ID of the Node Operator
/// @param keysCount Signing keys count
/// @param publicKeys Public keys to submit
/// @param signatures Signatures of `(deposit_message_root, domain)` tuples
/// https://github.com/ethereum/consensus-specs/blob/v1.4.0/specs/phase0/beacon-chain.md#signingdata
/// @param permit Optional. Permit to use stETH as bond
function addValidatorKeysStETH(
address from,
uint256 nodeOperatorId,
uint256 keysCount,
bytes memory publicKeys,
bytes memory signatures,
ICSAccounting.PermitInput memory permit
) external;
/// @notice Add new keys to the existing Node Operator using wstETH as a bond
/// @notice Due to the stETH rounding issue make sure to make approval or sign permit with extra 10 wei to avoid revert
/// @param from Sender address. Commonly equals to `msg.sender` except for the case of Node Operator creation by `*Gate.sol` contracts
/// @param nodeOperatorId ID of the Node Operator
/// @param keysCount Signing keys count
/// @param publicKeys Public keys to submit
/// @param signatures Signatures of `(deposit_message_root, domain)` tuples
/// https://github.com/ethereum/consensus-specs/blob/v1.4.0/specs/phase0/beacon-chain.md#signingdata
/// @param permit Optional. Permit to use wstETH as bond
function addValidatorKeysWstETH(
address from,
uint256 nodeOperatorId,
uint256 keysCount,
bytes memory publicKeys,
bytes memory signatures,
ICSAccounting.PermitInput memory permit
) external;
/// @notice Report EL rewards stealing for the given Node Operator
/// @notice The final locked amount will be equal to the stolen funds plus EL stealing additional fine
/// @param nodeOperatorId ID of the Node Operator
/// @param blockHash Execution layer block hash of the proposed block with EL rewards stealing
/// @param amount Amount of stolen EL rewards in ETH
function reportELRewardsStealingPenalty(
uint256 nodeOperatorId,
bytes32 blockHash,
uint256 amount
) external;
/// @notice Compensate EL rewards stealing penalty for the given Node Operator to prevent further validator exits
/// @dev Can only be called by the Node Operator manager
/// @param nodeOperatorId ID of the Node Operator
function compensateELRewardsStealingPenalty(
uint256 nodeOperatorId
) external payable;
/// @notice Cancel previously reported and not settled EL rewards stealing penalty for the given Node Operator
/// @notice The funds will be unlocked
/// @param nodeOperatorId ID of the Node Operator
/// @param amount Amount of penalty to cancel
function cancelELRewardsStealingPenalty(
uint256 nodeOperatorId,
uint256 amount
) external;
/// @notice Settle locked bond for the given Node Operators
/// @dev SETTLE_EL_REWARDS_STEALING_PENALTY_ROLE role is expected to be assigned to Easy Track
/// @param nodeOperatorIds IDs of the Node Operators
function settleELRewardsStealingPenalty(
uint256[] memory nodeOperatorIds
) external;
/// @notice Propose a new manager address for the Node Operator
/// @param nodeOperatorId ID of the Node Operator
/// @param proposedAddress Proposed manager address
function proposeNodeOperatorManagerAddressChange(
uint256 nodeOperatorId,
address proposedAddress
) external;
/// @notice Confirm a new manager address for the Node Operator.
/// Should be called from the currently proposed address
/// @param nodeOperatorId ID of the Node Operator
function confirmNodeOperatorManagerAddressChange(
uint256 nodeOperatorId
) external;
/// @notice Reset the manager address to the reward address.
/// Should be called from the reward address
/// @param nodeOperatorId ID of the Node Operator
function resetNodeOperatorManagerAddress(uint256 nodeOperatorId) external;
/// @notice Propose a new reward address for the Node Operator
/// @param nodeOperatorId ID of the Node Operator
/// @param proposedAddress Proposed reward address
function proposeNodeOperatorRewardAddressChange(
uint256 nodeOperatorId,
address proposedAddress
) external;
/// @notice Confirm a new reward address for the Node Operator.
/// Should be called from the currently proposed address
/// @param nodeOperatorId ID of the Node Operator
function confirmNodeOperatorRewardAddressChange(
uint256 nodeOperatorId
) external;
/// @notice Change rewardAddress if extendedManagerPermissions is enabled for the Node Operator
/// @param nodeOperatorId ID of the Node Operator
/// @param newAddress Proposed reward address
function changeNodeOperatorRewardAddress(
uint256 nodeOperatorId,
address newAddress
) external;
/// @notice Get the pointers to the head and tail of queue with the given priority.
/// @param queuePriority Priority of the queue to get the pointers.
/// @return head Pointer to the head of the queue.
/// @return tail Pointer to the tail of the queue.
function depositQueuePointers(
uint256 queuePriority
) external view returns (uint128 head, uint128 tail);
/// @notice Get the deposit queue item by an index
/// @param queuePriority Priority of the queue to get an item from
/// @param index Index of a queue item
/// @return Deposit queue item from the priority queue
function depositQueueItem(
uint256 queuePriority,
uint128 index
) external view returns (Batch);
/// @notice Clean the deposit queue from batches with no depositable keys
/// @dev Use **eth_call** to check how many items will be removed
/// @param maxItems How many queue items to review
/// @return removed Count of batches to be removed by visiting `maxItems` batches
/// @return lastRemovedAtDepth The value to use as `maxItems` to remove `removed` batches if the static call of the method was used
function cleanDepositQueue(
uint256 maxItems
) external returns (uint256 removed, uint256 lastRemovedAtDepth);
/// @notice Update depositable validators data and enqueue all unqueued keys for the given Node Operator.
/// Unqueued stands for vetted but not enqueued keys.
/// @dev The following rules are applied:
/// - Unbonded keys can not be depositable
/// - Unvetted keys can not be depositable
/// - Depositable keys count should respect targetLimit value
/// @param nodeOperatorId ID of the Node Operator
function updateDepositableValidatorsCount(uint256 nodeOperatorId) external;
/// Performs a one-time migration of allocated seats from the legacy or default queue to a priority queue
/// for an eligible node operator. This is possible, e.g., in the following scenario: A node
/// operator uploaded keys before CSM v2 and have no deposits due to a long queue.
/// After the CSM v2 release, the node operator has claimed the ICS or other priority node operator type.
/// This node operator type gives the node operator the ability to get several deposits through
/// the priority queue. So, by calling the migration method, the node operator can obtain seats
/// in the priority queue, even though they already have seats in the legacy queue.
/// The method can also be used by the node operators who joined CSM v2 permissionlessly after the release
/// and had their node operator type upgraded to ICS or another priority type.
/// The method does not remove the old queue items. Hence, the node operator can upload additional keys that
/// will take the place of the migrated keys in the original queue.
/// @param nodeOperatorId ID of the Node Operator
function migrateToPriorityQueue(uint256 nodeOperatorId) external;
/// @notice Get Node Operator info
/// @param nodeOperatorId ID of the Node Operator
/// @return Node Operator info
function getNodeOperator(
uint256 nodeOperatorId
) external view returns (NodeOperator memory);
/// @notice Get Node Operator management properties
/// @param nodeOperatorId ID of the Node Operator
/// @return Node Operator management properties
function getNodeOperatorManagementProperties(
uint256 nodeOperatorId
) external view returns (NodeOperatorManagementProperties memory);
/// @notice Get Node Operator owner. Owner is manager address if `extendedManagerPermissions` is enabled and reward address otherwise
/// @param nodeOperatorId ID of the Node Operator
/// @return Node Operator owner
function getNodeOperatorOwner(
uint256 nodeOperatorId
) external view returns (address);
/// @notice Get Node Operator non-withdrawn keys
/// @param nodeOperatorId ID of the Node Operator
/// @return Non-withdrawn keys count
function getNodeOperatorNonWithdrawnKeys(
uint256 nodeOperatorId
) external view returns (uint256);
/// @notice Get Node Operator total deposited keys
/// @param nodeOperatorId ID of the Node Operator
/// @return Total deposited keys count
function getNodeOperatorTotalDepositedKeys(
uint256 nodeOperatorId
) external view returns (uint256);
/// @notice Get Node Operator signing keys
/// @param nodeOperatorId ID of the Node Operator
/// @param startIndex Index of the first key
/// @param keysCount Count of keys to get
/// @return Signing keys
function getSigningKeys(
uint256 nodeOperatorId,
uint256 startIndex,
uint256 keysCount
) external view returns (bytes memory);
/// @notice Get Node Operator signing keys with signatures
/// @param nodeOperatorId ID of the Node Operator
/// @param startIndex Index of the first key
/// @param keysCount Count of keys to get
/// @return keys Signing keys
/// @return signatures Signatures of `(deposit_message_root, domain)` tuples
/// https://github.com/ethereum/consensus-specs/blob/v1.4.0/specs/phase0/beacon-chain.md#signingdata
function getSigningKeysWithSignatures(
uint256 nodeOperatorId,
uint256 startIndex,
uint256 keysCount
) external view returns (bytes memory keys, bytes memory signatures);
/// @notice Report Node Operator's keys as withdrawn and settle withdrawn amount
/// @notice Called by `CSVerifier` contract.
/// See `CSVerifier.processWithdrawalProof` to use this method permissionless
/// @param withdrawalsInfo An array for the validator withdrawals info structs
function submitWithdrawals(
ValidatorWithdrawalInfo[] calldata withdrawalsInfo
) external;
/// @notice Check if the given Node Operator's key is reported as withdrawn
/// @param nodeOperatorId ID of the Node Operator
/// @param keyIndex index of the key to check
/// @return Is validator reported as withdrawn or not
function isValidatorWithdrawn(
uint256 nodeOperatorId,
uint256 keyIndex
) external view returns (bool);
/// @notice Remove keys for the Node Operator and confiscate removal charge for each deleted key
/// This method is a part of the Optimistic Vetting scheme. After key deletion `totalVettedKeys`
/// is set equal to `totalAddedKeys`. If invalid keys are not removed, the unvetting process will be repeated
/// and `decreaseVettedSigningKeysCount` will be called by StakingRouter.
/// @param nodeOperatorId ID of the Node Operator
/// @param startIndex Index of the first key
/// @param keysCount Keys count to delete
function removeKeys(
uint256 nodeOperatorId,
uint256 startIndex,
uint256 keysCount
) external;
}
"
},
"src/interfaces/ITriggerableWithdrawalsGateway.sol": {
"content": "// SPDX-FileCopyrightText: 2025 Lido <info@lido.fi>
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.24;
struct ValidatorData {
uint256 stakingModuleId;
uint256 nodeOperatorId;
bytes pubkey;
}
interface ITriggerableWithdrawalsGateway {
/**
* @dev Submits Triggerable Withdrawal Requests to the Withdrawal Vault as full withdrawal requests
* for the specified validator public keys.
*
* @param triggerableExitsData An array of `ValidatorData` structs, each representing a validator
* for which a withdrawal request will be submitted. Each entry includes:
* - `stakingModuleId`: ID of the staking module.
* - `nodeOperatorId`: ID of the node operator.
* - `pubkey`: Validator public key, 48 bytes length.
* @param refundRecipient The address that will receive any excess ETH sent for fees.
* @param exitType A parameter indicating the type of exit, passed to the Staking Module.
*
* Emits `TriggerableExitRequest` event for each validator in list.
*
* @notice Reverts if:
* - The caller does not have the `ADD_FULL_WITHDRAWAL_REQUEST_ROLE`
* - The total fee value sent is insufficient to cover all provided TW requests.
* - There is not enough limit quota left in the current frame to process all requests.
*/
function triggerFullWithdrawals(
ValidatorData[] calldata triggerableExitsData,
address refundRecipient,
uint256 exitType
) external payable;
}
"
},
"node_modules/@openzeppelin/contracts/access/extensions/IAccessControlEnumerable.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/extensions/IAccessControlEnumerable.sol)
pragma solidity ^0.8.20;
import {IAccessControl} from "../IAccessControl.sol";
/**
* @dev External interface of AccessControlEnumerable declared to support ERC165 detection.
*/
interface IAccessControlEnumerable is IAccessControl {
/**
* @dev Returns one of the accounts that have `role`. `index` must be a
* value between 0 and {getRoleMemberCount}, non-inclusive.
*
* Role bearers are not sorted in any particular way, and their ordering may
* change at any point.
*
* WARNING: When using {getRoleMember} and {getRoleMemberCount}, make sure
* you perform all queries on the same block. See the following
* https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forum post]
* for more information.
*/
function getRoleMember(bytes32 role, uint256 index) external view returns (address);
/**
* @dev Returns the number of accounts that have `role`. Can be used
* together with {getRoleMember} to enumerate all bearers of a role.
*/
function getRoleMemberCount(bytes32 role) external view returns (uint256);
}
"
},
"node_modules/@openzeppelin/contracts/access/AccessControl.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/AccessControl.sol)
pragma solidity ^0.8.20;
import {IAccessControl} from "./IAccessControl.sol";
import {Context} from "../utils/Context.sol";
import {ERC165} from "../utils/introspection/ERC165.sol";
/**
* @dev Contract module that allows children to implement role-based access
* control mechanisms. This is a lightweight version that doesn't allow enumerating role
* members except through off-chain means by accessing the contract event logs. Some
* applications may benefit from on-chain enumerability, for those cases see
* {AccessControlEnumerable}.
*
* Roles are referred to by their `bytes32` identifier. These should be exposed
* in the external API and be unique. The best way to achieve this is by
* using `public constant` hash digests:
*
* ```solidity
* bytes32 public constant MY_ROLE = keccak256("MY_ROLE");
* ```
*
* Roles can be used to represent a set of permissions. To restrict access to a
* function call, use {hasRole}:
*
* ```solidity
* function foo() public {
* require(hasRole(MY_ROLE, msg.sender));
* ...
* }
* ```
*
* Roles can be granted and revoked dynamically via the {grantRole} and
* {revokeRole} functions. Each role has an associated admin role, and only
* accounts that have a role's admin role can call {grantRole} and {revokeRole}.
*
* By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means
* that only accounts with this role will be able to grant or revoke other
* roles. More complex role relationships can be created by using
* {_setRoleAdmin}.
*
* WARNING: The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to
* grant and revoke this role. Extra precautions should be taken to secure
* accounts that have been granted it. We recommend using {AccessControlDefaultAdminRules}
* to enforce additional security measures for this role.
*/
abstract contract AccessControl is Context, IAccessControl, ERC165 {
struct RoleData {
mapping(address account => bool) hasRole;
bytes32 adminRole;
}
mapping(bytes32 role => RoleData) private _roles;
bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;
/**
* @dev Modifier that checks that an account has a specific role. Reverts
* with an {AccessControlUnauthorizedAccount} error including the required role.
*/
modifier onlyRole(bytes32 role) {
_checkRole(role);
_;
}
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return interfaceId == type(IAccessControl).interfaceId || super.supportsInterface(interfaceId);
}
/**
* @dev Returns `true` if `account` has been granted `role`.
*/
function hasRole(bytes32 role, address account) public view virtual returns (bool) {
return _roles[role].hasRole[account];
}
/**
* @dev Reverts with an {AccessControlUnauthorizedAccount} error if `_msgSender()`
* is missing `role`. Overriding this function changes the behavior of the {onlyRole} modifier.
*/
function _checkRole(bytes32 role) internal view virtual {
_checkRole(role, _msgSender());
}
/**
* @dev Reverts with an {AccessControlUnauthorizedAccount} error if `account`
* is missing `role`.
*/
function _checkRole(bytes32 role, address account) internal view virtual {
if (!hasRole(role, account)) {
revert AccessControlUnauthorizedAccount(account, role);
}
}
/**
* @dev Returns the admin role that controls `role`. See {grantRole} and
* {revokeRole}.
*
* To change a role's admin, use {_setRoleAdmin}.
*/
function getRoleAdmin(bytes32 role) public view virtual returns (bytes32) {
return _roles[role].adminRole;
}
/**
* @dev 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 onlyRole(getRoleAdmin(role)) {
_grantRole(role, account);
}
/**
* @dev 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 onlyRole(getRoleAdmin(role)) {
_revokeRole(role, account);
}
/**
* @dev 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 {
if (callerConfirmation != _msgSender()) {
revert AccessControlBadConfirmation();
}
_revokeRole(role, callerConfirmation);
}
/**
* @dev Sets `adminRole` as ``role``'s admin role.
*
* Emits a {RoleAdminChanged} event.
*/
function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual {
bytes32 previousAdminRole = getRoleAdmin(role);
_roles[role].adminRole = adminRole;
emit RoleAdminChanged(role, previousAdminRole, adminRole);
}
/**
* @dev Attempts to grant `role` to `account` and returns a boolean indicating if `role` was granted.
*
* Internal function without access restriction.
*
* May emit a {RoleGranted} event.
*/
function _grantRole(bytes32 role, address account) internal virtual returns (bool) {
if (!hasRole(role, account)) {
_roles[role].hasRole[account] = true;
emit RoleGranted(role, account, _msgSender());
return true;
} else {
return false;
}
}
/**
* @dev Attempts to revoke `role` to `account` and returns a boolean indicating if `role` was revoked.
*
* Internal function without access restriction.
*
* May emit a {RoleRevoked} event.
*/
function _revokeRole(bytes32 role, address account) internal virtual returns (bool) {
if (hasRole(role, account)) {
_roles[role].hasRole[account] = false;
emit RoleRevoked(role, account, _msgSender());
return true;
} else {
return false;
}
}
}
"
},
"node_modules/@openzeppelin/contracts/utils/structs/EnumerableSet.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/structs/EnumerableSet.sol)
// This file was procedurally generated from scripts/generate/templates/EnumerableSet.js.
pragma solidity ^0.8.20;
/**
* @dev Library for managing
* https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive
* types.
*
* Sets have the following properties:
*
* - Elements are added, removed, and checked for existence in constant time
* (O(1)).
* - Elements are enumerated in O(n). No guarantees are made on the ordering.
*
* ```solidity
* contract Example {
* // Add the library methods
* using EnumerableSet for EnumerableSet.AddressSet;
*
* // Declare a set state variable
* EnumerableSet.AddressSet private mySet;
* }
* ```
*
* As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`)
* and `uint256` (`UintSet`) are supported.
*
* [WARNING]
* ====
* Trying to delete such a structure from storage will likely result in data corruption, rendering the structure
* unusable.
* See https://github.com/ethereum/solidity/pull/11843[ethereum/solidity#11843] for more info.
*
* In order to clean an EnumerableSet, you can either remove all elements one by one or create a fresh instance using an
* array of EnumerableSet.
* ====
*/
library EnumerableSet {
// To implement this library for multiple types with as little code
// repetition as possible, we write it in terms of a generic Set type with
// bytes32 values.
// The Set implementation uses private functions, and user-facing
// implementations (such as AddressSet) are just wrappers around the
// underlying Set.
// This means that we can only create new EnumerableSets for types that fit
// in bytes32.
struct Set {
// Storage of set values
bytes32[] _values;
// Position is the index of the value in the `values` array plus 1.
// Position 0 is used to mean a value is not in the
Submitted on: 2025-09-17 15:47:57
Comments
Log in to comment.
No comments yet.