@@ -12,6 +12,7 @@ import { IBondCurve } from "./interfaces/IBondCurve.sol";
1212import { INodeOperatorsRegistry } from "./interfaces/INodeOperatorsRegistry.sol " ;
1313import { ICuratedModule } from "./interfaces/ICuratedModule.sol " ;
1414import { IBaseModule } from "./interfaces/IBaseModule.sol " ;
15+ import { IWeightBoostProvider } from "./interfaces/IWeightBoostProvider.sol " ;
1516import { IStakingModule } from "./interfaces/IStakingModule.sol " ;
1617import { IStakingRouter } from "./interfaces/IStakingRouter.sol " ;
1718import { IMetaRegistry, OperatorMetadata } from "./interfaces/IMetaRegistry.sol " ;
@@ -50,6 +51,9 @@ contract MetaRegistry is IMetaRegistry, Initializable, AccessControlEnumerableUp
5051 mapping (uint256 nodeOperatorId = > OperatorMetadata) operatorMetadata;
5152 mapping (uint256 moduleId = > address moduleAddress ) moduleAddressCache;
5253 uint256 groupsCount;
54+ mapping (uint256 providerId = > WeightBoostProviderEntry entry ) weightBoostProviders;
55+ mapping (address provider = > uint256 providerId ) weightBoostProviderIdByAddress;
56+ uint256 weightBoostProvidersCount;
5357 }
5458
5559 bytes32 public constant MANAGE_OPERATOR_GROUPS_ROLE = keccak256 ("MANAGE_OPERATOR_GROUPS_ROLE " );
@@ -148,7 +152,49 @@ contract MetaRegistry is IMetaRegistry, Initializable, AccessControlEnumerableUp
148152
149153 $.bondCurveWeight[curveId] = weight;
150154 emit BondCurveWeightSet (curveId, weight);
151- MODULE.requestFullDepositInfoUpdate ();
155+ _requestFullDepositInfoUpdate ();
156+ }
157+
158+ /// @inheritdoc IMetaRegistry
159+ function addWeightBoostProvider (
160+ IWeightBoostProvider provider ,
161+ WeightBoostProviderMode mode
162+ ) external onlyRole (DEFAULT_ADMIN_ROLE) {
163+ address providerAddr = address (provider);
164+ if (providerAddr == address (0 )) revert InvalidWeightBoostProvider ();
165+
166+ MetaRegistryStorage storage $ = _storage ();
167+ if ($.weightBoostProviderIdByAddress[providerAddr] != 0 ) {
168+ revert WeightBoostProviderAlreadyAdded ();
169+ }
170+
171+ uint256 providerId = ++ $.weightBoostProvidersCount;
172+ $.weightBoostProviders[providerId] = WeightBoostProviderEntry ({
173+ provider: provider,
174+ mode: mode,
175+ enabled: true
176+ });
177+ $.weightBoostProviderIdByAddress[providerAddr] = providerId;
178+ emit WeightBoostProviderAdded (providerAddr, mode);
179+ _requestFullDepositInfoUpdate ();
180+ }
181+
182+ /// @inheritdoc IMetaRegistry
183+ function setWeightBoostProviderEnabled (
184+ IWeightBoostProvider provider ,
185+ bool enabled
186+ ) external onlyRole (DEFAULT_ADMIN_ROLE) {
187+ MetaRegistryStorage storage $ = _storage ();
188+ address providerAddr = address (provider);
189+ uint256 providerId = $.weightBoostProviderIdByAddress[providerAddr];
190+ if (providerId == 0 ) revert WeightBoostProviderNotFound ();
191+
192+ WeightBoostProviderEntry storage entry = $.weightBoostProviders[providerId];
193+ if (entry.enabled == enabled) revert SameWeightBoostProviderEnabled ();
194+
195+ entry.enabled = enabled;
196+ emit WeightBoostProviderEnabledSet (providerAddr, enabled);
197+ _requestFullDepositInfoUpdate ();
152198 }
153199
154200 /// @inheritdoc IMetaRegistry
@@ -159,6 +205,74 @@ contract MetaRegistry is IMetaRegistry, Initializable, AccessControlEnumerableUp
159205 _refreshOperatorWeight (groupId, nodeOperatorId);
160206 }
161207
208+ /// @inheritdoc IMetaRegistry
209+ function refreshGroupWeights (uint256 groupId ) external {
210+ if (groupId == NO_GROUP_ID) revert InvalidOperatorGroupId ();
211+ _refreshGroupWeights (groupId);
212+ }
213+
214+ /// @inheritdoc IMetaRegistry
215+ function notifyWeightBoostProviderConfigChanged () external {
216+ MetaRegistryStorage storage $ = _storage ();
217+ uint256 providerId = $.weightBoostProviderIdByAddress[msg .sender ];
218+ if (providerId == 0 ) return ;
219+
220+ if (! $.weightBoostProviders[providerId].enabled) return ;
221+
222+ emit WeightBoostProviderConfigChanged (msg .sender );
223+ _requestFullDepositInfoUpdate ();
224+ }
225+
226+ /// @inheritdoc IMetaRegistry
227+ function notifyWeightBoostChanged (uint256 nodeOperatorId ) external {
228+ MetaRegistryStorage storage $ = _storage ();
229+ uint256 providerId = $.weightBoostProviderIdByAddress[msg .sender ];
230+ if (providerId == 0 ) revert WeightBoostProviderNotFound ();
231+
232+ uint256 groupId = $.groupIndex.groupIdByOperatorId[nodeOperatorId];
233+ if (groupId == NO_GROUP_ID) return ;
234+
235+ WeightBoostProviderEntry storage entry = $.weightBoostProviders[providerId];
236+ if (! entry.enabled) return ;
237+
238+ if (entry.mode == WeightBoostProviderMode.NodeOperator) {
239+ _refreshOperatorWeight (groupId, nodeOperatorId);
240+ return ;
241+ }
242+
243+ _refreshGroupWeights (groupId);
244+ }
245+
246+ /// @inheritdoc IMetaRegistry
247+ function getWeightBoostProviders () external view returns (IWeightBoostProvider[] memory providers ) {
248+ MetaRegistryStorage storage $ = _storage ();
249+ uint256 providersCount = $.weightBoostProvidersCount;
250+ providers = new IWeightBoostProvider [](providersCount);
251+ for (uint256 i; i < providersCount; ++ i) {
252+ providers[i] = $.weightBoostProviders[i + 1 ].provider;
253+ }
254+ }
255+
256+ /// @inheritdoc IMetaRegistry
257+ function getWeightBoostProvidersCount () external view returns (uint256 count ) {
258+ count = _storage ().weightBoostProvidersCount;
259+ }
260+
261+ /// @inheritdoc IMetaRegistry
262+ function getWeightBoostProvider (uint256 providerId ) external view returns (WeightBoostProviderEntry memory entry ) {
263+ entry = _storage ().weightBoostProviders[providerId];
264+ }
265+
266+ /// @inheritdoc IMetaRegistry
267+ function getWeightBoostProviderMode (uint256 providerId ) external view returns (WeightBoostProviderMode mode ) {
268+ mode = _storage ().weightBoostProviders[providerId].mode;
269+ }
270+
271+ /// @inheritdoc IMetaRegistry
272+ function getWeightBoostProviderId (address provider ) external view returns (uint256 providerId ) {
273+ providerId = _storage ().weightBoostProviderIdByAddress[provider];
274+ }
275+
162276 /// @inheritdoc IMetaRegistry
163277 function getOperatorMetadata (uint256 nodeOperatorId ) external view returns (OperatorMetadata memory metadata ) {
164278 return _storage ().operatorMetadata[nodeOperatorId];
@@ -249,6 +363,7 @@ contract MetaRegistry is IMetaRegistry, Initializable, AccessControlEnumerableUp
249363
250364 uint256 groupId = ++ _storage ().groupsCount;
251365 _storeGroupData (groupId, groupInfo);
366+ _refreshGroupWeights (groupId);
252367 emit OperatorGroupCreated (groupId, groupInfo);
253368 }
254369
@@ -263,6 +378,7 @@ contract MetaRegistry is IMetaRegistry, Initializable, AccessControlEnumerableUp
263378 emit OperatorGroupCleared (groupId);
264379 } else {
265380 _storeGroupData (groupId, groupInfo);
381+ _refreshGroupWeights (groupId);
266382 emit OperatorGroupUpdated (groupId, groupInfo);
267383 }
268384 }
@@ -310,7 +426,6 @@ contract MetaRegistry is IMetaRegistry, Initializable, AccessControlEnumerableUp
310426 CachedOperatorGroup storage group = $.groups[groupId];
311427
312428 uint256 shareSum;
313- uint256 effectiveWeightSum;
314429 for (uint256 i; i < subNodeOperators.length ; ++ i) {
315430 uint64 noId = subNodeOperators[i].nodeOperatorId;
316431 uint16 share = subNodeOperators[i].share;
@@ -322,15 +437,10 @@ contract MetaRegistry is IMetaRegistry, Initializable, AccessControlEnumerableUp
322437 $.groupIndex.shareByOperatorId[noId] = share;
323438 group.subNodeOperatorIds.push (noId);
324439
325- uint256 effectiveWeight = _getLatestEffectiveWeight (noId, share);
326- _setEffectiveWeight (noId, effectiveWeight);
327- effectiveWeightSum += effectiveWeight;
328440 shareSum += share;
329441 }
330442
331443 if (shareSum != MAX_BP) revert InvalidSubNodeOperatorShares ();
332-
333- $.effectiveWeightCache.groupEffectiveWeightSum[groupId] = effectiveWeightSum;
334444 }
335445
336446 function _storeExternalOperators (uint256 groupId , ExternalOperator[] calldata externalOperators ) internal {
@@ -356,7 +466,8 @@ contract MetaRegistry is IMetaRegistry, Initializable, AccessControlEnumerableUp
356466 MetaRegistryStorage storage $ = _storage ();
357467 uint256 share = $.groupIndex.shareByOperatorId[noId];
358468
359- uint256 newWeight = _getLatestEffectiveWeight (noId, share);
469+ uint256 multiplierBP = _getWeightBoostMultiplierBP ($.groups[groupId], noId);
470+ uint256 newWeight = _getLatestEffectiveWeight (noId, share, multiplierBP);
360471 uint256 oldWeight = _setEffectiveWeight (noId, newWeight);
361472
362473 if (oldWeight != newWeight) {
@@ -371,6 +482,28 @@ contract MetaRegistry is IMetaRegistry, Initializable, AccessControlEnumerableUp
371482 }
372483 }
373484
485+ function _refreshGroupWeights (uint256 groupId ) internal {
486+ MetaRegistryStorage storage $ = _storage ();
487+ if (groupId > $.groupsCount) revert InvalidOperatorGroupId ();
488+
489+ CachedOperatorGroup storage group = $.groups[groupId];
490+ uint256 effectiveWeightSum;
491+ uint256 subOperatorsCount = group.subNodeOperatorIds.length ;
492+ for (uint256 i; i < subOperatorsCount; ++ i) {
493+ uint256 noId = group.subNodeOperatorIds[i];
494+ uint256 share = $.groupIndex.shareByOperatorId[noId];
495+ // Keep full-group and single-operator refreshes on the same ordered multiplier path.
496+ // Each Math.mulDiv floors, so pre-aggregating providers by mode can produce different weights.
497+ uint256 multiplierBP = _getWeightBoostMultiplierBP (group, noId);
498+ uint256 effectiveWeight = _getLatestEffectiveWeight (noId, share, multiplierBP);
499+ _setEffectiveWeight (noId, effectiveWeight);
500+ effectiveWeightSum += effectiveWeight;
501+ }
502+
503+ $.effectiveWeightCache.groupEffectiveWeightSum[groupId] = effectiveWeightSum;
504+ emit GroupWeightsRefreshed (groupId);
505+ }
506+
374507 function _setEffectiveWeight (uint256 nodeOperatorId , uint256 newWeight ) internal returns (uint256 oldWeight ) {
375508 MetaRegistryStorage storage $ = _storage ();
376509 oldWeight = $.effectiveWeightCache.operatorEffectiveWeight[nodeOperatorId];
@@ -383,6 +516,10 @@ contract MetaRegistry is IMetaRegistry, Initializable, AccessControlEnumerableUp
383516 MODULE.notifyNodeOperatorWeightChange (nodeOperatorId, oldWeight, newWeight);
384517 }
385518
519+ function _requestFullDepositInfoUpdate () internal {
520+ MODULE.requestFullDepositInfoUpdate ();
521+ }
522+
386523 function _storeOperatorMetadata (uint256 nodeOperatorId , OperatorMetadata memory metadata ) internal {
387524 if (bytes (metadata.name).length > MAX_NAME_LENGTH) revert OperatorNameTooLong ();
388525 if (bytes (metadata.description).length > MAX_DESCRIPTION_LENGTH) revert OperatorDescriptionTooLong ();
@@ -406,13 +543,60 @@ contract MetaRegistry is IMetaRegistry, Initializable, AccessControlEnumerableUp
406543 }
407544 }
408545
409- function _getLatestEffectiveWeight (uint256 nodeOperatorId , uint256 share ) internal view returns (uint256 ) {
410- uint256 baseWeight = _storage ().bondCurveWeight[ACCOUNTING.getBondCurveId (nodeOperatorId)];
546+ function _getLatestEffectiveWeight (
547+ uint256 nodeOperatorId ,
548+ uint256 share ,
549+ uint256 multiplierBP
550+ ) internal view returns (uint256 ) {
551+ uint256 baseWeight = _getOperatorBaseWeight (nodeOperatorId);
411552 if (baseWeight == 0 || share == 0 ) return 0 ;
412- uint256 weighted = Math.mulDiv (baseWeight, share, MAX_BP);
413- uint256 weightMul = ADDITIONAL_BOND_REGISTRY.getOperatorTierState (nodeOperatorId).weightMultiplier;
414- if (weightMul == MAX_BP) return weighted;
415- return Math.mulDiv (weighted, weightMul, MAX_BP);
553+
554+ uint256 boostedBaseWeight = Math.mulDiv (baseWeight, multiplierBP, MAX_BP);
555+ return Math.mulDiv (boostedBaseWeight, share, MAX_BP);
556+ }
557+
558+ function _getOperatorBaseWeight (uint256 nodeOperatorId ) internal view returns (uint256 ) {
559+ return _storage ().bondCurveWeight[ACCOUNTING.getBondCurveId (nodeOperatorId)];
560+ }
561+
562+ function _getWeightBoostMultiplierBP (
563+ CachedOperatorGroup storage group ,
564+ uint256 nodeOperatorId
565+ ) internal view returns (uint256 multiplierBP ) {
566+ MetaRegistryStorage storage $ = _storage ();
567+ multiplierBP = MAX_BP;
568+ uint256 providersCount = $.weightBoostProvidersCount;
569+ for (uint256 i; i < providersCount; ++ i) {
570+ WeightBoostProviderEntry storage entry = $.weightBoostProviders[i + 1 ];
571+ if (! entry.enabled) continue ;
572+
573+ IWeightBoostProvider provider = entry.provider;
574+ if (entry.mode == WeightBoostProviderMode.NodeOperator) {
575+ multiplierBP = Math.mulDiv (multiplierBP, provider.getWeightBoostMultiplierBP (nodeOperatorId), MAX_BP);
576+ } else {
577+ multiplierBP = Math.mulDiv (
578+ multiplierBP,
579+ _getProviderGroupMaxWeightBoostMultiplierBP (provider, group),
580+ MAX_BP
581+ );
582+ }
583+ }
584+ }
585+
586+ function _getProviderGroupMaxWeightBoostMultiplierBP (
587+ IWeightBoostProvider provider ,
588+ CachedOperatorGroup storage group
589+ ) internal view returns (uint256 maxMultiplierBP ) {
590+ uint256 subOperatorsCount = group.subNodeOperatorIds.length ;
591+ if (subOperatorsCount == 0 ) return MAX_BP;
592+
593+ maxMultiplierBP = provider.getWeightBoostMultiplierBP (group.subNodeOperatorIds[0 ]);
594+ for (uint256 i = 1 ; i < subOperatorsCount; ++ i) {
595+ uint256 candidateMultiplierBP = provider.getWeightBoostMultiplierBP (group.subNodeOperatorIds[i]);
596+ if (candidateMultiplierBP > maxMultiplierBP) {
597+ maxMultiplierBP = candidateMultiplierBP;
598+ }
599+ }
416600 }
417601
418602 /// @dev Returns the cached module address. Reverts if the address was
0 commit comments