-
Notifications
You must be signed in to change notification settings - Fork 346
/
StrategyManager.sol
392 lines (345 loc) · 16.8 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
391
392
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.12;
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 "../interfaces/IEigenPodManager.sol";
import "../permissions/Pausable.sol";
import "./StrategyManagerStorage.sol";
import "../libraries/EIP1271SignatureUtils.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
{
using SafeERC20 for IERC20;
// index for flag that pauses deposits when set
uint8 internal constant PAUSED_DEPOSITS = 0;
// chain id at the time of contract deployment
uint256 internal immutable ORIGINAL_CHAIN_ID;
modifier onlyStrategyWhitelister() {
require(
msg.sender == strategyWhitelister, "StrategyManager.onlyStrategyWhitelister: not the strategyWhitelister"
);
_;
}
modifier onlyStrategiesWhitelistedForDeposit(IStrategy strategy) {
require(
strategyIsWhitelistedForDeposit[strategy],
"StrategyManager.onlyStrategiesWhitelistedForDeposit: strategy not whitelisted"
);
_;
}
modifier onlyDelegationManager() {
require(msg.sender == address(delegation), "StrategyManager.onlyDelegationManager: not the DelegationManager");
_;
}
/**
* @param _delegation The delegation contract of EigenLayer.
* @param _slasher The primary slashing contract of EigenLayer.
* @param _eigenPodManager The contract that keeps track of EigenPod stakes for restaking beacon chain ether.
*/
constructor(
IDelegationManager _delegation,
IEigenPodManager _eigenPodManager,
ISlasher _slasher
) StrategyManagerStorage(_delegation, _eigenPodManager, _slasher) {
_disableInitializers();
ORIGINAL_CHAIN_ID = block.chainid;
}
// 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 _pauserRegistry Used for access control of pausing.
* @param initialOwner Ownership of this contract is transferred to this address.
* @param initialStrategyWhitelister The initial value of `strategyWhitelister` to set.
* @param initialPausedStatus The initial value of `_paused` to set.
*/
function initialize(
address initialOwner,
address initialStrategyWhitelister,
IPauserRegistry _pauserRegistry,
uint256 initialPausedStatus
) external initializer {
_DOMAIN_SEPARATOR = _calculateDomainSeparator();
_initializePauser(_pauserRegistry, initialPausedStatus);
_transferOwnership(initialOwner);
_setStrategyWhitelister(initialStrategyWhitelister);
}
/// @inheritdoc IStrategyManager
function depositIntoStrategy(
IStrategy strategy,
IERC20 token,
uint256 amount
) external onlyWhenNotPaused(PAUSED_DEPOSITS) nonReentrant returns (uint256 shares) {
shares = _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 shares) {
require(
!thirdPartyTransfersForbidden[strategy],
"StrategyManager.depositIntoStrategyWithSignature: third transfers disabled"
);
require(expiry >= block.timestamp, "StrategyManager.depositIntoStrategyWithSignature: signature expired");
// calculate struct hash, then increment `staker`'s nonce
uint256 nonce = nonces[staker];
bytes32 structHash = keccak256(abi.encode(DEPOSIT_TYPEHASH, staker, strategy, token, amount, nonce, expiry));
unchecked {
nonces[staker] = nonce + 1;
}
// calculate the digest hash
bytes32 digestHash = keccak256(abi.encodePacked("\x19\x01", domainSeparator(), structHash));
/**
* check validity of signature:
* 1) if `staker` is an EOA, then `signature` must be a valid ECDSA signature from `staker`,
* indicating their intention for this action
* 2) if `staker` is a contract, then `signature` will be checked according to EIP-1271
*/
EIP1271SignatureUtils.checkSignature_EIP1271(staker, digestHash, signature);
// deposit the tokens (from the `msg.sender`) and credit the new shares to the `staker`
shares = _depositIntoStrategy(staker, strategy, token, amount);
}
/// @inheritdoc IStrategyManager
function removeShares(address staker, IStrategy strategy, uint256 shares) external onlyDelegationManager {
_removeShares(staker, strategy, shares);
}
/// @inheritdoc IStrategyManager
function addShares(
address staker,
IERC20 token,
IStrategy strategy,
uint256 shares
) external onlyDelegationManager {
_addShares(staker, token, strategy, shares);
}
/// @inheritdoc IStrategyManager
function withdrawSharesAsTokens(
address recipient,
IStrategy strategy,
uint256 shares,
IERC20 token
) external onlyDelegationManager {
strategy.withdraw(recipient, token, shares);
}
/// @inheritdoc IStrategyManager
function setThirdPartyTransfersForbidden(IStrategy strategy, bool value) external onlyStrategyWhitelister {
_setThirdPartyTransfersForbidden(strategy, value);
}
/// @inheritdoc IStrategyManager
function setStrategyWhitelister(address newStrategyWhitelister) external onlyOwner {
_setStrategyWhitelister(newStrategyWhitelister);
}
/// @inheritdoc IStrategyManager
function addStrategiesToDepositWhitelist(
IStrategy[] calldata strategiesToWhitelist,
bool[] calldata thirdPartyTransfersForbiddenValues
) external onlyStrategyWhitelister {
require(
strategiesToWhitelist.length == thirdPartyTransfersForbiddenValues.length,
"StrategyManager.addStrategiesToDepositWhitelist: array lengths do not match"
);
uint256 strategiesToWhitelistLength = strategiesToWhitelist.length;
for (uint256 i = 0; i < strategiesToWhitelistLength;) {
// 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]);
_setThirdPartyTransfersForbidden(strategiesToWhitelist[i], thirdPartyTransfersForbiddenValues[i]);
}
unchecked {
++i;
}
}
}
/// @inheritdoc IStrategyManager
function removeStrategiesFromDepositWhitelist(IStrategy[] calldata strategiesToRemoveFromWhitelist)
external
onlyStrategyWhitelister
{
uint256 strategiesToRemoveFromWhitelistLength = strategiesToRemoveFromWhitelist.length;
for (uint256 i = 0; i < strategiesToRemoveFromWhitelistLength;) {
// change storage and emit event only if strategy is already in whitelist
if (strategyIsWhitelistedForDeposit[strategiesToRemoveFromWhitelist[i]]) {
strategyIsWhitelistedForDeposit[strategiesToRemoveFromWhitelist[i]] = false;
emit StrategyRemovedFromDepositWhitelist(strategiesToRemoveFromWhitelist[i]);
// Set mapping value to default false value
_setThirdPartyTransfersForbidden(strategiesToRemoveFromWhitelist[i], false);
}
unchecked {
++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 `stakerStrategyShares[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 {
// sanity checks on inputs
require(staker != address(0), "StrategyManager._addShares: staker cannot be zero address");
require(shares != 0, "StrategyManager._addShares: shares should not be zero!");
// if they dont have existing shares of this strategy, add it to their strats
if (stakerStrategyShares[staker][strategy] == 0) {
require(
stakerStrategyList[staker].length < MAX_STAKER_STRATEGY_LIST_LENGTH,
"StrategyManager._addShares: deposit would exceed MAX_STAKER_STRATEGY_LIST_LENGTH"
);
stakerStrategyList[staker].push(strategy);
}
// add the returned shares to their existing shares for this strategy
stakerStrategyShares[staker][strategy] += shares;
emit Deposit(staker, token, strategy, 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
_addShares(staker, token, strategy, shares);
// Increase shares delegated to operator, if needed
delegation.increaseDelegatedShares(staker, strategy, shares);
return shares;
}
/**
* @notice Decreases the shares that `staker` holds in `strategy` by `shareAmount`.
* @param staker The address to decrement shares from
* @param strategy The strategy for which the `staker`'s shares are being decremented
* @param shareAmount The amount of 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 _removeShares(address staker, IStrategy strategy, uint256 shareAmount) internal returns (bool) {
// sanity checks on inputs
require(shareAmount != 0, "StrategyManager._removeShares: shareAmount should not be zero!");
//check that the user has sufficient shares
uint256 userShares = stakerStrategyShares[staker][strategy];
require(shareAmount <= userShares, "StrategyManager._removeShares: shareAmount too high");
//unchecked arithmetic since we just checked this above
unchecked {
userShares = userShares - shareAmount;
}
// subtract the shares from the staker's existing shares for this strategy
stakerStrategyShares[staker][strategy] = userShares;
// if no existing shares, remove the strategy from the staker's dynamic array of strategies
if (userShares == 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;) {
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;
}
unchecked {
++j;
}
}
// if we didn't find the strategy, revert
require(j != stratsLength, "StrategyManager._removeStrategyFromStakerStrategyList: strategy not found");
// pop off the last entry in the list of strategies
stakerStrategyList[staker].pop();
}
/**
* @notice Internal function for modifying `thirdPartyTransfersForbidden`.
* Used inside of the `setThirdPartyTransfersForbidden` and `addStrategiesToDepositWhitelist` functions.
* @param strategy The strategy to set `thirdPartyTransfersForbidden` value to
* @param value bool value to set `thirdPartyTransfersForbidden` to
*/
function _setThirdPartyTransfersForbidden(IStrategy strategy, bool value) internal {
emit UpdatedThirdPartyTransfersForbidden(strategy, value);
thirdPartyTransfersForbidden[strategy] = value;
}
/**
* @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 shares = new uint256[](strategiesLength);
for (uint256 i = 0; i < strategiesLength;) {
shares[i] = stakerStrategyShares[staker][stakerStrategyList[staker][i]];
unchecked {
++i;
}
}
return (stakerStrategyList[staker], shares);
}
/// @notice Simple getter function that returns `stakerStrategyList[staker].length`.
function stakerStrategyListLength(address staker) external view returns (uint256) {
return stakerStrategyList[staker].length;
}
/// @inheritdoc IStrategyManager
function domainSeparator() public view returns (bytes32) {
if (block.chainid == ORIGINAL_CHAIN_ID) {
return _DOMAIN_SEPARATOR;
} else {
return _calculateDomainSeparator();
}
}
// @notice Internal function for calculating the current domain separator of this contract
function _calculateDomainSeparator() internal view returns (bytes32) {
return keccak256(abi.encode(DOMAIN_TYPEHASH, keccak256(bytes("EigenLayer")), block.chainid, address(this)));
}
}