Skip to content

Commit 6cd6051

Browse files
committed
feat: meta operator registry
1 parent d740a58 commit 6cd6051

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+1540
-1011
lines changed

AGENTS.md

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@
66
- `test/`: Forge tests (`*.t.sol`), plus `test/fork/*` for fork/integration and deployment tests.
77
- `script/`: Forge scripts (deploy/verify, per-chain variants).
88
- `artifacts/`, `broadcast/`, `out/`, `cache/`: build, deploy, and fork outputs.
9-
- `lib/`, `node_modules/`: dependencies; see `remappings.txt`.
10-
- `docs/`, `digest/`: documentation and design notes.
9+
- `gists/`: small code examples related to the module.
10+
- `node_modules/`: dependencies; see `remappings.txt`.
11+
- `docs/`: documentation and design notes.
1112

1213
## Build, Test, and Development Commands
1314

@@ -18,21 +19,31 @@
1819
- `just test-local`: spins up anvil fork, deploys, runs deployment+integration tests.
1920
- `just coverage` | `just coverage-lcov`: coverage (LCOV saved; see `lcov.html`).
2021
- Linting: `yarn lint:check` (prettier + solhint), `yarn lint:fix`, `yarn lint:solhint`.
22+
- Diff: `git diff --no-ext-diff`
2123

2224
## Coding Style & Naming Conventions
2325

2426
- Formatting: Prettier + `prettier-plugin-solidity` (Solidity `printWidth=80`, `tabWidth=4`, spaces only).
2527
- Linting: Solhint (`.solhint.json`) with `solhint:recommended` and `solhint-plugin-lido-csm`.
2628
- Versions: enforce `pragma solidity 0.8.33` (`compiler-version` rule).
2729
- Naming: contracts/libraries `CamelCase` (e.g., `CSModule`, `AssetRecovererLib`), interfaces `IName` (rule: `interface-starts-with-i`).
30+
- The if statement body should be enclosed in a block, for example: `if (cond) { do(); }`
31+
- Inline assembly should be well documented, preferably every non-trivial line with its own comment.
2832
- Conventions: prefer custom errors, calldata parameters, and struct packing (gas rules). Immutable vars styled as constants.
33+
- Keep things in one function unless composable or reusable.
34+
- Prefer short variable names where possible. Prefer same length variable names for related things.
35+
- Prefer early returns to else statements.
36+
- While refactoring keep comments added from existing implementations where applicable.
37+
- Make sure external functions in contracts and interfaces have proper natspec.
38+
- When last I looked, the year was 2026.
2939

3040
## Testing Guidelines
3141

3242
- Framework: Foundry/Forge with fuzzing (`fuzz.runs=256`).
3343
- Structure: unit tests in `test/*.t.sol`; fork suites under `test/fork/*` (deployment/integration).
3444
- Run: `just test-unit` for fast cycles; `CHAIN`/`RPC_URL` required for fork tests. Example: `export CHAIN=hoodi && export RPC_URL=<https-url>`.
3545
- Coverage: `just coverage-lcov` produces LCOV output (commit if relevant).
46+
- After making changes to the source code make sure you've either ran build command or unit tests.
3647

3748
## Commit & Pull Request Guidelines
3849

Justfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ import "curated.just"
4848
default: clean deps build test-all
4949

5050
build *args:
51-
forge build --skip test --force {{args}}
51+
forge build --skip test --skip script {{args}}
5252

5353
clean:
5454
forge clean

script/csm/DeployBase.s.sol

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,6 @@ struct DeployParams {
9191
uint256 defaultAllowedExitDelay;
9292
uint256 defaultExitDelayFee;
9393
uint256 defaultMaxElWithdrawalRequestFee;
94-
uint256 defaultDepositAllocationWeight;
9594
// VettedGate
9695
address identifiedCommunityStakersGateManager;
9796
uint256 identifiedCommunityStakersGateCurveId;
@@ -115,7 +114,6 @@ struct DeployParams {
115114
uint256 identifiedCommunityStakersGateAllowedExitDelay;
116115
uint256 identifiedCommunityStakersGateExitDelayFee;
117116
uint256 identifiedCommunityStakersGateMaxElWithdrawalRequestFee;
118-
uint256 identifiedCommunityStakersGateDepositAllocationWeight;
119117
// GateSeal
120118
address gateSealFactory;
121119
address sealingCommittee;
@@ -268,9 +266,7 @@ abstract contract DeployBase is Script {
268266
defaultAllowedExitDelay: config.defaultAllowedExitDelay,
269267
defaultExitDelayFee: config.defaultExitDelayFee,
270268
defaultMaxElWithdrawalRequestFee: config
271-
.defaultMaxElWithdrawalRequestFee,
272-
defaultDepositAllocationWeight: config
273-
.defaultDepositAllocationWeight
269+
.defaultMaxElWithdrawalRequestFee
274270
})
275271
});
276272

@@ -469,15 +465,6 @@ abstract contract DeployBase is Script {
469465
identifiedCommunityStakersGateBondCurveId,
470466
config.identifiedCommunityStakersGateMaxElWithdrawalRequestFee
471467
);
472-
if (
473-
config.identifiedCommunityStakersGateDepositAllocationWeight !=
474-
0
475-
) {
476-
parametersRegistry.setDepositAllocationWeight(
477-
identifiedCommunityStakersGateBondCurveId,
478-
config.identifiedCommunityStakersGateDepositAllocationWeight
479-
);
480-
}
481468

482469
feeDistributor.initialize({
483470
admin: address(deployer),

script/curated/DeployBase.s.sol

Lines changed: 37 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import { FeeOracle } from "../../src/FeeOracle.sol";
1616
import { Verifier } from "../../src/Verifier.sol";
1717
import { ParametersRegistry } from "../../src/ParametersRegistry.sol";
1818
import { ExitPenalties } from "../../src/ExitPenalties.sol";
19-
import { OperatorsData } from "../../src/OperatorsData.sol";
19+
import { MetaOperatorRegistry } from "../../src/MetaOperatorRegistry.sol";
2020
import { CuratedGate } from "../../src/CuratedGate.sol";
2121
import { CuratedGateFactory } from "../../src/CuratedGateFactory.sol";
2222

@@ -51,7 +51,6 @@ struct GateCurveParams {
5151
uint256 allowedExitDelay;
5252
uint256 exitDelayFee;
5353
uint256 maxElWithdrawalRequestFee;
54-
uint256 depositAllocationWeight;
5554
}
5655

5756
struct CuratedGateConfig {
@@ -115,8 +114,6 @@ struct CuratedDeployParams {
115114
uint256 defaultAllowedExitDelay;
116115
uint256 defaultExitDelayFee;
117116
uint256 defaultMaxElWithdrawalRequestFee;
118-
uint256 defaultDepositAllocationWeight;
119-
uint256 identifiedCommunityStakersGateDepositAllocationWeight;
120117
// Curated gates
121118
CuratedGateConfig[] curatedGates;
122119
// GateSeal
@@ -149,7 +146,7 @@ abstract contract DeployBase is Script {
149146
Verifier public verifier;
150147
HashConsensus public hashConsensus;
151148
ParametersRegistry public parametersRegistry;
152-
OperatorsData public operatorsData;
149+
MetaOperatorRegistry public metaOperatorsRegistry;
153150
CuratedGateFactory public curatedGateFactory;
154151
address[] public curatedGateInstances;
155152
address internal curatedGateImpl;
@@ -219,6 +216,18 @@ abstract contract DeployBase is Script {
219216

220217
accounting = Accounting(_deployProxy(deployer, address(dummyImpl)));
221218

219+
MetaOperatorRegistry metaOperatorsRegistryImpl = new MetaOperatorRegistry(
220+
address(curatedModule),
221+
locator.stakingRouter()
222+
);
223+
metaOperatorsRegistry = MetaOperatorRegistry(
224+
_deployProxy(
225+
config.proxyAdmin,
226+
address(metaOperatorsRegistryImpl)
227+
)
228+
);
229+
metaOperatorsRegistry.initialize(deployer);
230+
222231
oracle = FeeOracle(_deployProxy(deployer, address(dummyImpl)));
223232

224233
FeeDistributor feeDistributorImpl = new FeeDistributor({
@@ -277,9 +286,7 @@ abstract contract DeployBase is Script {
277286
defaultAllowedExitDelay: config.defaultAllowedExitDelay,
278287
defaultExitDelayFee: config.defaultExitDelayFee,
279288
defaultMaxElWithdrawalRequestFee: config
280-
.defaultMaxElWithdrawalRequestFee,
281-
defaultDepositAllocationWeight: config
282-
.defaultDepositAllocationWeight
289+
.defaultMaxElWithdrawalRequestFee
283290
})
284291
});
285292

@@ -386,12 +393,6 @@ abstract contract DeployBase is Script {
386393
curveId,
387394
params.maxElWithdrawalRequestFee
388395
);
389-
if (params.depositAllocationWeight != 0) {
390-
parametersRegistry.setDepositAllocationWeight(
391-
curveId,
392-
params.depositAllocationWeight
393-
);
394-
}
395396
}
396397
accounting.revokeRole(
397398
accounting.MANAGE_BOND_CURVES_ROLE(),
@@ -407,7 +408,8 @@ abstract contract DeployBase is Script {
407408
lidoLocator: config.lidoLocatorAddress,
408409
parametersRegistry: address(parametersRegistry),
409410
accounting: address(accounting),
410-
exitPenalties: address(exitPenalties)
411+
exitPenalties: address(exitPenalties),
412+
metaOperatorsRegistry: address(metaOperatorsRegistry)
411413
});
412414

413415
{
@@ -454,19 +456,11 @@ abstract contract DeployBase is Script {
454456

455457
strikes.initialize(deployer, address(ejector));
456458

457-
address operatorsDataImpl = address(
458-
new OperatorsData(locator.stakingRouter())
459-
);
460-
operatorsData = OperatorsData(
461-
_deployProxy(config.proxyAdmin, operatorsDataImpl)
462-
);
463-
operatorsData.initialize(deployer);
464-
465459
curatedGateImpl = address(
466460
new CuratedGate(
467-
address(curatedModule),
468461
config.stakingModuleId,
469-
address(operatorsData)
462+
address(curatedModule),
463+
address(metaOperatorsRegistry)
470464
)
471465
);
472466

@@ -638,12 +632,12 @@ abstract contract DeployBase is Script {
638632
gate.revokeRole(gate.DEFAULT_ADMIN_ROLE(), deployer);
639633
}
640634

641-
operatorsData.grantRole(
642-
operatorsData.DEFAULT_ADMIN_ROLE(),
635+
metaOperatorsRegistry.grantRole(
636+
metaOperatorsRegistry.DEFAULT_ADMIN_ROLE(),
643637
config.aragonAgent
644638
);
645-
operatorsData.revokeRole(
646-
operatorsData.DEFAULT_ADMIN_ROLE(),
639+
metaOperatorsRegistry.revokeRole(
640+
metaOperatorsRegistry.DEFAULT_ADMIN_ROLE(),
647641
deployer
648642
);
649643

@@ -687,6 +681,14 @@ abstract contract DeployBase is Script {
687681
deployJson.set("ChainId", chainId);
688682
deployJson.set("CuratedModule", address(curatedModule));
689683
deployJson.set("CuratedModuleImpl", address(curatedModuleImpl));
684+
deployJson.set(
685+
"MetaOperatorRegistry",
686+
address(metaOperatorsRegistry)
687+
);
688+
deployJson.set(
689+
"MetaOperatorRegistryImpl",
690+
address(metaOperatorsRegistryImpl)
691+
);
690692
deployJson.set("ParametersRegistry", address(parametersRegistry));
691693
deployJson.set(
692694
"ParametersRegistryImpl",
@@ -705,8 +707,6 @@ abstract contract DeployBase is Script {
705707
deployJson.set("ValidatorStrikesImpl", address(strikesImpl));
706708
deployJson.set("HashConsensus", address(hashConsensus));
707709
deployJson.set("Verifier", address(verifier));
708-
deployJson.set("OperatorsData", address(operatorsData));
709-
deployJson.set("OperatorsDataImpl", address(operatorsDataImpl));
710710
deployJson.set("CuratedGateFactory", address(curatedGateFactory));
711711
deployJson.set("CuratedGates", curatedGateInstances);
712712
deployJson.set("LidoLocator", config.lidoLocatorAddress);
@@ -766,7 +766,10 @@ abstract contract DeployBase is Script {
766766
address(gate)
767767
);
768768
}
769-
operatorsData.grantRole(operatorsData.SETTER_ROLE(), address(gate));
769+
metaOperatorsRegistry.grantRole(
770+
metaOperatorsRegistry.SET_OPERATOR_INFO_ROLE(),
771+
address(gate)
772+
);
770773
gate.grantRole(gate.PAUSE_ROLE(), config.resealManager);
771774
gate.grantRole(gate.RESUME_ROLE(), config.resealManager);
772775
gate.grantRole(
@@ -847,8 +850,8 @@ abstract contract DeployBase is Script {
847850
parametersRegistry.DEFAULT_ADMIN_ROLE(),
848851
config.secondAdminAddress
849852
);
850-
operatorsData.grantRole(
851-
operatorsData.DEFAULT_ADMIN_ROLE(),
853+
metaOperatorsRegistry.grantRole(
854+
metaOperatorsRegistry.DEFAULT_ADMIN_ROLE(),
852855
config.secondAdminAddress
853856
);
854857
for (uint256 i = 0; i < curatedGateInstances.length; i++) {

script/fork-helpers/SimulateVote.s.sol

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -195,14 +195,6 @@ contract SimulateVote is Script, ForkHelpersCommon {
195195
);
196196
vm.stopBroadcast();
197197
}
198-
{
199-
vm.startBroadcast(parametersRegistryAdmin);
200-
parametersRegistry.setDefaultDepositAllocationWeight(
201-
deployParams.defaultDepositAllocationWeight
202-
);
203-
vm.stopBroadcast();
204-
}
205-
206198
{
207199
OssifiableProxy oracleProxy = OssifiableProxy(
208200
payable(deploymentConfig.oracle)

src/Accounting.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ contract Accounting is
182182
) external onlyRole(SET_BOND_CURVE_ROLE) {
183183
_onlyExistingNodeOperator(nodeOperatorId);
184184
BondCurve._setBondCurve(nodeOperatorId, curveId);
185-
MODULE.updateDepositableValidatorsCount(nodeOperatorId);
185+
MODULE.onNodeOperatorBondCurveUpdated(nodeOperatorId);
186186
}
187187

188188
/// @inheritdoc IAccounting

src/CSModule.sol

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -385,6 +385,16 @@ contract CSModule is ICSModule, BaseModule {
385385
// the actual balances.
386386
}
387387

388+
/// @inheritdoc IBaseModule
389+
function onNodeOperatorBondCurveUpdated(
390+
uint256 nodeOperatorId
391+
) external override(IBaseModule) {
392+
_updateDepositableValidatorsCount({
393+
nodeOperatorId: nodeOperatorId,
394+
incrementNonceIfUpdated: true
395+
});
396+
}
397+
388398
/// @inheritdoc IStakingModule
389399
function getStakingModuleSummary()
390400
external
@@ -457,8 +467,8 @@ contract CSModule is ICSModule, BaseModule {
457467
uint256 nodeOperatorId,
458468
uint256 newCount,
459469
bool incrementNonceIfUpdated
460-
) internal override {
461-
super._applyDepositableValidatorsCount(
470+
) internal override returns (bool changed) {
471+
changed = super._applyDepositableValidatorsCount(
462472
no,
463473
nodeOperatorId,
464474
newCount,

src/CuratedGate.sol

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { ICuratedModule } from "./interfaces/ICuratedModule.sol";
1313
import { IMerkleGate } from "./interfaces/IMerkleGate.sol";
1414
import { ICuratedGate } from "./interfaces/ICuratedGate.sol";
1515
import { NodeOperatorManagementProperties } from "./interfaces/IBaseModule.sol";
16-
import { IOperatorsData, OperatorInfo } from "./interfaces/IOperatorsData.sol";
16+
import { IMetaOperatorRegistry, OperatorInfo } from "./interfaces/IMetaOperatorRegistry.sol";
1717
import { IAccounting } from "./interfaces/IAccounting.sol";
1818

1919
/// @notice Merkle gate for Curated Module v2
@@ -38,7 +38,7 @@ contract CuratedGate is
3838
IAccounting public immutable ACCOUNTING;
3939

4040
/// @inheritdoc ICuratedGate
41-
IOperatorsData public immutable OPERATORS_DATA;
41+
IMetaOperatorRegistry public immutable META_OPERATOR_REGISTRY;
4242

4343
/// @inheritdoc IMerkleGate
4444
bytes32 public treeRoot;
@@ -54,14 +54,26 @@ contract CuratedGate is
5454
/// @dev Tracks whether an address already consumed its eligibility
5555
mapping(address => bool) internal _consumedAddresses;
5656

57-
constructor(address module, uint256 moduleId, address operatorsData) {
58-
if (module == address(0)) revert ZeroModuleAddress();
59-
if (moduleId == 0) revert ZeroModuleId();
60-
if (operatorsData == address(0)) revert ZeroOperatorsDataAddress();
57+
constructor(
58+
uint256 moduleId,
59+
address module,
60+
address metaOperatorRegistry
61+
) {
62+
if (moduleId == 0) {
63+
revert ZeroModuleId();
64+
}
65+
if (module == address(0)) {
66+
revert ZeroModuleAddress();
67+
}
68+
if (metaOperatorRegistry == address(0)) {
69+
revert ZeroMetaOperatorRegistryAddress();
70+
}
71+
6172
MODULE = ICuratedModule(module);
6273
MODULE_ID = moduleId;
6374
ACCOUNTING = MODULE.ACCOUNTING();
64-
OPERATORS_DATA = IOperatorsData(operatorsData);
75+
META_OPERATOR_REGISTRY = IMetaOperatorRegistry(metaOperatorRegistry);
76+
6577
_disableInitializers();
6678
}
6779

@@ -128,7 +140,11 @@ contract CuratedGate is
128140
ownerEditsRestricted: false
129141
});
130142

131-
OPERATORS_DATA.set(MODULE_ID, nodeOperatorId, metadata);
143+
META_OPERATOR_REGISTRY.setOperatorMetadataAsAdmin(
144+
MODULE_ID,
145+
nodeOperatorId,
146+
metadata
147+
);
132148
}
133149

134150
/// @inheritdoc IMerkleGate

0 commit comments

Comments
 (0)