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/CSStrikes.sol": {
"content": "// SPDX-FileCopyrightText: 2025 Lido <info@lido.fi>
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.24;
import { MerkleProof } from "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";
import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import { AccessControlEnumerableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/extensions/AccessControlEnumerableUpgradeable.sol";
import { ICSModule } from "./interfaces/ICSModule.sol";
import { ICSAccounting } from "./interfaces/ICSAccounting.sol";
import { ICSExitPenalties } from "./interfaces/ICSExitPenalties.sol";
import { ICSParametersRegistry } from "./interfaces/ICSParametersRegistry.sol";
import { ICSEjector } from "./interfaces/ICSEjector.sol";
import { ICSStrikes } from "./interfaces/ICSStrikes.sol";
/// @author vgorkavenko
contract CSStrikes is
ICSStrikes,
Initializable,
AccessControlEnumerableUpgradeable
{
address public immutable ORACLE;
ICSModule public immutable MODULE;
ICSAccounting public immutable ACCOUNTING;
ICSExitPenalties public immutable EXIT_PENALTIES;
ICSParametersRegistry public immutable PARAMETERS_REGISTRY;
ICSEjector public ejector;
/// @notice The latest Merkle Tree root
bytes32 public treeRoot;
/// @notice CID of the last published Merkle tree
string public treeCid;
modifier onlyOracle() {
if (msg.sender != ORACLE) {
revert SenderIsNotOracle();
}
_;
}
constructor(
address module,
address oracle,
address exitPenalties,
address parametersRegistry
) {
if (module == address(0)) {
revert ZeroModuleAddress();
}
if (oracle == address(0)) {
revert ZeroOracleAddress();
}
if (exitPenalties == address(0)) {
revert ZeroExitPenaltiesAddress();
}
if (parametersRegistry == address(0)) {
revert ZeroParametersRegistryAddress();
}
MODULE = ICSModule(module);
ACCOUNTING = MODULE.accounting();
EXIT_PENALTIES = ICSExitPenalties(exitPenalties);
ORACLE = oracle;
PARAMETERS_REGISTRY = ICSParametersRegistry(parametersRegistry);
_disableInitializers();
}
function initialize(address admin, address _ejector) external initializer {
if (admin == address(0)) {
revert ZeroAdminAddress();
}
_setEjector(_ejector);
__AccessControlEnumerable_init();
_grantRole(DEFAULT_ADMIN_ROLE, admin);
}
/// @inheritdoc ICSStrikes
function setEjector(
address _ejector
) external onlyRole(DEFAULT_ADMIN_ROLE) {
_setEjector(_ejector);
}
/// @inheritdoc ICSStrikes
function processOracleReport(
bytes32 _treeRoot,
string calldata _treeCid
) external onlyOracle {
// @dev should be both empty or not empty
bool isNewRootEmpty = _treeRoot == bytes32(0);
bool isNewCidEmpty = bytes(_treeCid).length == 0;
if (isNewRootEmpty != isNewCidEmpty) {
revert InvalidReportData();
}
if (isNewRootEmpty) {
if (treeRoot != bytes32(0)) {
delete treeRoot;
delete treeCid;
emit StrikesDataWiped();
}
return;
}
bool isSameRoot = _treeRoot == treeRoot;
bool isSameCid = keccak256(bytes(_treeCid)) ==
keccak256(bytes(treeCid));
if (isSameRoot != isSameCid) {
revert InvalidReportData();
}
if (!isSameRoot) {
treeRoot = _treeRoot;
treeCid = _treeCid;
emit StrikesDataUpdated(_treeRoot, _treeCid);
}
}
/// @inheritdoc ICSStrikes
function processBadPerformanceProof(
KeyStrikes[] calldata keyStrikesList,
bytes32[] calldata proof,
bool[] calldata proofFlags,
address refundRecipient
) external payable {
// NOTE: We allow empty proofs to be delivered because there’s no way to use the tree’s
// internal nodes without brute-forcing the input data.
if (keyStrikesList.length == 0) {
revert EmptyKeyStrikesList();
}
if (msg.value == 0) {
revert ZeroMsgValue();
}
if (msg.value % keyStrikesList.length > 0) {
revert ValueNotEvenlyDivisible();
}
bytes[] memory pubkeys = new bytes[](keyStrikesList.length);
for (uint256 i; i < pubkeys.length; ++i) {
pubkeys[i] = MODULE.getSigningKeys(
keyStrikesList[i].nodeOperatorId,
keyStrikesList[i].keyIndex,
1
);
}
if (!verifyProof(keyStrikesList, pubkeys, proof, proofFlags)) {
revert InvalidProof();
}
refundRecipient = refundRecipient == address(0)
? msg.sender
: refundRecipient;
uint256 valuePerKey = msg.value / keyStrikesList.length;
for (uint256 i; i < keyStrikesList.length; ++i) {
_ejectByStrikes(
keyStrikesList[i],
pubkeys[i],
valuePerKey,
refundRecipient
);
}
}
/// @inheritdoc ICSStrikes
function getInitializedVersion() external view returns (uint64) {
return _getInitializedVersion();
}
/// @inheritdoc ICSStrikes
function verifyProof(
KeyStrikes[] calldata keyStrikesList,
bytes[] memory pubkeys,
bytes32[] calldata proof,
bool[] calldata proofFlags
) public view returns (bool) {
bytes32[] memory leaves = new bytes32[](keyStrikesList.length);
for (uint256 i; i < leaves.length; i++) {
leaves[i] = hashLeaf(keyStrikesList[i], pubkeys[i]);
}
return
MerkleProof.multiProofVerifyCalldata(
proof,
proofFlags,
treeRoot,
leaves
);
}
/// @inheritdoc ICSStrikes
function hashLeaf(
KeyStrikes calldata keyStrikes,
bytes memory pubkey
) public pure returns (bytes32) {
return
keccak256(
bytes.concat(
keccak256(
abi.encode(
keyStrikes.nodeOperatorId,
pubkey,
keyStrikes.data
)
)
)
);
}
function _setEjector(address _ejector) internal {
if (_ejector == address(0)) {
revert ZeroEjectorAddress();
}
ejector = ICSEjector(_ejector);
emit EjectorSet(_ejector);
}
function _ejectByStrikes(
KeyStrikes calldata keyStrikes,
bytes memory pubkey,
uint256 value,
address refundRecipient
) internal {
uint256 strikes = 0;
for (uint256 i; i < keyStrikes.data.length; ++i) {
strikes += keyStrikes.data[i];
}
uint256 curveId = ACCOUNTING.getBondCurveId(keyStrikes.nodeOperatorId);
(, uint256 threshold) = PARAMETERS_REGISTRY.getStrikesParams(curveId);
if (strikes < threshold) {
revert NotEnoughStrikesToEject();
}
EXIT_PENALTIES.processStrikesReport(keyStrikes.nodeOperatorId, pubkey);
ejector.ejectBadPerformer{ value: value }(
keyStrikes.nodeOperatorId,
keyStrikes.keyIndex,
refundRecipient
);
}
}
"
},
"node_modules/@openzeppelin/contracts/utils/cryptography/MerkleProof.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/cryptography/MerkleProof.sol)
pragma solidity ^0.8.20;
/**
* @dev These functions deal with verification of Merkle Tree proofs.
*
* The tree and the proofs can be generated using our
* https://github.com/OpenZeppelin/merkle-tree[JavaScript library].
* You will find a quickstart guide in the readme.
*
* WARNING: You should avoid using leaf values that are 64 bytes long prior to
* hashing, or use a hash function other than keccak256 for hashing leaves.
* This is because the concatenation of a sorted pair of internal nodes in
* the Merkle tree could be reinterpreted as a leaf value.
* OpenZeppelin's JavaScript library generates Merkle trees that are safe
* against this attack out of the box.
*/
library MerkleProof {
/**
*@dev The multiproof provided is not valid.
*/
error MerkleProofInvalidMultiproof();
/**
* @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree
* defined by `root`. For this, a `proof` must be provided, containing
* sibling hashes on the branch from the leaf to the root of the tree. Each
* pair of leaves and each pair of pre-images are assumed to be sorted.
*/
function verify(bytes32[] memory proof, bytes32 root, bytes32 leaf) internal pure returns (bool) {
return processProof(proof, leaf) == root;
}
/**
* @dev Calldata version of {verify}
*/
function verifyCalldata(bytes32[] calldata proof, bytes32 root, bytes32 leaf) internal pure returns (bool) {
return processProofCalldata(proof, leaf) == root;
}
/**
* @dev Returns the rebuilt hash obtained by traversing a Merkle tree up
* from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt
* hash matches the root of the tree. When processing the proof, the pairs
* of leafs & pre-images are assumed to be sorted.
*/
function processProof(bytes32[] memory proof, bytes32 leaf) internal pure returns (bytes32) {
bytes32 computedHash = leaf;
for (uint256 i = 0; i < proof.length; i++) {
computedHash = _hashPair(computedHash, proof[i]);
}
return computedHash;
}
/**
* @dev Calldata version of {processProof}
*/
function processProofCalldata(bytes32[] calldata proof, bytes32 leaf) internal pure returns (bytes32) {
bytes32 computedHash = leaf;
for (uint256 i = 0; i < proof.length; i++) {
computedHash = _hashPair(computedHash, proof[i]);
}
return computedHash;
}
/**
* @dev Returns true if the `leaves` can be simultaneously proven to be a part of a Merkle tree defined by
* `root`, according to `proof` and `proofFlags` as described in {processMultiProof}.
*
* CAUTION: Not all Merkle trees admit multiproofs. See {processMultiProof} for details.
*/
function multiProofVerify(
bytes32[] memory proof,
bool[] memory proofFlags,
bytes32 root,
bytes32[] memory leaves
) internal pure returns (bool) {
return processMultiProof(proof, proofFlags, leaves) == root;
}
/**
* @dev Calldata version of {multiProofVerify}
*
* CAUTION: Not all Merkle trees admit multiproofs. See {processMultiProof} for details.
*/
function multiProofVerifyCalldata(
bytes32[] calldata proof,
bool[] calldata proofFlags,
bytes32 root,
bytes32[] memory leaves
) internal pure returns (bool) {
return processMultiProofCalldata(proof, proofFlags, leaves) == root;
}
/**
* @dev Returns the root of a tree reconstructed from `leaves` and sibling nodes in `proof`. The reconstruction
* proceeds by incrementally reconstructing all inner nodes by combining a leaf/inner node with either another
* leaf/inner node or a proof sibling node, depending on whether each `proofFlags` item is true or false
* respectively.
*
* CAUTION: Not all Merkle trees admit multiproofs. To use multiproofs, it is sufficient to ensure that: 1) the tree
* is complete (but not necessarily perfect), 2) the leaves to be proven are in the opposite order they are in the
* tree (i.e., as seen from right to left starting at the deepest layer and continuing at the next layer).
*/
function processMultiProof(
bytes32[] memory proof,
bool[] memory proofFlags,
bytes32[] memory leaves
) internal pure returns (bytes32 merkleRoot) {
// This function rebuilds the root hash by traversing the tree up from the leaves. The root is rebuilt by
// consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the
// `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of
// the Merkle tree.
uint256 leavesLen = leaves.length;
uint256 proofLen = proof.length;
uint256 totalHashes = proofFlags.length;
// Check proof validity.
if (leavesLen + proofLen != totalHashes + 1) {
revert MerkleProofInvalidMultiproof();
}
// The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using
// `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop".
bytes32[] memory hashes = new bytes32[](totalHashes);
uint256 leafPos = 0;
uint256 hashPos = 0;
uint256 proofPos = 0;
// At each step, we compute the next hash using two values:
// - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we
// get the next hash.
// - depending on the flag, either another value from the "main queue" (merging branches) or an element from the
// `proof` array.
for (uint256 i = 0; i < totalHashes; i++) {
bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++];
bytes32 b = proofFlags[i]
? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++])
: proof[proofPos++];
hashes[i] = _hashPair(a, b);
}
if (totalHashes > 0) {
if (proofPos != proofLen) {
revert MerkleProofInvalidMultiproof();
}
unchecked {
return hashes[totalHashes - 1];
}
} else if (leavesLen > 0) {
return leaves[0];
} else {
return proof[0];
}
}
/**
* @dev Calldata version of {processMultiProof}.
*
* CAUTION: Not all Merkle trees admit multiproofs. See {processMultiProof} for details.
*/
function processMultiProofCalldata(
bytes32[] calldata proof,
bool[] calldata proofFlags,
bytes32[] memory leaves
) internal pure returns (bytes32 merkleRoot) {
// This function rebuilds the root hash by traversing the tree up from the leaves. The root is rebuilt by
// consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the
// `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of
// the Merkle tree.
uint256 leavesLen = leaves.length;
uint256 proofLen = proof.length;
uint256 totalHashes = proofFlags.length;
// Check proof validity.
if (leavesLen + proofLen != totalHashes + 1) {
revert MerkleProofInvalidMultiproof();
}
// The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using
// `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop".
bytes32[] memory hashes = new bytes32[](totalHashes);
uint256 leafPos = 0;
uint256 hashPos = 0;
uint256 proofPos = 0;
// At each step, we compute the next hash using two values:
// - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we
// get the next hash.
// - depending on the flag, either another value from the "main queue" (merging branches) or an element from the
// `proof` array.
for (uint256 i = 0; i < totalHashes; i++) {
bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++];
bytes32 b = proofFlags[i]
? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++])
: proof[proofPos++];
hashes[i] = _hashPair(a, b);
}
if (totalHashes > 0) {
if (proofPos != proofLen) {
revert MerkleProofInvalidMultiproof();
}
unchecked {
return hashes[totalHashes - 1];
}
} else if (leavesLen > 0) {
return leaves[0];
} else {
return proof[0];
}
}
/**
* @dev Sorts the pair (a, b) and hashes the result.
*/
function _hashPair(bytes32 a, bytes32 b) private pure returns (bytes32) {
return a < b ? _efficientHash(a, b) : _efficientHash(b, a);
}
/**
* @dev Implementation of keccak256(abi.encode(a, b)) that doesn't allocate or expand memory.
*/
function _efficientHash(bytes32 a, bytes32 b) private pure returns (bytes32 value) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, a)
mstore(0x20, b)
value := keccak256(0x00, 0x40)
}
}
}
"
},
"node_modules/@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (proxy/utils/Initializable.sol)
pragma solidity ^0.8.20;
/**
* @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
* behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an
* external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
* function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
*
* The initialization functions use a version number. Once a version number is used, it is consumed and cannot be
* reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in
* case an upgrade adds a module that needs to be initialized.
*
* For example:
*
* [.hljs-theme-light.nopadding]
* ```solidity
* contract MyToken is ERC20Upgradeable {
* function initialize() initializer public {
* __ERC20_init("MyToken", "MTK");
* }
* }
*
* contract MyTokenV2 is MyToken, ERC20PermitUpgradeable {
* function initializeV2() reinitializer(2) public {
* __ERC20Permit_init("MyToken");
* }
* }
* ```
*
* TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
* possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.
*
* CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
* that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
*
* [CAUTION]
* ====
* Avoid leaving a contract uninitialized.
*
* An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation
* contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke
* the {_disableInitializers} function in the constructor to automatically lock it when it is deployed:
*
* [.hljs-theme-light.nopadding]
* ```
* /// @custom:oz-upgrades-unsafe-allow constructor
* constructor() {
* _disableInitializers();
* }
* ```
* ====
*/
abstract contract Initializable {
/**
* @dev Storage of the initializable contract.
*
* It's implemented on a custom ERC-7201 namespace to reduce the risk of storage collisions
* when using with upgradeable contracts.
*
* @custom:storage-location erc7201:openzeppelin.storage.Initializable
*/
struct InitializableStorage {
/**
* @dev Indicates that the contract has been initialized.
*/
uint64 _initialized;
/**
* @dev Indicates that the contract is in the process of being initialized.
*/
bool _initializing;
}
// keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Initializable")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant INITIALIZABLE_STORAGE = 0xf0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00;
/**
* @dev The contract is already initialized.
*/
error InvalidInitialization();
/**
* @dev The contract is not initializing.
*/
error NotInitializing();
/**
* @dev Triggered when the contract has been initialized or reinitialized.
*/
event Initialized(uint64 version);
/**
* @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,
* `onlyInitializing` functions can be used to initialize parent contracts.
*
* Similar to `reinitializer(1)`, except that in the context of a constructor an `initializer` may be invoked any
* number of times. This behavior in the constructor can be useful during testing and is not expected to be used in
* production.
*
* Emits an {Initialized} event.
*/
modifier initializer() {
// solhint-disable-next-line var-name-mixedcase
InitializableStorage storage $ = _getInitializableStorage();
// Cache values to avoid duplicated sloads
bool isTopLevelCall = !$._initializing;
uint64 initialized = $._initialized;
// Allowed calls:
// - initialSetup: the contract is not in the initializing state and no previous version was
// initialized
// - construction: the contract is initialized at version 1 (no reininitialization) and the
// current contract is just being deployed
bool initialSetup = initialized == 0 && isTopLevelCall;
bool construction = initialized == 1 && address(this).code.length == 0;
if (!initialSetup && !construction) {
revert InvalidInitialization();
}
$._initialized = 1;
if (isTopLevelCall) {
$._initializing = true;
}
_;
if (isTopLevelCall) {
$._initializing = false;
emit Initialized(1);
}
}
/**
* @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the
* contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be
* used to initialize parent contracts.
*
* A reinitializer may be used after the original initialization step. This is essential to configure modules that
* are added through upgrades and that require initialization.
*
* When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer`
* cannot be nested. If one is invoked in the context of another, execution will revert.
*
* Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in
* a contract, executing them in the right order is up to the developer or operator.
*
* WARNING: Setting the version to 2**64 - 1 will prevent any future reinitialization.
*
* Emits an {Initialized} event.
*/
modifier reinitializer(uint64 version) {
// solhint-disable-next-line var-name-mixedcase
InitializableStorage storage $ = _getInitializableStorage();
if ($._initializing || $._initialized >= version) {
revert InvalidInitialization();
}
$._initialized = version;
$._initializing = true;
_;
$._initializing = false;
emit Initialized(version);
}
/**
* @dev Modifier to protect an initialization function so that it can only be invoked by functions with the
* {initializer} and {reinitializer} modifiers, directly or indirectly.
*/
modifier onlyInitializing() {
_checkInitializing();
_;
}
/**
* @dev Reverts if the contract is not in an initializing state. See {onlyInitializing}.
*/
function _checkInitializing() internal view virtual {
if (!_isInitializing()) {
revert NotInitializing();
}
}
/**
* @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
* Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
* to any version. It is recommended to use this to lock implementation contracts that are designed to be called
* through proxies.
*
* Emits an {Initialized} event the first time it is successfully executed.
*/
function _disableInitializers() internal virtual {
// solhint-disable-next-line var-name-mixedcase
InitializableStorage storage $ = _getInitializableStorage();
if ($._initializing) {
revert InvalidInitialization();
}
if ($._initialized != type(uint64).max) {
$._initialized = type(uint64).max;
emit Initialized(type(uint64).max);
}
}
/**
* @dev Returns the highest version that has been initialized. See {reinitializer}.
*/
function _getInitializedVersion() internal view returns (uint64) {
return _getInitializableStorage()._initialized;
}
/**
* @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}.
*/
function _isInitializing() internal view returns (bool) {
return _getInitializableStorage()._initializing;
}
/**
* @dev Returns a pointer to the storage namespace.
*/
// solhint-disable-next-line var-name-mixedcase
function _getInitializableStorage() private pure returns (InitializableStorage storage $) {
assembly {
$.slot := INITIALIZABLE_STORAGE
}
}
}
"
},
"node_modules/@openzeppelin/contracts-upgradeable/access/extensions/AccessControlEnumerableUpgradeable.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 "@openzeppelin/contracts/access/extensions/IAccessControlEnumerable.sol";
import {AccessControlUpgradeable} from "../AccessControlUpgradeable.sol";
import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
import {Initializable} from "../../proxy/utils/Initializable.sol";
/**
* @dev Extension of {AccessControl} that allows enumerating the members of each role.
*/
abstract contract AccessControlEnumerableUpgradeable is Initializable, IAccessControlEnumerable, AccessControlUpgradeable {
using EnumerableSet for EnumerableSet.AddressSet;
/// @custom:storage-location erc7201:openzeppelin.storage.AccessControlEnumerable
struct AccessControlEnumerableStorage {
mapping(bytes32 role => EnumerableSet.AddressSet) _roleMembers;
}
// keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.AccessControlEnumerable")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant AccessControlEnumerableStorageLocation = 0xc1f6fe24621ce81ec5827caf0253cadb74709b061630e6b55e82371705932000;
function _getAccessControlEnumerableStorage() private pure returns (AccessControlEnumerableStorage storage $) {
assembly {
$.slot := AccessControlEnumerableStorageLocation
}
}
function __AccessControlEnumerable_init() internal onlyInitializing {
}
function __AccessControlEnumerable_init_unchained() internal onlyInitializing {
}
/**
* @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) {
AccessControlEnumerableStorage storage $ = _getAccessControlEnumerableStorage();
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) {
AccessControlEnumerableStorage storage $ = _getAccessControlEnumerableStorage();
return $._roleMembers[role].length();
}
/**
* @dev Overload {AccessControl-_grantRole} to track enumerable memberships
*/
function _grantRole(bytes32 role, address account) internal virtual override returns (bool) {
AccessControlEnumerableStorage storage $ = _getAccessControlEnumerableStorage();
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) {
AccessControlEnumerableStorage storage $ = _getAccessControlEnumerableStorage();
bool revoked = super._revokeRole(role, account);
if (revoked) {
$._roleMembers[role].remove(account);
}
return revoked;
}
}
"
},
"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/ICSAccounting.sol": {
"content": "// SPDX-FileCopyrightText: 2025 Lido <info@lido.fi>
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.24;
import { ICSBondCore } from "./ICSBondCore.sol";
import { ICSBondCurve } from "./ICSBondCurve.sol";
import { ICSBondLock } from "./ICSBondLock.sol";
import { ICSFeeDistributor } from "./ICSFeeDistributor.sol";
import { IAssetRecovererLib } from "../lib/AssetRecovererLib.sol";
import { ICSModule } from "./ICSModule.sol";
interface ICSAccounting is
ICSBondCore,
ICSBondCurve,
ICSBondLock,
IAssetRecovererLib
{
struct PermitInput {
uint256 value;
uint256 deadline;
uint8 v;
bytes32 r;
bytes32 s;
}
event BondLockCompensated(uint256 indexed nodeOperatorId, uint256 amount);
event ChargePenaltyRecipientSet(address chargePenaltyRecipient);
error SenderIsNotModule();
error SenderIsNotEligible();
error ZeroModuleAddress();
error ZeroAdminAddress();
error ZeroFeeDistributorAddress();
error ZeroChargePenaltyRecipientAddress();
error NodeOperatorDoesNotExist();
error ElRewardsVaultReceiveFailed();
error InvalidBondCurvesLength();
function PAUSE_ROLE() external view returns (bytes32);
function RESUME_ROLE() external view returns (bytes32);
function MANAGE_BOND_CURVES_ROLE() external view returns (bytes32);
function SET_BOND_CURVE_ROLE() external view returns (bytes32);
function RECOVERER_ROLE() external view returns (bytes32);
function MODULE() external view returns (ICSModule);
function FEE_DISTRIBUTOR() external view returns (ICSFeeDistributor);
function feeDistributor() external view returns (ICSFeeDistributor);
function chargePenaltyRecipient() external view returns (address);
/// @notice Get the initialized version of the contract
function getInitializedVersion() external view returns (uint64);
/// @notice Resume reward claims and deposits
function resume() external;
/// @notice Pause reward claims and deposits for `duration` seconds
/// @dev Must be called together with `CSModule.pauseFor`
/// @dev Passing MAX_UINT_256 as `duration` pauses indefinitely
/// @param duration Duration of the pause in seconds
function pauseFor(uint256 duration) external;
/// @notice Set charge recipient address
/// @param _chargePenaltyRecipient Charge recipient address
function setChargePenaltyRecipient(
address _chargePenaltyRecipient
) external;
/// @notice Set bond lock period
/// @param period Period in seconds to retain bond lock
function setBondLockPeriod(uint256 period) external;
/// @notice Add a new bond curve
/// @param bondCurve Bond curve definition to add
/// @return id Id of the added curve
function addBondCurve(
BondCurveIntervalInput[] calldata bondCurve
) external returns (uint256 id);
/// @notice Update existing bond curve
/// @dev If the curve is updated to a curve with higher values for any point,
/// Extensive checks and actions should be performed by the method caller to avoid
/// inconsistency in the keys accounting. A manual update of the depositable validators count
/// in CSM might be required to ensure that the keys pointers are consistent.
/// @param curveId Bond curve ID to update
/// @param bondCurve Bond curve definition
function updateBondCurve(
uint256 curveId,
BondCurveIntervalInput[] calldata bondCurve
) external;
/// @notice Get the required bond in ETH (inc. missed and excess) for the given Node Operator to upload new deposit data
/// @param nodeOperatorId ID of the Node Operator
/// @param additionalKeys Number of new keys to add
/// @return Required bond amount in ETH
function getRequiredBondForNextKeys(
uint256 nodeOperatorId,
uint256 additionalKeys
) external view returns (uint256);
/// @notice Get the bond amount in wstETH required for the `keysCount` keys using the default bond curve
/// @param keysCount Keys count to calculate the required bond amount
/// @param curveId Id of the curve to perform calculations against
/// @return wstETH amount required for the `keysCount`
function getBondAmountByKeysCountWstETH(
uint256 keysCount,
uint256 curveId
) external view returns (uint256);
/// @notice Get the required bond in wstETH (inc. missed and excess) for the given Node Operator to upload new keys
/// @param nodeOperatorId ID of the Node Operator
/// @param additionalKeys Number of new keys to add
/// @return Required bond in wstETH
function getRequiredBondForNextKeysWstETH(
uint256 nodeOperatorId,
uint256 additionalKeys
) external view returns (uint256);
/// @notice Get the number of the unbonded keys
/// @param nodeOperatorId ID of the Node Operator
/// @return Unbonded keys count
function getUnbondedKeysCount(
uint256 nodeOperatorId
) external view returns (uint256);
/// @notice Get the number of the unbonded keys to be ejected using a forcedTargetLimit
/// Locked bond is not considered for this calculation to allow Node Operators to
/// compensate the locked bond via `compensateLockedBondETH` method before the ejection happens
/// @param nodeOperatorId ID of the Node Operator
/// @return Unbonded keys count
function getUnbondedKeysCountToEject(
uint256 nodeOperatorId
) external view returns (uint256);
/// @notice Get current and required bond amounts in ETH (stETH) for the given Node Operator
/// @dev To calculate excess bond amount subtract `required` from `current` value.
/// To calculate missed bond amount subtract `current` from `required` value
/// @param nodeOperatorId ID of the Node Operator
/// @return current Current bond amount in ETH
/// @return required Required bond amount in ETH
function getBondSummary(
uint256 nodeOperatorId
) external view returns (uint256 current, uint256 required);
/// @notice Get current and required bond amounts in stETH shares for the given Node Operator
/// @dev To calculate excess bond amount subtract `required` from `current` value.
/// To calculate missed bond amount subtract `current` from `required` value
/// @param nodeOperatorId ID of the Node Operator
/// @return current Current bond amount in stETH shares
/// @return required Required bond amount in stETH shares
function getBondSummaryShares(
uint256 nodeOperatorId
) external view returns (uint256 current, uint256 required);
/// @notice Get current claimable bond in stETH shares for the given Node Operator
/// @param nodeOperatorId ID of the Node Operator
/// @return Current claimable bond in stETH shares
function getClaimableBondShares(
uint256 nodeOperatorId
) external view returns (uint256);
/// @notice Get current claimable bond in stETH shares for the given Node Operator
/// Includes potential rewards distributed by the Fee Distributor
/// @param nodeOperatorId ID of the Node Operator
/// @param cumulativeFeeShares Cumulative fee stETH shares for the Node Operator
/// @param rewardsProof Merkle proof of the rewards
/// @return Current claimable bond in stETH shares
function getClaimableRewardsAndBondShares(
uint256 nodeOperatorId,
uint256 cumulativeFeeShares,
bytes32[] calldata rewardsProof
) external view returns (uint256);
/// @notice Unwrap the user's wstETH and deposit stETH to the bond for the given Node Operator
/// @dev Called by CSM exclusively. CSM should check node operator existence and update depositable validators count
/// @param from Address to unwrap wstETH from
/// @param nodeOperatorId ID of the Node Operator
/// @param wstETHAmount Amount of wstETH to deposit
/// @param permit wstETH permit for the contract
function depositWstETH(
address from,
uint256 nodeOperatorId,
uint256 wstETHAmount,
PermitInput calldata permit
) external;
/// @notice Unwrap the user's wstETH and deposit stETH to the bond for the given Node Operator
/// @dev Permissionless. Enqueues Node Operator's keys if needed
/// @param nodeOperatorId ID of the Node Operator
/// @param wstETHAmount Amount of wstETH to deposit
/// @param permit wstETH permit for the contract
function depositWstETH(
uint256 nodeOperatorId,
uint256 wstETHAmount,
PermitInput calldata permit
) external;
/// @notice Deposit user's stETH to the bond for the given Node Operator
/// @dev Called by CSM exclusively. CSM should check node operator existence and update depositable validators count
/// @param from Address to deposit stETH from.
/// @param nodeOperatorId ID of the Node Operator
/// @param stETHAmount Amount of stETH to deposit
/// @param permit stETH permit for the contract
function depositStETH(
address from,
uint256 nodeOperatorId,
uint256 stETHAmount,
PermitInput calldata permit
) external;
/// @notice Deposit user's stETH to the bond for the given Node Operator
/// @dev Permissionless. Enqueues Node Operator's keys if needed
/// @param nodeOperatorId ID of the Node Operator
/// @param stETHAmount Amount of stETH to deposit
/// @param permit stETH permit for the contract
function depositStETH(
uint256 nodeOperatorId,
uint256 stETHAmount,
PermitInput calldata permit
) external;
/// @notice Stake user's ETH with Lido and deposit stETH to the bond
/// @dev Called by CSM exclusively. CSM should check node operator existence and update depositable validators count
/// @param from Address to stake ETH and deposit stETH from
/// @param nodeOperatorId ID of the Node Operator
function depositETH(address from, uint256 nodeOperatorId) external payable;
/// @notice Stake user's ETH with Lido and deposit stETH to the bond
/// @dev Permissionless. Enqueues Node Operator's keys if needed
/// @param nodeOperatorId ID of the Node Operator
function depositETH(uint256 nodeOperatorId) external payable;
/// @notice Claim full reward (fee + bond) in stETH for the given Node Operator with desirable value.
/// `rewardsProof` and `cumulativeFeeShares` might be empty in order to claim only excess bond
/// @param nodeOperatorId ID of the Node Operator
/// @param stETHAmount Amount of stETH to claim
/// @param cumulativeFeeShares Cumulative fee stETH shares for the Node Operator
/// @param rewardsProof Merkle proof of the rewards
/// @return shares Amount of stETH shares claimed
/// @dev It's impossible to use single-leaf proof via this method, so this case should be treated carefully by
/// off-chain tooling, e.g. to make sure a tree has at least 2 leafs.
function claimRewardsStETH(
uint256 nodeOperatorId,
uint256 stETHAmount,
uint256 cumulativeFeeShares,
bytes32[] calldata rewardsProof
) external returns (uint256 shares);
/// @notice Claim full reward (fee + bond) in wstETH for the given Node Operator available for this moment.
/// `rewardsProof` and `cumulativeFeeShares` might be empty in order to claim only excess bond
/// @param nodeOpe
Submitted on: 2025-09-17 15:49:32
Comments
Log in to comment.
No comments yet.