-
Notifications
You must be signed in to change notification settings - Fork 360
/
Copy pathStrategyManager.sol
390 lines (346 loc) · 15.3 KB
/
StrategyManager.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.27;
import "@openzeppelin-upgrades/contracts/proxy/utils/Initializable.sol";
import "@openzeppelin-upgrades/contracts/access/OwnableUpgradeable.sol";
import "@openzeppelin-upgrades/contracts/security/ReentrancyGuardUpgradeable.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "../mixins/SignatureUtils.sol";
import "../interfaces/IEigenPodManager.sol";
import "../permissions/Pausable.sol";
import "./StrategyManagerStorage.sol";
/**
* @title The primary entry- and exit-point for funds into and out of EigenLayer.
* @author Layr Labs, Inc.
* @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service
* @notice This contract is for managing deposits in different strategies. The main
* functionalities are:
* - adding and removing strategies that any delegator can deposit into
* - enabling deposit of assets into specified strategy(s)
*/
contract StrategyManager is
Initializable,
OwnableUpgradeable,
ReentrancyGuardUpgradeable,
Pausable,
StrategyManagerStorage,
SignatureUtils
{
using SlashingLib for *;
using SafeERC20 for IERC20;
modifier onlyStrategyWhitelister() {
require(msg.sender == strategyWhitelister, OnlyStrategyWhitelister());
_;
}
modifier onlyStrategiesWhitelistedForDeposit(
IStrategy strategy
) {
require(strategyIsWhitelistedForDeposit[strategy], StrategyNotWhitelisted());
_;
}
modifier onlyDelegationManager() {
require(msg.sender == address(delegation), OnlyDelegationManager());
_;
}
/**
* @param _delegation The delegation contract of EigenLayer.
*/
constructor(
IDelegationManager _delegation,
IPauserRegistry _pauserRegistry
) StrategyManagerStorage(_delegation) Pausable(_pauserRegistry) {
_disableInitializers();
}
// EXTERNAL FUNCTIONS
/**
* @notice Initializes the strategy manager contract. Sets the `pauserRegistry` (currently **not** modifiable after being set),
* and transfers contract ownership to the specified `initialOwner`.
* @param initialOwner Ownership of this contract is transferred to this address.
* @param initialStrategyWhitelister The initial value of `strategyWhitelister` to set.
*/
function initialize(
address initialOwner,
address initialStrategyWhitelister,
uint256 initialPausedStatus
) external initializer {
_setPausedStatus(initialPausedStatus);
_transferOwnership(initialOwner);
_setStrategyWhitelister(initialStrategyWhitelister);
}
/// @inheritdoc IStrategyManager
function depositIntoStrategy(
IStrategy strategy,
IERC20 token,
uint256 amount
) external onlyWhenNotPaused(PAUSED_DEPOSITS) nonReentrant returns (uint256 depositShares) {
depositShares = _depositIntoStrategy(msg.sender, strategy, token, amount);
}
/// @inheritdoc IStrategyManager
function depositIntoStrategyWithSignature(
IStrategy strategy,
IERC20 token,
uint256 amount,
address staker,
uint256 expiry,
bytes memory signature
) external onlyWhenNotPaused(PAUSED_DEPOSITS) nonReentrant returns (uint256 depositShares) {
// Cache staker's nonce to avoid sloads.
uint256 nonce = nonces[staker];
// Assert that the signature is valid.
_checkIsValidSignatureNow({
signer: staker,
signableDigest: calculateStrategyDepositDigestHash(staker, strategy, token, amount, nonce, expiry),
signature: signature,
expiry: expiry
});
// Increment the nonce for the staker.
unchecked {
nonces[staker] = nonce + 1;
}
// deposit the tokens (from the `msg.sender`) and credit the new shares to the `staker`
depositShares = _depositIntoStrategy(staker, strategy, token, amount);
}
/// @inheritdoc IShareManager
function removeDepositShares(
address staker,
IStrategy strategy,
uint256 depositSharesToRemove
) external onlyDelegationManager {
_removeDepositShares(staker, strategy, depositSharesToRemove);
}
/// @inheritdoc IShareManager
function addShares(
address staker,
IStrategy strategy,
IERC20 token,
uint256 shares
) external onlyDelegationManager returns (uint256, uint256) {
return _addShares(staker, token, strategy, shares);
}
/// @inheritdoc IShareManager
function withdrawSharesAsTokens(
address staker,
IStrategy strategy,
IERC20 token,
uint256 shares
) external onlyDelegationManager {
strategy.withdraw(staker, token, shares);
}
/// @inheritdoc IShareManager
function increaseBurnableShares(IStrategy strategy, uint256 addedSharesToBurn) external onlyDelegationManager {
burnableShares[strategy] += addedSharesToBurn;
emit BurnableSharesIncreased(strategy, addedSharesToBurn);
}
/// @inheritdoc IStrategyManager
function burnShares(
IStrategy strategy
) external nonReentrant {
uint256 sharesToBurn = burnableShares[strategy];
burnableShares[strategy] = 0;
emit BurnableSharesDecreased(strategy, sharesToBurn);
// burning shares is functionally the same as withdrawing but with different destination address
strategy.withdraw(DEFAULT_BURN_ADDRESS, strategy.underlyingToken(), sharesToBurn);
}
/// @inheritdoc IStrategyManager
function setStrategyWhitelister(
address newStrategyWhitelister
) external onlyOwner {
_setStrategyWhitelister(newStrategyWhitelister);
}
/// @inheritdoc IStrategyManager
function addStrategiesToDepositWhitelist(
IStrategy[] calldata strategiesToWhitelist
) external onlyStrategyWhitelister {
uint256 strategiesToWhitelistLength = strategiesToWhitelist.length;
for (uint256 i = 0; i < strategiesToWhitelistLength; ++i) {
// change storage and emit event only if strategy is not already in whitelist
if (!strategyIsWhitelistedForDeposit[strategiesToWhitelist[i]]) {
strategyIsWhitelistedForDeposit[strategiesToWhitelist[i]] = true;
emit StrategyAddedToDepositWhitelist(strategiesToWhitelist[i]);
}
}
}
/// @inheritdoc IStrategyManager
function removeStrategiesFromDepositWhitelist(
IStrategy[] calldata strategiesToRemoveFromWhitelist
) external onlyStrategyWhitelister {
uint256 strategiesToRemoveFromWhitelistLength = strategiesToRemoveFromWhitelist.length;
for (uint256 i = 0; i < strategiesToRemoveFromWhitelistLength; ++i) {
// change storage and emit event only if strategy is already in whitelist
if (strategyIsWhitelistedForDeposit[strategiesToRemoveFromWhitelist[i]]) {
strategyIsWhitelistedForDeposit[strategiesToRemoveFromWhitelist[i]] = false;
emit StrategyRemovedFromDepositWhitelist(strategiesToRemoveFromWhitelist[i]);
}
}
}
// INTERNAL FUNCTIONS
/**
* @notice This function adds `shares` for a given `strategy` to the `staker` and runs through the necessary update logic.
* @param staker The address to add shares to
* @param token The token that is being deposited (used for indexing)
* @param strategy The Strategy in which the `staker` is receiving shares
* @param shares The amount of shares to grant to the `staker`
* @dev In particular, this function calls `delegation.increaseDelegatedShares(staker, strategy, shares)` to ensure that all
* delegated shares are tracked, increases the stored share amount in `stakerDepositShares[staker][strategy]`, and adds `strategy`
* to the `staker`'s list of strategies, if it is not in the list already.
*/
function _addShares(
address staker,
IERC20 token,
IStrategy strategy,
uint256 shares
) internal returns (uint256, uint256) {
// sanity checks on inputs
require(staker != address(0), StakerAddressZero());
require(shares != 0, SharesAmountZero());
uint256 prevDepositShares = stakerDepositShares[staker][strategy];
// if they dont have prevDepositShares of this strategy, add it to their strats
if (prevDepositShares == 0) {
require(stakerStrategyList[staker].length < MAX_STAKER_STRATEGY_LIST_LENGTH, MaxStrategiesExceeded());
stakerStrategyList[staker].push(strategy);
}
// add the returned depositedShares to their existing shares for this strategy
stakerDepositShares[staker][strategy] = prevDepositShares + shares;
emit Deposit(staker, token, strategy, shares);
return (prevDepositShares, shares);
}
/**
* @notice Internal function in which `amount` of ERC20 `token` is transferred from `msg.sender` to the Strategy-type contract
* `strategy`, with the resulting shares credited to `staker`.
* @param staker The address that will be credited with the new shares.
* @param strategy The Strategy contract to deposit into.
* @param token The ERC20 token to deposit.
* @param amount The amount of `token` to deposit.
* @return shares The amount of *new* shares in `strategy` that have been credited to the `staker`.
*/
function _depositIntoStrategy(
address staker,
IStrategy strategy,
IERC20 token,
uint256 amount
) internal onlyStrategiesWhitelistedForDeposit(strategy) returns (uint256 shares) {
// transfer tokens from the sender to the strategy
token.safeTransferFrom(msg.sender, address(strategy), amount);
// deposit the assets into the specified strategy and get the equivalent amount of shares in that strategy
shares = strategy.deposit(token, amount);
// add the returned shares to the staker's existing shares for this strategy
(uint256 prevDepositShares, uint256 addedShares) = _addShares(staker, token, strategy, shares);
// Increase shares delegated to operator
delegation.increaseDelegatedShares({
staker: staker,
strategy: strategy,
prevDepositShares: prevDepositShares,
addedShares: addedShares
});
return shares;
}
/**
* @notice Decreases the shares that `staker` holds in `strategy` by `depositSharesToRemove`.
* @param staker The address to decrement shares from
* @param strategy The strategy for which the `staker`'s shares are being decremented
* @param depositSharesToRemove The amount of deposit shares to decrement
* @dev If the amount of shares represents all of the staker`s shares in said strategy,
* then the strategy is removed from stakerStrategyList[staker] and 'true' is returned. Otherwise 'false' is returned.
*/
function _removeDepositShares(
address staker,
IStrategy strategy,
uint256 depositSharesToRemove
) internal returns (bool) {
// sanity checks on inputs
require(depositSharesToRemove != 0, SharesAmountZero());
//check that the user has sufficient shares
uint256 userDepositShares = stakerDepositShares[staker][strategy];
// This check technically shouldn't actually ever revert because depositSharesToRemove is already
// checked to not exceed max amount of shares when the withdrawal was queued in the DelegationManager
require(depositSharesToRemove <= userDepositShares, SharesAmountTooHigh());
userDepositShares = userDepositShares - depositSharesToRemove;
// subtract the shares from the staker's existing shares for this strategy
stakerDepositShares[staker][strategy] = userDepositShares;
// if no existing shares, remove the strategy from the staker's dynamic array of strategies
if (userDepositShares == 0) {
_removeStrategyFromStakerStrategyList(staker, strategy);
// return true in the event that the strategy was removed from stakerStrategyList[staker]
return true;
}
// return false in the event that the strategy was *not* removed from stakerStrategyList[staker]
return false;
}
/**
* @notice Removes `strategy` from `staker`'s dynamic array of strategies, i.e. from `stakerStrategyList[staker]`
* @param staker The user whose array will have an entry removed
* @param strategy The Strategy to remove from `stakerStrategyList[staker]`
*/
function _removeStrategyFromStakerStrategyList(address staker, IStrategy strategy) internal {
//loop through all of the strategies, find the right one, then replace
uint256 stratsLength = stakerStrategyList[staker].length;
uint256 j = 0;
for (; j < stratsLength; ++j) {
if (stakerStrategyList[staker][j] == strategy) {
//replace the strategy with the last strategy in the list
stakerStrategyList[staker][j] = stakerStrategyList[staker][stakerStrategyList[staker].length - 1];
break;
}
}
// if we didn't find the strategy, revert
require(j != stratsLength, StrategyNotFound());
// pop off the last entry in the list of strategies
stakerStrategyList[staker].pop();
}
/**
* @notice Internal function for modifying the `strategyWhitelister`. Used inside of the `setStrategyWhitelister` and `initialize` functions.
* @param newStrategyWhitelister The new address for the `strategyWhitelister` to take.
*/
function _setStrategyWhitelister(
address newStrategyWhitelister
) internal {
emit StrategyWhitelisterChanged(strategyWhitelister, newStrategyWhitelister);
strategyWhitelister = newStrategyWhitelister;
}
// VIEW FUNCTIONS
/// @inheritdoc IStrategyManager
function getDeposits(
address staker
) external view returns (IStrategy[] memory, uint256[] memory) {
uint256 strategiesLength = stakerStrategyList[staker].length;
uint256[] memory depositedShares = new uint256[](strategiesLength);
for (uint256 i = 0; i < strategiesLength; ++i) {
depositedShares[i] = stakerDepositShares[staker][stakerStrategyList[staker][i]];
}
return (stakerStrategyList[staker], depositedShares);
}
function getStakerStrategyList(
address staker
) external view returns (IStrategy[] memory) {
return stakerStrategyList[staker];
}
/// @inheritdoc IStrategyManager
function stakerStrategyListLength(
address staker
) external view returns (uint256) {
return stakerStrategyList[staker].length;
}
/// @inheritdoc IStrategyManager
function calculateStrategyDepositDigestHash(
address staker,
IStrategy strategy,
IERC20 token,
uint256 amount,
uint256 nonce,
uint256 expiry
) public view returns (bytes32) {
/// forgefmt: disable-next-item
return _calculateSignableDigest(
keccak256(
abi.encode(
DEPOSIT_TYPEHASH,
staker,
strategy,
token,
amount,
nonce,
expiry
)
)
);
}
}