# 📝 Curated Module v2. Spec

Curated Module v2 (CM v2) is a new chapter for the Lido protocol. Based on the solid codebase of CSM, it inherits new concepts introduced in the latest [CSM v3](https://hackmd.io/@lido/csm-v3-spec), like support for `0x02` validators, integration features, and many more. A robust bonding mechanism allows CM v2 to streamline Node Operator management, reduce operational complexity, and increase protocol security. With CM v2, the curated part of the Lido Validator set will benefit from all the advantages CSM has to offer.
Since most of the essential features of CM v2 are inherited from the latest version of CSM (CSM v3), this document will be framed as an amendment to the original [CSM v3 Specification](https://hackmd.io/@lido/csm-v3-spec). It will only cover aspects that are new or different in CM v2 compared to CSM v3.
> 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.33](https://github.com/ethereum/solidity/tree/v0.8.33)
- Developed in [Foundry](https://github.com/foundry-rs/foundry)
## General Architecture

The scheme above illustrates the smart contract architecture of CM v2 and the changes made compared to CSM v3.
### New and changed contracts
:::info
For the missing contract descriptions, please refer to the [CSM v3 Specification](https://hackmd.io/@lido/csm-v3-spec).
:::
#### `CuratedModule.sol`
*CuratedModule on the scheme*
:::info
New in CM v2
:::
`CuratedModule.sol` is a core module contract conforming to the `IStakingModule` and `IStakingModuleV2` interfaces. The contract inherits from `BaseModule.sol` (cotains all common parts with CSM) to allow for the extended functionality while maintaining consistency with the original CSM. It stores information about Node Operators and deposit data (DD). This contract is responsible for all interactions with the `StakingRouter`, namely, the DD management and some of the Node Operator's parameters. Node Operators manage their validator keys and other parameters that they can modify through this contract.
**Changes compared to CSM v3:**
- The management system for the NodeOperators address is changed to allow custom role members to set both `rewardAddress` and `managerAddress` in emergency case.
- Stake distribution is done via a ["greedy" weighted stake allocation strategy](#Allocation-Strategy).
#### `CuratedGate.sol`
*CuratedGates on the scheme*
:::info
New in CM v2
:::
`CuratedGate.sol` is a supplementary contract that enables Node Operator creation for the vetted addresses, which serves as an entry point to `CuratedModule.sol`. Alongside Node Operator creation, a contract can assign a custom Node Operator type (bondCurveId) in `Accounting.sol`. Deployed using `CuratedGateFactory.sol` to allow the addition of the new instances later without additional code security audits. The list of curated participants is individually upgradable for each instance of the `CuratedGate.sol` using a dedicated EasyTrack factory.
#### `MetaOperatorsRegistry.sol`
*MetaRegistry on the scheme*
:::info
New in CM v2
:::
`MetaOperatorsRegistry.sol` is a supplementary contract that stores information about `OperatorGroups` and operator weights (see [Meta Operators Registry](#Meta-Operators-Registry)).
It also stores Node Operator names and descriptions. Both Node Operators and CM v2 Committee multisig can edit the values.
### Off-chain tools and actors
#### `CM Bot`
:::info
Changed in CM v2
:::
`CM Bot` is a daemon application that monitors and reports withdrawal, slashing, and consolidation events associated with CM validators. Also responsible for validator ejection invocation due to strikes.
> [CM Bot repo](https://github.com/lidofinance/csm-prover-tool)
**Changes in CM v2:**
- The bot can now also serve CM v2. There will be a separate instances for CSM and CM.
#### `CM Oracle`
:::info
Changed in CM v2
:::
`CM Oracle` (also known as CM Performance Oracle) is a module in the common Lido on Ethereum (LoE) Oracle set. It is operated by the existing Oracles set alongside [Accounting Oracle](https://docs.lido.fi/contracts/accounting-oracle), [Validator Exit Bus Oracle](https://docs.lido.fi/contracts/validators-exit-bus-oracle), and CSM Performance Oracle. It is responsible for calculating the CM v2 Node Operators' reward distribution and strike assignment based on their performance on the CL. `CM Oracle` uses the same code as [`CSM Oracle`](https://hackmd.io/@lido/csm-v3-spec#CSM-Oracle) does.
#### `CMC`
:::info
New in CM v2
:::
`CMC` (also known as CM Committee Multisig) is an off-chain committee responsible for overseeing CM v2 and managing several flows and operations within it. Somewhat similar to [Community Staking Module Committee](https://research.lido.fi/t/community-staking-module-committee/8333) Multisig, but with the extended permissions.
## Fundamental concepts
### Meta Operators Registry
`CuratedModule.sol` uses `MetaOperatorsRegistry.sol` contract to fetch information about Node Operators' stake and allocation weights. `MetaOperatorsRegistry.sol` has a permissioned method (expected to be called by Curated Module Committee) to add information about `OperatorGroup`. `OperatorGroup` has the following form:
```solidity
struct OperatorGroup {
SubNodeOperator[] subNodeOperators, // A list of sub-node operators
ExternalOperator[] externalOperators // A list of external operators data
}
struct SubNodeOperator {
uint64 nodeOperatorId // Id of the sub node operator in CMv2
uint16 share // Sub-node operator share in BP
}
struct ExternalOperator {
uint8 externalOpperatorType // Type of the external operator
bytes externalOperatorData // Generelized data about external operator outside CMv2
}
```
These structures are used to determine effective weights for sub-node operators and to account for stake in external modules.
#### Sub-node operator weights
Sub-node operator weights are calculated as follows. Assume we have 3 sub-node-operators with weights $w_1$, $w_2$, $w_3$, and the sub-node operator shares $s_1$, $s_2$, $s_3$ . Then, effective weights for the sub-node operators are $ew_1 = (w_1*s_1)/10000$, $ew_2 =(w_2*s_2)/10000$, $ew_3 =(w_3*s_3)/10000$ respectively.

The sum of the effective weights should not exceed $max(w_1, w_2, w_3)$. Hence, $\sum_{i=1}^{n}s_i \le 10000$. To ensure consistency we can require this sum to be equal to 10_000 BP when creating, or updating `OperatorGroup`.
`SubNodeOperator.share` allows node operators to determine stake distribution between their sub-node operators. For example, if the operator wants to get ~80% of the stake allocated to their DVT setup, and the rest to the vanilla sub-node operator, they can set shares to 8000 BP, and 2000 BP.
:::info
`SubNodeOperator.share` does not guarantee the exact stake split between operators, but rather determine the weight distribution between sub-node operators If sub-node operators have different original weights.
:::
#### Accounting for the external stake
Now we need to determine how to account for the stake in the external modules. We need to fetch active stake for all `ExternalOperator` record in `OperatorGroup`, sum it up into $totalExternalStake$ and calculate additional stake for each sub-node-operator in the group as $es_1 = totalExternalStake * ew_1 /(ew_1+ew_2+ew_3)$, $es_2 = totalExternalStake * ew_2 /(ew_1+ew_2+ew_3)$, and $es_3 = totalExternalStake * ew_3 /(ew_1+ew_2+ew_3)$ respectively. This will allow us to split the external stake with respect to the sub-node-operators' effective weights and ensure fair stake allocation.

#### Restrictions
- One operator can be a part of only one `OperatorGroup`.
- One external operator can be associated with only one `OperatorGroup`.
- Only CMv1 should be supported in the first version of `MetaOperatorsRegistry.sol` as an external module.
- The sum of sub-node operator shares should always be 10000 BP.
- If the operator is not a participant of an `OperatorGroup`, return 0 weight for this node operator to disallow stake allocation to the operators not added to the group.
:::info
If the operator is not added to the `OperatorGroup` and gets some allocations, after adding them to the group with the other sub-node-operators, we might end up with the stake over the target allocation on this Node Operator. Hence, we prohibit allocations to the operators not added to the `OperatorGroup`.
:::
### Allocation Strategy
CM v2 uses the "greedy" weighted stake allocation strategy to determine the order in which keys are provided for deposits. The high-level description of the algorithm is the following:
First, `currentStake` and `targetStake` are determined for each Node Operator in the module. `targetStake` is defined with respect to the Node Operator allocation weight obtained from [Meta Operators Registry](#Meta-Operators-Registry) as `targetStake = totalStakeInModule * operatorWeight / totalOperatorsWeight`. `currentStake` also accounts for external stake via [Meta Operators Registry](#Meta-Operators-Registry).

Second, all Node Operators are sorted by their `imbalance`, where `imbalance = max(0, targetStake - currentStake)`, and `capacities` are determined for each Node Operator.

Next, stake is allocated as follows:
- We start allocating stake from the most imbalanced Node Operator
- We allocate stake to this Node Operator until:
- Node Operator target stake is reached (1) OR
- Node Operator capacity is exhausted (2) OR
- There is no more stake to allocate (3)
- Then we move to the next most imbalanced Node Operator

## Main flows
:::info
For the missing flow descriptions, please refer to the [CSM v3 Specification](https://hackmd.io/@lido/csm-v3-spec).
:::
### Create Node Operator
:::info
Changed in CM v2
- Node Operator creation is done via `CuratedGates` and requires no keys or bond upload.
:::

Node Operator creation uses [`CuratedGate.sol`](#CuratedGatesol) instances attached to [`CuratedModule.sol`](#CuratedModulesol) via `CREATE_NODE_OPERATOR_ROLE`. Unlike CSM, CM v2 assumes that the creation of the Node Operator is performed without uploading deposit data or a bond. Deposit data can be uploaded later using the flow below.
Different instances of [`CuratedGate.sol`](#CuratedGatesol) represent different Node Operator types. Node Operators can join Curated Module v2 using the [`CuratedGate.sol`](#CuratedGatesol) instance, where their address is added to the participants list. Once used, the address can not be used again to join via the same instance of [`CuratedGate.sol`](#CuratedGatesol).
#### Operator addition to `OperatorGroup`
To become eligible for deposits, a Node Operator should be a part of an OperatorGroup in the `MetaOperatorsRegistry.sol`. Adding to the group is a permissioned operation performed by CMC.
### Stake Allocation
:::info
Changed in CM v2
- FIFO queues are replaced with a "greedy" weighted stake allocation strategy [described above](#Allocation-Strategy).
- Unlike CSM v3 that can have deployments supporting either `0x01` or `0x02` validator WC, CM v2 only supports `0x02`.
:::
#### Initial `32 ETH` deposits

Once uploaded, deposit data becomes available for allocation of the initial `32 ETH` deposits. To allocate initial deposits to the CM v2 Node Operators, the `StakingRouter` calls the `obtainDepositData(depositsCount)` method to retrieve the next `depositsCount` depositable keys, as determined by the stake [allocation strategy](#Allocation-Strategy).
#### Top-ups for `0x02` keys

`0x02` validators get deposits in two phases:
- [Initial `32 ETH` deposit](#Initial-32-ETH-deposits).
- Top-ups up to `2048 ETH`.
The top-up phase involves a call from the `StakingRouter` to [`CuratedModule.sol`](#CuratedModulesol), providing information about the keys planned for top-up and top-up limits calculated using CL proofs. [`CuratedModule.sol`](#CuratedModulesol) calculates the stake allocation using the `maxDepositAmount` provided and allocates the stake among the provided keys, respecting the calculated allocation and the `topUpLimits` supplied by the `StakingRouter`. [`CuratedModule.sol`](#CuratedModulesol) also verifies that the keys provided belong to the corresponding Node Operators for security reasons.
### Node Operator Addresses Management
:::info
Changed in CM v2
- A dedicated role capable of changing both manager and reward addresses is added.
:::
Node Operators in CM v2 can manage their addresses in the same way as CSM Node Operators. The primary difference is that in CM v2, designated role members can update both the manager and reward addresses. It is assumed that these changes are made directly by the Lido DAO via on-chain voting or by a designated emergency committee, which acts only if the Node Operator's `managerAddress` is compromised and no longer reachable.
To streamline Node Operator operations, it is proposed to set `extendedManagerPermissions = true` for all Node Operators in CM v2. This will allow for flexibility in address management.
## Contracts specifications
:::info
For the missing contract specifications, please refer to the [CSM v3 Specification](https://hackmd.io/@lido/csm-v3-spec).
:::
### [`CuratedModule.sol`](https://github.com/lidofinance/community-staking-module/tree/develop/docs/src/src/CuratedModule.sol)
### [`CuratedGate.sol`](https://github.com/lidofinance/community-staking-module/tree/develop/docs/src/src/CuratedGate.sol)
### [`MetaOperatorsRegistry.sol`](https://github.com/lidofinance/community-staking-module/tree/develop/docs/src/src/MetaOperatorsRegistry.sol)
## Administrative actions
Curated Module v2 contracts support a set of administrative actions, including:
- Changing the configuration options.
- Upgrading the system's code.
Each action can only be performed by a designated admin (`DEFAULT_ADMIN_ROLE`) or other role members. Only members of `DEFAULT_ADMIN_ROLE` can manage role members for the roles in CM v2 contracts.
## Roles to actors mapping
### [`CuratedModule.sol`](#CuratedModulesol)
| Role | Assignee |
| ------------------------------------------ | ------------------------------------------------- |
| `DEFAULT_ADMIN_ROLE` | Aragon Agent |
| `PAUSE_ROLE` | Gate Seal contract and DG Reseal Manager |
| `RESUME_ROLE` | DG Reseal Manager |
| `STAKING_ROUTER_ROLE` | `StakingRouter` contract |
| `REPORT_GENERAL_DELAYED_PENALTY_ROLE` | CM Committee Multisig |
| `SETTLE_GENERAL_DELAYED_PENALTY_ROLE` | `EasyTrackEVMScriptExecutor` |
| `VERIFIER_ROLE` | [`Verifier.sol`](#Verifier) |
| `REPORT_REGULAR_WITHDRAWN_VALIDATORS_ROLE` | [`Verifier.sol`](#Verifier) |
| `REPORT_SLASHED_WITHDRAWN_VALIDATORS_ROLE` | `EasyTrackEVMScriptExecutor` |
| `RECOVERER_ROLE` | Not assigned by default |
| `CREATE_NODE_OPERATOR_ROLE` | Instances of [`CuratedGate.sol`](#CuratedGatesol) |
| `OPERATOR_ADDRESSES_ADMIN_ROLE` | Not assigned by default |
### `Accounting.sol`
| Role | Assignee |
| ------------------------- | --------------------------------------------------------------------------- |
| `DEFAULT_ADMIN_ROLE` | Aragon Agent |
| `PAUSE_ROLE` | Gate Seal contract and DG Reseal Manager |
| `RESUME_ROLE` | DG Reseal Manager |
| `MANAGE_BOND_CURVES_ROLE` | Not assigned by default |
| `SET_BOND_CURVE_ROLE` | Instances of [`CuratedGate.sol`](#CuratedGatesol) |
| `RECOVERER_ROLE` | Not assigned by default |
### `FeeDistributor.sol`
| Role | Assignee |
| -------------------- | ----------------------- |
| `DEFAULT_ADMIN_ROLE` | Aragon Agent |
| `RECOVERER_ROLE` | Not assigned by default |
### `FeeOracle.sol`
| Role | Assignee |
| -------------------------------- | ---------------------------------------------------- |
| `DEFAULT_ADMIN_ROLE` | Aragon Agent |
| `SUBMIT_DATA_ROLE` | Not assigned by default |
| `PAUSE_ROLE` | Gate Seal contract and DG Reseal Manager |
| `RESUME_ROLE` | DG Reseal Manager |
| `RECOVERER_ROLE` | Not assigned by default |
| `MANAGE_CONSENSUS_CONTRACT_ROLE` | Not assigned by default |
| `MANAGE_CONSENSUS_VERSION_ROLE` | Not assigned by default |
### `HashConsensus.sol`
| Role | Assignee |
| -------------------------------- | ----------------------- |
| `DEFAULT_ADMIN_ROLE` | Aragon Agent |
| `MANAGE_MEMBERS_AND_QUORUM_ROLE` | Aragon Agent |
| `DISABLE_CONSENSUS_ROLE` | Not assigned by default |
| `MANAGE_FRAME_CONFIG_ROLE` | Not assigned by default |
| `MANAGE_FAST_LANE_CONFIG_ROLE` | Not assigned by default |
| `MANAGE_REPORT_PROCESSOR_ROLE` | Not assigned by default |
### `Verifier.sol`
| Role | Assignee |
| -------------------- | ---------------------------------------------------- |
| `DEFAULT_ADMIN_ROLE` | Aragon Agent |
| `PAUSE_ROLE` | Gate Seal contract and DG Reseal Manager |
| `RESUME_ROLE` | DG Reseal Manager |
### `ParametersRegistry.sol`
:::info
Note that the contract uses a custom role check modifier that allows both `roleMember` and `roleAdmin` to call methods
:::
| Role | Assignee |
| ------------------------------------------- | ----------------------- |
| `DEFAULT_ADMIN_ROLE` | Aragon Agent |
| `MANAGE_GENERAL_PENALTIES_AND_CHARGES_ROLE` | CM Committee Multisig |
| `MANAGE_KEYS_LIMIT_ROLE` | Not assigned by default |
| `MANAGE_QUEUE_CONFIG_ROLE` | Not assigned by default |
| `MANAGE_PERFORMANCE_PARAMETERS_ROLE` | Not assigned by default |
| `MANAGE_REWARD_SHARE_ROLE` | Not assigned by default |
| `MANAGE_VALIDATOR_EXIT_PARAMETERS_ROLE` | Not assigned by default |
### `Ejector.sol`
| Role | Assignee |
| ---------------------------- | ---------------------------------------------------- |
| `DEFAULT_ADMIN_ROLE` | Aragon Agent |
| `PAUSE_ROLE` | Gate Seal contract and DG Reseal Manager |
| `RESUME_ROLE` | DG Reseal Manager |
| `RECOVERER_ROLE` | Not assigned by default |
### `ValidatorStrikes.sol`
| Role | Assignee |
| ---------------------------- | ----------------------- |
| `DEFAULT_ADMIN_ROLE` | Aragon Agent |
### `ExitPenalties.sol`
This contract does not have roles.
### [`CuratedGate.sol`](#CuratedGatesol)
| Role | Assignee |
| -------------------- | ---------------------------- |
| `DEFAULT_ADMIN_ROLE` | Aragon Agent |
| `PAUSE_ROLE` | CM Committee Multisig |
| `RESUME_ROLE` | Not assigned by default |
| `SET_TREE_ROLE` | `EasyTrackEVMScriptExecutor` |
| `RECOVERER_ROLE` | Not assigned by default |
### [`MetaOperatorsRegistry.sol`](#MetaOperatorsRegistrysol)
`TBD`
## Upgradability
[`CuratedModule.sol`](#CuratedModulesol), `Accounting.sol`, `FeeOracle.sol`, `FeeDistributor.sol`, `ParametersRegistry.sol`, `ValidatorStrikes.sol`, `ExitPenalties.sol`, [`CuratedGate.sol`](#CuratedGatesol), and [`MetaOperatorsRegistry.sol`](#MetaOperatorsRegistrysol) are upgradable using [OssifiableProxy](https://github.com/lidofinance/community-staking-module/blob/main/src/lib/proxy/OssifiableProxy.sol) contracts.
[`Verifier.sol`](#Verifier), [`HashConsensus.sol`](#HashConsensussol), and [`Ejector.sol`](#Ejectorsol) are not upgradable and should be redeployed if needed.
## Security considerations
CM v2 shares all of the security considerations mentioned for CSM v3 in [CSM v3 Specification](https://hackmd.io/@lido/csm-v3-spec#Security-considerations).
## Known issues
CM v2 shares all of the known issues mentioned for CSM v3 in [CSM v3 Specification](https://hackmd.io/@lido/csm-v3-spec#Known-issues).
## Links
- [CSM v3 Specification](https://hackmd.io/@lido/csm-v3-spec)
- [CM v2 Features Summary](https://hackmd.io/@lido/cm-v2-features)
- [LIP-29. Community Staking Module v2](https://github.com/lidofinance/lido-improvement-proposals/blob/develop/LIPS/lip-29.md)
- [LIP-26. Community Staking Module](https://github.com/lidofinance/lido-improvement-proposals/blob/develop/LIPS/lip-26.md)
- [DG Reseal Manager](https://github.com/lidofinance/dual-governance/blob/main/contracts/ResealManager.sol)