Early draft. WIP -- # 馃摑 Community Staking Module Spec ![image](https://hackmd.io/_uploads/S1G7OO6JR.png) The [Community Staking Landscape](https://hackmd.io/@lido/Byp775Ay6) describes the motivation for the Community Staking Module (CSM) development. A detailed description of the CSM design principles can be found in the [CSM Architecture](https://hackmd.io/@lido/rJMcGj0Ap) document. This document provides a detailed description of the technical implementation of CSM and related pieces of software, which should be considered an extension of the [CSM Architecture](https://hackmd.io/@lido/rJMcGj0Ap). > Terms validator, key, validator key, and deposit data meanings are the same within the document - [Project repo](https://github.com/lidofinance/community-staking-module) - Written in [Solidity 0.8.24](https://github.com/ethereum/solidity/tree/v0.8.24) - Developed in [Foundry](https://github.com/foundry-rs/foundry) ## General Architecture ![image](https://hackmd.io/_uploads/B1KYh2RW0.png) As depicted in the scheme above, CSM is a set of Ethereum smart contracts and off-chain tools, namely: ### Contracts **`CSmodule.sol`** (CSM on the scheme) - a core module contract conforming to the `IStakingModule` interface. Stores information about Node Operators and deposit data (DD). This contract is used as an entry point for the Node Operators. It is responsible for all interactions with the `StakingRouter`, namely, the DD queue management and Node Operator's params. **`CSAccounting.sol`** (Accounting in the scheme) - a supplementary contract responsible for the management of bond, rewards, and penalties. It stores bond tokens in the form of `stETH` shares, provides information about the bond required, and provides interfaces for the penalties. **`CSVerifier.sol`** (Verifier on the scheme) - a utility contract responsible for the validation of the CL data proofs using EIP-4788. It accepts proof of the validator withdrawal and slashing events and reports these facts to the `CSModule.sol` if the proof is valid. **`CSEarlyAdoption.sol`** (EarlyAdoption on the scheme) - a supplementary contract responsible for the Early Adoption members' verification. It validates if the address is eligible to create a Node Operator during the Early Adoption period or if the address is eligible to create a Node Operator with the discounted bond curve once the Early Adoption period is over. It stores information about eligible addresses that have already been used to create a Node Operator. **`CSFeeDistributor.sol`** (FeeDistributor on the scheme) - a supplementary contract that stores non-claimed and non-distributed Node Operator rewards on balance and the latest root of a rewards distribution Merkle tree root. It accepts calls from `CSAccounting.sol` with reward claim requests and stores data about already claimed rewards by the Node Operator. It receives non-distributed rewards from the `CSModule.sol` each time the `StakingRouter` mints the new portion of the Node Operators' rewards. **`CSFeeOracle.sol`** (FeeOracle on the scheme) - a utility contract responsible for the execution of the CSM Oracle report once the consensus is reached in the `HashConsensus.sol` contract, namely, transforming non-distributed rewards to non-claimed rewards stored on the `CSFeeDistributor.sol`, and reporting the latest root of rewards distribution Merkle tree to the `CSFeeDistributor.sol`. Inherited from the `BaseOracle.sol` from LoE. **`HashConsensus.sol`** - a utility contract responsible for reaching consensus between CSM Oracle members. Uses the standard code of the `HashConsensus` contract from the LoE. **`EasyTrack`** - a utility contract responsible for the application of the reported EL stealing penalties. A part of the common `EasyTrack` setup within LoE. **`GateSeal`** - a utility contract responsible for the one-time pause of the `CSModule.sol` and `CSAccounting.sol` contracts to prevent possible exploitation of the module through zero-day vulnerability. Uses the standard code of the `GateSeal` contract from LoE. ### Off-chain tools **`CSM Bot`** - a daemon application responsible for monitoring and reporting the withdrawal and slashing events associated with the CSM validators. **`EL stealing detector`** - a daemon application or EOA responsible for detecting and reporting the EL stealing facts by the CSM validators. Assumed to be EOA controlled by the dev team at the early stages of the MEV monitoring software maturity and later converted to the automated bot to avoid false-positive activations. **`CSM Oracle`** - a module in the common LoE oracle set. Operated by the existing oracles set alongside Accounting Oracle and Validator Exit Bus Oracle. It is responsible for the calculation of the CSM Node Operators' rewards distribution based on their attestation performance on the CL. ## Main flows ### Create Node Operator ![image](https://hackmd.io/_uploads/HJrHTH2JC.png) Node Operator creation is done using `CSModule.sol`. To avoid flooding of the module with empty Node Operators, at least one deposit data and corresponding bond amount is required to create a Node Operator. Prior to Node Operator creation, a required bond amount should be fetched from the `CSAccounting.sol`. Depending on the selected token, this amount should be: - attached as a payment to the transaction (ETH); - approved to be transferred by `CSAccounting.sol` (stETH, wstETH); - included in permit data approving transfers by `CSAccounting.sol` (stETH, wstETH); ### Upload deposit data ![image](https://hackmd.io/_uploads/r1_U6H2kR.png) Node Operators can upload deposit data after Node Operator creation. Prior to uploading, the required bond amount should be fetched from `CSAccounting.sol`, and corresponding approvals, permits, or direct attachments as a payment should be performed the same way as for the [Node Operator creation](#CreateNodeOperator). ### Delete deposit data ![image](https://hackmd.io/_uploads/BJpltIp1C.png) The node operator can request deposit data deletion if it has not been deposited yet. The Node Operator requests deposit data deletion from `CSModule.sol` to do it. `CSModule.sol` validates that deposit data has not yet been deposited. If deletion is possible `CSAccounting.sol` confiscate `deletionFee` from the Node Operator's bond. ### Top-up bond without deposit data upload ![image](https://hackmd.io/_uploads/HJv6hhAW0.png) CSM Node Operators can top-up bond at any time to have excess bond in advance or compensate for the penalties. Top-up is done via `CSModule.sol`. Once funds are transferred to `CSAccounting.sol`, `CSModule.sol` is informed about the bond amount change and corresponding changes in the depositable keys for the Node Operator to account for it. ### Stake allocation To determine the next portion of the validator keys to be deposited, CSM utilizes the FIFO queue. A description of the queue can be found in the [CSM Architecture](https://hackmd.io/gGRgZ0yeTnm-9SSFuHrXwg#Stake-allocation-queue) document and in a [separate spec](https://hackmd.io/@lido/ryw2Qo5ia). #### Basic flow ![image](https://hackmd.io/_uploads/BksD6ShkR.png) Once uploaded, deposit data is placed in the queue. To allocate stake to the CSM Node Operators, `StakingRouter` calls the [`obtainDepositData(depositsCount)`](https://github.com/lidofinance/lido-dao/blob/master/contracts/0.8.9/interfaces/IStakingModule.sol#L140) method to get the next `depositsCount` [depositable keys](https://hackmd.io/gGRgZ0yeTnm-9SSFuHrXwg#Depositable-keys) from the keys queue. #### Invalid keys ![image](https://hackmd.io/_uploads/B1xFpr3J0.png) Due to [optimistic vetting approach](https://hackmd.io/gGRgZ0yeTnm-9SSFuHrXwg#Deposit-data-validation-and-invalidation-aka-vetting-and-unvetting), invalid keys might be present in the queue. DSM is responsible for the detection and reporting of invalid keys through `StakingRouter`. If invalid keys are detected, a call to `decreaseOperatorVettedKeys` is expected from `StakingRouter` to `CSModule.sol`. ### Rewards distribution ![image](https://hackmd.io/_uploads/SJI9aS2JC.png) `StakingRouter` mint rewards for CSM Node Operators on each report of the `AccountingOracle`. `CSModule.sol` bypasses minted rewards to the `CSFeeDistributor.sol`. Once the report slot is reached for the next CSM Oracle report, the rewards distribution tree is [calculated](https://hackmd.io/gGRgZ0yeTnm-9SSFuHrXwg#Performance-Oracle) by each Oracle member. After reaching the quorum, a new Merkle tree root is submitted to the `CSFeeDistributor.sol`, and the corresponding portion of the rewards is transferred from the non-distributed to the non-claimed state. ### Rewards claim ![image](https://hackmd.io/_uploads/S1Jh1SbgA.png) Total rewards for the CSM Node Operators are comprised of [bond rewards and staking fees](https://hackmd.io/gGRgZ0yeTnm-9SSFuHrXwg#%F0%9F%A4%91-Step-2-Rewards). To claim the total rewards, the Node Operator needs to bring proof of the latest `cumulativeFeeShares` in the rewards tree. With that proof `CSAccounting.sol` pulls the Node Operator's portion of the staking fees from the `CSFeeDistributor.sol` and combines it with the Node Operator's bond. After that, all bond funds exceeding the bond required for the currently active keys are available for claim. Node Operator can transfer staking rewards to the bond without transferring it to the reward address by passing `0` as the amount requested for the claim. If there are no new rewards to pull from the `CSFeeDistributor.sol` Node Operator can still claim excess bond using the same flow. ### EL stealing penalty ![image](https://hackmd.io/_uploads/SJ0TI8-gR.png) If the Node Operator commits EL rewards stealing (or violates the [Lido MEV policy](https://research.lido.fi/t/discussion-draft-lido-on-ethereum-block-proposer-rewards-policy-2-0/3132/13)) the fact of stealing and stolen amount are reported to the `CSModule.sol` by the EL stealing detector actor. The corresponding amount of the bond funds is locked by the `CSAccounting.sol`. Node Operator can compensate for the stolen funds and fixed fee voluntarily. If the Node Operator does not compensate for the stolen funds, `EasyTrack` is started to confirm the penalty application. Once enacted, a penalty is applied (locked funds are burned), and the bond curve for the Node Operator is reset to the default one. ### Slashing reporting ![image](https://hackmd.io/_uploads/HyXHgUaJA.png) If one of the CSM validators is slashed, the CSM Bot will report it. The report is submitted to the `CSVerifier.sol` to validate proof against beaconBlockRoot. If the proof is valid, the report is bypassed to the `CSModule.sol`. `CSModule.sol` marks the validator as slashed and requests bond penalization for the Node Operator by `CSAccounting.sol`. ### Withdrawal reporting ![image](https://hackmd.io/_uploads/SkhtbUpJC.png) Once the CSM validator is withdrawn, the CSM Bot will report it. The report is submitted to the `CSVerifier.sol` to validate proof against beaconBlockRoot. If the proof is valid, the report is bypassed to the `CSModule.sol`. `CSModule.sol` marks the validator as withdrawn and requests bond penalization for the Node Operator by `CSAccounting.sol` if the withdrawal balance is lower than 32 ETH. If the withdrawn validator is slashed, the bond curve is reset to the default one for the Node Operator by `CSAccounting.sol`. ## Contracts specifications ### `CSModule.sol` The core contract of the system. It fully implements the `IStakingModule` interface to allow system connection to the StakingRouter in a proper way. The contract itself provides an interface for the Node Operators' creation and management. It stores the data about Node Operators and their validator keys. The keys storage system is inherited from the Curated Module. The keys queue is managed by the contract. #### Struct: NodeOperator ``` solidity struct NodeOperator { address managerAddress; address proposedManagerAddress; address rewardAddress; address proposedRewardAddress; bool active; uint256 targetLimit; uint8 targetLimitMode; uint256 stuckPenaltyEndTimestamp; uint256 totalExitedKeys; // @dev only increased uint256 totalAddedKeys; // @dev increased and decreased when removed uint256 totalWithdrawnKeys; // @dev only increased uint256 totalDepositedKeys; // @dev only increased uint256 totalVettedKeys; // @dev both increased and decreased uint256 stuckValidatorsCount; // @dev both increased and decreased uint256 refundedValidatorsCount; // @dev only increased uint256 depositableValidatorsCount; // @dev any value uint256 enqueuedCount; // Tracks how many places are occupied by the node operator's keys in the queue. } ``` Stores information about the Node Operator's parameters and state. #### Function: resume Resume module ```solidity function resume() external onlyRole(RESUME_ROLE); ``` #### Function: pauseFor Pause module for `duration` seconds ```solidity function pauseFor(uint256 duration) external onlyRole(PAUSE_ROLE); ``` **Parameters** |Name|Type|Description| |----|----|-----------| |`duration`|`uint256`|Duration of the pause in seconds| #### Function: activatePublicRelease Activate public release mode Enable permissionless creation of the Node Operators Remove the keys limit for the Node Operators ```solidity function activatePublicRelease() external onlyRole(MODULE_MANAGER_ROLE); ``` #### Function: setKeyRemovalCharge Set the key removal charge. A charge is taken from the bond for each removed key ```solidity function setKeyRemovalCharge(uint256 amount) external onlyRole(MODULE_MANAGER_ROLE); ``` **Parameters** |Name|Type|Description| |----|----|-----------| |`amount`|`uint256`|Amount of wei to be charged for removing a single key| #### Function: addNodeOperatorETH Add a new Node Operator using ETH as a bond ```solidity function addNodeOperatorETH( uint256 keysCount, bytes calldata publicKeys, bytes calldata signatures, address managerAddress, address rewardAddress, bytes32[] calldata eaProof, address referrer ) external payable whenResumed; ``` **Parameters** |Name|Type|Description| |----|----|-----------| |`keysCount`|`uint256`|Signing keys count| |`publicKeys`|`bytes`|Public keys to submit| |`signatures`|`bytes`|Signatures of `(deposit_message_root, domain)` tuples https://github.com/ethereum/consensus-specs/blob/v1.4.0/specs/phase0/beacon-chain.md#signingdata| |`managerAddress`|`address`|Optional. Used as `managerAddress` for the Node Operator. If not passed `msg.sender` will be used| |`rewardAddress`|`address`|Optional. Used as `rewardAddress` for the Node Operator. If not passed `msg.sender` will be used| |`eaProof`|`bytes32[]`|Optional. Merkle proof of the sender being eligible for the Early Adoption| |`referrer`|`address`|Optional. Referrer address| #### Function: addNodeOperatorStETH Add a new Node Operator using stETH as a bond Due to the stETH rounding issue make sure to make approval or sign permit with extra 10 wei to avoid revert ```solidity function addNodeOperatorStETH( uint256 keysCount, bytes calldata publicKeys, bytes calldata signatures, address managerAddress, address rewardAddress, ICSAccounting.PermitInput calldata permit, bytes32[] calldata eaProof, address referrer ) external whenResumed; ``` **Parameters** |Name|Type|Description| |----|----|-----------| |`keysCount`|`uint256`|Signing keys count| |`publicKeys`|`bytes`|Public keys to submit| |`signatures`|`bytes`|Signatures of `(deposit_message_root, domain)` tuples https://github.com/ethereum/consensus-specs/blob/v1.4.0/specs/phase0/beacon-chain.md#signingdata| |`managerAddress`|`address`|Optional. Used as `managerAddress` for the Node Operator. If not passed `msg.sender` will be used| |`rewardAddress`|`address`|Optional. Used as `rewardAddress` for the Node Operator. If not passed `msg.sender` will be used| |`permit`|`ICSAccounting.PermitInput`|Optional. Permit to use stETH as bond| |`eaProof`|`bytes32[]`|Optional. Merkle proof of the sender being eligible for the Early Adoption| |`referrer`|`address`|Optional, Referrer address| #### Function: addNodeOperatorWstETH Add a new Node Operator using wstETH as a bond Due to the stETH rounding issue make sure to make approval or sign permit with extra 10 wei to avoid revert ```solidity function addNodeOperatorWstETH( uint256 keysCount, bytes calldata publicKeys, bytes calldata signatures, address managerAddress, address rewardAddress, ICSAccounting.PermitInput calldata permit, bytes32[] calldata eaProof, address referrer ) external whenResumed; ``` **Parameters** |Name|Type|Description| |----|----|-----------| |`keysCount`|`uint256`|Signing keys count| |`publicKeys`|`bytes`|Public keys to submit| |`signatures`|`bytes`|Signatures of `(deposit_message_root, domain)` tuples https://github.com/ethereum/consensus-specs/blob/v1.4.0/specs/phase0/beacon-chain.md#signingdata| |`managerAddress`|`address`|Optional. Used as `managerAddress` for the Node Operator. If not passed `msg.sender` will be used| |`rewardAddress`|`address`|Optional. Used as `rewardAddress` for the Node Operator. If not passed `msg.sender` will be used| |`permit`|`ICSAccounting.PermitInput`|Optional. Permit to use wstETH as bond| |`eaProof`|`bytes32[]`|Optional. Merkle proof of the sender being eligible for the Early Adoption| |`referrer`|`address`|Optional. Referrer address| #### Function: addValidatorKeysETH Add new keys to the Node Operator using ETH as a bond ```solidity function addValidatorKeysETH( uint256 nodeOperatorId, uint256 keysCount, bytes calldata publicKeys, bytes calldata signatures ) external payable whenResumed; ``` **Parameters** |Name|Type|Description| |----|----|-----------| |`nodeOperatorId`|`uint256`|ID of the Node Operator| |`keysCount`|`uint256`|Signing keys count| |`publicKeys`|`bytes`|Public keys to submit| |`signatures`|`bytes`|Signatures of `(deposit_message_root, domain)` tuples https://github.com/ethereum/consensus-specs/blob/v1.4.0/specs/phase0/beacon-chain.md#signingdata| #### Function: addValidatorKeysStETH Add new keys to the Node Operator using stETH as a bond Due to the stETH rounding issue make sure to make approval or sign permit with extra 10 wei to avoid revert ```solidity function addValidatorKeysStETH( uint256 nodeOperatorId, uint256 keysCount, bytes calldata publicKeys, bytes calldata signatures, ICSAccounting.PermitInput calldata permit ) external whenResumed; ``` **Parameters** |Name|Type|Description| |----|----|-----------| |`nodeOperatorId`|`uint256`|ID of the Node Operator| |`keysCount`|`uint256`|Signing keys count| |`publicKeys`|`bytes`|Public keys to submit| |`signatures`|`bytes`|Signatures of `(deposit_message_root, domain)` tuples https://github.com/ethereum/consensus-specs/blob/v1.4.0/specs/phase0/beacon-chain.md#signingdata| |`permit`|`ICSAccounting.PermitInput`|Optional. Permit to use stETH as bond| #### Function: addValidatorKeysWstETH Add new keys to the Node Operator using wstETH as a bond Due to the stETH rounding issue make sure to make approval or sign permit with extra 10 wei to avoid revert ```solidity function addValidatorKeysWstETH( uint256 nodeOperatorId, uint256 keysCount, bytes calldata publicKeys, bytes calldata signatures, ICSAccounting.PermitInput calldata permit ) external whenResumed; ``` **Parameters** |Name|Type|Description| |----|----|-----------| |`nodeOperatorId`|`uint256`|ID of the Node Operator| |`keysCount`|`uint256`|Signing keys count| |`publicKeys`|`bytes`|Public keys to submit| |`signatures`|`bytes`|Signatures of `(deposit_message_root, domain)` tuples https://github.com/ethereum/consensus-specs/blob/v1.4.0/specs/phase0/beacon-chain.md#signingdata| |`permit`|`ICSAccounting.PermitInput`|Optional. Permit to use wstETH as bond| #### Function: depositETH Stake user's ETH to Lido and make a deposit in stETH to the bond ```solidity function depositETH(uint256 nodeOperatorId) external payable; ``` **Parameters** |Name|Type|Description| |----|----|-----------| |`nodeOperatorId`|`uint256`|ID of the Node Operator| #### Function: depositStETH Deposit user's stETH to the bond for the given Node Operator ```solidity function depositStETH(uint256 nodeOperatorId, uint256 stETHAmount, ICSAccounting.PermitInput calldata permit) external; ``` **Parameters** |Name|Type|Description| |----|----|-----------| |`nodeOperatorId`|`uint256`|ID of the Node Operator| |`stETHAmount`|`uint256`|Amount of stETH to deposit| |`permit`|`ICSAccounting.PermitInput`|Optional. Permit to use stETH as bond| #### Function: depositWstETH Unwrap the user's wstETH and make a deposit in stETH to the bond for the given Node Operator ```solidity function depositWstETH(uint256 nodeOperatorId, uint256 wstETHAmount, ICSAccounting.PermitInput calldata permit) external; ``` **Parameters** |Name|Type|Description| |----|----|-----------| |`nodeOperatorId`|`uint256`|ID of the Node Operator| |`wstETHAmount`|`uint256`|Amount of wstETH to deposit| |`permit`|`ICSAccounting.PermitInput`|Optional. Permit to use wstETH as bond| #### Function: claimRewardsStETH Claim full reward (fees + bond rewards) in stETH for the given Node Operator If `stETHAmount` exceeds the current claimable amount, the claimable amount will be used instead If `rewardsProof` is not provided, only excess bond will be available for claim ```solidity function claimRewardsStETH( uint256 nodeOperatorId, uint256 stETHAmount, uint256 cumulativeFeeShares, bytes32[] memory rewardsProof ) external; ``` **Parameters** |Name|Type|Description| |----|----|-----------| |`nodeOperatorId`|`uint256`|ID of the Node Operator| |`stETHAmount`|`uint256`|Amount of stETH to claim| |`cumulativeFeeShares`|`uint256`|Optional. Cumulative fee stETH shares for the Node Operator| |`rewardsProof`|`bytes32[]`|Optional. Merkle proof of the rewards| #### Function: claimRewardsWstETH Claim full reward (fees + bond rewards) in 褑stETH for the given Node Operator If `wstETHAmount` exceeds the current claimable amount, the claimable amount will be used instead If `rewardsProof` is not provided, only excess bond will be available for claim ```solidity function claimRewardsWstETH( uint256 nodeOperatorId, uint256 wstETHAmount, uint256 cumulativeFeeShares, bytes32[] memory rewardsProof ) external; ``` **Parameters** |Name|Type|Description| |----|----|-----------| |`nodeOperatorId`|`uint256`|ID of the Node Operator| |`wstETHAmount`|`uint256`|Amount of wstETH to claim| |`cumulativeFeeShares`|`uint256`|Optional. Cumulative fee stETH shares for the Node Operator| |`rewardsProof`|`bytes32[]`|Optional. Merkle proof of the rewards| #### Function: requestRewardsETH Request full reward (fees + bond rewards) in Withdrawal NFT (unstETH) for the given Node Operator Amounts less than `MIN_STETH_WITHDRAWAL_AMOUNT` (see LidoWithdrawalQueue contract) are not allowed Amounts above `MAX_STETH_WITHDRAWAL_AMOUNT` should be requested in several transactions If `ethAmount` exceeds the current claimable amount, the claimable amount will be used instead If `rewardsProof` is not provided, only excess bond will be available for claim *Reverts if amount isn't between `MIN_STETH_WITHDRAWAL_AMOUNT` and `MAX_STETH_WITHDRAWAL_AMOUNT`* ```solidity function requestRewardsETH( uint256 nodeOperatorId, uint256 ethAmount, uint256 cumulativeFeeShares, bytes32[] memory rewardsProof ) external; ``` **Parameters** |Name|Type|Description| |----|----|-----------| |`nodeOperatorId`|`uint256`|ID of the Node Operator| |`ethAmount`|`uint256`|Amount of ETH to request| |`cumulativeFeeShares`|`uint256`|Optional. Cumulative fee stETH shares for the Node Operator| |`rewardsProof`|`bytes32[]`|Optional. Merkle proof of the rewards| #### Function: proposeNodeOperatorManagerAddressChange Propose a new manager address for the Node Operator ```solidity function proposeNodeOperatorManagerAddressChange(uint256 nodeOperatorId, address proposedAddress) external; ``` **Parameters** |Name|Type|Description| |----|----|-----------| |`nodeOperatorId`|`uint256`|ID of the Node Operator| |`proposedAddress`|`address`|Proposed manager address| #### Function: confirmNodeOperatorManagerAddressChange Confirm a new manager address for the Node Operator ```solidity function confirmNodeOperatorManagerAddressChange(uint256 nodeOperatorId) external; ``` **Parameters** |Name|Type|Description| |----|----|-----------| |`nodeOperatorId`|`uint256`|ID of the Node Operator| #### Function: proposeNodeOperatorRewardAddressChange Propose a new reward address for the Node Operator ```solidity function proposeNodeOperatorRewardAddressChange(uint256 nodeOperatorId, address proposedAddress) external; ``` **Parameters** |Name|Type|Description| |----|----|-----------| |`nodeOperatorId`|`uint256`|ID of the Node Operator| |`proposedAddress`|`address`|Proposed reward address| #### Function: confirmNodeOperatorRewardAddressChange Confirm a new reward address for the Node Operator ```solidity function confirmNodeOperatorRewardAddressChange(uint256 nodeOperatorId) external; ``` **Parameters** |Name|Type|Description| |----|----|-----------| |`nodeOperatorId`|`uint256`|ID of the Node Operator| #### Function: resetNodeOperatorManagerAddress Reset the manager address to the reward address ```solidity function resetNodeOperatorManagerAddress(uint256 nodeOperatorId) external; ``` **Parameters** |Name|Type|Description| |----|----|-----------| |`nodeOperatorId`|`uint256`|ID of the Node Operator| #### Function: onRewardsMinted Called when rewards are minted for the module *Passes through the minted stETH shares to the fee distributor* ```solidity function onRewardsMinted(uint256 totalShares) external onlyRole(STAKING_ROUTER_ROLE); ``` #### Function: updateStuckValidatorsCount Update stuck validators count for Node Operators *Called by StakingRouter* *If the stuck keys count is above zero for the Node Operator, the depositable validators count is set to 0 for this Node Operator* ```solidity function updateStuckValidatorsCount(bytes calldata nodeOperatorIds, bytes calldata stuckValidatorsCounts) external onlyRole(STAKING_ROUTER_ROLE); ``` **Parameters** |Name|Type|Description| |----|----|-----------| |`nodeOperatorIds`|`bytes`|bytes packed array of Node Operator IDs| |`stuckValidatorsCounts`|`bytes`|bytes packed array of stuck validators counts| #### Function: updateExitedValidatorsCount Updates exited validators count for Node Operators *Called by StakingRouter* ```solidity function updateExitedValidatorsCount(bytes calldata nodeOperatorIds, bytes calldata exitedValidatorsCounts) external onlyRole(STAKING_ROUTER_ROLE); ``` **Parameters** |Name|Type|Description| |----|----|-----------| |`nodeOperatorIds`|`bytes`|bytes packed array of Node Operator IDs| |`exitedValidatorsCounts`|`bytes`|bytes packed array of exited validators counts| #### Function: updateRefundedValidatorsCount Update refunded validators count for the Node Operator *Called by StakingRouter* *`refundedValidatorsCount` is not used in the module* ```solidity function updateRefundedValidatorsCount(uint256 nodeOperatorId, uint256 refundedValidatorsCount) external onlyRole(STAKING_ROUTER_ROLE); ``` **Parameters** |Name|Type|Description| |----|----|-----------| |`nodeOperatorId`|`uint256`|ID of the Node Operator| |`refundedValidatorsCount`|`uint256`|Number of refunded validators| #### Function: updateTargetValidatorsLimits Update target limits for Node Operator *Called by StakingRouter* ```solidity function updateTargetValidatorsLimits(uint256 nodeOperatorId, uint8 targetLimitMode, uint256 targetLimit) external onlyRole(STAKING_ROUTER_ROLE); ``` **Parameters** |Name|Type|Description| |----|----|-----------| |`nodeOperatorId`|`uint256`|ID of the Node Operator| |`targetLimitMode`|`uint8`|Target limit mode for the Node Operator 0 - disabled 1 - soft mode 2 - forced mode| |`targetLimit`|`uint256`|Target limit of validators| #### Function: onExitedAndStuckValidatorsCountsUpdated Called by the Staking Router when exited and stuck validators counts updated ```solidity function onExitedAndStuckValidatorsCountsUpdated() external onlyRole(STAKING_ROUTER_ROLE); ``` #### Function: unsafeUpdateValidatorsCount Unsafe update of validators count for Node Operators by DAO Called by Staking Router ```solidity function unsafeUpdateValidatorsCount( uint256 nodeOperatorId, uint256 exitedValidatorsKeysCount, uint256 stuckValidatorsKeysCount ) external onlyRole(STAKING_ROUTER_ROLE); ``` #### Function: decreaseOperatorVettedKeys Decrease totalVettedKeys for the Node Operator Called by Staking Router ```solidity function decreaseOperatorVettedKeys(uint256[] calldata nodeOperatorIds, uint256[] calldata vettedKeysByOperator) external onlyRole(STAKING_ROUTER_ROLE); ``` **Parameters** |Name|Type|Description| |----|----|-----------| |`nodeOperatorIds`|`uint256[]`|IDs of the Node Operators to decrease totalVettedKeys for| |`vettedKeysByOperator`|`uint256[]`|Corresponding values for totalVettedKeys decrease| #### Function: removeKeys Remove keys for the Node Operator and confiscate removal charge for each deleted key ```solidity function removeKeys(uint256 nodeOperatorId, uint256 startIndex, uint256 keysCount) external; ``` **Parameters** |Name|Type|Description| |----|----|-----------| |`nodeOperatorId`|`uint256`|ID of the Node Operator| |`startIndex`|`uint256`|Index of the first key| |`keysCount`|`uint256`|Keys count to delete| #### Function: normalizeQueue Perform queue normalization for the given Node Operator Normalization stands for adding vetted but not enqueued keys to the queue ```solidity function normalizeQueue(uint256 nodeOperatorId) external; ``` **Parameters** |Name|Type|Description| |----|----|-----------| |`nodeOperatorId`|`uint256`|ID of the Node Operator| #### Function: reportELRewardsStealingPenalty Report EL rewards stealing for the given Node Operator The amount equal to the stolen funds plus EL stealing fine will be locked ```solidity function reportELRewardsStealingPenalty(uint256 nodeOperatorId, bytes32 blockHash, uint256 amount) external onlyRole(REPORT_EL_REWARDS_STEALING_PENALTY_ROLE); ``` **Parameters** |Name|Type|Description| |----|----|-----------| |`nodeOperatorId`|`uint256`|ID of the Node Operator| |`blockHash`|`bytes32`|Execution layer block hash of the proposed block with EL rewards stealing| |`amount`|`uint256`|Amount of stolen EL rewards in ETH| #### Function: cancelELRewardsStealingPenalty Cancel previously reported and not settled EL rewards stealing penalty for the given Node Operator The funds will be unlocked ```solidity function cancelELRewardsStealingPenalty(uint256 nodeOperatorId, uint256 amount) external onlyRole(REPORT_EL_REWARDS_STEALING_PENALTY_ROLE); ``` **Parameters** |Name|Type|Description| |----|----|-----------| |`nodeOperatorId`|`uint256`|ID of the Node Operator| |`amount`|`uint256`|Amount of penalty to cancel| #### Function: settleELRewardsStealingPenalty Settles blocked bond for the given Node Operators *Should be called by the Easy Track* ```solidity function settleELRewardsStealingPenalty(uint256[] memory nodeOperatorIds) external onlyRole(SETTLE_EL_REWARDS_STEALING_PENALTY_ROLE); ``` **Parameters** |Name|Type|Description| |----|----|-----------| |`nodeOperatorIds`|`uint256[]`|IDs of the Node Operators| #### Function: compensateELRewardsStealingPenalty Compensate EL rewards stealing penalty for the given Node Operator to prevent further validator exits *Expected to be called by the Node Operator, but can be called by anyone* ```solidity function compensateELRewardsStealingPenalty(uint256 nodeOperatorId) external payable; ``` **Parameters** |Name|Type|Description| |----|----|-----------| |`nodeOperatorId`|`uint256`|ID of the Node Operator| #### Function: submitWithdrawal Report Node Operator's key as withdrawn and settle withdrawn amount Called by the Verifier contract. See `CSVerifier.processWithdrawalProof` to use this method permissionless ```solidity function submitWithdrawal(uint256 nodeOperatorId, uint256 keyIndex, uint256 amount) external onlyRole(VERIFIER_ROLE); ``` **Parameters** |Name|Type|Description| |----|----|-----------| |`nodeOperatorId`|`uint256`|ID of the Node Operator| |`keyIndex`|`uint256`|Index of the withdrawn key in the Node Operator's keys storage| |`amount`|`uint256`|Amount of withdrawn ETH in wei| #### Function: submitInitialSlashing Report Node Operator's key as slashed and apply the initial slashing penalty Called by the Verifier contract. See `CSVerifier.processSlashingProof` to use this method permissionless ```solidity function submitInitialSlashing(uint256 nodeOperatorId, uint256 keyIndex) external onlyRole(VERIFIER_ROLE); ``` **Parameters** |Name|Type|Description| |----|----|-----------| |`nodeOperatorId`|`uint256`|ID of the Node Operator| |`keyIndex`|`uint256`|Index of the slashed key in the Node Operator's keys storage| #### Function: onWithdrawalCredentialsChanged Called by the Staking Router when withdrawal credentials changed by DAO *Resets the key removal charge* *Changing the WC means that the current deposit data in the queue is not valid anymore and can't be deposited So, the key removal charge should be reset to 0 to allow Node Operators to remove the keys without any charge. After keys removal the DAO should set the new key removal charge.* ```solidity function onWithdrawalCredentialsChanged() external onlyRole(STAKING_ROUTER_ROLE); ``` #### Function: obtainDepositData Get the next `depositsCount` of depositable keys with signatures from the queue *Second param `depositCalldata` is not used* ```solidity function obtainDepositData(uint256 depositsCount, bytes calldata) external onlyRole(STAKING_ROUTER_ROLE) returns (bytes memory publicKeys, bytes memory signatures); ``` **Parameters** |Name|Type|Description| |----|----|-----------| |`depositsCount`|`uint256`|Count of deposits to get| |`<none>`|`bytes`|| **Returns** |Name|Type|Description| |----|----|-----------| |`publicKeys`|`bytes`|Public keys| |`signatures`|`bytes`|Signatures| #### Function: cleanDepositQueue Clean the deposit queue from batches with no depositable keys *Use **eth_call** to check how many items will be removed* ```solidity function cleanDepositQueue(uint256 maxItems) external returns (uint256 toRemove); ``` **Parameters** |Name|Type|Description| |----|----|-----------| |`maxItems`|`uint256`|How many queue items to review| **Returns** |Name|Type|Description| |----|----|-----------| |`toRemove`|`uint256`|Number of the deposit data removed from the queue| #### Function: recoverStETHShares Recover all stETH shares from the contract *There should be no stETH shares on the contract balance during regular operation* ```solidity function recoverStETHShares() external; ``` #### Function: depositQueueItem Get the deposit queue item by an index ```solidity function depositQueueItem(uint128 index) external view returns (Batch item); ``` **Parameters** |Name|Type|Description| |----|----|-----------| |`index`|`uint128`|Index of a queue item| #### Function: isValidatorSlashed Check if the given Node Operator's key is reported as slashed ```solidity function isValidatorSlashed(uint256 nodeOperatorId, uint256 keyIndex) external view returns (bool); ``` **Parameters** |Name|Type|Description| |----|----|-----------| |`nodeOperatorId`|`uint256`|ID of the Node Operator| |`keyIndex`|`uint256`|Index of the key to check| #### Function: isValidatorWithdrawn Check if the given Node Operator's key is reported as withdrawn ```solidity function isValidatorWithdrawn(uint256 nodeOperatorId, uint256 keyIndex) external view returns (bool); ``` **Parameters** |Name|Type|Description| |----|----|-----------| |`nodeOperatorId`|`uint256`|ID of the Node Operator| |`keyIndex`|`uint256`|index of the key to check| #### Function: getType Get the module type ```solidity function getType() external view returns (bytes32); ``` **Returns** |Name|Type|Description| |----|----|-----------| |`<none>`|`bytes32`|Module type| #### Function: getStakingModuleSummary Get staking module summary ```solidity function getStakingModuleSummary() external view returns (uint256, uint256, uint256); ``` #### Function: getNodeOperator Get Node Operator info ```solidity function getNodeOperator(uint256 nodeOperatorId) external view returns (NodeOperator memory); ``` **Parameters** |Name|Type|Description| |----|----|-----------| |`nodeOperatorId`|`uint256`|ID of the Node Operator| **Returns** |Name|Type|Description| |----|----|-----------| |`<none>`|`NodeOperator`|Node Operator info| #### Function: getNodeOperatorNonWithdrawnKeys Get Node Operator non-withdrawn keys ```solidity function getNodeOperatorNonWithdrawnKeys(uint256 nodeOperatorId) external view returns (uint256); ``` **Parameters** |Name|Type|Description| |----|----|-----------| |`nodeOperatorId`|`uint256`|ID of the Node Operator| **Returns** |Name|Type|Description| |----|----|-----------| |`<none>`|`uint256`|Non-withdrawn keys count| #### Function: getNodeOperatorRewardAddress Get Node Operator reward address ```solidity function getNodeOperatorRewardAddress(uint256 nodeOperatorId) external view returns (address); ``` **Parameters** |Name|Type|Description| |----|----|-----------| |`nodeOperatorId`|`uint256`|ID of the Node Operator| **Returns** |Name|Type|Description| |----|----|-----------| |`<none>`|`address`|Reward address| #### Function: getNodeOperatorSummary Get Node Operator summary depositableValidatorsCount depends on: - totalVettedKeys - totalDepositedKeys - totalExitedKeys - targetLimitMode - targetValidatorsCount - totalunbondedKeys - totalStuckKeys ```solidity function getNodeOperatorSummary(uint256 nodeOperatorId) external view returns ( uint8 targetLimitMode, uint256 targetValidatorsCount, uint256 stuckValidatorsCount, uint256 refundedValidatorsCount, uint256 stuckPenaltyEndTimestamp, uint256 totalExitedValidators, uint256 totalDepositedValidators, uint256 depositableValidatorsCount ); ``` **Parameters** |Name|Type|Description| |----|----|-----------| |`nodeOperatorId`|`uint256`|ID of the Node Operator| **Returns** |Name|Type|Description| |----|----|-----------| |`targetLimitMode`|`uint8`|Target limit mode| |`targetValidatorsCount`|`uint256`|Target validators count| |`stuckValidatorsCount`|`uint256`|Stuck validators count| |`refundedValidatorsCount`|`uint256`|Refunded validators count| |`stuckPenaltyEndTimestamp`|`uint256`|Stuck penalty end timestamp| |`totalExitedValidators`|`uint256`|Total exited validators| |`totalDepositedValidators`|`uint256`|Total deposited validators| |`depositableValidatorsCount`|`uint256`|Depositable validators count| #### Function: getSigningKeys Get Node Operator signing keys ```solidity function getSigningKeys(uint256 nodeOperatorId, uint256 startIndex, uint256 keysCount) external view returns (bytes memory); ``` **Parameters** |Name|Type|Description| |----|----|-----------| |`nodeOperatorId`|`uint256`|ID of the Node Operator| |`startIndex`|`uint256`|Index of the first key| |`keysCount`|`uint256`|Count of keys to get| **Returns** |Name|Type|Description| |----|----|-----------| |`<none>`|`bytes`|Signing keys| #### Function: getSigningKeysWithSignatures Get Node Operator signing keys with signatures ```solidity function getSigningKeysWithSignatures(uint256 nodeOperatorId, uint256 startIndex, uint256 keysCount) external view returns (bytes memory keys, bytes memory signatures); ``` **Parameters** |Name|Type|Description| |----|----|-----------| |`nodeOperatorId`|`uint256`|ID of the Node Operator| |`startIndex`|`uint256`|Index of the first key| |`keysCount`|`uint256`|Count of keys to get| **Returns** |Name|Type|Description| |----|----|-----------| |`keys`|`bytes`|Signing keys| |`signatures`|`bytes`|Signatures of `(deposit_message_root, domain)` tuples https://github.com/ethereum/consensus-specs/blob/v1.4.0/specs/phase0/beacon-chain.md#signingdata| #### Function: getNonce Get nonce of the module ```solidity function getNonce() external view returns (uint256); ``` #### Function: getNodeOperatorsCount Get total number of Node Operators ```solidity function getNodeOperatorsCount() external view returns (uint256); ``` #### Function: getActiveNodeOperatorsCount Get total number of active Node Operators ```solidity function getActiveNodeOperatorsCount() external view returns (uint256); ``` #### Function: getNodeOperatorIsActive Get Node Operator active status ```solidity function getNodeOperatorIsActive(uint256 nodeOperatorId) external view returns (bool); ``` **Parameters** |Name|Type|Description| |----|----|-----------| |`nodeOperatorId`|`uint256`|ID of the Node Operator| #### Function: getNodeOperatorIds Get IDs of Node Operators ```solidity function getNodeOperatorIds(uint256 offset, uint256 limit) external view returns (uint256[] memory nodeOperatorIds); ``` **Parameters** |Name|Type|Description| |----|----|-----------| |`offset`|`uint256`|Offset of the first Node Operator ID to get| |`limit`|`uint256`|Count of Node Operator IDs to get| ### `CSAccounting.sol` #### Function: resume Resume accounting ```solidity function resume() external onlyRole(RESUME_ROLE); ``` #### Function: pauseFor Pause accounting for `duration` seconds *Must be called together with `CSModule.pauseFor`* ```solidity function pauseFor(uint256 duration) external onlyRole(PAUSE_ROLE); ``` **Parameters** |Name|Type|Description| |----|----|-----------| |`duration`|`uint256`|Duration of the pause in seconds| #### Function: setChargeRecipient Set charge recipient address ```solidity function setChargeRecipient(address _chargeRecipient) external onlyRole(ACCOUNTING_MANAGER_ROLE); ``` **Parameters** |Name|Type|Description| |----|----|-----------| |`_chargeRecipient`|`address`|Charge recipient address| #### Function: setLockedBondRetentionPeriod Set bond lock retention period ```solidity function setLockedBondRetentionPeriod(uint256 retention) external onlyRole(ACCOUNTING_MANAGER_ROLE); ``` **Parameters** |Name|Type|Description| |----|----|-----------| |`retention`|`uint256`|Period in seconds to retain bond lock| #### Function: addBondCurve Add a new bond curve ```solidity function addBondCurve(uint256[] memory bondCurve) external onlyRole(ADD_BOND_CURVE_ROLE) returns (uint256); ``` **Parameters** |Name|Type|Description| |----|----|-----------| |`bondCurve`|`uint256[]`|Bond curve definition to add| #### Function: setDefaultBondCurve Set default bond curve ```solidity function setDefaultBondCurve(uint256 curveId) external onlyRole(SET_DEFAULT_BOND_CURVE_ROLE); ``` **Parameters** |Name|Type|Description| |----|----|-----------| |`curveId`|`uint256`|ID of the bond curve to set as default| #### Function: setBondCurve Set the bond curve for the given Node Operator ```solidity function setBondCurve(uint256 nodeOperatorId, uint256 curveId) external onlyRole(SET_BOND_CURVE_ROLE); ``` **Parameters** |Name|Type|Description| |----|----|-----------| |`nodeOperatorId`|`uint256`|ID of the Node Operator| |`curveId`|`uint256`|ID of the bond curve to set| #### Function: resetBondCurve Reset bond curve to the default one for the given Node Operator ```solidity function resetBondCurve(uint256 nodeOperatorId) external onlyRole(RESET_BOND_CURVE_ROLE); ``` **Parameters** |Name|Type|Description| |----|----|-----------| |`nodeOperatorId`|`uint256`|ID of the Node Operator| #### Function: depositETH Stake user's ETH with Lido and deposit stETH to the bond *小alled by CSM exclusively* ```solidity function depositETH(address from, uint256 nodeOperatorId) external payable whenResumed onlyCSM returns (uint256 shares); ``` **Parameters** |Name|Type|Description| |----|----|-----------| |`from`|`address`|Address to stake ETH and deposit stETH from| |`nodeOperatorId`|`uint256`|ID of the Node Operator| **Returns** |Name|Type|Description| |----|----|-----------| |`shares`|`uint256`|stETH shares amount| #### Function: depositStETH Deposit user's stETH to the bond for the given Node Operator *小alled by CSM exclusively* ```solidity function depositStETH(address from, uint256 nodeOperatorId, uint256 stETHAmount, PermitInput calldata permit) external whenResumed onlyCSM returns (uint256 shares); ``` **Parameters** |Name|Type|Description| |----|----|-----------| |`from`|`address`|Address to deposit stETH from| |`nodeOperatorId`|`uint256`|ID of the Node Operator| |`stETHAmount`|`uint256`|Amount of stETH to deposit| |`permit`|`PermitInput`|stETH permit for the contract| **Returns** |Name|Type|Description| |----|----|-----------| |`shares`|`uint256`|stETH shares amount| #### Function: depositWstETH Unwrap the user's wstETH and deposit stETH to the bond for the given Node Operator *小alled by CSM exclusively* ```solidity function depositWstETH(address from, uint256 nodeOperatorId, uint256 wstETHAmount, PermitInput calldata permit) external whenResumed onlyCSM returns (uint256 shares); ``` **Parameters** |Name|Type|Description| |----|----|-----------| |`from`|`address`|Address to unwrap wstETH from| |`nodeOperatorId`|`uint256`|ID of the Node Operator| |`wstETHAmount`|`uint256`|Amount of wstETH to deposit| |`permit`|`PermitInput`|wstETH permit for the contract| **Returns** |Name|Type|Description| |----|----|-----------| |`shares`|`uint256`|stETH shares amount| #### Function: claimRewardsStETH 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 *小alled by CSM exclusively* ```solidity function claimRewardsStETH( uint256 nodeOperatorId, uint256 stETHAmount, uint256 cumulativeFeeShares, bytes32[] memory rewardsProof ) external whenResumed onlyCSM; ``` **Parameters** |Name|Type|Description| |----|----|-----------| |`nodeOperatorId`|`uint256`|ID of the Node Operator| |`stETHAmount`|`uint256`|Amount of stETH to claim| |`cumulativeFeeShares`|`uint256`|Cumulative fee stETH shares for the Node Operator| |`rewardsProof`|`bytes32[]`|Merkle proof of the rewards| #### Function: claimRewardsWstETH 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 *小alled by CSM exclusively* ```solidity function claimRewardsWstETH( uint256 nodeOperatorId, uint256 wstETHAmount, uint256 cumulativeFeeShares, bytes32[] memory rewardsProof ) external whenResumed onlyCSM; ``` **Parameters** |Name|Type|Description| |----|----|-----------| |`nodeOperatorId`|`uint256`|ID of the Node Operator| |`wstETHAmount`|`uint256`|Amount of wstETH to claim| |`cumulativeFeeShares`|`uint256`|Cumulative fee stETH shares for the Node Operator| |`rewardsProof`|`bytes32[]`|Merkle proof of the rewards| #### Function: requestRewardsETH Request full reward (fee + bond) in Withdrawal NFT (unstETH) for the given Node Operator available for this moment. `rewardsProof` and `cumulativeFeeShares` might be empty in order to claim only excess bond *Reverts if amount isn't between `MIN_STETH_WITHDRAWAL_AMOUNT` and `MAX_STETH_WITHDRAWAL_AMOUNT`* *小alled by CSM exclusively* ```solidity function requestRewardsETH( uint256 nodeOperatorId, uint256 ethAmount, uint256 cumulativeFeeShares, bytes32[] memory rewardsProof ) external whenResumed onlyCSM; ``` **Parameters** |Name|Type|Description| |----|----|-----------| |`nodeOperatorId`|`uint256`|ID of the Node Operator| |`ethAmount`|`uint256`|Amount of ETH to request| |`cumulativeFeeShares`|`uint256`|Cumulative fee stETH shares for the Node Operator| |`rewardsProof`|`bytes32[]`|Merkle proof of the rewards| #### Function: lockBondETH Lock bond in ETH for the given Node Operator *小alled by CSM exclusively* ```solidity function lockBondETH(uint256 nodeOperatorId, uint256 amount) external onlyCSM; ``` **Parameters** |Name|Type|Description| |----|----|-----------| |`nodeOperatorId`|`uint256`|ID of the Node Operator| |`amount`|`uint256`|Amount to lock in ETH (stETH)| #### Function: releaseLockedBondETH Release locked bond in ETH for the given Node Operator *小alled by CSM exclusively* ```solidity function releaseLockedBondETH(uint256 nodeOperatorId, uint256 amount) external onlyCSM; ``` **Parameters** |Name|Type|Description| |----|----|-----------| |`nodeOperatorId`|`uint256`|ID of the Node Operator| |`amount`|`uint256`|Amount to release in ETH (stETH)| #### Function: compensateLockedBondETH Compensate locked bond ETH for the given Node Operator ```solidity function compensateLockedBondETH(uint256 nodeOperatorId) external payable onlyCSM; ``` **Parameters** |Name|Type|Description| |----|----|-----------| |`nodeOperatorId`|`uint256`|ID of the Node Operator| #### Function: settleLockedBondETH Settle locked bond ETH for the given Node Operator *小alled by CSM exclusively* ```solidity function settleLockedBondETH(uint256 nodeOperatorId) external onlyCSM returns (uint256 lockedAmount); ``` **Parameters** |Name|Type|Description| |----|----|-----------| |`nodeOperatorId`|`uint256`|ID of the Node Operator| #### Function: penalize Penalize bond by burning stETH shares of the given Node Operator *小alled by CSM exclusively* ```solidity function penalize(uint256 nodeOperatorId, uint256 amount) external onlyCSM; ``` **Parameters** |Name|Type|Description| |----|----|-----------| |`nodeOperatorId`|`uint256`|ID of the Node Operator| |`amount`|`uint256`|Amount to penalize in ETH (stETH)| #### Function: chargeFee Charge fee from bond by transferring stETH shares of the given Node Operator to the charge recipient *小alled by CSM exclusively* ```solidity function chargeFee(uint256 nodeOperatorId, uint256 amount) external onlyCSM; ``` **Parameters** |Name|Type|Description| |----|----|-----------| |`nodeOperatorId`|`uint256`|ID of the Node Operator| |`amount`|`uint256`|Amount to charge in ETH (stETH)| #### Function: recoverERC20 Recover ERC20 tokens from the contract ```solidity function recoverERC20(address token, uint256 amount) external override; ``` **Parameters** |Name|Type|Description| |----|----|-----------| |`token`|`address`|Address of the ERC20 token to recover| |`amount`|`uint256`|Amount of the ERC20 token to recover| #### Function: recoverStETHShares Recover all stETH shares from the contract *Accounts for the bond funds stored during recovery* ```solidity function recoverStETHShares() external; ``` #### Function: getBondSummary Get current and required bond amounts in ETH (stETH) for the given Node Operator *To calculate excess bond amount subtract `required` from `current` value. To calculate missed bond amount subtract `current` from `required` value* ```solidity function getBondSummary(uint256 nodeOperatorId) public view returns (uint256 current, uint256 required); ``` **Parameters** |Name|Type|Description| |----|----|-----------| |`nodeOperatorId`|`uint256`|ID of the Node Operator| **Returns** |Name|Type|Description| |----|----|-----------| |`current`|`uint256`|Current bond amount in ETH| |`required`|`uint256`|Required bond amount in ETH| #### Function: getBondSummaryShares Get current and required bond amounts in stETH shares for the given Node Operator *To calculate excess bond amount subtract `required` from `current` value. To calculate missed bond amount subtract `current` from `required` value* ```solidity function getBondSummaryShares(uint256 nodeOperatorId) public view returns (uint256 current, uint256 required); ``` **Parameters** |Name|Type|Description| |----|----|-----------| |`nodeOperatorId`|`uint256`|ID of the Node Operator| **Returns** |Name|Type|Description| |----|----|-----------| |`current`|`uint256`|Current bond amount in stETH shares| |`required`|`uint256`|Required bond amount in stETH shares| #### Function: getUnbondedKeysCount Get the number of the unbonded keys ```solidity function getUnbondedKeysCount(uint256 nodeOperatorId) public view returns (uint256); ``` **Parameters** |Name|Type|Description| |----|----|-----------| |`nodeOperatorId`|`uint256`|ID of the Node Operator| **Returns** | Name | Type | Description | | -------- | --------- | ------------------- | | `<none>` | `uint256` Unbonded keys count nt | #### Function: getUnbondedKeysCountToEject Get the number of the unbonded keys to be ejected using a forcedTargetLimit ```solidity function getUnbondedKeysCountToEject(uint256 nodeOperatorId) public view returns (uint256); ``` **Parameters** |Name|Type|Description| |----|----|-----------| |`nodeOperatorId`|`uint256`|ID of the Node Operator| **Returns** | Name | Type | Description | | -------- | --------- | ------------------- | | `<none>` | `uint256` Unbonded keys count nt | #### Function: getRequiredBondForNextKeys Get the required bond in ETH (inc. missed and excess) for the given Node Operator to upload new deposit data ```solidity function getRequiredBondForNextKeys(uint256 nodeOperatorId, uint256 additionalKeys) public view returns (uint256); ``` **Parameters** |Name|Type|Description| |----|----|-----------| |`nodeOperatorId`|`uint256`|ID of the Node Operator| |`additionalKeys`|`uint256`|Number of new keys to add| **Returns** | Name | Type | Description | | -------- | --------- | --------------------------- | | `<none>` | `uint256` Required bond amount in ETH TH | #### Function: getBondAmountByKeysCountWstETH Get the bond amount in wstETH required for the `keysCount` keys using the default bond curve ```solidity function getBondAmountByKeysCountWstETH(uint256 keysCount) public view returns (uint256); ``` **Parameters** | Name | Type | Description | | ----------- | --------- | ------------------------------------------------ | | `keysCount` | `uint256` | Keys count to calculate the required bond amount | #### Function: getBondAmountByKeysCountWstETH Get the bond amount in wstETH required for the `keysCount` keys using the custom bond curve ```solidity function getBondAmountByKeysCountWstETH(uint256 keysCount, BondCurve memory curve) public view returns (uint256); ``` **Parameters** |Name|Type|Description| |----|----|-----------| |`keysCount`|`uint256`|Keys count to calculate the required bond amount| |`curve`|`BondCurve`|Bond curve definition. Use CSBondCurve.getBondCurve(id) method to get the definition for the exiting curve| #### Function: getRequiredBondForNextKeysWstETH Get the required bond in wstETH (inc. missed and excess) for the given Node Operator to upload new keys ```solidity function getRequiredBondForNextKeysWstETH(uint256 nodeOperatorId, uint256 additionalKeys) public view returns (uint256); ``` **Parameters** |Name|Type|Description| |----|----|-----------| |`nodeOperatorId`|`uint256`|ID of the Node Operator| |`additionalKeys`|`uint256`|Number of new keys to add| **Returns** | Name | Type | Description | | -------- | --------- | ----------------------- | | `<none>` | `uint256` Required bond in wstETH TH | #### Function: totalBondShares Get total bond shares (stETH) stored on the contract ```solidity function totalBondShares() public view returns (uint256); ``` **Returns** | Name | Type | Description | | -------- | --------- | ------------------------- | | `<none>` | `uint256` Total bond shares (stETH) H) | #### Function: getBondShares Get bond shares (stETH) for the given Node Operator ```solidity function getBondShares(uint256 nodeOperatorId) public view returns (uint256); ``` **Parameters** |Name|Type|Description| |----|----|-----------| |`nodeOperatorId`|`uint256`|ID of the Node Operator| **Returns** | Name | Type | Description | | -------- | --------- | -------------------- | | `<none>` | `uint256` Bond in stETH shares es | #### Function: getBond Get bond amount in ETH (stETH) for the given Node Operator ```solidity function getBond(uint256 nodeOperatorId) public view returns (uint256); ``` **Parameters** |Name|Type|Description| |----|----|-----------| |`nodeOperatorId`|`uint256`|ID of the Node Operator| **Returns** | Name | Type | Description | | -------- | --------- | -------------------------- | | `<none>` | `uint256` Bond amount in ETH (stETH) H) | #### Function: defaultBondCurveId ```solidity function defaultBondCurveId() public view returns (uint256); ``` #### Function: getCurveInfo Return bond curve for the given curve id *Get default bond curve info if `curveId` is `0` or invalid* ```solidity function getCurveInfo(uint256 curveId) public view returns (BondCurve memory); ``` **Parameters** |Name|Type|Description| |----|----|-----------| |`curveId`|`uint256`|Curve id to get bond curve for| **Returns** | Name | Type | Description | | -------- | ----------- | ----------- | | `<none>` | `BondCurve` | Bond curve | #### Function: getBondCurve Get bond curve for the given Node Operator ```solidity function getBondCurve(uint256 nodeOperatorId) public view returns (BondCurve memory); ``` **Parameters** |Name|Type|Description| |----|----|-----------| |`nodeOperatorId`|`uint256`|ID of the Node Operator| **Returns** | Name | Type | Description | | -------- | ----------- | ----------- | | `<none>` | `BondCurve` | Bond curve | #### Function: getBondAmountByKeysCount Get required bond in ETH for the given number of keys for default bond curve *To calculate the amount for the new keys 2 calls are required: getBondAmountByKeysCount(newTotal) - getBondAmountByKeysCount(currentTotal)* ```solidity function getBondAmountByKeysCount(uint256 keys) public view returns (uint256); ``` **Parameters** |Name|Type|Description| |----|----|-----------| |`keys`|`uint256`|Number of keys to get required bond for| **Returns** | Name | Type | Description | | -------- | --------- | -------------------------------- | | `<none>` | `uint256` Amount for particular keys count nt | #### Function: getKeysCountByBondAmount Get keys count for the given bond amount with default bond curve ```solidity function getKeysCountByBondAmount(uint256 amount) public view returns (uint256); ``` **Parameters** |Name|Type|Description| |----|----|-----------| |`amount`|`uint256`|Bond amount in ETH (stETH)to get keys count for| **Returns** | Name | Type | Description | | -------- | --------- | ----------- | | `<none>` | `uint256` | Keys count | #### Function: getBondAmountByKeysCount Get required bond in ETH for the given number of keys for particular bond curve. *To calculate the amount for the new keys 2 calls are required: getBondAmountByKeysCount(newTotal, curve) - getBondAmountByKeysCount(currentTotal, curve)* ```solidity function getBondAmountByKeysCount(uint256 keys, BondCurve memory curve) public pure returns (uint256); ``` **Parameters** |Name|Type|Description| |----|----|-----------| |`keys`|`uint256`|Number of keys to get required bond for| |`curve`|`BondCurve`|Bond curve to get required bond for| **Returns** | Name | Type | Description | | -------- | --------- | ------------------------------------------------------------- | | `<none>` | `uint256` Required bond amount in ETH (stETH) for particular keys count nt | #### Function: getKeysCountByBondAmount Get keys count for the given bond amount for particular bond curve. ```solidity function getKeysCountByBondAmount(uint256 amount, BondCurve memory curve) public pure returns (uint256); ``` **Parameters** |Name|Type|Description| |----|----|-----------| |`amount`|`uint256`|Bond amount to get keys count for| |`curve`|`BondCurve`|Bond curve to get keys count for| **Returns** | Name | Type | Description | | -------- | --------- | ----------- | | `<none>` | `uint256` | Keys count | #### Function: getBondLockRetentionPeriod Get default bond lock retention period ```solidity function getBondLockRetentionPeriod() external view returns (uint256 retention); ``` **Returns** |Name|Type|Description| |----|----|-----------| |`retention`|`uint256`|Default bond lock retention period| #### Function: getLockedBondInfo Get information about the locked bond for the given Node Operator ```solidity function getLockedBondInfo(uint256 nodeOperatorId) public view returns (BondLock memory); ``` **Parameters** |Name|Type|Description| |----|----|-----------| |`nodeOperatorId`|`uint256`|ID of the Node Operator| **Returns** | Name | Type | Description | | -------- | ---------- | ---------------- | | `<none>` | `BondLock` Locked bond info fo | #### Function: getActualLockedBond Get amount of the locked bond in ETH (stETH) by the given Node Operator ```solidity function getActualLockedBond(uint256 nodeOperatorId) public view returns (uint256); ``` **Parameters** |Name|Type|Description| |----|----|-----------| |`nodeOperatorId`|`uint256`|ID of the Node Operator| **Returns** | Name | Type | Description | | -------- | --------- | -------------------------------- | | `<none>` | `uint256` Amount of the actual locked bond nd | ### `CSVerifier.sol` #### Function: processSlashingProof Verify slashing proof and report slashing to the module for valid proofs ```solidity function processSlashingProof( ProvableBeaconBlockHeader calldata beaconBlock, SlashingWitness calldata witness, uint256 nodeOperatorId, uint256 keyIndex ) external; ``` **Parameters** |Name|Type|Description| |----|----|-----------| |`beaconBlock`|`ProvableBeaconBlockHeader`|Beacon block header| |`witness`|`SlashingWitness`|Slashing witness| |`nodeOperatorId`|`uint256`|ID of the Node Operator| |`keyIndex`|`uint256`|Index of the validator key in the Node Operator's key storage| #### Function: processWithdrawalProof Verify withdrawal proof and report withdrawal to the module for valid proofs ```solidity function processWithdrawalProof( ProvableBeaconBlockHeader calldata beaconBlock, WithdrawalWitness calldata witness, uint256 nodeOperatorId, uint256 keyIndex ) external; ``` **Parameters** |Name|Type|Description| |----|----|-----------| |`beaconBlock`|`ProvableBeaconBlockHeader`|Beacon block header| |`witness`|`WithdrawalWitness`|Withdrawal witness| |`nodeOperatorId`|`uint256`|ID of the Node Operator| |`keyIndex`|`uint256`|Index of the validator key in the Node Operator's key storage| #### Function: processHistoricalWithdrawalProof Verify withdrawal proof against historical summaries data and report withdrawal to the module for valid proofs ```solidity function processHistoricalWithdrawalProof( ProvableBeaconBlockHeader calldata beaconBlock, HistoricalHeaderWitness calldata oldBlock, WithdrawalWitness calldata witness, uint256 nodeOperatorId, uint256 keyIndex ) external; ``` **Parameters** |Name|Type|Description| |----|----|-----------| |`beaconBlock`|`ProvableBeaconBlockHeader`|Beacon block header| |`oldBlock`|`HistoricalHeaderWitness`|Historical block header witness| |`witness`|`WithdrawalWitness`|Withdrawal witness| |`nodeOperatorId`|`uint256`|ID of the Node Operator| |`keyIndex`|`uint256`|Index of the validator key in the Node Operator's key storage| ### `CSEarlyAdoption.sol` #### Function: consume Validate EA eligibility proof and mark it as consumed *Called only by the module* ```solidity function consume(address sender, bytes32[] calldata proof) external; ``` **Parameters** |Name|Type|Description| |----|----|-----------| |`sender`|`address`|Address to be verified alongside the proof| |`proof`|`bytes32[]`|Merkle proof of EA eligibility| #### Function: consumed Check if the address has already claimed EA access ```solidity function consumed(address sender) external view returns (bool); ``` **Parameters** |Name|Type|Description| |----|----|-----------| |`sender`|`address`|Address to check| #### Function: isEligible Check if the address is eligible to claim EA access ```solidity function isEligible(address sender, bytes32[] calldata proof) public view returns (bool); ``` **Parameters** |Name|Type|Description| |----|----|-----------| |`sender`|`address`|Address to check| |`proof`|`bytes32[]`|Merkle proof of EA eligibility| ### `CSFeeDistributor.sol` #### Function: distributeFees Distribute fees to the Accounting in favor of the Node Operator ```solidity function distributeFees(uint256 nodeOperatorId, uint256 shares, bytes32[] calldata proof) external onlyAccounting returns (uint256); ``` **Parameters** |Name|Type|Description| |----|----|-----------| |`nodeOperatorId`|`uint256`|ID of the Node Operator| |`shares`|`uint256`|Total Amount of stETH shares earned as fees| |`proof`|`bytes32[]`|Merkle proof of the leaf| **Returns** |Name|Type|Description| |----|----|-----------| |`<none>`|`uint256`|Amount of stETH shares distributed| #### Function: processOracleReport Receive the data of the Merkle tree from the Oracle contract and process it ```solidity function processOracleReport(bytes32 _treeRoot, string calldata _treeCid, uint256 distributed) external onlyRole(ORACLE_ROLE); ``` #### Function: recoverERC20 Recover ERC20 tokens (except for stETH) from the contract *Any stETH transferred to feeDistributor is treated as a donation and can not be recovered* ```solidity function recoverERC20(address token, uint256 amount) external override; ``` **Parameters** |Name|Type|Description| |----|----|-----------| |`token`|`address`|Address of the ERC20 token to recover| |`amount`|`uint256`|Amount of the ERC20 token to recover| #### Function: pendingToDistribute Get the Amount of stETH shares that are pending to be distributed ```solidity function pendingToDistribute() external view returns (uint256); ``` #### Function: getFeesToDistribute Get the Amount of stETH shares that can be distributed in favor of the Node Operator ```solidity function getFeesToDistribute(uint256 nodeOperatorId, uint256 shares, bytes32[] calldata proof) public view returns (uint256); ``` **Parameters** |Name|Type|Description| |----|----|-----------| |`nodeOperatorId`|`uint256`|ID of the Node Operator| |`shares`|`uint256`|Total Amount of stETH shares earned as fees| |`proof`|`bytes32[]`|Merkle proof of the leaf| **Returns** |Name|Type|Description| |----|----|-----------| |`<none>`|`uint256`|Amount of stETH shares that can be distributed| #### Function: hashLeaf Get a hash of a leaf *Double hash the leaf to prevent second preimage attacks* ```solidity function hashLeaf(uint256 nodeOperatorId, uint256 shares) public pure returns (bytes32); ``` **Parameters** |Name|Type|Description| |----|----|-----------| |`nodeOperatorId`|`uint256`|ID of the Node Operator| |`shares`|`uint256`|Amount of stETH shares| ### `CSFeeOracle.sol` #### Function: setFeeDistributorContract Set a new fee distributor contract *_setFeeDistributorContract() reverts if zero address* ```solidity function setFeeDistributorContract(address feeDistributorContract) external onlyRole(CONTRACT_MANAGER_ROLE); ``` **Parameters** |Name|Type|Description| |----|----|-----------| |`feeDistributorContract`|`address`|Address of the new fee distributor contract| #### Function: setPerformanceThreshold Set a new performance threshold value in basis points ```solidity function setPerformanceThreshold(uint256 valueBP) external onlyRole(CONTRACT_MANAGER_ROLE); ``` **Parameters** |Name|Type|Description| |----|----|-----------| |`valueBP`|`uint256`|performance threshold in basis points| #### Function: submitReportData Submit the data for a committee report ```solidity function submitReportData(ReportData calldata data, uint256 contractVersion) external whenResumed; ``` **Parameters** |Name|Type|Description| |----|----|-----------| |`data`|`ReportData`|Data for a committee report| |`contractVersion`|`uint256`|Version of the oracle consensus rules| #### Function: resume Resume accepting oracle reports ```solidity function resume() external whenPaused onlyRole(RESUME_ROLE); ``` #### Function: pauseFor Pause accepting oracle reports for a `duration` seconds ```solidity function pauseFor(uint256 duration) external onlyRole(PAUSE_ROLE); ``` **Parameters** |Name|Type|Description| |----|----|-----------| |`duration`|`uint256`|Duration of the pause in seconds| #### Function: pauseUntil Pause accepting oracle reports until a timestamp ```solidity function pauseUntil(uint256 pauseUntilInclusive) external onlyRole(PAUSE_ROLE); ``` **Parameters** |Name|Type|Description| |----|----|-----------| |`pauseUntilInclusive`|`uint256`|Timestamp until which the oracle reports are paused| #### Function: getConsensusContract Returns the address of the HashConsensus contract. ```solidity function getConsensusContract() external view returns (address); ``` #### Function: setConsensusContract Sets the address of the HashConsensus contract. ```solidity function setConsensusContract(address addr) external onlyRole(MANAGE_CONSENSUS_CONTRACT_ROLE); ``` #### Function: getConsensusVersion Returns the current consensus version expected by the oracle contract. Consensus version must change every time consensus rules change, meaning that an oracle looking at the same reference slot would calculate a different hash. ```solidity function getConsensusVersion() external view returns (uint256); ``` #### Function: setConsensusVersion Sets the consensus version expected by the oracle contract. ```solidity function setConsensusVersion(uint256 version) external onlyRole(MANAGE_CONSENSUS_VERSION_ROLE); ``` #### Function: getConsensusReport Data provider interface Returns the last consensus report hash and metadata. ```solidity function getConsensusReport() external view returns (bytes32 hash, uint256 refSlot, uint256 processingDeadlineTime, bool processingStarted); ``` #### Function: submitConsensusReport Consensus contract interface Called by HashConsensus contract to push a consensus report for processing. Note that submitting the report doesn't require the processor to start processing it right away, this can happen later (see `getLastProcessingRefSlot`). Until processing is started, HashConsensus is free to reach consensus on another report for the same reporting frame an submit it using this same function, or to lose the consensus on the submitted report, notifying the processor via `discardConsensusReport`. ```solidity function submitConsensusReport(bytes32 reportHash, uint256 refSlot, uint256 deadline) external; ``` #### Function: discardConsensusReport Called by HashConsensus contract to notify that the report for the given ref. slot is not a conensus report anymore and should be discarded. This can happen when a member changes their report, is removed from the set, or when the quorum value gets increased. Only called when, for the given reference slot: 1. there previously was a consensus report; AND 1. processing of the consensus report hasn't started yet; AND 2. report processing deadline is not expired yet; AND 3. there's no consensus report now (otherwise, `submitConsensusReport` is called instead). Can be called even when there's no submitted non-discarded consensus report for the current reference slot, i.e. can be called multiple times in succession. ```solidity function discardConsensusReport(uint256 refSlot) external; ``` #### Function: getLastProcessingRefSlot Returns the last reference slot for which processing of the report was started. ```solidity function getLastProcessingRefSlot() external view returns (uint256); ``` ## Administrative actions Community Staking Module contracts support a set of administrative actions, including: - Changing the configuration options. - Upgrading the system's code. Each of these actions can only be performed by a designated admin (`DEFAULT_ADMIN_ROLE`) (set by a configuration option). ## Roles to actors mapping ### `CSModule.sol` | Role | Assignee | | ----------------------------------------- | ---------------------- | | `DEFAULT_ADMIN_ROLE` | Aragon Voting | | `PAUSE_ROLE` | Gate Seal contract | | `RESUME_ROLE` | Aragon Voting | | `MODULE_MANAGER_ROLE` | Aragon Voting | | `STAKING_ROUTER_ROLE` | StakingRouter contract | | `REPORT_EL_REWARDS_STEALING_PENALTY_ROLE` | CS Multisig or Bot EOA | | `SETTLE_EL_REWARDS_STEALING_PENALTY_ROLE` | Dedicated EasyTrack | | `VERIFIER_ROLE` | `CSVerifier.sol` | | `RECOVERER_ROLE` | Aragon Voting | ### `CSAccounting.sol` | Role | Assignee | | ----------------------------- | ------------------------------ | | `DEFAULT_ADMIN_ROLE` | Aragon Voting | | `PAUSE_ROLE` | Gate Seal contract | | `RESUME_ROLE` | Aragon Voting | | `ACCOUNTING_MANAGER_ROLE` | Aragon Voting | | `ADD_BOND_CURVE_ROLE` | Aragon Voting | | `SET_DEFAULT_BOND_CURVE_ROLE` | Aragon Voting | | `SET_BOND_CURVE_ROLE` | CS Multisig and `CSModule.sol` | | `RESET_BOND_CURVE_ROLE` | CS Multisig and `CSModule.sol` | | `RECOVERER_ROLE` | Aragon Voting | ### `CSFeeDistributor.sol` | Role | Assignee | | -------------------- | ----------------- | | `DEFAULT_ADMIN_ROLE` | Aragon Voting | | `RECOVERER_ROLE` | Aragon Voting | | `ORACLE_ROLE` | `CSFeeOracle.sol` | ### `CSFeeOracle.sol` | Role | Assignee | | -------------------------------- | ----------------------- | | `DEFAULT_ADMIN_ROLE` | Aragon Voting | | `CONTRACT_MANAGER_ROLE` | Aragon Voting | | `SUBMIT_DATA_ROLE` | Not assigned by default | | `PAUSE_ROLE` | GateSeal contract | | `RESUME_ROLE` | Aragon Voting | | `RECOVERER_ROLE` | Aragon Voting | | `MANAGE_CONSENSUS_CONTRACT_ROLE` | Aragon Voting | | `MANAGE_CONSENSUS_VERSION_ROLE` | Aragon Voting | ### `HashConsensus.sol` | Role | Assignee | | -------------------------------- | ------------- | | `DEFAULT_ADMIN_ROLE` | Aragon Voting | | `MANAGE_MEMBERS_AND_QUORUM_ROLE` | Aragon Voting | | `DISABLE_CONSENSUS_ROLE` | Aragon Voting | | `MANAGE_FRAME_CONFIG_ROLE` | Aragon Voting | | `MANAGE_FAST_LANE_CONFIG_ROLE` | Aragon Voting | | `MANAGE_REPORT_PROCESSOR_ROLE` | Aragon Voting | ### `CSEarlyAdoption.sol` Roles are not used for this contract. ### `CSVerifier.sol` Roles are not used for this contract. ## Upgradability `CSmodule.sol`, `CSAccounting.sol`, `CSFeeOracle.sol`, and `CSFeeDistributor.sol` are upgradable using [OssifiableProxy](https://github.com/lidofinance/community-staking-module/blob/main/src/lib/proxy/OssifiableProxy.sol) contracts. `CSVerifier.sol` is not upgradable and should be re-deployed if needed. ## Security considerations ### Bond exposure to negative stETH rebase The bond stored in stETH inevitably inherits all stETH features, including the possibility of a negative rebase. The effective bond amount (counted in ETH) will decrease in case of a negative rebase. This can lead to the case when a relatively large Node Operator might end up with unbonded keys. Also, it might result in an effective bond being lower than the bond required. Hence, Node Operators will lose part of their rewards. ### Malicious Oracles can steal all unclaimed CSM rewards A single updatable Merkle tree approach to the rewards distribution allows malicious Oracles to collude and submit a version of the Merkle tree, indicating that all rewards should be allocated to a single Node Operator (previously created by malicious actors). The worst-case scenario is when all unclaimed rewards stored on the CSM contract will be available for claim by a single Node Operator.