Skip to content

Commit 16bedaa

Browse files
committed
feat: add weight boost providers
1 parent 846d4a5 commit 16bedaa

13 files changed

Lines changed: 858 additions & 39 deletions

script/curated/DeployBase.s.sol

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ import { BaseOracle } from "../../src/lib/base-oracle/BaseOracle.sol";
2727
import { IVerifier } from "../../src/interfaces/IVerifier.sol";
2828
import { IParametersRegistry } from "../../src/interfaces/IParametersRegistry.sol";
2929
import { IBondCurve } from "../../src/interfaces/IBondCurve.sol";
30+
import { IMetaRegistry } from "../../src/interfaces/IMetaRegistry.sol";
31+
import { IWeightBoostProvider } from "../../src/interfaces/IWeightBoostProvider.sol";
3032

3133
import { JsonObj, Json } from "../utils/Json.sol";
3234
import { Dummy } from "../utils/Dummy.sol";
@@ -348,6 +350,10 @@ abstract contract DeployBase is Script {
348350

349351
accounting.grantRole(accounting.MANAGE_BOND_CURVES_ROLE(), address(deployer));
350352
accounting.grantRole(accounting.SET_BOND_CURVE_MULTIPLIER_ROLE(), address(additionalBondRegistry));
353+
metaRegistry.addWeightBoostProvider(
354+
IWeightBoostProvider(address(additionalBondRegistry)),
355+
IMetaRegistry.WeightBoostProviderMode.NodeOperator
356+
);
351357
metaRegistry.grantRole(metaRegistry.SET_BOND_CURVE_WEIGHT_ROLE(), deployer);
352358

353359
for (uint256 i = 0; i < gatesCount; i++) {

src/Accounting.sol

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ import { PausableWithRoles } from "./abstract/PausableWithRoles.sol";
1515

1616
import { AssetRecovererLib } from "./lib/AssetRecovererLib.sol";
1717

18-
import { IStakingModule } from "./interfaces/IStakingModule.sol";
1918
import { IBaseModule, NodeOperatorManagementProperties } from "./interfaces/IBaseModule.sol";
2019
import { IAccounting } from "./interfaces/IAccounting.sol";
2120
import { IFeeDistributor } from "./interfaces/IFeeDistributor.sol";
@@ -614,7 +613,7 @@ contract Accounting is
614613
}
615614

616615
function _onlyExistingNodeOperator(uint256 nodeOperatorId) internal view {
617-
if (nodeOperatorId < IStakingModule(address(MODULE)).getNodeOperatorsCount()) return;
616+
if (nodeOperatorId < MODULE.getNodeOperatorsCount()) return;
618617

619618
revert NodeOperatorDoesNotExist();
620619
}

src/AdditionalBondRegistry.sol

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { IAccounting } from "./interfaces/IAccounting.sol";
1010
import { ICuratedModule } from "./interfaces/ICuratedModule.sol";
1111
import { IMetaRegistry } from "./interfaces/IMetaRegistry.sol";
1212
import { IAdditionalBondRegistry, TierInfo, OperatorTierState } from "./interfaces/IAdditionalBondRegistry.sol";
13+
import { IWeightBoostProvider } from "./interfaces/IWeightBoostProvider.sol";
1314
import { MAX_BP } from "./lib/Constants.sol";
1415

1516
/// @notice Manages operator tiers.
@@ -96,7 +97,7 @@ contract AdditionalBondRegistry is IAdditionalBondRegistry, Initializable, Acces
9697
$.operatorTier[nodeOperatorId] = tierId;
9798
emit TierSelected(nodeOperatorId, tierId);
9899

99-
META_REGISTRY.refreshOperatorWeight(nodeOperatorId);
100+
META_REGISTRY.notifyWeightBoostChanged(nodeOperatorId);
100101
}
101102

102103
/// @inheritdoc IAdditionalBondRegistry
@@ -127,6 +128,12 @@ contract AdditionalBondRegistry is IAdditionalBondRegistry, Initializable, Acces
127128
state.curveMultiplier = ACCOUNTING.getBondCurveMultiplier(nodeOperatorId);
128129
}
129130

131+
/// @inheritdoc IWeightBoostProvider
132+
function getWeightBoostMultiplierBP(uint256 nodeOperatorId) external view returns (uint256 multiplierBP) {
133+
AdditionalBondRegistryStorage storage $ = _storage();
134+
multiplierBP = MAX_BP + $.tiers[$.operatorTier[nodeOperatorId]].weightMultiplier;
135+
}
136+
130137
/// @inheritdoc IAdditionalBondRegistry
131138
function getTierInfo(uint256 tierId) public view returns (TierInfo memory) {
132139
AdditionalBondRegistryStorage storage $ = _storage();

src/MetaRegistry.sol

Lines changed: 198 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { IBondCurve } from "./interfaces/IBondCurve.sol";
1212
import { INodeOperatorsRegistry } from "./interfaces/INodeOperatorsRegistry.sol";
1313
import { ICuratedModule } from "./interfaces/ICuratedModule.sol";
1414
import { IBaseModule } from "./interfaces/IBaseModule.sol";
15+
import { IWeightBoostProvider } from "./interfaces/IWeightBoostProvider.sol";
1516
import { IStakingModule } from "./interfaces/IStakingModule.sol";
1617
import { IStakingRouter } from "./interfaces/IStakingRouter.sol";
1718
import { 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

src/abstract/BondCurve.sol

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ abstract contract BondCurve is IBondCurve, Initializable {
7373

7474
/// @inheritdoc IBondCurve
7575
function getBondAmountByKeysCount(uint256 keys, uint256 curveId) public view returns (uint256) {
76-
return BondCurvesLib.getBondAmountByKeysCount(_getBondCurveStorage(), keys, curveId, MAX_BP);
76+
return getBondAmountByKeysCount(keys, curveId, MAX_BP);
7777
}
7878

7979
/// @inheritdoc IBondCurve
@@ -83,7 +83,7 @@ abstract contract BondCurve is IBondCurve, Initializable {
8383

8484
/// @inheritdoc IBondCurve
8585
function getKeysCountByBondAmount(uint256 amount, uint256 curveId) public view returns (uint256) {
86-
return BondCurvesLib.getKeysCountByBondAmount(_getBondCurveStorage(), amount, curveId, MAX_BP);
86+
return getKeysCountByBondAmount(amount, curveId, MAX_BP);
8787
}
8888

8989
/// @inheritdoc IBondCurve

0 commit comments

Comments
 (0)