Skip to content

Commit 1c91b06

Browse files
committed
feat: operator centric rewards
feat: add new interfaces feat(wip): implement `createOperatorSetPerformanceRewardsSubmission` chore: forge fmt fix: compile feat(wip): implement `setOperatorSetOperatorSplit` fix: review changes fix: add missing `onlyWhenPaused` + `checkCanCall` feat(wip): add missing `getOperatorSetPerformanceSplit` + rename internals test(wip): `setOperatorSetPerformanceSplit` test(wip): `createOperatorSetPerformanceRewardsSubmission` - some failing
1 parent 8a6a9d4 commit 1c91b06

File tree

5 files changed

+1265
-24
lines changed

5 files changed

+1265
-24
lines changed

src/contracts/core/RewardsCoordinator.sol

Lines changed: 60 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ contract RewardsCoordinator is
2929
PermissionControllerMixin
3030
{
3131
using SafeERC20 for IERC20;
32+
using OperatorSetLib for OperatorSet;
3233

3334
modifier onlyRewardsUpdater() {
3435
require(msg.sender == rewardsUpdater, UnauthorizedCaller());
@@ -171,12 +172,40 @@ contract RewardsCoordinator is
171172
submissionNonce[avs] = nonce + 1;
172173

173174
emit OperatorDirectedAVSRewardsSubmissionCreated(
174-
msg.sender, avs, operatorDirectedRewardsSubmissionHash, nonce, operatorDirectedRewardsSubmission
175+
msg.sender, avs, nonce, operatorDirectedRewardsSubmissionHash, operatorDirectedRewardsSubmission
175176
);
176177
operatorDirectedRewardsSubmission.token.safeTransferFrom(msg.sender, address(this), totalAmount);
177178
}
178179
}
179180

181+
/// @inheritdoc IRewardsCoordinator
182+
function createOperatorSetPerformanceRewardsSubmission(
183+
OperatorSet calldata operatorSet,
184+
OperatorDirectedRewardsSubmission[] calldata rewardsSubmissions
185+
)
186+
external
187+
onlyWhenNotPaused(PAUSED_OPERATOR_SET_PERFORMANCE_REWARDS_SUBMISSION)
188+
checkCanCall(operatorSet.avs)
189+
nonReentrant
190+
{
191+
require(allocationManager.isOperatorSet(operatorSet), InvalidOperatorSet());
192+
for (uint256 i = 0; i < rewardsSubmissions.length; i++) {
193+
OperatorDirectedRewardsSubmission calldata rewardsSubmission = rewardsSubmissions[i];
194+
uint256 nonce = submissionNonce[operatorSet.avs];
195+
bytes32 rewardsSubmissionHash = keccak256(abi.encode(operatorSet.avs, nonce, rewardsSubmission));
196+
197+
uint256 totalAmount = _validateOperatorDirectedRewardsSubmission(rewardsSubmission);
198+
199+
isOperatorSetPerformanceRewardsSubmissionHash[operatorSet.avs][rewardsSubmissionHash] = true;
200+
submissionNonce[operatorSet.avs] = nonce + 1;
201+
202+
emit OperatorDirectedOperatorSetRewardsSubmissionCreated(
203+
msg.sender, operatorSet, nonce, rewardsSubmissionHash, rewardsSubmission
204+
);
205+
rewardsSubmission.token.safeTransferFrom(msg.sender, address(this), totalAmount);
206+
}
207+
}
208+
180209
/// @inheritdoc IRewardsCoordinator
181210
function processClaim(
182211
RewardsMerkleClaim calldata claim,
@@ -268,8 +297,8 @@ contract RewardsCoordinator is
268297
uint16 split
269298
) external onlyWhenNotPaused(PAUSED_OPERATOR_AVS_SPLIT) checkCanCall(operator) {
270299
uint32 activatedAt = uint32(block.timestamp) + activationDelay;
271-
uint16 oldSplit = _getOperatorSplit(operatorAVSSplitBips[operator][avs]);
272-
_setOperatorSplit(operatorAVSSplitBips[operator][avs], split, activatedAt);
300+
uint16 oldSplit = _getOperatorSplit(_operatorAVSSplitBips[operator][avs]);
301+
_setOperatorSplit(_operatorAVSSplitBips[operator][avs], split, activatedAt);
273302

274303
emit OperatorAVSSplitBipsSet(msg.sender, operator, avs, activatedAt, oldSplit, split);
275304
}
@@ -280,12 +309,28 @@ contract RewardsCoordinator is
280309
uint16 split
281310
) external onlyWhenNotPaused(PAUSED_OPERATOR_PI_SPLIT) checkCanCall(operator) {
282311
uint32 activatedAt = uint32(block.timestamp) + activationDelay;
283-
uint16 oldSplit = _getOperatorSplit(operatorPISplitBips[operator]);
284-
_setOperatorSplit(operatorPISplitBips[operator], split, activatedAt);
312+
uint16 oldSplit = _getOperatorSplit(_operatorPISplitBips[operator]);
313+
_setOperatorSplit(_operatorPISplitBips[operator], split, activatedAt);
285314

286315
emit OperatorPISplitBipsSet(msg.sender, operator, activatedAt, oldSplit, split);
287316
}
288317

318+
/// @inheritdoc IRewardsCoordinator
319+
function setOperatorSetPerformanceSplit(
320+
address operator,
321+
OperatorSet calldata operatorSet,
322+
uint16 split
323+
) external onlyWhenNotPaused(PAUSED_OPERATOR_SET_OPERATOR_SPLIT) checkCanCall(operator) {
324+
require(allocationManager.isOperatorSet(operatorSet), InvalidOperatorSet());
325+
require(split <= ONE_HUNDRED_IN_BIPS, SplitExceedsMax());
326+
327+
uint32 activatedAt = uint32(block.timestamp) + activationDelay;
328+
uint16 oldSplit = _getOperatorSplit(_operatorSetPerformanceSplitBips[operator][operatorSet.key()]);
329+
_setOperatorSplit(_operatorSetPerformanceSplitBips[operator][operatorSet.key()], split, activatedAt);
330+
331+
emit OperatorSetPerformanceSplitBipsSet(msg.sender, operator, operatorSet, activatedAt, oldSplit, split);
332+
}
333+
289334
/// @inheritdoc IRewardsCoordinator
290335
function setRewardsUpdater(
291336
address _rewardsUpdater
@@ -602,14 +647,22 @@ contract RewardsCoordinator is
602647

603648
/// @inheritdoc IRewardsCoordinator
604649
function getOperatorAVSSplit(address operator, address avs) external view returns (uint16) {
605-
return _getOperatorSplit(operatorAVSSplitBips[operator][avs]);
650+
return _getOperatorSplit(_operatorAVSSplitBips[operator][avs]);
606651
}
607652

608653
/// @inheritdoc IRewardsCoordinator
609654
function getOperatorPISplit(
610655
address operator
611656
) external view returns (uint16) {
612-
return _getOperatorSplit(operatorPISplitBips[operator]);
657+
return _getOperatorSplit(_operatorPISplitBips[operator]);
658+
}
659+
660+
/// @inheritdoc IRewardsCoordinator
661+
function getOperatorSetPerformanceSplit(
662+
address operator,
663+
OperatorSet calldata operatorSet
664+
) external view returns (uint16) {
665+
return _getOperatorSplit(_operatorSetPerformanceSplitBips[operator][operatorSet.key()]);
613666
}
614667

615668
/// @inheritdoc IRewardsCoordinator

src/contracts/core/RewardsCoordinatorStorage.sol

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,14 @@ abstract contract RewardsCoordinatorStorage is IRewardsCoordinator {
2727
uint8 internal constant PAUSED_REWARD_ALL_STAKERS_AND_OPERATORS = 4;
2828
/// @dev Index for flag that pauses calling createOperatorDirectedAVSRewardsSubmission
2929
uint8 internal constant PAUSED_OPERATOR_DIRECTED_AVS_REWARDS_SUBMISSION = 5;
30+
/// @dev Index for flag that pauses calling setOperatorSetPerformanceRewardsSubmission
31+
uint8 internal constant PAUSED_OPERATOR_SET_PERFORMANCE_REWARDS_SUBMISSION = 6;
3032
/// @dev Index for flag that pauses calling setOperatorAVSSplit
31-
uint8 internal constant PAUSED_OPERATOR_AVS_SPLIT = 6;
33+
uint8 internal constant PAUSED_OPERATOR_AVS_SPLIT = 7;
3234
/// @dev Index for flag that pauses calling setOperatorPISplit
33-
uint8 internal constant PAUSED_OPERATOR_PI_SPLIT = 7;
35+
uint8 internal constant PAUSED_OPERATOR_PI_SPLIT = 8;
36+
/// @dev Index for flag that pauses calling setOperatorSetPerformanceSplit
37+
uint8 internal constant PAUSED_OPERATOR_SET_OPERATOR_SPLIT = 9;
3438

3539
/// @dev Salt for the earner leaf, meant to distinguish from tokenLeaf since they have the same sized data
3640
uint8 internal constant EARNER_LEAF_SALT = 0;
@@ -115,14 +119,21 @@ abstract contract RewardsCoordinatorStorage is IRewardsCoordinator {
115119

116120
// Construction
117121

118-
/// @notice Mapping: avs => operatorDirectedAVSRewardsSubmissionHash => bool to check if operator-directed rewards submission hash has been submitted
119-
mapping(address => mapping(bytes32 => bool)) public isOperatorDirectedAVSRewardsSubmissionHash;
122+
/// @notice Returns whether a `hash` is a `valid` operator set performance rewards submission hash for a given `avs`.
123+
mapping(address avs => mapping(bytes32 hash => bool valid)) public isOperatorDirectedAVSRewardsSubmissionHash;
120124

121-
/// @notice Mapping: operator => avs => OperatorSplit. The split an operator takes for a specific AVS.
122-
mapping(address => mapping(address => OperatorSplit)) internal operatorAVSSplitBips;
125+
/// @notice Returns whether a `hash` is a `valid` operator set performance rewards submission hash for a given `avs`.
126+
mapping(address avs => mapping(bytes32 hash => bool valid)) public isOperatorSetPerformanceRewardsSubmissionHash;
123127

124-
/// @notice Mapping: operator => OperatorPISplit. The split an operator takes for Programmatic Incentives.
125-
mapping(address => OperatorSplit) internal operatorPISplitBips;
128+
/// @notice Returns the `split` an `operator` takes for an `avs`.
129+
mapping(address operator => mapping(address avs => OperatorSplit split)) internal _operatorAVSSplitBips;
130+
131+
/// @notice Returns the `split` an `operator` takes for Programmatic Incentives.
132+
mapping(address operator => OperatorSplit split) internal _operatorPISplitBips;
133+
134+
/// @notice Returns the `split` an `operator` takes for a given operator set.
135+
mapping(address operator => mapping(bytes32 operatorSetKey => OperatorSplit split)) internal
136+
_operatorSetPerformanceSplitBips;
126137

127138
constructor(
128139
IDelegationManager _delegationManager,

src/contracts/interfaces/IRewardsCoordinator.sol

Lines changed: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
pragma solidity ^0.8.27;
33

44
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
5+
import "../libraries/OperatorSetLib.sol";
56
import "./IPauserRegistry.sol";
67
import "./IStrategy.sol";
78

@@ -27,6 +28,8 @@ interface IRewardsCoordinatorErrors {
2728
error NewRootMustBeForNewCalculatedPeriod();
2829
/// @dev Thrown when rewards end timestamp has not elapsed.
2930
error RewardsEndTimestampNotElapsed();
31+
/// @dev Thrown when an invalid operator set is provided.
32+
error InvalidOperatorSet();
3033

3134
/// Rewards Submissions
3235

@@ -270,13 +273,29 @@ interface IRewardsCoordinatorEvents is IRewardsCoordinatorTypes {
270273
* @param operatorDirectedRewardsSubmission The Operator-Directed Rewards Submission. Contains the token, start timestamp, duration, operator rewards, description and, strategy and multipliers.
271274
*/
272275
event OperatorDirectedAVSRewardsSubmissionCreated(
273-
address indexed caller,
276+
address caller,
274277
address indexed avs,
278+
uint256 indexed submissionNonce,
275279
bytes32 indexed operatorDirectedRewardsSubmissionHash,
276-
uint256 submissionNonce,
277280
OperatorDirectedRewardsSubmission operatorDirectedRewardsSubmission
278281
);
279282

283+
/**
284+
* @notice Emitted when an AVS creates a valid performance based `OperatorDirectedRewardsSubmission`
285+
* @param caller The address calling `createOperatorSetPerformanceRewardsSubmission`.
286+
* @param operatorSet The operatorSet on behalf of which the performance rewards are being submitted.
287+
* @param performanceRewardsSubmissionHash Keccak256 hash of (`avs`, `submissionNonce` and `performanceRewardsSubmission`).
288+
* @param submissionNonce Current nonce of the operatorSet. Used to generate a unique submission hash.
289+
* @param performanceRewardsSubmission The Performance Rewards Submission. Contains the token, start timestamp, duration, description and, strategy and multipliers.
290+
*/
291+
event OperatorDirectedOperatorSetRewardsSubmissionCreated(
292+
address caller,
293+
OperatorSet indexed operatorSet,
294+
uint256 indexed submissionNonce,
295+
bytes32 indexed performanceRewardsSubmissionHash,
296+
OperatorDirectedRewardsSubmission performanceRewardsSubmission
297+
);
298+
280299
/// @notice rewardsUpdater is responsible for submiting DistributionRoots, only owner can set rewardsUpdater
281300
event RewardsUpdaterSet(address indexed oldRewardsUpdater, address indexed newRewardsUpdater);
282301

@@ -321,6 +340,24 @@ interface IRewardsCoordinatorEvents is IRewardsCoordinatorTypes {
321340
uint16 newOperatorPISplitBips
322341
);
323342

343+
/**
344+
* @notice Emitted when the operator split for a given operatorSet is set.
345+
* @param caller The address calling `setOperatorSetPerformanceSplit`.
346+
* @param operator The operator on behalf of which the split is being set.
347+
* @param operatorSet The operatorSet for which the split is being set.
348+
* @param activatedAt The timestamp at which the split will be activated.
349+
* @param oldOperatorSetSplitBips The old split for the operator for the operatorSet.
350+
* @param newOperatorSetSplitBips The new split for the operator for the operatorSet.
351+
*/
352+
event OperatorSetPerformanceSplitBipsSet(
353+
address indexed caller,
354+
address indexed operator,
355+
OperatorSet indexed operatorSet,
356+
uint32 activatedAt,
357+
uint16 oldOperatorSetSplitBips,
358+
uint16 newOperatorSetSplitBips
359+
);
360+
324361
event ClaimerForSet(address indexed earner, address indexed oldClaimer, address indexed claimer);
325362

326363
/// @notice rootIndex is the specific array index of the newly created root in the storage array
@@ -420,6 +457,13 @@ interface IRewardsCoordinator is IRewardsCoordinatorErrors, IRewardsCoordinatorE
420457
OperatorDirectedRewardsSubmission[] calldata operatorDirectedRewardsSubmissions
421458
) external;
422459

460+
/// @notice operatorSet parallel of createAVSPerformanceRewardsSubmission
461+
/// @dev sender must be the avs of the given operatorSet
462+
function createOperatorSetPerformanceRewardsSubmission(
463+
OperatorSet calldata operatorSet,
464+
OperatorDirectedRewardsSubmission[] calldata performanceRewardsSubmissions
465+
) external;
466+
423467
/**
424468
* @notice Claim rewards against a given root (read from _distributionRoots[claim.rootIndex]).
425469
* Earnings are cumulative so earners don't have to claim against all distribution roots they have earnings for,
@@ -522,6 +566,14 @@ interface IRewardsCoordinator is IRewardsCoordinatorErrors, IRewardsCoordinatorE
522566
*/
523567
function setOperatorPISplit(address operator, uint16 split) external;
524568

569+
/**
570+
* @notice Sets the split for a specific operator for a specific operatorSet.
571+
* @param operator The operator who is setting the split.
572+
* @param operatorSet The operatorSet for which the split is being set by the operator.
573+
* @param split The split for the operator for the specific operatorSet in bips.
574+
*/
575+
function setOperatorSetPerformanceSplit(address operator, OperatorSet calldata operatorSet, uint16 split) external;
576+
525577
/**
526578
* @notice Sets the permissioned `rewardsUpdater` address which can post new roots
527579
* @dev Only callable by the contract owner
@@ -570,6 +622,12 @@ interface IRewardsCoordinator is IRewardsCoordinatorErrors, IRewardsCoordinatorE
570622
address operator
571623
) external view returns (uint16);
572624

625+
/// @notice Returns the split for a specific `operator` for a given `operatorSet`
626+
function getOperatorSetPerformanceSplit(
627+
address operator,
628+
OperatorSet calldata operatorSet
629+
) external view returns (uint16);
630+
573631
/// @notice return the hash of the earner's leaf
574632
function calculateEarnerLeafHash(
575633
EarnerTreeMerkleLeaf calldata leaf

src/test/mocks/AllocationManagerMock.sol

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,29 @@
22
pragma solidity ^0.8.9;
33

44
import "forge-std/Test.sol";
5-
import "../../contracts/interfaces/IStrategy.sol";
6-
import "../../contracts/libraries/Snapshots.sol";
5+
import "src/contracts/interfaces/IStrategy.sol";
6+
import "src/contracts/libraries/Snapshots.sol";
7+
import "src/contracts/libraries/OperatorSetLib.sol";
78

89
contract AllocationManagerMock is Test {
910
using Snapshots for Snapshots.DefaultWadHistory;
11+
using OperatorSetLib for OperatorSet;
1012

1113
receive() external payable {}
1214
fallback() external payable {}
1315

16+
mapping(bytes32 operatorSetKey => bool) public _isOperatorSet;
1417
mapping(address avs => uint256) public getOperatorSetCount;
1518
mapping(address => mapping(IStrategy => Snapshots.DefaultWadHistory)) internal _maxMagnitudeHistory;
1619

20+
function setIsOperatorSet(OperatorSet memory operatorSet, bool boolean) external {
21+
_isOperatorSet[operatorSet.key()] = boolean;
22+
}
23+
24+
function isOperatorSet(OperatorSet memory operatorSet) external view returns (bool) {
25+
return _isOperatorSet[operatorSet.key()];
26+
}
27+
1728
function setMaxMagnitudes(
1829
address operator,
1930
IStrategy[] calldata strategies,

0 commit comments

Comments
 (0)