Skip to content

Commit 1dff320

Browse files
authored
feat: Rewards v2 (#315)
* chore: bump up eigenlayer contracts dependency * feat: createAVSPerformanceRewardsSubmission in AVSServiceManager * feat: setClaimerFor * feat: updated IServiceManager * fix: onlyOwner for setClaimerFor * refactor: commission to split terminology * test: not owner * test: erc20 not approved * test: single submission * test: multiple submissions * test: setClaimer * chore: updated events * chore: updated submodules
1 parent 67ae0ef commit 1dff320

File tree

7 files changed

+1022
-151
lines changed

7 files changed

+1022
-151
lines changed

lib/eigenlayer-contracts

src/ServiceManagerBase.sol

Lines changed: 118 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,9 @@ abstract contract ServiceManagerBase is ServiceManagerBaseStorage {
7272
* @param _metadataURI is the metadata URI for the AVS
7373
* @dev only callable by the owner
7474
*/
75-
function updateAVSMetadataURI(string memory _metadataURI) public virtual onlyOwner {
75+
function updateAVSMetadataURI(
76+
string memory _metadataURI
77+
) public virtual onlyOwner {
7678
_avsDirectory.updateAVSMetadataURI(_metadataURI);
7779
}
7880

@@ -87,25 +89,95 @@ abstract contract ServiceManagerBase is ServiceManagerBaseStorage {
8789
* @dev This function will revert if the `rewardsSubmission` is malformed,
8890
* e.g. if the `strategies` and `weights` arrays are of non-equal lengths
8991
*/
90-
function createAVSRewardsSubmission(IRewardsCoordinator.RewardsSubmission[] calldata rewardsSubmissions)
91-
public
92-
virtual
93-
onlyRewardsInitiator
94-
{
92+
function createAVSRewardsSubmission(
93+
IRewardsCoordinator.RewardsSubmission[] calldata rewardsSubmissions
94+
) public virtual onlyRewardsInitiator {
9595
for (uint256 i = 0; i < rewardsSubmissions.length; ++i) {
9696
// transfer token to ServiceManager and approve RewardsCoordinator to transfer again
9797
// in createAVSRewardsSubmission() call
98-
rewardsSubmissions[i].token.transferFrom(msg.sender, address(this), rewardsSubmissions[i].amount);
99-
uint256 allowance =
100-
rewardsSubmissions[i].token.allowance(address(this), address(_rewardsCoordinator));
98+
rewardsSubmissions[i].token.transferFrom(
99+
msg.sender,
100+
address(this),
101+
rewardsSubmissions[i].amount
102+
);
103+
uint256 allowance = rewardsSubmissions[i].token.allowance(
104+
address(this),
105+
address(_rewardsCoordinator)
106+
);
101107
rewardsSubmissions[i].token.approve(
102-
address(_rewardsCoordinator), rewardsSubmissions[i].amount + allowance
108+
address(_rewardsCoordinator),
109+
rewardsSubmissions[i].amount + allowance
103110
);
104111
}
105112

106113
_rewardsCoordinator.createAVSRewardsSubmission(rewardsSubmissions);
107114
}
108115

116+
/**
117+
* @notice Creates a new operator-directed rewards submission, to be split amongst the operators and
118+
* set of stakers delegated to operators who are registered to this `avs`.
119+
* @param operatorDirectedRewardsSubmissions The operator-directed rewards submissions being created.
120+
* @dev Only callabe by the permissioned rewardsInitiator address
121+
* @dev The duration of the `rewardsSubmission` cannot exceed `MAX_REWARDS_DURATION`
122+
* @dev The tokens are sent to the `RewardsCoordinator` contract
123+
* @dev This contract needs a token approval of sum of all `operatorRewards` in the `operatorDirectedRewardsSubmissions`, before calling this function.
124+
* @dev Strategies must be in ascending order of addresses to check for duplicates
125+
* @dev Operators must be in ascending order of addresses to check for duplicates.
126+
* @dev This function will revert if the `operatorDirectedRewardsSubmissions` is malformed.
127+
*/
128+
function createOperatorDirectedAVSRewardsSubmission(
129+
IRewardsCoordinator.OperatorDirectedRewardsSubmission[]
130+
calldata operatorDirectedRewardsSubmissions
131+
) public virtual onlyRewardsInitiator {
132+
for (
133+
uint256 i = 0;
134+
i < operatorDirectedRewardsSubmissions.length;
135+
++i
136+
) {
137+
// Calculate total amount of token to transfer
138+
uint256 totalAmount = 0;
139+
for (
140+
uint256 j = 0;
141+
j <
142+
operatorDirectedRewardsSubmissions[i].operatorRewards.length;
143+
++j
144+
) {
145+
totalAmount += operatorDirectedRewardsSubmissions[i]
146+
.operatorRewards[j]
147+
.amount;
148+
}
149+
150+
// Transfer token to ServiceManager and approve RewardsCoordinator to transfer again
151+
// in createAVSPerformanceRewardsSubmission() call
152+
operatorDirectedRewardsSubmissions[i].token.transferFrom(
153+
msg.sender,
154+
address(this),
155+
totalAmount
156+
);
157+
uint256 allowance = operatorDirectedRewardsSubmissions[i]
158+
.token
159+
.allowance(address(this), address(_rewardsCoordinator));
160+
operatorDirectedRewardsSubmissions[i].token.approve(
161+
address(_rewardsCoordinator),
162+
totalAmount + allowance
163+
);
164+
}
165+
166+
_rewardsCoordinator.createOperatorDirectedAVSRewardsSubmission(
167+
address(this),
168+
operatorDirectedRewardsSubmissions
169+
);
170+
}
171+
172+
/**
173+
* @notice Forwards a call to Eigenlayer's RewardsCoordinator contract to set the address of the entity that can call `processClaim` on behalf of this contract.
174+
* @param claimer The address of the entity that can call `processClaim` on behalf of the earner
175+
* @dev Only callabe by the owner.
176+
*/
177+
function setClaimerFor(address claimer) public virtual onlyOwner {
178+
_rewardsCoordinator.setClaimerFor(claimer);
179+
}
180+
109181
/**
110182
* @notice Forwards a call to EigenLayer's AVSDirectory contract to confirm operator registration with the AVS
111183
* @param operator The address of the operator to register.
@@ -122,7 +194,9 @@ abstract contract ServiceManagerBase is ServiceManagerBaseStorage {
122194
* @notice Forwards a call to EigenLayer's AVSDirectory contract to confirm operator deregistration from the AVS
123195
* @param operator The address of the operator to deregister.
124196
*/
125-
function deregisterOperatorFromAVS(address operator) public virtual onlyRegistryCoordinator {
197+
function deregisterOperatorFromAVS(
198+
address operator
199+
) public virtual onlyRegistryCoordinator {
126200
_avsDirectory.deregisterOperatorFromAVS(operator);
127201
}
128202

@@ -131,7 +205,9 @@ abstract contract ServiceManagerBase is ServiceManagerBaseStorage {
131205
* @param newRewardsInitiator The new rewards initiator address
132206
* @dev only callable by the owner
133207
*/
134-
function setRewardsInitiator(address newRewardsInitiator) external onlyOwner {
208+
function setRewardsInitiator(
209+
address newRewardsInitiator
210+
) external onlyOwner {
135211
_setRewardsInitiator(newRewardsInitiator);
136212
}
137213

@@ -146,7 +222,12 @@ abstract contract ServiceManagerBase is ServiceManagerBaseStorage {
146222
* @dev No guarantee is made on uniqueness of each element in the returned array.
147223
* The off-chain service should do that validation separately
148224
*/
149-
function getRestakeableStrategies() external virtual view returns (address[] memory) {
225+
function getRestakeableStrategies()
226+
external
227+
view
228+
virtual
229+
returns (address[] memory)
230+
{
150231
uint256 quorumCount = _registryCoordinator.quorumCount();
151232

152233
if (quorumCount == 0) {
@@ -161,10 +242,13 @@ abstract contract ServiceManagerBase is ServiceManagerBaseStorage {
161242
address[] memory restakedStrategies = new address[](strategyCount);
162243
uint256 index = 0;
163244
for (uint256 i = 0; i < _registryCoordinator.quorumCount(); i++) {
164-
uint256 strategyParamsLength = _stakeRegistry.strategyParamsLength(uint8(i));
245+
uint256 strategyParamsLength = _stakeRegistry.strategyParamsLength(
246+
uint8(i)
247+
);
165248
for (uint256 j = 0; j < strategyParamsLength; j++) {
166-
restakedStrategies[index] =
167-
address(_stakeRegistry.strategyParamsByIndex(uint8(i), j).strategy);
249+
restakedStrategies[index] = address(
250+
_stakeRegistry.strategyParamsByIndex(uint8(i), j).strategy
251+
);
168252
index++;
169253
}
170254
}
@@ -178,35 +262,41 @@ abstract contract ServiceManagerBase is ServiceManagerBaseStorage {
178262
* @dev No guarantee is made on whether the operator has shares for a strategy in a quorum or uniqueness
179263
* of each element in the returned array. The off-chain service should do that validation separately
180264
*/
181-
function getOperatorRestakedStrategies(address operator)
182-
external
183-
virtual
184-
view
185-
returns (address[] memory)
186-
{
265+
function getOperatorRestakedStrategies(
266+
address operator
267+
) external view virtual returns (address[] memory) {
187268
bytes32 operatorId = _registryCoordinator.getOperatorId(operator);
188-
uint192 operatorBitmap = _registryCoordinator.getCurrentQuorumBitmap(operatorId);
269+
uint192 operatorBitmap = _registryCoordinator.getCurrentQuorumBitmap(
270+
operatorId
271+
);
189272

190273
if (operatorBitmap == 0 || _registryCoordinator.quorumCount() == 0) {
191274
return new address[](0);
192275
}
193276

194277
// Get number of strategies for each quorum in operator bitmap
195-
bytes memory operatorRestakedQuorums = BitmapUtils.bitmapToBytesArray(operatorBitmap);
278+
bytes memory operatorRestakedQuorums = BitmapUtils.bitmapToBytesArray(
279+
operatorBitmap
280+
);
196281
uint256 strategyCount;
197282
for (uint256 i = 0; i < operatorRestakedQuorums.length; i++) {
198-
strategyCount += _stakeRegistry.strategyParamsLength(uint8(operatorRestakedQuorums[i]));
283+
strategyCount += _stakeRegistry.strategyParamsLength(
284+
uint8(operatorRestakedQuorums[i])
285+
);
199286
}
200287

201288
// Get strategies for each quorum in operator bitmap
202289
address[] memory restakedStrategies = new address[](strategyCount);
203290
uint256 index = 0;
204291
for (uint256 i = 0; i < operatorRestakedQuorums.length; i++) {
205292
uint8 quorum = uint8(operatorRestakedQuorums[i]);
206-
uint256 strategyParamsLength = _stakeRegistry.strategyParamsLength(quorum);
293+
uint256 strategyParamsLength = _stakeRegistry.strategyParamsLength(
294+
quorum
295+
);
207296
for (uint256 j = 0; j < strategyParamsLength; j++) {
208-
restakedStrategies[index] =
209-
address(_stakeRegistry.strategyParamsByIndex(quorum, j).strategy);
297+
restakedStrategies[index] = address(
298+
_stakeRegistry.strategyParamsByIndex(quorum, j).strategy
299+
);
210300
index++;
211301
}
212302
}

src/interfaces/IServiceManager.sol

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,37 @@ interface IServiceManager is IServiceManagerUI {
2020
* @dev This function will revert if the `rewardsSubmission` is malformed,
2121
* e.g. if the `strategies` and `weights` arrays are of non-equal lengths
2222
*/
23-
function createAVSRewardsSubmission(IRewardsCoordinator.RewardsSubmission[] calldata rewardsSubmissions) external;
23+
function createAVSRewardsSubmission(
24+
IRewardsCoordinator.RewardsSubmission[] calldata rewardsSubmissions
25+
) external;
26+
27+
/**
28+
* @notice Creates a new operator-directed rewards submission on behalf of an AVS, to be split amongst the operators and
29+
* set of stakers delegated to operators who are registered to the `avs`.
30+
* @param operatorDirectedRewardsSubmissions The operator-directed rewards submissions being created
31+
* @dev Only callabe by the permissioned rewardsInitiator address
32+
* @dev The duration of the `rewardsSubmission` cannot exceed `MAX_REWARDS_DURATION`
33+
* @dev The tokens are sent to the `RewardsCoordinator` contract
34+
* @dev This contract needs a token approval of sum of all `operatorRewards` in the `operatorDirectedRewardsSubmissions`, before calling this function.
35+
* @dev Strategies must be in ascending order of addresses to check for duplicates
36+
* @dev Operators must be in ascending order of addresses to check for duplicates.
37+
* @dev This function will revert if the `operatorDirectedRewardsSubmissions` is malformed.
38+
*/
39+
function createOperatorDirectedAVSRewardsSubmission(
40+
IRewardsCoordinator.OperatorDirectedRewardsSubmission[]
41+
calldata operatorDirectedRewardsSubmissions
42+
) external;
43+
44+
/**
45+
* @notice Forwards a call to Eigenlayer's RewardsCoordinator contract to set the address of the entity that can call `processClaim` on behalf of this contract.
46+
* @param claimer The address of the entity that can call `processClaim` on behalf of the earner
47+
* @dev Only callabe by the owner.
48+
*/
49+
function setClaimerFor(address claimer) external;
2450

2551
// EVENTS
26-
event RewardsInitiatorUpdated(address prevRewardsInitiator, address newRewardsInitiator);
52+
event RewardsInitiatorUpdated(
53+
address prevRewardsInitiator,
54+
address newRewardsInitiator
55+
);
2756
}

src/unaudited/ECDSAServiceManagerBase.sol

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,21 @@ abstract contract ECDSAServiceManagerBase is
106106
_createAVSRewardsSubmission(rewardsSubmissions);
107107
}
108108

109+
/// @inheritdoc IServiceManager
110+
function createOperatorDirectedAVSRewardsSubmission(
111+
IRewardsCoordinator.OperatorDirectedRewardsSubmission[]
112+
calldata operatorDirectedRewardsSubmissions
113+
) external virtual onlyRewardsInitiator {
114+
_createOperatorDirectedAVSRewardsSubmission(
115+
operatorDirectedRewardsSubmissions
116+
);
117+
}
118+
119+
/// @inheritdoc IServiceManager
120+
function setClaimerFor(address claimer) external virtual onlyOwner {
121+
_setClaimerFor(claimer);
122+
}
123+
109124
/// @inheritdoc IServiceManagerUI
110125
function registerOperatorToAVS(
111126
address operator,
@@ -203,6 +218,64 @@ abstract contract ECDSAServiceManagerBase is
203218
);
204219
}
205220

221+
/**
222+
* @notice Creates a new operator-directed rewards submission, to be split amongst the operators and
223+
* set of stakers delegated to operators who are registered to this `avs`.
224+
* @param operatorDirectedRewardsSubmissions The operator-directed rewards submissions being created.
225+
*/
226+
function _createOperatorDirectedAVSRewardsSubmission(
227+
IRewardsCoordinator.OperatorDirectedRewardsSubmission[]
228+
calldata operatorDirectedRewardsSubmissions
229+
) internal virtual {
230+
for (
231+
uint256 i = 0;
232+
i < operatorDirectedRewardsSubmissions.length;
233+
++i
234+
) {
235+
// Calculate total amount of token to transfer
236+
uint256 totalAmount = 0;
237+
for (
238+
uint256 j = 0;
239+
j <
240+
operatorDirectedRewardsSubmissions[i].operatorRewards.length;
241+
++j
242+
) {
243+
totalAmount += operatorDirectedRewardsSubmissions[i]
244+
.operatorRewards[j]
245+
.amount;
246+
}
247+
248+
// Transfer token to ServiceManager and approve RewardsCoordinator to transfer again
249+
// in createOperatorDirectedAVSRewardsSubmission() call
250+
operatorDirectedRewardsSubmissions[i].token.transferFrom(
251+
msg.sender,
252+
address(this),
253+
totalAmount
254+
);
255+
uint256 allowance = operatorDirectedRewardsSubmissions[i]
256+
.token
257+
.allowance(address(this), rewardsCoordinator);
258+
operatorDirectedRewardsSubmissions[i].token.approve(
259+
rewardsCoordinator,
260+
totalAmount + allowance
261+
);
262+
}
263+
264+
IRewardsCoordinator(rewardsCoordinator)
265+
.createOperatorDirectedAVSRewardsSubmission(
266+
address(this),
267+
operatorDirectedRewardsSubmissions
268+
);
269+
}
270+
271+
/**
272+
* @notice Forwards a call to Eigenlayer's RewardsCoordinator contract to set the address of the entity that can call `processClaim` on behalf of this contract.
273+
* @param claimer The address of the entity that can call `processClaim` on behalf of the earner.
274+
*/
275+
function _setClaimerFor(address claimer) internal virtual {
276+
IRewardsCoordinator(rewardsCoordinator).setClaimerFor(claimer);
277+
}
278+
206279
/**
207280
* @notice Retrieves the addresses of all strategies that are part of the current quorum.
208281
* @dev Fetches the quorum configuration from the ECDSAStakeRegistry and extracts the strategy addresses.

0 commit comments

Comments
 (0)