Skip to content

Instantly share code, notes, and snippets.

@ChaseTheLight01
Created February 28, 2024 18:12
Show Gist options
  • Save ChaseTheLight01/0480c20f1944fccfee632cb37cdb6100 to your computer and use it in GitHub Desktop.
Save ChaseTheLight01/0480c20f1944fccfee632cb37cdb6100 to your computer and use it in GitHub Desktop.
LightChaserV3_Cantina_EigenLayer_Contracts

LightChaser-V3

Twitter: @ChaseTheLight99

Generated for: Cantina : EigenLayer : eigenLayer-contracts

Generated on: 2024-02-28

Total findings: 235

Total HIGH findings: 0

Total Medium findings: 1

Total Low findings: 38

Total Gas findings: 76

Total Refactoring findings: 0

Total NonCritical findings: 120

Total Disputed findings: 0

Summary for Medium findings

Number Details Instances
[Medium-1] Privileged functions can create points of failure 26

Summary for Low findings

Number Details Instances
[Low-1] Function with two array parameter missing a length check 1
[Low-2] Missing checks for address(0x0) when updating address state variables 1
[Low-3] Some tokens may revert when zero value transfers are made 3
[Low-4] Low level calls in solidity versions preceding 0.8.14 can result in an optimiser bug 16
[Low-5] Int casting block.timestamp can reduce the lifespan of a contract 1
[Low-6] Upgradable contracts should have a __gap variable 5
[Low-7] Using > when declaring solidity version without specifying an upperbound can cause future vulnerabilities 1
[Low-8] Uses of EIP712 does not include a version string 1
[Low-9] Contracts are vulnerable to fee-on-transfer accounting-related issues 1
[Low-10] Use SafeCast to safely downcast variables 3
[Low-11] The nonReentrant modifier should be first in a function declaration 5
[Low-12] Public or external initialize functions should be protected with the initializer modifier 1
[Low-13] Function calls within for loops 9
[Low-14] For loops in public or external functions should be avoided due to high gas costs and possible DOS 16
[Low-15] No limits when setting min/max amounts 3
[Low-16] Initializer function can be front run 8
[Low-17] Use _disableInitializers() to ensure initialization occurs once 1
[Low-18] Loss of precision 5
[Low-19] Missing zero address check in constructor 1
[Low-20] Ownable is inherited but not used 2
[Low-21] Use of onlyOwner functions can be lost 4
[Low-22] Critical functions should be a two step procedure 6
[Low-23] Remaining eth may not be refunded to users 1
[Low-24] Absence of Reentrancy Guard with OpenZeppelin's sendValue Function Usage 1
[Low-25] Sending tokens in a for loop 1
[Low-26] Revert on Transfer to the Zero Address 2
[Low-27] Unsafe uint to int conversion 11
[Low-28] Missing zero address check in initializer 5
[Low-29] Critical functions should have a timelock 5
[Low-30] Consider implementing two-step procedure for updating protocol addresses 1
[Low-31] Prefer skip over revert model in iteration 4
[Low-32] Use of abi.encodePacked with dynamic types inside keccak256 4
[Low-33] Nonce variables should be uint256 1
[Low-34] Constructors missing validation 7
[Low-35] Contract contains payable functions but no withdraw/sweep function 1
[Low-36] State variables not capped at reasonable values 1
[Low-37] Large transfers may not work with some ERC20 tokens 3
[Low-38] Functions calling contracts/addresses with transfer hooks are missing reentrancy guards 4

Summary for NonCritical findings

Number Details Instances
[NonCritical-1] A function which defines named returns in it's declaration doesn't need to use return 3
[NonCritical-2] Some if-statement can be converted to a ternary 5
[NonCritical-3] Addresses shouldn't be hard-coded 1
[NonCritical-4] Cyclomatic complexity in functions 2
[NonCritical-5] For loop iterates on arrays not indexed 5
[NonCritical-6] It is standard for all external and public functions to be override from an interface 130
[NonCritical-7] Using abi.encodePacked can result in hash collision when used in hashing functions 5
[NonCritical-8] Non constant/immutable state variables are missing a setter post deployment 1
[NonCritical-9] Contracts do not use their OZ upgradable counterparts 5
[NonCritical-10] Default address(0) can be returned 2
[NonCritical-11] Consider adding emergency-stop functionality 2
[NonCritical-12] Employ Explicit Casting to Bytes or Bytes32 for Enhanced Code Clarity and Meaning 10
[NonCritical-13] It's not standard to end and begin a code object on the same line 4
[NonCritical-14] Use string.concat() on strings instead of abi.encodePacked() for clearer semantic meaning 6
[NonCritical-15] Consider making private state variables internal to increase flexibility 2
[NonCritical-16] Contract can be bricked by the use of both 'Ownable' and 'Pausable' in the same contract 1
[NonCritical-17] Floating pragma should be avoided 3
[NonCritical-18] Empty function blocks 22
[NonCritical-19] In functions which accept an address as a parameter, there should be a zero address check to prevent bugs 81
[NonCritical-20] Enum values should be used in place of constant array indexes 7
[NonCritical-21] Default bool values are manually set 1
[NonCritical-22] Default address values are manually set 1
[NonCritical-23] Ownable2Step should be used in place of Ownable 6
[NonCritical-24] Functions which are either private or internal should have a preceding _ in their name 48
[NonCritical-25] Private and internal state variables should have a preceding _ in their name unless they are constants 9
[NonCritical-26] Contract lines should not be longer than 120 characters for readability 297
[NonCritical-27] Setters should prevent re-setting of the same value 5
[NonCritical-28] Specific imports should be used where possible so only used code is imported 54
[NonCritical-29] Use newer solidity versions 4
[NonCritical-30] Not all event definitions are utilizing indexed variables. 2
[NonCritical-31] Function names should differ to make the code more readable 20
[NonCritical-32] It is convention to make the array size of __gap 50 4
[NonCritical-33] Functions within contracts are not ordered according to the solidity style guide 7
[NonCritical-34] Double type casts create complexity within the code 3
[NonCritical-35] Emits without msg.sender parameter 3
[NonCritical-36] Functions with array parameters should have length checks in place 13
[NonCritical-37] Interface imports should be declared first 8
[NonCritical-38] Upgradable contract constructor should have the initialize modifier 8
[NonCritical-39] All interfaces used within a project should be imported 3
[NonCritical-40] SPDX identifier should be the in the first line of a solidity file 1
[NonCritical-41] Use allowlist/denylist rather than whitelist/blacklist 44
[NonCritical-42] Multiple mappings can be replaced with a single struct mapping 1
[NonCritical-43] Unused state variables present 2
[NonCritical-44] Unused mappings present 2
[NonCritical-45] Unused modifiers present 1
[NonCritical-46] Constants should be on the left side of the 17
[NonCritical-47] Defined named returns not used within function 1
[NonCritical-48] Initialize functions do not emit an event 8
[NonCritical-49] Both immutable and constant state variables should be CONSTANT_CASE 10
[NonCritical-50] Consider using named mappings 18
[NonCritical-51] Contract inherits Pausable but does not expose pausing/unpausing functionality 1
[NonCritical-52] Uses of EIP712 does not include a salt 1
[NonCritical-53] Use a single contract or library for system wide constants 7
[NonCritical-54] Consider using modifiers for address control 14
[NonCritical-55] Off-by-one timestamp error 6
[NonCritical-56] Variables should be used in place of magic numbers to improve readability 28
[NonCritical-57] Redundant else statement 12
[NonCritical-58] Use EIP-5767 to manage EIP712 domains 1
[NonCritical-59] Constant array index within iteration 1
[NonCritical-60] Empty bytes check is missing 29
[NonCritical-61] Use max instead of 0xfff... 2
[NonCritical-62] Use scopes sparingly 7
[NonCritical-63] Remove unnecessary solhint-disable 3
[NonCritical-64] Consider using SMTChecker 39
[NonCritical-65] Top level declarations should be separated by two blank lines 18
[NonCritical-66] Contracts should have full test coverage 11
[NonCritical-67] Assembly block creates dirty bits 4
[NonCritical-68] Consider using named function calls 52
[NonCritical-69] Public state variables should include natspec comments 1
[NonCritical-70] Lack of space near the operator 3
[NonCritical-71] Lack Of Brace Spacing 3
[NonCritical-72] Using while for unbounded loops isn’t recommended 2
[NonCritical-73] Common functions should be refactored to a common base contract 6
[NonCritical-74] Use of override is unnecessary 4
[NonCritical-75] If statement control structures do not comply with best practices 1
[NonCritical-76] Incorrect withdraw declaration 1
[NonCritical-77] Owner can renounce while system is paused 6
[NonCritical-78] Consider adding formal verification proofs 11
[NonCritical-79] Consider bounding input array length 14
[NonCritical-80] Missing events in sensitive functions 11
[NonCritical-81] Consider implementing EIP-5267 to securely describe EIP-712 domains being used 1
[NonCritical-82] Add inline comments for unnamed variables in function declarations 14
[NonCritical-83] Don't assume specific ETH balance 1
[NonCritical-84] Avoid mutating function parameters 1
[NonCritical-85] Pure function is not defined as such in interface 1
[NonCritical-86] It is best practice to use linear inheritance 8
[NonCritical-87] Contracts with only unimplemented functions can be labeled as abstract 1
[NonCritical-88] Funds can be trapped due to unreverting local payable call 2
[NonCritical-89] Superfluous parameter can only be one value 13
[NonCritical-90] Consider only defining one library/interface/contract per sol file 1
[NonCritical-91] Multiline comments should be terminated with '*/' and not '**/' 7
[NonCritical-92] There should not be more than one space before assignment 2
[NonCritical-93] Public variable declarations should have NatSpec descriptions 1
[NonCritical-94] Use the Modern Upgradeable Contract Paradigm 4
[NonCritical-95] Upgrade openzeppelin to the Latest Version - 5.0.0 13
[NonCritical-96] Use a struct to encapsulate multiple function parameters 14
[NonCritical-97] Using delete instead of setting mapping to 0 saves gas 1
[NonCritical-98] Contracts inherits pausable without utilising whenNotPaused 7
[NonCritical-99] Long numbers should include underscores to improve readability and prevent typos 2
[NonCritical-100] Consider using a format prettier or forge fmt 45
[NonCritical-101] Avoid defining a function in a single line including it's contents 164
[NonCritical-102] Floating pragma defined inconsistently 8
[NonCritical-103] Avoid revertible function calls in a constructor 1
[NonCritical-104] Use the same Solidity version in non library/interface files throughout the project 3
[NonCritical-105] Inconsistent checks of address params against address(0) 2
[NonCritical-106] Avoid declaring variables with the names of defined functions within the project 93
[NonCritical-107] Upgradeable contract uses non-upgradeable version of the OpenZeppelin libraries/contracts 8
[NonCritical-108] All verbatim blocks are considered identical by deduplicator and can incorrectly be unified 3
[NonCritical-109] Simplify complex require statements 1
[NonCritical-110] Constructors should emit an event 15
[NonCritical-111] Avoid single line non empty object declarations 8
[NonCritical-112] Struct fields should be mixedCase 2
[NonCritical-113] Consider using 'using-for' syntax when using libraries 56
[NonCritical-114] Consider validating all user inputs 75
[NonCritical-115] Constructor with array parameters has no empty array checks 1
[NonCritical-116] Consider providing a ranged getter for array state variables 5
[NonCritical-117] Consider using named returns 113
[NonCritical-118] Avoid external calls in modifiers 18
[NonCritical-119] Consider migrating from Ownable to AccessControl 6
[NonCritical-120] ERC777 tokens can introduce reentrancy risks 1

Summary for Gas findings

Number Details Instances Gas
[Gas-1] Multiple accesses of the same mapping/array key/index should be cached 15 9450
[Gas-2] Only emit event in setter function if the state variable was changed 9 0.0
[Gas-3] State variables which are not modified within functions should be set as constants or immutable for values set at deployment 1 0.0
[Gas-4] Shorten the array rather than copying to a new one 14 0.0
[Gas-5] Using bools for storage incurs overhead 5 1750
[Gas-6] Consider Using Solady's Gas Optimized Lib for Math 4 0.0
[Gas-7] x + y is more efficient than using += for state variables (likewise for -=) 4 80
[Gas-8] There is a 32 byte length threshold for error strings, strings longer than this consume more gas 63 55566
[Gas-9] Public functions not used internally can be marked as external to save gas 2 0.0
[Gas-10] Calldata should be used in place of memory function parameters when not mutated 4 208
[Gas-11] Usage of smaller uint/int types causes overhead 45 111375
[Gas-12] Use != 0 instead of > 0 10 300
[Gas-13] Integer increments by one can be unchecked to save on gas fees 14 23520
[Gas-14] Use byte32 in place of string 1 0.0
[Gas-15] Default bool values are manually reset 2 0.0
[Gas-16] Default int values are manually reset 40 0.0
[Gas-17] Mappings used within a function more than once should be cached to save gas 2 400
[Gas-18] Use assembly to check for the zero address 21 0.0
[Gas-19] Divisions which do not divide by -X cannot overflow or overflow so such operations can be unchecked to save gas 4 0.0
[Gas-20] Divisions of powers of 2 can be replaced by a right shift operation to save gas 1 0.0
[Gas-21] Struct variables can be packed into fewer storage slots 3 22500
[Gas-22] Consider activating via-ir for deploying 39 380250
[Gas-23] Use bitmap to save gas 15 15750
[Gas-24] Use assembly hashing 8 0.0
[Gas-25] Consider using OZ EnumerateSet in place of nested mappings 6 36000
[Gas-26] Use selfBalance() in place of address(this).balance 1 800
[Gas-27] Use assembly to emit events 53 106742
[Gas-28] Use solady library where possible to save gas 4 16000
[Gas-29] Counting down in for statements is more gas efficient 21 0.0
[Gas-30] State variables can be packed into fewer storage slots by truncating timestamp bytes 4 40000
[Gas-31] Using private rather than public for constants and immutables, saves gas 8 0.0
[Gas-32] Mark Functions That Revert For Normal Users As payable 24 14400
[Gas-33] Function names can be optimized 11 15488
[Gas-34] Lack of unchecked in loops 15 17100
[Gas-35] Consider migrating require statements to custom errors 145 294350
[Gas-36] Consider not using libraries when implementing simple functionality. 6 36000
[Gas-37] Where a value is casted more than once, consider caching the result to save gas 3 0.0
[Gas-38] Assembly let var only used on once 4 0.0
[Gas-39] Assembly let var never used 1 0.0
[Gas-40] Use assembly to validate msg.sender 22 0.0
[Gas-41] Simple checks for zero uint can be done using assembly to save gas 17 1734
[Gas-42] Use Unchecked for Divisions on Constant or Immutable Values 1 0.0
[Gas-43] Using nested if to save gas 11 726
[Gas-44] Optimize Deployment Size by Fine-tuning IPFS Hash 11 1282600
[Gas-45] Avoid Unnecessary Public Variables 36 28512000
[Gas-46] Optimize Storage with Byte Truncation for Time Related State Variables 4 32000
[Gas-47] Stack variable cost less than state variables while used in emiting event 15 2025
[Gas-48] Stack variable cost less than mappings while used in emiting event 1 9
[Gas-49] Avoid emitting event on every iteration 3 3375
[Gas-50] Remove unused modifiers 1 0.0
[Gas-51] Inline modifiers used only once 2 0.0
[Gas-52] Use s.x = s.x + y instead of s.x += y for memory structs (same for -= etc) 3 900
[Gas-53] Time state variables can be truncated to uint32 5 500000
[Gas-54] Constants are cheaper than Enums 3 180000
[Gas-55] ++X costs slightly less gas than X++ (same with --) 13 845
[Gas-56] Solidity versions 0.8.19 and above are more gas efficient 4 16000
[Gas-57] Variables that can be set to immutable 1 0.0
[Gas-58] Variable declared within iteration 5 0.0
[Gas-59] Calling .length in a for loop wastes gas 14 23086
[Gas-60] Internal functions only used once can be inlined so save gas 24 17280
[Gas-61] Constructors can be marked as payable to save deployment gas 15 0.0
[Gas-62] Merge events to save gas 2 1500
[Gas-63] Use assembly scratch space to build calldata for external calls 74 1204720
[Gas-64] Use assembly scratch space to build calldata for event emits 41 369820
[Gas-65] Consider using solady's "FixedPointMathLib" 21 0.0
[Gas-66] Same cast is done multiple times 4 0.0
[Gas-67] Assigning to structs can be more efficient 10 13000
[Gas-68] An inefficient way of checking if a integer is even is being used (X % 2 == 0) 1 0.0
[Gas-69] Internal functions never used once can be removed 18 0.0
[Gas-70] Empty functions should be removed to save gas 22 0.0
[Gas-71] Use OZ Array.unsafeAccess() to avoid repeated array length checks 1 2100
[Gas-72] State variable read in a loop 5 144850
[Gas-73] Use uint256(1)/uint256(2) instead of true/false to save gas for changes 28 6703200
[Gas-74] Avoid emitting events in loops 3 3375
[Gas-75] Enable IR-based code generation 11 0.0
[Gas-76] Write direct outcome, instead of performing mathematical operations for constant state variables 1 0.0

[Medium-1] Privileged functions can create points of failure

Resolution

Ensure such accounts are protected and consider implementing multi sig to prevent a single point of failure

Num of instances: 26

Findings

Click to show findings

['69']

68:     function createDelayedWithdrawal(
69:         address podOwner, // <= FOUND
70:         address recipient
71:     ) external payable onlyEigenPod(podOwner) onlyWhenNotPaused(PAUSED_DELAYED_WITHDRAWAL_CLAIMS)  // <= FOUND

['118']

118:     function setWithdrawalDelayBlocks(uint256 newValue) external onlyOwner  // <= FOUND

['430']

430:     function setMinWithdrawalDelayBlocks(uint256 newMinWithdrawalDelayBlocks) external onlyOwner  // <= FOUND

['444']

441:     function setStrategyWithdrawalDelayBlocks(
442:         IStrategy[] calldata strategies,
443:         uint256[] calldata withdrawalDelayBlocks
444:     ) external onlyOwner  // <= FOUND

['302']

294:     function verifyWithdrawalCredentials(
295:         uint64 oracleTimestamp,
296:         BeaconChainProofs.StateRootProof calldata stateRootProof,
297:         uint40[] calldata validatorIndices,
298:         bytes[] calldata validatorFieldsProofs,
299:         bytes32[][] calldata validatorFields
300:     )
301:         external
302:         onlyEigenPodOwner // <= FOUND
303:         onlyWhenNotPaused(PAUSED_EIGENPODS_VERIFY_CREDENTIALS) // <= FOUND
304:         
305:         proofIsForValidTimestamp(oracleTimestamp)
306:         
307:         hasEnabledRestaking
308:     

['351']

348:     function withdrawNonBeaconChainETHBalanceWei(
349:         address recipient,
350:         uint256 amountToWithdraw
351:     ) external onlyEigenPodOwner onlyWhenNotPaused(PAUSED_NON_PROOF_WITHDRAWALS)  // <= FOUND

['366']

362:     function recoverTokens(
363:         IERC20[] memory tokenList,
364:         uint256[] memory amountsToWithdraw,
365:         address recipient
366:     ) external onlyEigenPodOwner onlyWhenNotPaused(PAUSED_NON_PROOF_WITHDRAWALS)  // <= FOUND

['383']

381:     function activateRestaking()
382:         external
383:         onlyWhenNotPaused(PAUSED_EIGENPODS_VERIFY_CREDENTIALS) // <= FOUND
384:         onlyEigenPodOwner // <= FOUND
385:         hasNeverRestaked
386:     

['394']

394:     function withdrawBeforeRestaking() external onlyEigenPodOwner hasNeverRestaked  // <= FOUND

['112']

111:     function recordBeaconChainETHBalanceUpdate(
112:         address podOwner, // <= FOUND
113:         int256 sharesDelta
114:     ) external onlyEigenPod(podOwner) nonReentrant  // <= FOUND

['240']

240:     function updateBeaconChainOracle(IBeaconChainOracle newBeaconChainOracle) external onlyOwner  // <= FOUND

['248']

248:     function setDenebForkTimestamp(uint64 newDenebForkTimestamp) external onlyOwner  // <= FOUND

['229']

229:     function setStrategyWhitelister(address newStrategyWhitelister) external onlyOwner  // <= FOUND

['388']

384:     function increaseDelegatedShares(
385:         address staker,
386:         IStrategy strategy,
387:         uint256 shares
388:     ) external onlyStrategyManagerOrEigenPodManager  // <= FOUND

['411']

407:     function decreaseDelegatedShares(
408:         address staker,
409:         IStrategy strategy,
410:         uint256 shares
411:     ) external onlyStrategyManagerOrEigenPodManager  // <= FOUND

['407']

403:     function stake(
404:         bytes calldata pubkey,
405:         bytes calldata signature,
406:         bytes32 depositDataRoot
407:     ) external payable onlyEigenPodManager  // <= FOUND

['421']

421:     function withdrawRestakedBeaconChainETH(address recipient, uint256 amountWei) external onlyEigenPodManager  // <= FOUND

['156']

155:     function removeShares(
156:         address podOwner,  // <= FOUND
157:         uint256 shares
158:     ) external onlyDelegationManager  // <= FOUND

['174']

173:     function addShares(
174:         address podOwner, // <= FOUND
175:         uint256 shares
176:     ) external onlyDelegationManager returns (uint256)  // <= FOUND

['197']

196:     function withdrawSharesAsTokens(
197:         address podOwner,  // <= FOUND
198:         address destination, 
199:         uint256 shares
200:     ) external onlyDelegationManager  // <= FOUND

['99']

96:     function deposit(
97:         IERC20 token,
98:         uint256 amount
99:     ) external virtual override onlyWhenNotPaused(PAUSED_DEPOSITS) onlyStrategyManager returns (uint256 newShares)  // <= FOUND

['138']

134:     function withdraw(
135:         address recipient,
136:         IERC20 token,
137:         uint256 amountShares
138:     ) external virtual override onlyWhenNotPaused(PAUSED_WITHDRAWALS) onlyStrategyManager  // <= FOUND

['174']

170:     function removeShares(
171:         address staker,
172:         IStrategy strategy,
173:         uint256 shares
174:     ) external onlyDelegationManager  // <= FOUND

['184']

179:     function addShares(
180:         address staker,
181:         IERC20 token,
182:         IStrategy strategy,
183:         uint256 shares
184:     ) external onlyDelegationManager  // <= FOUND

['194']

189:     function withdrawSharesAsTokens(
190:         address recipient,
191:         IStrategy strategy,
192:         uint256 shares,
193:         IERC20 token
194:     ) external onlyDelegationManager  // <= FOUND

['200']

200:     function migrateQueuedWithdrawal(DeprecatedStruct_QueuedWithdrawal memory queuedWithdrawal) external onlyDelegationManager returns(bool, bytes32)  // <= FOUND

[Low-1] Function with two array parameter missing a length check

Resolution

In Solidity, if two array parameters are used within a function and one of their lengths is used as the for-loop range, it's essential to have a length check. If the arrays are not the same length, you could experience out-of-bounds errors or unintended behavior. This could happen if the function tries to access an index that doesn't exist in the shorter array.

Resolution: Always validate that the lengths of both arrays are the same before entering the loop. Add a require statement at the start of the function to check that both arrays are of equal length. This helps maintain the integrity of the function and prevents potential errors due to differing array lengths. This requirement ensures the function fails early if the arrays don't match, rather than failing unpredictably or silently during execution.

Num of instances: 1

Findings

Click to show findings

['323']

323:     function completeQueuedWithdrawals(
324:         Withdrawal[] calldata withdrawals,
325:         IERC20[][] calldata tokens,
326:         uint256[] calldata middlewareTimesIndexes,
327:         bool[] calldata receiveAsTokens
328:     ) external onlyWhenNotPaused(PAUSED_EXIT_WITHDRAWAL_QUEUE) nonReentrant {
329:         for (uint256 i = 0; i < withdrawals.length; ++i) { // <= FOUND
330:             _completeQueuedWithdrawal(withdrawals[i], tokens[i], middlewareTimesIndexes[i], receiveAsTokens[i]);
331:         }
332:     }

[Low-2] Missing checks for address(0x0) when updating address state variables

Num of instances: 1

Findings

Click to show findings

['229']

229:     function setStrategyWhitelister(address newStrategyWhitelister) external onlyOwner {
230:         _setStrategyWhitelister(newStrategyWhitelister);
231:     }

[Low-3] Some tokens may revert when zero value transfers are made

Resolution

Reason: In Solidity, ERC20 token transfers of value 0 can sometimes lead to unexpected issues. This is particularly relevant when dealing with fractional token amounts that round to 0 when less than 1 of the smallest unit is transferred, leading to an effective transfer of nothing while still consuming gas. Furthermore, some ERC20 token implementations may revert on attempts to transfer a value of 0. However, note that this issue doesn't generally apply to wrapper native tokens like WETH.

Resolution: It's advisable to include a condition before any transfer operation to bypass the transaction if the transfer amount is 0. This saves unnecessary gas expenditure and prevents potential function reverts. For handling fractions, ensure token decimals are appropriately assigned and contemplate setting a minimum transfer threshold to avoid rounding down to 0. When dealing with wrapped tokens like WETH, special consideration should be given to their unique characteristics.

Num of instances: 3

Findings

Click to show findings

['362']

362:     function recoverTokens(
363:         IERC20[] memory tokenList,
364:         uint256[] memory amountsToWithdraw,
365:         address recipient
366:     ) external onlyEigenPodOwner onlyWhenNotPaused(PAUSED_NON_PROOF_WITHDRAWALS) {
367:         require(
368:             tokenList.length == amountsToWithdraw.length,
369:             "EigenPod.recoverTokens: tokenList and amountsToWithdraw must be same length"
370:         );
371:         for (uint256 i = 0; i < tokenList.length; i++) {
372:             tokenList[i].safeTransfer(recipient, amountsToWithdraw[i]); // <= FOUND
373:         }
374:     }

['192']

192:     function _afterWithdrawal(address recipient, IERC20 token, uint256 amountToSend) internal virtual { // <= FOUND
193:         token.safeTransfer(recipient, amountToSend); // <= FOUND
194:     }

['323']

323:     function _depositIntoStrategy(
324:         address staker,
325:         IStrategy strategy,
326:         IERC20 token,
327:         uint256 amount
328:     ) internal onlyStrategiesWhitelistedForDeposit(strategy) returns (uint256 shares) {
329:         
330:         token.safeTransferFrom(msg.sender, address(strategy), amount);
331: 
332:         
333:         shares = strategy.deposit(token, amount);
334: 
335:         
336:         _addShares(staker, token, strategy, shares);
337: 
338:         
339:         delegation.increaseDelegatedShares(staker, strategy, shares);
340: 
341:         return shares;
342:     }

[Low-4] Low level calls in solidity versions preceding 0.8.14 can result in an optimiser bug

Resolution

In Solidity versions 0.8.13 and 0.8.14, a known optimizer bug presents potential risks when a variable is used in a separate assembly block from the one in which it was stored. Specifically, the 'mstore' operation could be optimized out due to this bug, leading to the use of uninitialized memory. Although the current code does not exhibit this risky pattern of execution, it does utilize 'mstore' within assembly blocks, which introduces a vulnerability risk for future code modifications. As a preventative measure, it is advisable to avoid the usage of the afflicted Solidity versions, 0.8.13 and 0.8.14. Instead, consider utilizing a version that is not impacted by this optimizer bug to prevent potential memory initialization issues in your smart contract.

Num of instances: 16

Findings

Click to show findings

['14']

14:         assembly { // <= FOUND
15:             
16:             
17:             tempBytes := mload(0x40)
18: 
19:             
20:             
21:             let length := mload(_preBytes)
22:             mstore(tempBytes, length)
23: 
24:             
25:             
26:             
27:             let mc := add(tempBytes, 0x20)
28:             
29:             
30:             let end := add(mc, length)
31: 
32:             for {
33:                 
34:                 
35:                 let cc := add(_preBytes, 0x20)
36:             } lt(mc, end) {
37:                 
38:                 mc := add(mc, 0x20)
39:                 cc := add(cc, 0x20)
40:             } {
41:                 
42:                 
43:                 mstore(mc, mload(cc))
44:             }
45: 
46:             
47:             
48:             
49:             length := mload(_postBytes)
50:             mstore(tempBytes, add(length, mload(tempBytes)))
51: 
52:             
53:             
54:             mc := end
55:             
56:             
57:             end := add(mc, length)
58: 
59:             for {
60:                 let cc := add(_postBytes, 0x20)
61:             } lt(mc, end) {
62:                 mc := add(mc, 0x20)
63:                 cc := add(cc, 0x20)

['86']

86:        assembly { // <= FOUND
87:             
88:             
89:             
90:             let fslot := sload(_preBytes.slot)
91:             
92:             
93:             
94:             
95:             
96:             
97:             
98:             let slength := div(and(fslot, sub(mul(0x100, iszero(and(fslot, 1))), 1)), 2)
99:             let mlength := mload(_postBytes)
100:             let newlength := add(slength, mlength)
101:             
102:             
103:             
104:             switch add(lt(slength, 32), lt(newlength, 32))
105:             case 2 {
106:                 
107:                 
108:                 
109:                 sstore(
110:                     _preBytes.slot,
111:                     
112:                     
113:                     add(
114:                         
115:                         
116:                         fslot,
117:                         add(
118:                             mul(
119:                                 div(
120:                                     
121:                                     mload(add(_postBytes, 0x20)),
122:                                     
123:                                     exp(0x100, sub(32, mlength))
124:                                 ),
125:                                 
126:                                 
127:                                 exp(0x100, sub(32, newlength))
128:                             ),
129:                             
130:                             
131:                             mul(mlength, 2)
132:                         )
133:                     )
134:                 )
135:             }

['226']

226:         assembly { // <= FOUND
227:             switch iszero(_length)
228:             case 0 {
229:                 
230:                 
231:                 tempBytes := mload(0x40)
232: 
233:                 
234:                 
235:                 
236:                 
237:                 
238:                 
239:                 
240:                 
241:                 let lengthmod := and(_length, 31)
242: 
243:                 
244:                 
245:                 
246:                 
247:                 let mc := add(add(tempBytes, lengthmod), mul(0x20, iszero(lengthmod)))
248:                 let end := add(mc, _length)
249: 
250:                 for {
251:                     
252:                     
253:                     let cc := add(add(add(_bytes, lengthmod), mul(0x20, iszero(lengthmod))), _start)
254:                 } lt(mc, end) {
255:                     mc := add(mc, 0x20)
256:                     cc := add(cc, 0x20)
257:                 } {
258:                     mstore(mc, mload(cc))
259:                 }
260: 
261:                 mstore(tempBytes, _length)
262: 
263:                 
264:                 
265:                 mstore(0x40, and(add(mc, 31), not(31)))
266:             }
267:             
268:             default {
269:                 tempBytes := mload(0x40)
270:                 
271:                 
272:                 mstore(tempBytes, 0)
273: 
274:                 mstore(0x40, add(tempBytes, 0x20))
275:             }

['285']

285:         assembly { // <= FOUND
286:             tempAddress := div(mload(add(add(_bytes, 0x20), _start)), 0x1000000000000000000000000)
287:         }

['296']

296:         assembly { // <= FOUND
297:             tempUint := mload(add(add(_bytes, 0x1), _start))
298:         }

['307']

307:         assembly { // <= FOUND
308:             tempUint := mload(add(add(_bytes, 0x2), _start))
309:         }

['318']

318:         assembly { // <= FOUND
319:             tempUint := mload(add(add(_bytes, 0x4), _start))
320:         }

['329']

329:         assembly { // <= FOUND
330:             tempUint := mload(add(add(_bytes, 0x8), _start))
331:         }

['340']

340:         assembly { // <= FOUND
341:             tempUint := mload(add(add(_bytes, 0xc), _start))
342:         }

['351']

351:         assembly { // <= FOUND
352:             tempUint := mload(add(add(_bytes, 0x10), _start))
353:         }

['362']

362:         assembly { // <= FOUND
363:             tempUint := mload(add(add(_bytes, 0x20), _start))
364:         }

['373']

373:         assembly { // <= FOUND
374:             tempBytes32 := mload(add(add(_bytes, 0x20), _start))
375:         }

['383']

383:         assembly { // <= FOUND
384:             let length := mload(_preBytes)
385: 
386:             
387:             switch eq(length, mload(_postBytes))
388:             case 1 {
389:                 
390:                 
391:                 
392:                 
393:                 let cb := 1
394: 
395:                 let mc := add(_preBytes, 0x20)
396:                 let end := add(mc, length)
397: 
398:                 for {
399:                     let cc := add(_postBytes, 0x20)
400:                 } 
401:                 eq(add(lt(mc, end), cb), 2) {
402:                     mc := add(mc, 0x20)
403:                     cc := add(cc, 0x20)
404:                 } {
405:                     
406:                     if iszero(eq(mload(mc), mload(cc))) {
407:                         
408:                         success := 0
409:                         cb := 0
410:                     }
411:                 }
412:             }
413:             default {
414:                 
415:                 success := 0
416:             }
417:         }

['425']

425:         assembly { // <= FOUND
426:             
427:             let fslot := sload(_preBytes.slot)
428:             
429:             let slength := div(and(fslot, sub(mul(0x100, iszero(and(fslot, 1))), 1)), 2)
430:             let mlength := mload(_postBytes)
431: 
432:             
433:             switch eq(slength, mlength)
434:             case 1 {
435:                 
436:                 
437:                 
438:                 if iszero(iszero(slength)) {
439:                     switch lt(slength, 32)
440:                     case 1 {
441:                         
442:                         fslot := mul(div(fslot, 0x100), 0x100)
443: 
444:                         if iszero(eq(fslot, mload(add(_postBytes, 0x20)))) {
445:                             
446:                             success := 0
447:                         }
448:                     }
449:                     default {
450:                         
451:                         
452:                         
453:                         
454:                         let cb := 1
455: 
456:                         
457:                         mstore(0x0, _preBytes.slot)
458:                         let sc := keccak256(0x0, 0x20)
459: 
460:                         let mc := add(_postBytes, 0x20)
461:                         let end := add(mc, mlength)
462: 
463:                         
464:                         
465:                         
466:                         for {
467: 
468:                         } eq(add(lt(mc, end), cb), 2) {
469:                             sc := add(sc, 1)
470:                             mc := add(mc, 0x20)
471:                         } {
472:                             if iszero(eq(sload(sc), mload(mc))) {
473:                                 
474:                                 success := 0

['61']

61:                 assembly { // <= FOUND
62:                     mstore(0x00, computedHash)
63:                     mstore(0x20, mload(add(proof, i)))
64:                     computedHash := keccak256(0x00, 0x40)
65:                     index := div(index, 2)
66:                 }

['120']

120:                 assembly { // <= FOUND
121:                     mstore(0x00, mload(computedHash))
122:                     mstore(0x20, mload(add(proof, i)))
123:                     if iszero(staticcall(sub(gas(), 2000), 2, 0x00, 0x40, computedHash, 0x20)) {
124:                         revert(0, 0)
125:                     }
126:                     index := div(index, 2)
127:                 }

[Low-5] Int casting block.timestamp can reduce the lifespan of a contract

Resolution

Casting block.timestamp (a Unix epoch time in seconds) to an integer type with a smaller size in Solidity can potentially reduce the lifespan of a smart contract. The block.timestamp returns a value that increases over time and can eventually exceed the maximum value storable in smaller integer types. For example, casting block.timestamp to an int32 could lead to an overflow by the year 2038 (the year when Unix time exceeds the maximum value storable in a 32-bit integer). This overflow would result in incorrect timestamp values, potentially disrupting the contract's logic or rendering it unusable. Therefore, it's important to use an integer type that can accommodate the range of expected timestamp values throughout the contract's intended lifespan.

Num of instances: 1

Findings

Click to show findings

['734']

734:         mostRecentWithdrawalTimestamp = uint32(block.timestamp); // <= FOUND

[Low-6] Upgradable contracts should have a __gap variable

Resolution

This is to ensure the team can add new state variables without compromising compatability.

Num of instances: 5

Findings

Click to show findings

['11']

11: contract AVSDirectory is
12:     Initializable,
13:     OwnableUpgradeable,
14:     Pausable,
15:     AVSDirectoryStorage,
16:     ReentrancyGuardUpgradeable
17: 

['25']

25: contract EigenPodManager is
26:     Initializable,
27:     OwnableUpgradeable,
28:     Pausable,
29:     EigenPodPausingConstants,
30:     EigenPodManagerStorage,
31:     ReentrancyGuardUpgradeable
32: 

['29']

29: contract Slasher is Initializable, OwnableUpgradeable, ISlasher, Pausable 

['22']

22: contract StrategyManager is
23:     Initializable,
24:     OwnableUpgradeable,
25:     ReentrancyGuardUpgradeable,
26:     Pausable,
27:     StrategyManagerStorage
28: 

['12']

12: abstract contract UpgradeableSignatureCheckingUtils is Initializable 

[Low-7] Using > when declaring solidity version without specifying an upperbound can cause future vulnerabilities

Resolution

Such instances should be locked to a particular solidity version or use the ^ attribute instead of > as this is more limited in what versions can be used.

Num of instances: 1

Findings

Click to show findings

['2']

2: pragma solidity >=0.5.0; // <= FOUND

[Low-8] Uses of EIP712 does not include a version string

Resolution

It is standard for uses of EIP712 to include a version string, not doing so can cause future incompatibilities

Num of instances: 1

Findings

Click to show findings

['16']

14:     
15:     bytes32 public constant DOMAIN_TYPEHASH =
16:         keccak256("EIP712Domain(string name,uint256 chainId,address verifyingContract)"); // <= FOUND

[Low-9] Contracts are vulnerable to fee-on-transfer accounting-related issues

Resolution

The below-listed functions use transferFrom() to move funds from the sender to the recipient but fail to verify if the received token amount matches the transferred amount. This could pose an issue with fee-on-transfer tokens, where the post-transfer balance might be less than anticipated, leading to balance inconsistencies. There might be subsequent checks for a second transfer, but an attacker might exploit leftover funds (such as those accidentally sent by another user) to gain unjustified credit. A practical solution is to gauge the balance prior and post-transfer, and consider the differential as the transferred amount, instead of the predefined amount.

Num of instances: 1

Findings

Click to show findings

['323']

323:     function _depositIntoStrategy(
324:         address staker,
325:         IStrategy strategy,
326:         IERC20 token,
327:         uint256 amount
328:     ) internal onlyStrategiesWhitelistedForDeposit(strategy) returns (uint256 shares) {
329:         
330:         token.safeTransferFrom(msg.sender, address(strategy), amount); // <= FOUND
331: 
332:         
333:         shares = strategy.deposit(token, amount);
334: 
335:         
336:         _addShares(staker, token, strategy, shares);
337: 
338:         
339:         delegation.increaseDelegatedShares(staker, strategy, shares);
340: 
341:         return shares;
342:     }

[Low-10] Use SafeCast to safely downcast variables

Resolution

Downcasting int/uints in Solidity can be unsafe due to the potential for data loss and unintended behavior. When downcasting a larger integer type to a smaller one (e.g., uint256 to uint128), the value may exceed the range of the target type, leading to truncation and loss of significant digits. This data loss can result in unexpected state changes, incorrect calculations, or other contract vulnerabilities, ultimately compromising the contracts functionality and reliability. To prevent these risks, developers should carefully consider the range of values their variables may hold and ensure that proper checks are in place to prevent out-of-range values before performing downcasting. Also consider using OZ SafeCast functionality.

Num of instances: 3

Findings

Click to show findings

['76']

68:     function createDelayedWithdrawal(
69:         address podOwner,
70:         address recipient
71:     ) external payable onlyEigenPod(podOwner) onlyWhenNotPaused(PAUSED_DELAYED_WITHDRAWAL_CLAIMS) {
72:         require(
73:             recipient != address(0),
74:             "DelayedWithdrawalRouter.createDelayedWithdrawal: recipient cannot be zero address"
75:         );
76:         uint224 withdrawalAmount = uint224(msg.value); // <= FOUND
77:         if (withdrawalAmount != 0) {
78:             DelayedWithdrawal memory delayedWithdrawal = DelayedWithdrawal({
79:                 amount: withdrawalAmount,
80:                 blockCreated: uint32(block.number) // <= FOUND
81:             });
82:             _userWithdrawals[recipient].delayedWithdrawals.push(delayedWithdrawal);
83:             emit DelayedWithdrawalCreated(
84:                 podOwner,
85:                 recipient,
86:                 withdrawalAmount,
87:                 _userWithdrawals[recipient].delayedWithdrawals.length - 1
88:             );
89:         }
90:     }

['266']

258:     function _deployPod() internal returns (IEigenPod) {
259:         
260:         require(numPods + 1 <= maxPods, "EigenPodManager._deployPod: pod limit reached");
261:         ++numPods;
262:         
263:         IEigenPod pod = IEigenPod(
264:             Create2.deploy(
265:                 0,
266:                 bytes32(uint256(uint160(msg.sender))), // <= FOUND
267:                 
268:                 abi.encodePacked(beaconProxyBytecode, abi.encode(eigenPodBeacon, ""))
269:             )
270:         );
271:         pod.initialize(msg.sender);
272:         
273:         ownerToPod[msg.sender] = pod;
274:         emit PodDeployed(address(pod), msg.sender);
275:         return pod;
276:     }

['323']

317:     function getPod(address podOwner) public view returns (IEigenPod) {
318:         IEigenPod pod = ownerToPod[podOwner];
319:         
320:         if (address(pod) == address(0)) {
321:             pod = IEigenPod(
322:                 Create2.computeAddress(
323:                     bytes32(uint256(uint160(podOwner))),  // <= FOUND
324:                     keccak256(abi.encodePacked(beaconProxyBytecode, abi.encode(eigenPodBeacon, ""))) 
325:                 )
326:             );
327:         }
328:         return pod;
329:     }

[Low-11] The nonReentrant modifier should be first in a function declaration

Resolution

In Solidity, the nonReentrant modifier is essential for securing smart contracts against reentrancy attacks. It should be positioned first in a function declaration. This placement is critical because it ensures that the modifier's protection is applied before any other code or modifiers in the function. If placed later, other modifiers could potentially be executed in a reentrant manner before nonReentrant has a chance to lock the function, leaving the contract vulnerable. Thus, prioritizing nonReentrant as the first modifier is a best practice for robust smart contract security, effectively guarding against unintended reentries and related exploits.

Num of instances: 5

Findings

Click to show findings

['305']

305:     function completeQueuedWithdrawal(
306:         Withdrawal calldata withdrawal,
307:         IERC20[] calldata tokens,
308:         uint256 middlewareTimesIndex,
309:         bool receiveAsTokens
310:     ) external onlyWhenNotPaused(PAUSED_EXIT_WITHDRAWAL_QUEUE) nonReentrant { // <= FOUND
311:         _completeQueuedWithdrawal(withdrawal, tokens, middlewareTimesIndex, receiveAsTokens);
312:     }

['323']

323:     function completeQueuedWithdrawals(
324:         Withdrawal[] calldata withdrawals,
325:         IERC20[][] calldata tokens,
326:         uint256[] calldata middlewareTimesIndexes,
327:         bool[] calldata receiveAsTokens
328:     ) external onlyWhenNotPaused(PAUSED_EXIT_WITHDRAWAL_QUEUE) nonReentrant { // <= FOUND
329:         for (uint256 i = 0; i < withdrawals.length; ++i) {
330:             _completeQueuedWithdrawal(withdrawals[i], tokens[i], middlewareTimesIndexes[i], receiveAsTokens[i]);
331:         }
332:     }

['111']

111:     function recordBeaconChainETHBalanceUpdate(
112:         address podOwner,
113:         int256 sharesDelta
114:     ) external onlyEigenPod(podOwner) nonReentrant { // <= FOUND
115:         require(podOwner != address(0), "EigenPodManager.recordBeaconChainETHBalanceUpdate: podOwner cannot be zero address");
116:         require(sharesDelta % int256(GWEI_TO_WEI) == 0,
117:             "EigenPodManager.recordBeaconChainETHBalanceUpdate: sharesDelta must be a whole Gwei amount");
118:         int256 currentPodOwnerShares = podOwnerShares[podOwner];
119:         int256 updatedPodOwnerShares = currentPodOwnerShares + sharesDelta;
120:         podOwnerShares[podOwner] = updatedPodOwnerShares;
121: 
122:         
123:         int256 changeInDelegatableShares = _calculateChangeInDelegatableShares({
124:             sharesBefore: currentPodOwnerShares,
125:             sharesAfter: updatedPodOwnerShares
126:         });
127:         
128:         if (changeInDelegatableShares != 0) {
129:             if (changeInDelegatableShares < 0) {
130:                 delegationManager.decreaseDelegatedShares({
131:                     staker: podOwner,
132:                     strategy: beaconChainETHStrategy,
133:                     shares: uint256(-changeInDelegatableShares)
134:                 });
135:             } else {
136:                 delegationManager.increaseDelegatedShares({
137:                     staker: podOwner,
138:                     strategy: beaconChainETHStrategy,
139:                     shares: uint256(changeInDelegatableShares)
140:                 });
141:             }
142:         }
143:         emit PodSharesUpdated(podOwner, sharesDelta);
144:     }

['105']

105:     function depositIntoStrategy(
106:         IStrategy strategy,
107:         IERC20 token,
108:         uint256 amount
109:     ) external onlyWhenNotPaused(PAUSED_DEPOSITS) nonReentrant returns (uint256 shares) { // <= FOUND
110:         shares = _depositIntoStrategy(msg.sender, strategy, token, amount);
111:     }

['134']

134:     function depositIntoStrategyWithSignature(
135:         IStrategy strategy,
136:         IERC20 token,
137:         uint256 amount,
138:         address staker,
139:         uint256 expiry,
140:         bytes memory signature
141:     ) external onlyWhenNotPaused(PAUSED_DEPOSITS) nonReentrant returns (uint256 shares) { // <= FOUND
142:         require(
143:             !thirdPartyTransfersForbidden[strategy],
144:             "StrategyManager.depositIntoStrategyWithSignature: third transfers disabled"
145:         );
146:         require(expiry >= block.timestamp, "StrategyManager.depositIntoStrategyWithSignature: signature expired");
147:         
148:         uint256 nonce = nonces[staker];
149:         bytes32 structHash = keccak256(abi.encode(DEPOSIT_TYPEHASH, staker, strategy, token, amount, nonce, expiry));
150:         unchecked {
151:             nonces[staker] = nonce + 1;
152:         }
153: 
154:         
155:         bytes32 digestHash = keccak256(abi.encodePacked("\x19\x01", domainSeparator(), structHash));
156: 
157:         
158: 
163:         EIP1271SignatureUtils.checkSignature_EIP1271(staker, digestHash, signature);
164: 
165:         
166:         shares = _depositIntoStrategy(staker, strategy, token, amount);
167:     }

[Low-12] Public or external initialize functions should be protected with the initializer modifier

Resolution

The initializer modifiers ensures two key aspects. A) Only an authorised account can initialize B) initialization can only be done once. Consider using such a modifier and in instances where initialization can be done more than once, the function name should be changed to reflect that.

Num of instances: 1

Findings

Click to show findings

['33']

33:     function initialize( // <= FOUND
34:         address,
35:         IPauserRegistry,
36:         uint256
37:     ) external {}

[Low-13] Function calls within for loops

Resolution

Making function calls or external calls within loops in Solidity can lead to inefficient gas usage, potential bottlenecks, and increased vulnerability to attacks. Each function call or external call consumes gas, and when executed within a loop, the gas cost multiplies, potentially causing the transaction to run out of gas or exceed block gas limits. This can result in transaction failure or unpredictable behavior.

Num of instances: 9

Findings

Click to show findings

['329']

329:        for (uint256 i = 0; i < withdrawals.length; ++i) {
330:             _completeQueuedWithdrawal(withdrawals[i], tokens[i], middlewareTimesIndexes[i], receiveAsTokens[i]); // <= FOUND
331:         }

['610']

610:            for (uint256 i = 0; i < withdrawal.strategies.length; ) {
611:                 require(
612:                     withdrawal.startBlock + strategyWithdrawalDelayBlocks[withdrawal.strategies[i]] <= block.number, 
613:                     "DelegationManager._completeQueuedWithdrawal: withdrawalDelayBlocks period has not yet passed for this strategy"
614:                 );
615: 
616:                 
617: 
619:                 if (withdrawal.strategies[i] == beaconChainETHStrategy) {
620:                     address staker = withdrawal.staker;
621:                     
622: 
625:                     uint256 increaseInDelegateableShares = eigenPodManager.addShares({ // <= FOUND
626:                         podOwner: staker,
627:                         shares: withdrawal.shares[i]
628:                     });
629:                     address podOwnerOperator = delegatedTo[staker];
630:                     
631:                     if (podOwnerOperator != address(0)) {
632:                         _increaseOperatorShares({ // <= FOUND
633:                             operator: podOwnerOperator,
634:                             
635:                             staker: staker,
636:                             strategy: withdrawal.strategies[i],
637:                             shares: increaseInDelegateableShares
638:                         });
639:                     }
640:                 } else {
641:                     strategyManager.addShares(msg.sender, tokens[i], withdrawal.strategies[i], withdrawal.shares[i]); // <= FOUND
642:                     
643:                     if (currentOperator != address(0)) {
644:                         _increaseOperatorShares({ // <= FOUND
645:                             operator: currentOperator,
646:                             
647:                             staker: msg.sender,
648:                             strategy: withdrawal.strategies[i],
649:                             shares: withdrawal.shares[i]
650:                         });
651:                     }
652:                 }
653:                 unchecked { ++i; }
654:             }

['690']

690:        for (uint256 i = 0; i < strategies.length;) {
691:             
692:             if (operator != address(0)) {
693:                 _decreaseOperatorShares({ // <= FOUND
694:                     operator: operator,
695:                     staker: staker,
696:                     strategy: strategies[i],
697:                     shares: shares[i]
698:                 });
699:             }
700: 
701:             
702:             if (strategies[i] == beaconChainETHStrategy) {
703:                 
704: 
709:                 eigenPodManager.removeShares(staker, shares[i]); // <= FOUND
710:             } else {
711:                 require(
712:                     staker == withdrawer || !strategyManager.thirdPartyTransfersForbidden(strategies[i]),
713:                     "DelegationManager._removeSharesAndQueueWithdrawal: withdrawer must be same address as staker if thirdPartyTransfersForbidden are set"
714:                 );
715:                 
716:                 strategyManager.removeShares(staker, strategies[i], shares[i]); // <= FOUND
717:             }
718: 
719:             unchecked { ++i; }
720:         }

['241']

241:            for (uint256 i = 0; i < strategies.length; i++) {
242:                 IStrategy[] memory singleStrategy = new IStrategy[](1);
243:                 uint256[] memory singleShare = new uint256[](1);
244:                 singleStrategy[0] = strategies[i];
245:                 singleShare[0] = shares[i];
246: 
247:                 withdrawalRoots[i] = _removeSharesAndQueueWithdrawal({ // <= FOUND
248:                     staker: staker,
249:                     operator: operator,
250:                     withdrawer: staker,
251:                     strategies: singleStrategy,
252:                     shares: singleShare
253:                 });
254:             }

['273']

273:        for (uint256 i = 0; i < queuedWithdrawalParams.length; i++) {
274:             require(queuedWithdrawalParams[i].strategies.length == queuedWithdrawalParams[i].shares.length, "DelegationManager.queueWithdrawal: input length mismatch");
275:             require(queuedWithdrawalParams[i].withdrawer == msg.sender, "DelegationManager.queueWithdrawal: withdrawer must be staker");
276: 
277:             
278:             
279:             
280:             withdrawalRoots[i] = _removeSharesAndQueueWithdrawal({ // <= FOUND
281:                 staker: msg.sender,
282:                 operator: operator,
283:                 withdrawer: queuedWithdrawalParams[i].withdrawer,
284:                 strategies: queuedWithdrawalParams[i].strategies,
285:                 shares: queuedWithdrawalParams[i].shares
286:             });
287:         }

['592']

592:            for (uint256 i = 0; i < withdrawal.strategies.length; ) {
593:                 require(
594:                     withdrawal.startBlock + strategyWithdrawalDelayBlocks[withdrawal.strategies[i]] <= block.number,
595:                     "DelegationManager._completeQueuedWithdrawal: withdrawalDelayBlocks period has not yet passed for this strategy"
596:                 );
597: 
598:                 _withdrawSharesAsTokens({ // <= FOUND
599:                     staker: withdrawal.staker,
600:                     withdrawer: msg.sender,
601:                     strategy: withdrawal.strategies[i],
602:                     shares: withdrawal.shares[i],
603:                     token: tokens[i]
604:                 });
605:                 unchecked { ++i; }
606:             }

['24']

24:        for (uint256 i = 0; i < _pausers.length; i++) {
25:             _setIsPauser(_pausers[i], true); // <= FOUND
26:         }

['247']

247:        for (uint256 i = 0; i < strategiesToWhitelistLength; ) {
248:             
249:             if (!strategyIsWhitelistedForDeposit[strategiesToWhitelist[i]]) {
250:                 strategyIsWhitelistedForDeposit[strategiesToWhitelist[i]] = true;
251:                 emit StrategyAddedToDepositWhitelist(strategiesToWhitelist[i]);
252:                 _setThirdPartyTransfersForbidden(strategiesToWhitelist[i], thirdPartyTransfersForbiddenValues[i]); // <= FOUND
253:             }
254:             unchecked {
255:                 ++i;
256:             }
257:         }

['268']

268:        for (uint256 i = 0; i < strategiesToRemoveFromWhitelistLength; ) {
269:             
270:             if (strategyIsWhitelistedForDeposit[strategiesToRemoveFromWhitelist[i]]) {
271:                 strategyIsWhitelistedForDeposit[strategiesToRemoveFromWhitelist[i]] = false;
272:                 emit StrategyRemovedFromDepositWhitelist(strategiesToRemoveFromWhitelist[i]);
273:                 
274:                 _setThirdPartyTransfersForbidden(strategiesToRemoveFromWhitelist[i], false); // <= FOUND
275:             }
276:             unchecked {
277:                 ++i;
278:             }
279:         }

[Low-14] For loops in public or external functions should be avoided due to high gas costs and possible DOS

Resolution

In Solidity, for loops can potentially cause Denial of Service (DoS) attacks if not handled carefully. DoS attacks can occur when an attacker intentionally exploits the gas cost of a function, causing it to run out of gas or making it too expensive for other users to call. Below are some scenarios where for loops can lead to DoS attacks: Nested for loops can become exceptionally gas expensive and should be used sparingly

Num of instances: 16

Findings

Click to show findings

['337']

336:     function migrateQueuedWithdrawals(IStrategyManager.DeprecatedStruct_QueuedWithdrawal[] memory withdrawalsToMigrate) external {
337:         for(uint256 i = 0; i < withdrawalsToMigrate.length;) { // <= FOUND
338:             IStrategyManager.DeprecatedStruct_QueuedWithdrawal memory withdrawalToMigrate = withdrawalsToMigrate[i];
339:             
340:             (bool isDeleted, bytes32 oldWithdrawalRoot) = strategyManager.migrateQueuedWithdrawal(withdrawalToMigrate);
341:             
342:             if (isDeleted) {
343:                 address staker = withdrawalToMigrate.staker;
344:                 
345:                 uint256 nonce = cumulativeWithdrawalsQueued[staker];
346:                 cumulativeWithdrawalsQueued[staker]++;
347: 
348:                 Withdrawal memory migratedWithdrawal = Withdrawal({
349:                     staker: staker,
350:                     delegatedTo: withdrawalToMigrate.delegatedAddress,
351:                     withdrawer: withdrawalToMigrate.withdrawerAndNonce.withdrawer,
352:                     nonce: nonce,
353:                     startBlock: withdrawalToMigrate.withdrawalStartBlock,
354:                     strategies: withdrawalToMigrate.strategies,
355:                     shares: withdrawalToMigrate.shares
356:                 });
357: 
358:                 
359:                 bytes32 newRoot = calculateWithdrawalRoot(migratedWithdrawal);
360:                 
361:                 require(!pendingWithdrawals[newRoot], "DelegationManager.migrateQueuedWithdrawals: withdrawal already exists");
362:                 pendingWithdrawals[newRoot] = true;
363: 
364:                 emit WithdrawalQueued(newRoot, migratedWithdrawal);
365: 
366:                 emit WithdrawalMigrated(oldWithdrawalRoot, newRoot);
367:             }
368:             unchecked {
369:                 ++i;
370:             }
371:         }
372:         
373:     }

['133']

128:     function getUserDelayedWithdrawals(address user) external view returns (DelayedWithdrawal[] memory) {
129:         uint256 delayedWithdrawalsCompleted = _userWithdrawals[user].delayedWithdrawalsCompleted;
130:         uint256 totalDelayedWithdrawals = _userWithdrawals[user].delayedWithdrawals.length;
131:         uint256 userDelayedWithdrawalsLength = totalDelayedWithdrawals - delayedWithdrawalsCompleted;
132:         DelayedWithdrawal[] memory userDelayedWithdrawals = new DelayedWithdrawal[](userDelayedWithdrawalsLength);
133:         for (uint256 i = 0; i < userDelayedWithdrawalsLength; i++) { // <= FOUND
134:             userDelayedWithdrawals[i] = _userWithdrawals[user].delayedWithdrawals[delayedWithdrawalsCompleted + i];
135:         }
136:         return userDelayedWithdrawals;
137:     }

['147']

140:     function getClaimableUserDelayedWithdrawals(address user) external view returns (DelayedWithdrawal[] memory) {
141:         uint256 delayedWithdrawalsCompleted = _userWithdrawals[user].delayedWithdrawalsCompleted;
142:         uint256 totalDelayedWithdrawals = _userWithdrawals[user].delayedWithdrawals.length;
143:         uint256 userDelayedWithdrawalsLength = totalDelayedWithdrawals - delayedWithdrawalsCompleted;
144: 
145:         uint256 firstNonClaimableWithdrawalIndex = userDelayedWithdrawalsLength;
146: 
147:         for (uint256 i = 0; i < userDelayedWithdrawalsLength; i++) { // <= FOUND
148:             DelayedWithdrawal memory delayedWithdrawal = _userWithdrawals[user].delayedWithdrawals[
149:                 delayedWithdrawalsCompleted + i
150:             ];
151:             
152:             if (block.number < delayedWithdrawal.blockCreated + withdrawalDelayBlocks) {
153:                 firstNonClaimableWithdrawalIndex = i;
154:                 break;
155:             }
156:         }
157:         uint256 numberOfClaimableWithdrawals = firstNonClaimableWithdrawalIndex;
158:         DelayedWithdrawal[] memory claimableDelayedWithdrawals = new DelayedWithdrawal[](numberOfClaimableWithdrawals);
159: 
160:         if (numberOfClaimableWithdrawals != 0) {
161:             for (uint256 i = 0; i < numberOfClaimableWithdrawals; i++) { // <= FOUND
162:                 claimableDelayedWithdrawals[i] = _userWithdrawals[user].delayedWithdrawals[
163:                     delayedWithdrawalsCompleted + i
164:                 ];
165:             }
166:         }
167:         return claimableDelayedWithdrawals;
168:     }

['241']

211:     function undelegate(address staker) external onlyWhenNotPaused(PAUSED_ENTER_WITHDRAWAL_QUEUE) returns (bytes32[] memory withdrawalRoots) {
212:         require(isDelegated(staker), "DelegationManager.undelegate: staker must be delegated to undelegate");
213:         require(!isOperator(staker), "DelegationManager.undelegate: operators cannot be undelegated");
214:         require(staker != address(0), "DelegationManager.undelegate: cannot undelegate zero address");
215:         address operator = delegatedTo[staker];
216:         require(
217:             msg.sender == staker ||
218:                 msg.sender == operator ||
219:                 msg.sender == _operatorDetails[operator].delegationApprover,
220:             "DelegationManager.undelegate: caller cannot undelegate staker"
221:         );
222: 
223:         
224:         
225:         (IStrategy[] memory strategies, uint256[] memory shares) = getDelegatableShares(staker);
226: 
227:         
228:         if (msg.sender != staker) {
229:             emit StakerForceUndelegated(staker, operator);
230:         }
231: 
232:         
233:         emit StakerUndelegated(staker, operator);
234:         delegatedTo[staker] = address(0);
235: 
236:         
237:         if (strategies.length == 0) {
238:             withdrawalRoots = new bytes32[](0);
239:         } else {
240:             withdrawalRoots = new bytes32[](strategies.length);
241:             for (uint256 i = 0; i < strategies.length; i++) { // <= FOUND
242:                 IStrategy[] memory singleStrategy = new IStrategy[](1);
243:                 uint256[] memory singleShare = new uint256[](1);
244:                 singleStrategy[0] = strategies[i];
245:                 singleShare[0] = shares[i];
246: 
247:                 withdrawalRoots[i] = _removeSharesAndQueueWithdrawal({
248:                     staker: staker,
249:                     operator: operator,
250:                     withdrawer: staker,
251:                     strategies: singleStrategy,
252:                     shares: singleShare
253:                 });
254:             }
255:         }
256: 
257:         return withdrawalRoots;
258:     }

['273']

267:     function queueWithdrawals(
268:         QueuedWithdrawalParams[] calldata queuedWithdrawalParams
269:     ) external onlyWhenNotPaused(PAUSED_ENTER_WITHDRAWAL_QUEUE) returns (bytes32[] memory) {
270:         bytes32[] memory withdrawalRoots = new bytes32[](queuedWithdrawalParams.length);
271:         address operator = delegatedTo[msg.sender];
272: 
273:         for (uint256 i = 0; i < queuedWithdrawalParams.length; i++) { // <= FOUND
274:             require(queuedWithdrawalParams[i].strategies.length == queuedWithdrawalParams[i].shares.length, "DelegationManager.queueWithdrawal: input length mismatch");
275:             require(queuedWithdrawalParams[i].withdrawer == msg.sender, "DelegationManager.queueWithdrawal: withdrawer must be staker");
276: 
277:             
278:             
279:             
280:             withdrawalRoots[i] = _removeSharesAndQueueWithdrawal({
281:                 staker: msg.sender,
282:                 operator: operator,
283:                 withdrawer: queuedWithdrawalParams[i].withdrawer,
284:                 strategies: queuedWithdrawalParams[i].strategies,
285:                 shares: queuedWithdrawalParams[i].shares
286:             });
287:         }
288:         return withdrawalRoots;
289:     }

['329']

323:     function completeQueuedWithdrawals(
324:         Withdrawal[] calldata withdrawals,
325:         IERC20[][] calldata tokens,
326:         uint256[] calldata middlewareTimesIndexes,
327:         bool[] calldata receiveAsTokens
328:     ) external onlyWhenNotPaused(PAUSED_EXIT_WITHDRAWAL_QUEUE) nonReentrant {
329:         for (uint256 i = 0; i < withdrawals.length; ++i) { // <= FOUND
330:             _completeQueuedWithdrawal(withdrawals[i], tokens[i], middlewareTimesIndexes[i], receiveAsTokens[i]);
331:         }
332:     }

['211']

185:     function verifyBalanceUpdates(
186:         uint64 oracleTimestamp,
187:         uint40[] calldata validatorIndices,
188:         BeaconChainProofs.StateRootProof calldata stateRootProof,
189:         bytes[] calldata validatorFieldsProofs,
190:         bytes32[][] calldata validatorFields
191:     ) external onlyWhenNotPaused(PAUSED_EIGENPODS_VERIFY_BALANCE_UPDATE) {
192:         require(
193:             (validatorIndices.length == validatorFieldsProofs.length) && (validatorFieldsProofs.length == validatorFields.length),
194:             "EigenPod.verifyBalanceUpdates: validatorIndices and proofs must be same length"
195:         );
196: 
197:         
198:         require(
199:             oracleTimestamp + VERIFY_BALANCE_UPDATE_WINDOW_SECONDS >= block.timestamp,
200:             "EigenPod.verifyBalanceUpdates: specified timestamp is too far in past"
201:         );
202: 
203:         
204:         BeaconChainProofs.verifyStateRootAgainstLatestBlockRoot({
205:             latestBlockRoot: eigenPodManager.getBlockRootAtTimestamp(oracleTimestamp),
206:             beaconStateRoot: stateRootProof.beaconStateRoot,
207:             stateRootProof: stateRootProof.proof
208:         });
209: 
210:         int256 sharesDeltaGwei;
211:         for (uint256 i = 0; i < validatorIndices.length; i++) { // <= FOUND
212:             sharesDeltaGwei += _verifyBalanceUpdate(
213:                 oracleTimestamp,
214:                 validatorIndices[i],
215:                 stateRootProof.beaconStateRoot,
216:                 validatorFieldsProofs[i], 
217:                 validatorFields[i]
218:             );
219:         }
220:         eigenPodManager.recordBeaconChainETHBalanceUpdate(podOwner, sharesDeltaGwei * int256(GWEI_TO_WEI));
221:     }

['255']

232:     function verifyAndProcessWithdrawals(
233:         uint64 oracleTimestamp,
234:         BeaconChainProofs.StateRootProof calldata stateRootProof,
235:         BeaconChainProofs.WithdrawalProof[] calldata withdrawalProofs,
236:         bytes[] calldata validatorFieldsProofs,
237:         bytes32[][] calldata validatorFields,
238:         bytes32[][] calldata withdrawalFields
239:     ) external onlyWhenNotPaused(PAUSED_EIGENPODS_VERIFY_WITHDRAWAL) {
240:         require(
241:             (validatorFields.length == validatorFieldsProofs.length) &&
242:                 (validatorFieldsProofs.length == withdrawalProofs.length) &&
243:                 (withdrawalProofs.length == withdrawalFields.length),
244:             "EigenPod.verifyAndProcessWithdrawals: inputs must be same length"
245:         );
246: 
247:         
248:         BeaconChainProofs.verifyStateRootAgainstLatestBlockRoot({
249:             latestBlockRoot: eigenPodManager.getBlockRootAtTimestamp(oracleTimestamp),
250:             beaconStateRoot: stateRootProof.beaconStateRoot,
251:             stateRootProof: stateRootProof.proof
252:         });
253: 
254:         VerifiedWithdrawal memory withdrawalSummary;
255:         for (uint256 i = 0; i < withdrawalFields.length; i++) { // <= FOUND
256:             VerifiedWithdrawal memory verifiedWithdrawal = _verifyAndProcessWithdrawal(
257:                 stateRootProof.beaconStateRoot,
258:                 withdrawalProofs[i],
259:                 validatorFieldsProofs[i],
260:                 validatorFields[i],
261:                 withdrawalFields[i]
262:             );
263: 
264:             withdrawalSummary.amountToSendGwei += verifiedWithdrawal.amountToSendGwei;
265:             withdrawalSummary.sharesDeltaGwei += verifiedWithdrawal.sharesDeltaGwei;
266:         }
267: 
268:         
269:         
270:         if (withdrawalSummary.amountToSendGwei != 0) {
271:             _sendETH_AsDelayedWithdrawal(podOwner, withdrawalSummary.amountToSendGwei * GWEI_TO_WEI);
272:         }
273:         
274:         if (withdrawalSummary.sharesDeltaGwei != 0) {
275:             eigenPodManager.recordBeaconChainETHBalanceUpdate(podOwner, withdrawalSummary.sharesDeltaGwei * int256(GWEI_TO_WEI));
276:         }
277:     }

['333']

294:     function verifyWithdrawalCredentials(
295:         uint64 oracleTimestamp,
296:         BeaconChainProofs.StateRootProof calldata stateRootProof,
297:         uint40[] calldata validatorIndices,
298:         bytes[] calldata validatorFieldsProofs,
299:         bytes32[][] calldata validatorFields
300:     )
301:         external
302:         onlyEigenPodOwner
303:         onlyWhenNotPaused(PAUSED_EIGENPODS_VERIFY_CREDENTIALS)
304:         
305:         proofIsForValidTimestamp(oracleTimestamp)
306:         
307:         hasEnabledRestaking
308:     {
309:         require(
310:             (validatorIndices.length == validatorFieldsProofs.length) &&
311:                 (validatorFieldsProofs.length == validatorFields.length),
312:             "EigenPod.verifyWithdrawalCredentials: validatorIndices and proofs must be same length"
313:         );
314: 
315:         
316: 
320:         require(
321:             oracleTimestamp + VERIFY_BALANCE_UPDATE_WINDOW_SECONDS >= block.timestamp,
322:             "EigenPod.verifyWithdrawalCredentials: specified timestamp is too far in past"
323:         );
324: 
325:         
326:         BeaconChainProofs.verifyStateRootAgainstLatestBlockRoot({
327:             latestBlockRoot: eigenPodManager.getBlockRootAtTimestamp(oracleTimestamp),
328:             beaconStateRoot: stateRootProof.beaconStateRoot,
329:             stateRootProof: stateRootProof.proof
330:         });
331: 
332:         uint256 totalAmountToBeRestakedWei;
333:         for (uint256 i = 0; i < validatorIndices.length; i++) { // <= FOUND
334:             totalAmountToBeRestakedWei += _verifyWithdrawalCredentials(
335:                 oracleTimestamp,
336:                 stateRootProof.beaconStateRoot,
337:                 validatorIndices[i],
338:                 validatorFieldsProofs[i],
339:                 validatorFields[i]
340:             );
341:         }
342: 
343:         
344:         eigenPodManager.recordBeaconChainETHBalanceUpdate(podOwner, int256(totalAmountToBeRestakedWei));
345:     }

['371']

362:     function recoverTokens(
363:         IERC20[] memory tokenList,
364:         uint256[] memory amountsToWithdraw,
365:         address recipient
366:     ) external onlyEigenPodOwner onlyWhenNotPaused(PAUSED_NON_PROOF_WITHDRAWALS) {
367:         require(
368:             tokenList.length == amountsToWithdraw.length,
369:             "EigenPod.recoverTokens: tokenList and amountsToWithdraw must be same length"
370:         );
371:         for (uint256 i = 0; i < tokenList.length; i++) { // <= FOUND
372:             tokenList[i].safeTransfer(recipient, amountsToWithdraw[i]);
373:         }
374:     }

['247']

238:     function addStrategiesToDepositWhitelist(
239:         IStrategy[] calldata strategiesToWhitelist,
240:         bool[] calldata thirdPartyTransfersForbiddenValues
241:     ) external onlyStrategyWhitelister {
242:         require(
243:             strategiesToWhitelist.length == thirdPartyTransfersForbiddenValues.length,
244:             "StrategyManager.addStrategiesToDepositWhitelist: array lengths do not match"
245:         );
246:         uint256 strategiesToWhitelistLength = strategiesToWhitelist.length;
247:         for (uint256 i = 0; i < strategiesToWhitelistLength; ) { // <= FOUND
248:             
249:             if (!strategyIsWhitelistedForDeposit[strategiesToWhitelist[i]]) {
250:                 strategyIsWhitelistedForDeposit[strategiesToWhitelist[i]] = true;
251:                 emit StrategyAddedToDepositWhitelist(strategiesToWhitelist[i]);
252:                 _setThirdPartyTransfersForbidden(strategiesToWhitelist[i], thirdPartyTransfersForbiddenValues[i]);
253:             }
254:             unchecked {
255:                 ++i;
256:             }
257:         }
258:     }

['268']

264:     function removeStrategiesFromDepositWhitelist(
265:         IStrategy[] calldata strategiesToRemoveFromWhitelist
266:     ) external onlyStrategyWhitelister {
267:         uint256 strategiesToRemoveFromWhitelistLength = strategiesToRemoveFromWhitelist.length;
268:         for (uint256 i = 0; i < strategiesToRemoveFromWhitelistLength; ) { // <= FOUND
269:             
270:             if (strategyIsWhitelistedForDeposit[strategiesToRemoveFromWhitelist[i]]) {
271:                 strategyIsWhitelistedForDeposit[strategiesToRemoveFromWhitelist[i]] = false;
272:                 emit StrategyRemovedFromDepositWhitelist(strategiesToRemoveFromWhitelist[i]);
273:                 
274:                 _setThirdPartyTransfersForbidden(strategiesToRemoveFromWhitelist[i], false);
275:             }
276:             unchecked {
277:                 ++i;
278:             }
279:         }
280:     }

['441']

437:     function getDeposits(address staker) external view returns (IStrategy[] memory, uint256[] memory) {
438:         uint256 strategiesLength = stakerStrategyList[staker].length;
439:         uint256[] memory shares = new uint256[](strategiesLength);
440: 
441:         for (uint256 i = 0; i < strategiesLength; ) { // <= FOUND
442:             shares[i] = stakerStrategyShares[staker][stakerStrategyList[staker][i]];
443:             unchecked {
444:                 ++i;
445:             }
446:         }
447:         return (stakerStrategyList[staker], shares);
448:     }

['868']

863:     function getOperatorShares(
864:         address operator,
865:         IStrategy[] memory strategies
866:     ) public view returns (uint256[] memory) {
867:         uint256[] memory shares = new uint256[](strategies.length);
868:         for (uint256 i = 0; i < strategies.length; ++i) { // <= FOUND
869:             shares[i] = operatorShares[operator][strategies[i]];
870:         }
871:         return shares;
872:     }

['906']

878:     function getDelegatableShares(address staker) public view returns (IStrategy[] memory, uint256[] memory) {
879:         
880:         int256 podShares = eigenPodManager.podOwnerShares(staker);
881:         (IStrategy[] memory strategyManagerStrats, uint256[] memory strategyManagerShares) 
882:             = strategyManager.getDeposits(staker);
883: 
884:         
885:         if (podShares <= 0) {
886:             return (strategyManagerStrats, strategyManagerShares);
887:         }
888: 
889:         IStrategy[] memory strategies;
890:         uint256[] memory shares;
891: 
892:         if (strategyManagerStrats.length == 0) {
893:             
894:             strategies = new IStrategy[](1);
895:             shares = new uint256[](1);
896:             strategies[0] = beaconChainETHStrategy;
897:             shares[0] = uint256(podShares);
898:         } else {
899:             
900:             
901:             
902:             strategies = new IStrategy[](strategyManagerStrats.length + 1);
903:             shares = new uint256[](strategies.length);
904:             
905:             
906:             for (uint256 i = 0; i < strategyManagerStrats.length; ) { // <= FOUND
907:                 strategies[i] = strategyManagerStrats[i];
908:                 shares[i] = strategyManagerShares[i];
909: 
910:                 unchecked { ++i; }
911:             }
912: 
913:             
914:             strategies[strategies.length - 1] = beaconChainETHStrategy;
915:             shares[strategies.length - 1] = uint256(podShares);
916:         }
917: 
918:         return (strategies, shares);
919:     }

['928']

926:     function getWithdrawalDelay(IStrategy[] calldata strategies) public view returns (uint256) {
927:         uint256 withdrawalDelay = minWithdrawalDelayBlocks;
928:         for (uint256 i = 0; i < strategies.length; ++i) { // <= FOUND
929:             uint256 currWithdrawalDelay = strategyWithdrawalDelayBlocks[strategies[i]];
930:             if (currWithdrawalDelay > withdrawalDelay) {
931:                 withdrawalDelay = currWithdrawalDelay;
932:             }
933:         }
934:         return withdrawalDelay;
935:     }

[Low-15] No limits when setting min/max amounts

Resolution

When settings min/max state variables, ensure there a require checks in place to prevent incorrect values from being set

Num of instances: 3

Findings

Click to show findings

['430']

430:     function setMinWithdrawalDelayBlocks(uint256 newMinWithdrawalDelayBlocks) external onlyOwner {
431:         _setMinWithdrawalDelayBlocks(newMinWithdrawalDelayBlocks);
432:     }

['231']

231:     function setMaxPods(uint256 newMaxPods) external onlyUnpauser {
232:         _setMaxPods(newMaxPods);
233:     }

['46']

46:     function setTVLLimits(uint256 newMaxPerDeposit, uint256 newMaxTotalDeposits) external onlyUnpauser {
47:         _setTVLLimits(newMaxPerDeposit, newMaxTotalDeposits);
48:     }

[Low-16] Initializer function can be front run

Resolution

In Solidity contract deployment, not making the initialize() function call atomic with the contract creation can leave a vulnerability window. A malicious actor could exploit this time gap and call initialize() before the intended initialization. This action could disrupt the contract's setup, potentially necessitating a full contract re-deployment to ensure proper initialization. To mitigate such risks, it's advised to use a factory contract. This factory contract can be programmed to deploy and initialize a new contract in a single atomic transaction, closing the window of vulnerability and ensuring correct and secure contract initialization.

Num of instances: 8

Findings

Click to show findings

['45']

41:     function initialize(
42:         address initialOwner,
43:         IPauserRegistry _pauserRegistry,
44:         uint256 initialPausedStatus
45:     ) external initializer  // <= FOUND

['58']

53:     function initialize(
54:         address initOwner,
55:         IPauserRegistry _pauserRegistry,
56:         uint256 initPausedStatus,
57:         uint256 _withdrawalDelayBlocks
58:     ) external initializer  // <= FOUND

['76']

69:     function initialize(
70:         address initialOwner,
71:         IPauserRegistry _pauserRegistry,
72:         uint256 initialPausedStatus,
73:         uint256 _minWithdrawalDelayBlocks,
74:         IStrategy[] calldata _strategies,
75:         uint256[] calldata _withdrawalDelayBlocks
76:     ) external initializer  // <= FOUND

['156']

156:     function initialize(address _podOwner) external initializer  // <= FOUND

['63']

57:     function initialize(
58:         uint256 _maxPods,
59:         IBeaconChainOracle _beaconChainOracle,
60:         address initialOwner,
61:         IPauserRegistry _pauserRegistry,
62:         uint256 _initPausedStatus
63:     ) external initializer  // <= FOUND

['70']

70:     function initialize(IERC20 _underlyingToken, IPauserRegistry _pauserRegistry) public virtual initializer  // <= FOUND

['34']

29:     function initialize(
30:         uint256 _maxPerDeposit,
31:         uint256 _maxTotalDeposits,
32:         IERC20 _underlyingToken,
33:         IPauserRegistry _pauserRegistry
34:     ) public virtual initializer  // <= FOUND

['87']

82:     function initialize(
83:         address initialOwner,
84:         address initialStrategyWhitelister,
85:         IPauserRegistry _pauserRegistry,
86:         uint256 initialPausedStatus
87:     ) external initializer  // <= FOUND

[Low-17] Use _disableInitializers() to ensure initialization occurs once

Resolution

disableInitializers() should be used in upgradeable contracts to ensure the initializer functions can't be called more than once. In upgradeable contracts, initializer functions set initial state and values, but if they can be invoked multiple times, it could lead to unexpected behavior or vulnerabilities. By calling disableInitializers() after the initial setup, you essentially lock the initializer functions, ensuring they can only be called once during the contract's lifecycle. This prevents repeated initializations, helping to maintain the integrity and security of the contract, and providing a safeguard against potential manipulation or misuse of the initialization functions.

Num of instances: 1

Findings

Click to show findings

['29']

29: contract Slasher is Initializable, OwnableUpgradeable, ISlasher, Pausable  // <= FOUND

[Low-18] Loss of precision

Resolution

Dividing by large numbers in Solidity can cause a loss of precision due to the language's inherent integer division behavior. Solidity does not support floating-point arithmetic, and as a result, division between integers yields an integer result, truncating any fractional part. When dividing by a large number, the resulting value may become significantly smaller, leading to a loss of precision, as the fractional part is discarded.

Num of instances: 5

Findings

Click to show findings

['340']

340:     function getWithdrawalEpoch(WithdrawalProof memory withdrawalProof) internal pure returns (uint64) { // <= FOUND
341:         return
342:             Endian.fromLittleEndianUint64(withdrawalProof.slotRoot) / SLOTS_PER_EPOCH;
343:     }

['770']

770:     function _timestampToEpoch(uint64 timestamp) internal view returns (uint64) { // <= FOUND
771:         require(timestamp >= GENESIS_TIME, "EigenPod._timestampToEpoch: timestamp is before genesis");
772:         return (timestamp - GENESIS_TIME) / BeaconChainProofs.SECONDS_PER_EPOCH; // <= FOUND
773:     }

['134']

134:     function withdraw(
135:         address recipient,
136:         IERC20 token,
137:         uint256 amountShares
138:     ) external virtual override onlyWhenNotPaused(PAUSED_WITHDRAWALS) onlyStrategyManager {
139:         
140:         _beforeWithdrawal(recipient, token, amountShares);
141: 
142:         
143:         uint256 priorTotalShares = totalShares;
144: 
145:         require(
146:             amountShares <= priorTotalShares,
147:             "StrategyBase.withdraw: amountShares must be less than or equal to totalShares"
148:         );
149: 
150:         
151: 
154:         
155:         uint256 virtualPriorTotalShares = priorTotalShares + SHARES_OFFSET;
156:         uint256 virtualTokenBalance = _tokenBalance() + BALANCE_OFFSET;
157:         
158:         uint256 amountToSend = (virtualTokenBalance * amountShares) / virtualPriorTotalShares;
159: 
160:         
161:         totalShares = priorTotalShares - amountShares;
162: 
163:         _afterWithdrawal(recipient, token, amountToSend);
164:     }

['211']

211:     function sharesToUnderlyingView(uint256 amountShares) public view virtual override returns (uint256) { // <= FOUND
212:         
213:         uint256 virtualTotalShares = totalShares + SHARES_OFFSET;
214:         uint256 virtualTokenBalance = _tokenBalance() + BALANCE_OFFSET;
215:         
216:         return (virtualTokenBalance * amountShares) / virtualTotalShares;
217:     }

['237']

237:     function underlyingToSharesView(uint256 amountUnderlying) public view virtual returns (uint256) { // <= FOUND
238:         
239:         uint256 virtualTotalShares = totalShares + SHARES_OFFSET;
240:         uint256 virtualTokenBalance = _tokenBalance() + BALANCE_OFFSET;
241:         
242:         return (amountUnderlying * virtualTotalShares) / virtualTokenBalance;
243:     }

[Low-19] Missing zero address check in constructor

Resolution

In Solidity, constructors often take address parameters to initialize important components of a contract, such as owner or linked contracts. However, without a check, there's a risk that an address parameter could be mistakenly set to the zero address (0x0). This could occur due to a mistake or oversight during contract deployment. A zero address in a crucial role can cause serious issues, as it cannot perform actions like a normal address, and any funds sent to it are irretrievable. Therefore, it's crucial to include a zero address check in constructors to prevent such potential problems. If a zero address is detected, the constructor should revert the transaction.

Num of instances: 1

Findings

Click to show findings

['23']

23:     constructor(address[] memory _pausers, address _unpauser) { // <= FOUND
24:         for (uint256 i = 0; i < _pausers.length; i++) {
25:             _setIsPauser(_pausers[i], true);
26:         }
27:         _setUnpauser(_unpauser);
28:     }

[Low-20] Ownable is inherited but not used

Resolution

In Solidity, inheriting the Ownable contract from OpenZeppelin without utilizing its features can lead to unnecessary code bloat and potential confusion. The Ownable contract is designed to provide a basic access control mechanism, where there is an account (an owner) that can be granted exclusive access to specific functions. However, if the inherited Ownable functionalities like onlyOwner modifier are not used in the contract, it indicates an oversight in the contract design. This unnecessary inheritance could mislead developers or auditors into assuming certain access control mechanisms are in place when they are not. The resolution is straightforward: if Ownable is not needed, it should be removed from the inheritance chain to streamline the contract and avoid misunderstandings about its access control mechanisms.

Num of instances: 2

Findings

Click to show findings

['11']

11: contract AVSDirectory is
12:     Initializable,
13:     OwnableUpgradeable,
14:     Pausable,
15:     AVSDirectoryStorage,
16:     ReentrancyGuardUpgradeable
17: 

['29']

29: contract Slasher is Initializable, OwnableUpgradeable, ISlasher, Pausable 

[Low-21] Use of onlyOwner functions can be lost

Resolution

In Solidity, renouncing ownership of a contract essentially transfers ownership to the zero address. This is an irreversible operation and has considerable security implications. If the renounceOwnership function is used, the contract will lose the ability to perform any operations that are limited to the owner. This can be problematic if there are any bugs, flaws, or unexpected events that require owner intervention to resolve. Therefore, in some instances, it is better to disable or omit the renounceOwnership function, and instead implement a secure transferOwnership function. This way, if necessary, ownership can be transferred to a new, trusted party without losing the potential for administrative intervention.

Num of instances: 4

Findings

Click to show findings

['13']

11: contract DelayedWithdrawalRouter is
12:     Initializable,
13:     OwnableUpgradeable, // <= FOUND
14:     ReentrancyGuardUpgradeable,
15:     Pausable,
16:     IDelayedWithdrawalRouter
17: 

['21']

21: contract DelegationManager is Initializable, OwnableUpgradeable, Pausable, DelegationManagerStorage, ReentrancyGuardUpgradeable  // <= FOUND

['27']

25: contract EigenPodManager is
26:     Initializable,
27:     OwnableUpgradeable, // <= FOUND
28:     Pausable,
29:     EigenPodPausingConstants,
30:     EigenPodManagerStorage,
31:     ReentrancyGuardUpgradeable
32: 

['24']

22: contract StrategyManager is
23:     Initializable,
24:     OwnableUpgradeable, // <= FOUND
25:     ReentrancyGuardUpgradeable,
26:     Pausable,
27:     StrategyManagerStorage
28: 

[Low-22] Critical functions should be a two step procedure

Resolution

Critical functions in Solidity contracts should follow a two-step procedure to enhance security, minimize human error, and ensure proper access control. By dividing sensitive operations into distinct phases, such as initiation and confirmation, developers can introduce a safeguard against unintended actions or unauthorized access.

Num of instances: 6

Findings

Click to show findings

['118']

118:     function setWithdrawalDelayBlocks(uint256 newValue) external onlyOwner  // <= FOUND

['430']

430:     function setMinWithdrawalDelayBlocks(uint256 newMinWithdrawalDelayBlocks) external onlyOwner  // <= FOUND

['441']

441:     function setStrategyWithdrawalDelayBlocks( // <= FOUND
442:         IStrategy[] calldata strategies,
443:         uint256[] calldata withdrawalDelayBlocks
444:     ) external onlyOwner 

['248']

248:     function setDenebForkTimestamp(uint64 newDenebForkTimestamp) external onlyOwner  // <= FOUND

['229']

229:     function setStrategyWhitelister(address newStrategyWhitelister) external onlyOwner  // <= FOUND

['240']

240:     function updateBeaconChainOracle(IBeaconChainOracle newBeaconChainOracle) external onlyOwner  // <= FOUND

[Low-23] Remaining eth may not be refunded to users

Resolution

When a contract function accepts Ethereum and executes a .call() or similar function that also forwards Ethereum value, it's important to check for and refund any remaining balance. This is because some of the supplied value may not be used during the call execution due to gas constraints, a revert in the called contract, or simply because not all the value was needed.

If you do not account for this remaining balance, it can become "locked" in the contract. It's crucial to either return the remaining balance to the sender or handle it in a way that ensures it is not permanently stuck. Neglecting to do so can lead to loss of funds and degradation of the contract's reliability. Furthermore, it's good practice to ensure fairness and trust with your users by returning unused funds.

Num of instances: 1

Findings

Click to show findings

['403']

403:     function stake(
404:         bytes calldata pubkey,
405:         bytes calldata signature,
406:         bytes32 depositDataRoot
407:     ) external payable onlyEigenPodManager {
408:         
409:         require(msg.value == 32 ether, "EigenPod.stake: must initially stake for any validator with 32 ether");
410:         ethPOS.deposit{value: 32 ether}(pubkey, _podWithdrawalCredentials(), signature, depositDataRoot); // <= FOUND
411:         emit EigenPodStaked(pubkey);
412:     }

[Low-24] Absence of Reentrancy Guard with OpenZeppelin's sendValue Function Usage

Resolution

The Address.sol library from OpenZeppelin (OZ) is utilized for Ether transfers through a 'sendValue' call in several functions. OZ's Address.sol documentation explicitly advises being cautious of reentrancy vulnerabilities when transferring control to 'recipient', suggesting the use of {ReentrancyGuard} or the checks-effects-interactions pattern. Given the complexity of recent vulnerabilities like 'view Re-Entrancy' and potential future expansions or integrations of the project with other contracts, it's highly recommended to incorporate a re-entrancy guard as a best practice. This will secure the system against reentrancy attacks and ensure the safety of Ether transfers, ensuring the stability of the system as it evolves.

Num of instances: 1

Findings

Click to show findings

['739']

739:     function _sendETH(address recipient, uint256 amountWei) internal {
740:         Address.sendValue(payable(recipient), amountWei); // <= FOUND
741:     }

[Low-25] Sending tokens in a for loop

Resolution

Performing token transfers in a loop in a Solidity contract is generally not recommended due to various reasons. One of these reasons is the "Fail-Silently" issue.

In a Solidity loop, if one transfer operation fails, it causes the entire transaction to fail. This issue can be particularly troublesome when you're dealing with multiple transfers in one transaction. For instance, if you're looping through an array of recipients to distribute dividends or rewards, a single failed transfer will prevent all the subsequent recipients from receiving their transfers. This could be due to the recipient contract throwing an exception or due to other issues like a gas limit being exceeded.

Moreover, such a design could also inadvertently lead to a situation where a malicious contract intentionally causes a failure when receiving Ether to prevent other participants from getting their rightful transfers. This could open up avenues for griefing attacks in the system.

Resolution: To mitigate this problem, it's typically recommended to follow the "withdraw pattern" in your contracts instead of pushing payments. In this model, each recipient would be responsible for triggering their own payment. This separates each transfer operation, so a failure in one doesn't impact the others. Additionally, it greatly reduces the chance of malicious interference as the control over fund withdrawal lies with the intended recipient and not with an external loop operation.

Num of instances: 1

Findings

Click to show findings

['371']

371:        for (uint256 i = 0; i < tokenList.length; i++) {
372:             tokenList[i].safeTransfer(recipient, amountsToWithdraw[i]); // <= FOUND
373:         }

[Low-26] Revert on Transfer to the Zero Address

Resolution

Many ERC-20 and ERC-721 token contracts implement a safeguard that reverts transactions which attempt to transfer tokens to the zero address. This is because such transfers are often the result of programming errors. The OpenZeppelin library, a popular choice for implementing these standards, includes this safeguard. For token contract developers who want to avoid unintentional transfers to the zero address, it's good practice to include a condition that reverts the transaction if the recipient's address is the zero address.

Num of instances: 2

Findings

Click to show findings

['362']

362:     function recoverTokens(
363:         IERC20[] memory tokenList,
364:         uint256[] memory amountsToWithdraw,
365:         address recipient
366:     ) external onlyEigenPodOwner onlyWhenNotPaused(PAUSED_NON_PROOF_WITHDRAWALS) {
367:         require(
368:             tokenList.length == amountsToWithdraw.length,
369:             "EigenPod.recoverTokens: tokenList and amountsToWithdraw must be same length"
370:         );
371:         for (uint256 i = 0; i < tokenList.length; i++) {
372:             tokenList[i].safeTransfer(recipient, amountsToWithdraw[i]); // <= FOUND
373:         }
374:     }

['192']

192:     function _afterWithdrawal(address recipient, IERC20 token, uint256 amountToSend) internal virtual {
193:         token.safeTransfer(recipient, amountToSend); // <= FOUND
194:     }

[Low-27] Unsafe uint to int conversion

Resolution

Unsafe conversion from uint to int in Solidity can lead to unexpected overflows if the unsigned integer value exceeds the positive limit of the corresponding signed integer type. This is due to the fact that signed integers use one bit for the sign, effectively halving the positive range. An example is converting a uint256 number greater than type(uint128).max to an int256, which can result in an overflow. To mitigate this risk, consider using libraries like SafeCast, which include functions specifically designed to safely cast between different numerical types. They perform necessary checks and revert the transaction if an unsafe cast is attempted, thereby enhancing the security and robustness of the code.

Num of instances: 11

Findings

Click to show findings

['220']

220:         eigenPodManager.recordBeaconChainETHBalanceUpdate(podOwner, sharesDeltaGwei * int256(GWEI_TO_WEI)); // <= FOUND

['275']

275:             eigenPodManager.recordBeaconChainETHBalanceUpdate(podOwner, withdrawalSummary.sharesDeltaGwei * int256(GWEI_TO_WEI)); // <= FOUND

['344']

344: 
345:         
346:         eigenPodManager.recordBeaconChainETHBalanceUpdate(podOwner, int256(totalAmountToBeRestakedWei)); // <= FOUND

['761']

761:         return
762:             int256(uint256(newAmountGwei)) - int256(uint256(previousAmountGwei)); // <= FOUND

['116']

116:         require(sharesDelta % int256(GWEI_TO_WEI) == 0, // <= FOUND
117:             "EigenPodManager.recordBeaconChainETHBalanceUpdate: sharesDelta must be a whole Gwei amount");

['161']

161:         int256 updatedPodOwnerShares = podOwnerShares[podOwner] - int256(shares); // <= FOUND

['181']

181:         int256 updatedPodOwnerShares = currentPodOwnerShares + int256(shares); // <= FOUND

['184']

184: 
185:         emit PodSharesUpdated(podOwner, int256(shares)); // <= FOUND

['214']

214:                 emit PodSharesUpdated(podOwner, int256(currentShareDeficit)); // <= FOUND

['217']

217:                 podOwnerShares[podOwner] += int256(shares); // <= FOUND

['218']

218:                 emit PodSharesUpdated(podOwner, int256(shares)); // <= FOUND

[Low-28] Missing zero address check in initializer

Resolution

Initializer functions in contracts often set important parameters or addresses. Failing to check for the zero address (0x0000000000000000000000000000000000000000) in initializers can lead to unintended behavior, as this address typically signifies an unset or default value. Transfers to or interactions with the zero address can result in permanent loss of assets or broken functionality. It's crucial to add checks using require(targetAddress != address(0), "Address cannot be zero") in initializers to prevent accidentally setting important state variables or parameters to this address, ensuring the system's integrity and user asset safety.

Num of instances: 5

Findings

Click to show findings

['41']

41:     function initialize(
42:         address initialOwner,
43:         IPauserRegistry _pauserRegistry,
44:         uint256 initialPausedStatus
45:     ) external initializer {
46:         _initializePauser(_pauserRegistry, initialPausedStatus);
47:         _DOMAIN_SEPARATOR = _calculateDomainSeparator();
48:         _transferOwnership(initialOwner);
49:     }

['53']

53:     function initialize(
54:         address initOwner,
55:         IPauserRegistry _pauserRegistry,
56:         uint256 initPausedStatus,
57:         uint256 _withdrawalDelayBlocks
58:     ) external initializer {
59:         _transferOwnership(initOwner);
60:         _initializePauser(_pauserRegistry, initPausedStatus);
61:         _setWithdrawalDelayBlocks(_withdrawalDelayBlocks);
62:     }

['69']

69:     function initialize(
70:         address initialOwner,
71:         IPauserRegistry _pauserRegistry,
72:         uint256 initialPausedStatus,
73:         uint256 _minWithdrawalDelayBlocks,
74:         IStrategy[] calldata _strategies,
75:         uint256[] calldata _withdrawalDelayBlocks
76:     ) external initializer {
77:         _initializePauser(_pauserRegistry, initialPausedStatus);
78:         _DOMAIN_SEPARATOR = _calculateDomainSeparator();
79:         _transferOwnership(initialOwner);
80:         _setMinWithdrawalDelayBlocks(_minWithdrawalDelayBlocks);
81:         _setStrategyWithdrawalDelayBlocks(_strategies, _withdrawalDelayBlocks);
82:     }

['57']

57:     function initialize(
58:         uint256 _maxPods,
59:         IBeaconChainOracle _beaconChainOracle,
60:         address initialOwner,
61:         IPauserRegistry _pauserRegistry,
62:         uint256 _initPausedStatus
63:     ) external initializer {
64:         _setMaxPods(_maxPods);
65:         _updateBeaconChainOracle(_beaconChainOracle);
66:         _transferOwnership(initialOwner);
67:         _initializePauser(_pauserRegistry, _initPausedStatus);
68:     }

['82']

82:     function initialize(
83:         address initialOwner,
84:         address initialStrategyWhitelister,
85:         IPauserRegistry _pauserRegistry,
86:         uint256 initialPausedStatus
87:     ) external initializer {
88:         _DOMAIN_SEPARATOR = _calculateDomainSeparator();
89:         _initializePauser(_pauserRegistry, initialPausedStatus);
90:         _transferOwnership(initialOwner);
91:         _setStrategyWhitelister(initialStrategyWhitelister);
92:     }

[Low-29] Critical functions should have a timelock

Resolution

Critical functions, especially those affecting protocol parameters or user funds, are potential points of failure or exploitation. To mitigate risks, incorporating a timelock on such functions can be beneficial. A timelock requires a waiting period between the time an action is initiated and when it's executed, giving stakeholders time to react, potentially vetoing malicious or erroneous changes. To implement, integrate a smart contract like OpenZeppelin's TimelockController or build a custom mechanism. This ensures governance decisions or administrative changes are transparent and allows for community or multi-signature interventions, enhancing protocol security and trustworthiness.

Num of instances: 5

Findings

Click to show findings

['118']

118:     function setWithdrawalDelayBlocks(uint256 newValue) external onlyOwner  // <= FOUND

['430']

430:     function setMinWithdrawalDelayBlocks(uint256 newMinWithdrawalDelayBlocks) external onlyOwner  // <= FOUND

['441']

441:     function setStrategyWithdrawalDelayBlocks( // <= FOUND
442:         IStrategy[] calldata strategies,
443:         uint256[] calldata withdrawalDelayBlocks
444:     ) external onlyOwner 

['248']

248:     function setDenebForkTimestamp(uint64 newDenebForkTimestamp) external onlyOwner  // <= FOUND

['229']

229:     function setStrategyWhitelister(address newStrategyWhitelister) external onlyOwner  // <= FOUND

[Low-30] Consider implementing two-step procedure for updating protocol addresses

Resolution

Implementing a two-step procedure for updating protocol addresses adds an extra layer of security. In such a system, the first step initiates the change, and the second step, after a predefined delay, confirms and finalizes it. This delay allows stakeholders or monitoring tools to observe and react to unintended or malicious changes. If an unauthorized change is detected, corrective actions can be taken before the change is finalized. To achieve this, introduce a "proposed address" state variable and a "delay period". Upon an update request, set the "proposed address". After the delay, if not contested, the main protocol address can be updated.

Num of instances: 1

Findings

Click to show findings

['229']

229:     function setStrategyWhitelister(address newStrategyWhitelister) external onlyOwner { // <= FOUND
230:         _setStrategyWhitelister(newStrategyWhitelister);
231:     }

[Low-31] Prefer skip over revert model in iteration

Resolution

It is preferable to skip operations on an array index when a condition is not met rather than reverting the whole transaction as reverting can introduce the possiblity of malicous actors purposefully introducing array objects which fail conditional checks within for/while loops so group operations fail. As such it is recommended to simply skip such array indices over reverting unless there is a valid security or logic reason behind not doing so.

Num of instances: 4

Findings

Click to show findings

['273']

273:        for (uint256 i = 0; i < queuedWithdrawalParams.length; i++) { // <= FOUND
274:             require(queuedWithdrawalParams[i].strategies.length == queuedWithdrawalParams[i].shares.length, "DelegationManager.queueWithdrawal: input length mismatch"); // <= FOUND
275:             require(queuedWithdrawalParams[i].withdrawer == msg.sender, "DelegationManager.queueWithdrawal: withdrawer must be staker"); // <= FOUND
276: 
277:             
278:             
279:             
280:             withdrawalRoots[i] = _removeSharesAndQueueWithdrawal({
281:                 staker: msg.sender,
282:                 operator: operator,
283:                 withdrawer: queuedWithdrawalParams[i].withdrawer,
284:                 strategies: queuedWithdrawalParams[i].strategies,
285:                 shares: queuedWithdrawalParams[i].shares
286:             });
287:         }

['592']

592:            for (uint256 i = 0; i < withdrawal.strategies.length; ) { // <= FOUND
593:                 require(
594:                     withdrawal.startBlock + strategyWithdrawalDelayBlocks[withdrawal.strategies[i]] <= block.number,
595:                     "DelegationManager._completeQueuedWithdrawal: withdrawalDelayBlocks period has not yet passed for this strategy"
596:                 );
597: 
598:                 _withdrawSharesAsTokens({
599:                     staker: withdrawal.staker,
600:                     withdrawer: msg.sender,
601:                     strategy: withdrawal.strategies[i],
602:                     shares: withdrawal.shares[i],
603:                     token: tokens[i]
604:                 });
605:                 unchecked { ++i; }
606:             }

['610']

610:            for (uint256 i = 0; i < withdrawal.strategies.length; ) { // <= FOUND
611:                 require(
612:                     withdrawal.startBlock + strategyWithdrawalDelayBlocks[withdrawal.strategies[i]] <= block.number, 
613:                     "DelegationManager._completeQueuedWithdrawal: withdrawalDelayBlocks period has not yet passed for this strategy"
614:                 );
615: 
616:                 
617: 
619:                 if (withdrawal.strategies[i] == beaconChainETHStrategy) {
620:                     address staker = withdrawal.staker;
621:                     
622: 
625:                     uint256 increaseInDelegateableShares = eigenPodManager.addShares({
626:                         podOwner: staker,
627:                         shares: withdrawal.shares[i]
628:                     });
629:                     address podOwnerOperator = delegatedTo[staker];
630:                     
631:                     if (podOwnerOperator != address(0)) {
632:                         _increaseOperatorShares({
633:                             operator: podOwnerOperator,
634:                             
635:                             staker: staker,
636:                             strategy: withdrawal.strategies[i],
637:                             shares: increaseInDelegateableShares
638:                         });
639:                     }
640:                 } else {
641:                     strategyManager.addShares(msg.sender, tokens[i], withdrawal.strategies[i], withdrawal.shares[i]);
642:                     
643:                     if (currentOperator != address(0)) {
644:                         _increaseOperatorShares({
645:                             operator: currentOperator,
646:                             
647:                             staker: msg.sender,
648:                             strategy: withdrawal.strategies[i],
649:                             shares: withdrawal.shares[i]
650:                         });
651:                     }
652:                 }
653:                 unchecked { ++i; }
654:             }

['690']

690:        for (uint256 i = 0; i < strategies.length;) { // <= FOUND
691:             
692:             if (operator != address(0)) {
693:                 _decreaseOperatorShares({
694:                     operator: operator,
695:                     staker: staker,
696:                     strategy: strategies[i],
697:                     shares: shares[i]
698:                 });
699:             }
700: 
701:             
702:             if (strategies[i] == beaconChainETHStrategy) {
703:                 
704: 
709:                 eigenPodManager.removeShares(staker, shares[i]);
710:             } else {
711:                 require(
712:                     staker == withdrawer || !strategyManager.thirdPartyTransfersForbidden(strategies[i]),
713:                     "DelegationManager._removeSharesAndQueueWithdrawal: withdrawer must be same address as staker if thirdPartyTransfersForbidden are set"
714:                 );
715:                 
716:                 strategyManager.removeShares(staker, strategies[i], shares[i]);
717:             }
718: 
719:             unchecked { ++i; }
720:         }

[Low-32] Use of abi.encodePacked with dynamic types inside keccak256

Resolution

Using abi.encodePacked with dynamic types for hashing functions like keccak256 can be risky due to the potential for hash collisions. This function concatenates arguments tightly, without padding, which might lead to different inputs producing the same hash. This is especially problematic with dynamic types, where the boundaries between inputs can blur. To mitigate this, use abi.encode instead. abi.encode pads its arguments to 32 bytes, creating clear distinctions between different inputs and significantly reducing the chance of hash collisions. This approach ensures more reliable and collision-resistant hashing, crucial for maintaining data integrity and security in smart contracts.

Num of instances: 4

Findings

Click to show findings

['977']

977:         
978:         bytes32 stakerDigestHash = keccak256(abi.encodePacked("\x19\x01", domainSeparator(), stakerStructHash)); // <= FOUND

['1001']

1001:         
1002:         bytes32 approverDigestHash = keccak256(abi.encodePacked("\x19\x01", domainSeparator(), approverStructHash)); // <= FOUND

['321']

321:             pod = IEigenPod(
322:                 Create2.computeAddress(
323:                     bytes32(uint256(uint160(podOwner))),  // <= FOUND
324:                     keccak256(abi.encodePacked(beaconProxyBytecode, abi.encode(eigenPodBeacon, "")))  // <= FOUND
325:                 )
326:             );

['155']

155: 
156:         
157:         bytes32 digestHash = keccak256(abi.encodePacked("\x19\x01", domainSeparator(), structHash)); // <= FOUND

[Low-33] Nonce variables should be uint256

Resolution

Using uint256 for nonce variables is a best practice in Solidity for several reasons. Nonce, typically used for ensuring the uniqueness of transactions or for generating pseudorandom numbers, can potentially be incremented many times. Choosing uint256 ensures that the nonce has a sufficiently large range to avoid overflow, even under heavy usage or over an extended period. An overflow in a nonce variable can lead to security vulnerabilities, such as replay attacks or other unexpected behaviors. By using uint256, developers can mitigate these risks, ensuring more robust and secure smart contract operations, especially in systems with high transaction throughput or long-term operational expectancy.

Num of instances: 1

Findings

Click to show findings

['140']

140:         uint96 nonce; // <= FOUND

[Low-34] Constructors missing validation

Resolution

In Solidity, when values are being assigned in constructors to unsigned or integer variables, it's crucial to ensure the provided values adhere to the protocol's specific operational boundaries as laid out in the project specifications and documentation. If the constructors lack appropriate validation checks, there's a risk of setting state variables with values that could cause unexpected and potentially detrimental behavior within the contract's operations, violating the intended logic of the protocol. This can compromise the contract's security and impact the maintainability and reliability of the system. In order to avoid such issues, it is recommended to incorporate rigorous validation checks in constructors. These checks should align with the project's defined rules and constraints, making use of Solidity's built-in require function to enforce these conditions. If the validation checks fail, the require function will cause the transaction to revert, ensuring the integrity and adherence to the protocol's expected behavior.

Num of instances: 7

Findings

Click to show findings

['36']

36:     constructor(IDelegationManager _delegation) {
37:         delegation = _delegation; // <= FOUND
38:     }

['72']

72:     constructor(IDelegationManager _delegation, IEigenPodManager _eigenPodManager, ISlasher _slasher) {
73:         delegation = _delegation; // <= FOUND
74:         eigenPodManager = _eigenPodManager; // <= FOUND
75:         slasher = _slasher; // <= FOUND
76:     }

['44']

44:     constructor(IEigenPodManager _eigenPodManager) {
45:         require(
46:             address(_eigenPodManager) != address(0),
47:             "DelayedWithdrawalRouter.constructor: _eigenPodManager cannot be zero address"
48:         );
49:         eigenPodManager = _eigenPodManager; // <= FOUND
50:         _disableInitializers();
51:     }

['104']

104:     constructor(IStrategyManager _strategyManager, ISlasher _slasher, IEigenPodManager _eigenPodManager) {
105:         strategyManager = _strategyManager; // <= FOUND
106:         eigenPodManager = _eigenPodManager; // <= FOUND
107:         slasher = _slasher; // <= FOUND
108:     }

['140']

140:     constructor(
141:         IETHPOSDeposit _ethPOS,
142:         IDelayedWithdrawalRouter _delayedWithdrawalRouter,
143:         IEigenPodManager _eigenPodManager,
144:         uint64 _MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR,
145:         uint64 _GENESIS_TIME
146:     ) {
147:         ethPOS = _ethPOS; // <= FOUND
148:         delayedWithdrawalRouter = _delayedWithdrawalRouter; // <= FOUND
149:         eigenPodManager = _eigenPodManager; // <= FOUND
150:         MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR = _MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR; // <= FOUND
151:         GENESIS_TIME = _GENESIS_TIME; // <= FOUND
152:         _disableInitializers();
153:     }

['69']

69:     constructor(
70:         IETHPOSDeposit _ethPOS,
71:         IBeacon _eigenPodBeacon,
72:         IStrategyManager _strategyManager,
73:         ISlasher _slasher,
74:         IDelegationManager _delegationManager
75:     ) {
76:         ethPOS = _ethPOS; // <= FOUND
77:         eigenPodBeacon = _eigenPodBeacon; // <= FOUND
78:         strategyManager = _strategyManager; // <= FOUND
79:         slasher = _slasher; // <= FOUND
80:         delegationManager = _delegationManager; // <= FOUND
81:     }

['65']

65:     constructor(IStrategyManager _strategyManager) {
66:         strategyManager = _strategyManager; // <= FOUND
67:         _disableInitializers();
68:     }

[Low-35] Contract contains payable functions but no withdraw/sweep function

Resolution

In smart contract development, particularly for Ethereum, having payable functions without a corresponding withdraw or sweep function can lead to potential issues. Payable functions allow the contract to receive Ether, but without a mechanism to withdraw these funds, the Ether can become locked within the contract indefinitely. This situation might be intentional in some cases (like a burn function), but generally, it’s a design oversight. A withdraw or sweep function is necessary to transfer Ether out of the contract to a specific address, typically the owner's or a designated recipient. Without this, the contract lacks flexibility in managing its funds, potentially leading to lost or inaccessible Ether.

Num of instances: 1

Findings

Click to show findings

['11']

11: contract DelayedWithdrawalRouter is
12:     Initializable,
13:     OwnableUpgradeable,
14:     ReentrancyGuardUpgradeable,
15:     Pausable,
16:     IDelayedWithdrawalRouter
17: 

[Low-36] State variables not capped at reasonable values

Resolution

Setting boundaries on state variables in smart contracts is essential for maintaining system integrity and user protection. Without caps on values, variables could reach extremes that exploit or disrupt contract functionality, leading to potential loss or unintended consequences for users. Implementing checks for minimum and maximum permissible values can prevent such issues, ensuring variables remain within a safe and reasonable range. This practice guards against attacks aimed at destabilizing the contract, such as griefing, where attackers intentionally cause distress by exploiting vulnerabilities. Proper validation promotes contract reliability, user trust, and a healthier ecosystem by mitigating risks associated with unbounded state changes.

Num of instances: 1

Findings

Click to show findings

['287']

285:     function _setMaxPods(uint256 _maxPods) internal {
286:         emit MaxPodsUpdated(maxPods, _maxPods);
287:         maxPods = _maxPods; // <= FOUND
288:     }

[Low-37] Large transfers may not work with some ERC20 tokens

Resolution

Large transfers with some ERC20 tokens may not work due to various reasons. Some tokens may have transfer restrictions built into the contract, such as daily transfer limits or maximum transfer sizes per transaction, to comply with regulatory requirements or to mitigate risks. Others may face issues with rounding errors when dealing with large quantities, especially if they have a high number of decimal places. Resolution involves carefully reading the token's contract to understand its constraints and behaviors and performing transfers accordingly. It may also be necessary to split large transfers into smaller increments if the token enforces specific transfer limits.

Num of instances: 3

Findings

Click to show findings

['362']

362:     function recoverTokens(
363:         IERC20[] memory tokenList,
364:         uint256[] memory amountsToWithdraw,
365:         address recipient
366:     ) external onlyEigenPodOwner onlyWhenNotPaused(PAUSED_NON_PROOF_WITHDRAWALS) {
367:         require(
368:             tokenList.length == amountsToWithdraw.length,
369:             "EigenPod.recoverTokens: tokenList and amountsToWithdraw must be same length"
370:         );
371:         for (uint256 i = 0; i < tokenList.length; i++) {
372:             tokenList[i].safeTransfer(recipient, amountsToWithdraw[i]); // <= FOUND
373:         }
374:     }

['192']

192:     function _afterWithdrawal(address recipient, IERC20 token, uint256 amountToSend) internal virtual { // <= FOUND
193:         token.safeTransfer(recipient, amountToSend); // <= FOUND
194:     }

['323']

323:     function _depositIntoStrategy(
324:         address staker,
325:         IStrategy strategy,
326:         IERC20 token,
327:         uint256 amount
328:     ) internal onlyStrategiesWhitelistedForDeposit(strategy) returns (uint256 shares) {
329:         
330:         token.safeTransferFrom(msg.sender, address(strategy), amount);
331: 
332:         
333:         shares = strategy.deposit(token, amount);
334: 
335:         
336:         _addShares(staker, token, strategy, shares);
337: 
338:         
339:         delegation.increaseDelegatedShares(staker, strategy, shares);
340: 
341:         return shares;
342:     }

[Low-38] Functions calling contracts/addresses with transfer hooks are missing reentrancy guards

Resolution

While adherence to the check-effects-interaction pattern is commendable, the absence of a reentrancy guard in functions, especially where transfer hooks might be present, can expose the protocol users to risks of read-only reentrancies. Such reentrancy vulnerabilities can be exploited to execute malicious actions even without altering the contract state. Without a reentrancy guard, the only potential mitigation would be to blocklist the entire protocol - an extreme and disruptive measure. Therefore, incorporating a reentrancy guard into these functions is vital to bolster security, as it helps protect against both traditional reentrancy attacks and read-only reentrancies, ensuring robust and safe protocol operations.

Num of instances: 4

Findings

Click to show findings

['362']

362:     function recoverTokens(
363:         IERC20[] memory tokenList,
364:         uint256[] memory amountsToWithdraw,
365:         address recipient
366:     ) external onlyEigenPodOwner onlyWhenNotPaused(PAUSED_NON_PROOF_WITHDRAWALS) {
367:         require(
368:             tokenList.length == amountsToWithdraw.length,
369:             "EigenPod.recoverTokens: tokenList and amountsToWithdraw must be same length"
370:         );
371:         for (uint256 i = 0; i < tokenList.length; i++) {
372:             tokenList[i].safeTransfer(recipient, amountsToWithdraw[i]); // <= FOUND
373:         }
374:     }

['192']

192:     function _afterWithdrawal(address recipient, IERC20 token, uint256 amountToSend) internal virtual { // <= FOUND
193:         token.safeTransfer(recipient, amountToSend);
194:     }

['362']

362:     function recoverTokens(
363:         IERC20[] memory tokenList,
364:         uint256[] memory amountsToWithdraw,
365:         address recipient
366:     ) external onlyEigenPodOwner onlyWhenNotPaused(PAUSED_NON_PROOF_WITHDRAWALS) {
367:         require(
368:             tokenList.length == amountsToWithdraw.length,
369:             "EigenPod.recoverTokens: tokenList and amountsToWithdraw must be same length"
370:         );
371:         for (uint256 i = 0; i < tokenList.length; i++) {
372:             tokenList[i].safeTransfer(recipient, amountsToWithdraw[i]); // <= FOUND
373:         }
374:     }

['192']

192:     function _afterWithdrawal(address recipient, IERC20 token, uint256 amountToSend) internal virtual {
193:         token.safeTransfer(recipient, amountToSend); // <= FOUND
194:     }

[NonCritical-1] A function which defines named returns in it's declaration doesn't need to use return

Resolution

Remove the return statement once ensuring it is safe to do so

Num of instances: 3

Findings

Click to show findings

['326']

324:     function hashValidatorBLSPubkey(bytes memory validatorPubkey) internal pure returns (bytes32 pubkeyHash) {
325:         require(validatorPubkey.length == 48, "Input should be 48 bytes in length");
326:         return sha256(abi.encodePacked(validatorPubkey, bytes16(0))); // <= FOUND
327:     }

['122']

96:     function deposit(
97:         IERC20 token,
98:         uint256 amount
99:     ) external virtual override onlyWhenNotPaused(PAUSED_DEPOSITS) onlyStrategyManager returns (uint256 newShares) {
100:         
101:         _beforeDeposit(token, amount);
102: 
103:         
104:         uint256 priorTotalShares = totalShares;
105: 
106:         
107: 
110:         
111:         uint256 virtualShareAmount = priorTotalShares + SHARES_OFFSET;
112:         uint256 virtualTokenBalance = _tokenBalance() + BALANCE_OFFSET;
113:         
114:         uint256 virtualPriorTokenBalance = virtualTokenBalance - amount;
115:         newShares = (amount * virtualShareAmount) / virtualPriorTokenBalance;
116: 
117:         
118:         require(newShares != 0, "StrategyBase.deposit: newShares cannot be zero");
119: 
120:         
121:         totalShares = (priorTotalShares + newShares);
122:         return newShares; // <= FOUND
123:     }

['341']

323:     function _depositIntoStrategy(
324:         address staker,
325:         IStrategy strategy,
326:         IERC20 token,
327:         uint256 amount
328:     ) internal onlyStrategiesWhitelistedForDeposit(strategy) returns (uint256 shares) {
329:         
330:         token.safeTransferFrom(msg.sender, address(strategy), amount);
331: 
332:         
333:         shares = strategy.deposit(token, amount);
334: 
335:         
336:         _addShares(staker, token, strategy, shares);
337: 
338:         
339:         delegation.increaseDelegatedShares(staker, strategy, shares);
340: 
341:         return shares; // <= FOUND
342:     }

[NonCritical-2] Some if-statement can be converted to a ternary

Resolution

Improving code readability and compactness is an integral part of optimal programming practices. The use of ternary operators in place of if-else conditions is one such measure. Ternary operators allow us to write conditional statements in a more concise manner, thereby enhancing readability and simplicity. They follow the syntax condition ? exprIfTrue : exprIfFalse, which interprets as "if the condition is true, evaluate to exprIfTrue, else evaluate to exprIfFalse". By adopting this approach, we make our code more streamlined and intuitive, which could potentially aid in better understanding and maintenance of the codebase.

Num of instances: 5

Findings

Click to show findings

['237']

237:         if (strategies.length == 0) {
238:             withdrawalRoots = new bytes32[](0); // <= FOUND
239:         }

['486']

486:         if (validatorEffectiveBalanceGwei > MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR) {
487:             validatorInfo.restakedBalanceGwei = MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR; // <= FOUND
488:         }

['544']

544:         if (validatorEffectiveBalanceGwei > MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR) {
545:             newRestakedBalanceGwei = MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR; // <= FOUND
546:         }

['668']

668:         if (withdrawalAmountGwei > MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR) {
669:             amountToQueueGwei = MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR; // <= FOUND
670:         }

['96']

96:         if (address(pod) == address(0)) {
97:             
98:             pod = _deployPod(); // <= FOUND
99:         }

[NonCritical-3] Addresses shouldn't be hard-coded

Num of instances: 1

Findings

Click to show findings

['38']

38: 
40:     IStrategy public constant beaconChainETHStrategy = IStrategy(0xbeaC0eeEeeeeEEeEeEEEEeeEEeEeeeEeeEEBEaC0); // <= FOUND

[NonCritical-4] Cyclomatic complexity in functions

Resolution

Cyclomatic complexity is a software metric used to measure the complexity of a program. It quantifies the number of linearly independent paths through a program's source code, giving an idea of how complex the control flow is. High cyclomatic complexity may indicate a higher risk of defects and can make the code harder to understand, test, and maintain. It often suggests that a function or method is trying to do too much, and a refactor might be needed. By breaking down complex functions into smaller, more focused pieces, you can improve readability, ease of testing, and overall maintainability.

Num of instances: 2

Findings

Click to show findings

['422']

422:     function equalStorage(bytes storage _preBytes, bytes memory _postBytes) internal view returns (bool) { // <= FOUND
423:         bool success = true;
424: 
425:         assembly {
426:             
427:             let fslot := sload(_preBytes.slot)
428:             
429:             let slength := div(and(fslot, sub(mul(0x100, iszero(and(fslot, 1))), 1)), 2)
430:             let mlength := mload(_postBytes)
431: 
432:             
433:             switch eq(slength, mlength)
434:             case 1 {
435:                 
436:                 
437:                 
438:                 if iszero(iszero(slength)) {
439:                     switch lt(slength, 32)
440:                     case 1 {
441:                         
442:                         fslot := mul(div(fslot, 0x100), 0x100)
443: 
444:                         if iszero(eq(fslot, mload(add(_postBytes, 0x20)))) {
445:                             
446:                             success := 0
447:                         }
448:                     }
449:                     default {
450:                         
451:                         
452:                         
453:                         
454:                         let cb := 1
455: 
456:                         
457:                         mstore(0x0, _preBytes.slot)
458:                         let sc := keccak256(0x0, 0x20)
459: 
460:                         let mc := add(_postBytes, 0x20)
461:                         let end := add(mc, mlength)
462: 
463:                         
464:                         
465:                         
466:                         for {
467: 
468:                         } eq(add(lt(mc, end), cb), 2) {
469:                             sc := add(sc, 1)
470:                             mc := add(mc, 0x20)
471:                         } {

['294']

294:     function _calculateChangeInDelegatableShares(int256 sharesBefore, int256 sharesAfter) internal pure returns (int256) { // <= FOUND
295:         if (sharesBefore <= 0) {
296:             
297:             if (sharesAfter <= 0) {
298:                 return 0;
299:             
300:             } else {
301:                 return sharesAfter;
302:             }
303:         } else {
304:             
305:             if (sharesAfter <= 0) {
306:                 return (-sharesBefore);
307:             
308:             
309:             } else {
310:                 return (sharesAfter - sharesBefore);
311:             }
312:         }
313:     }

[NonCritical-5] For loop iterates on arrays not indexed

Resolution

Ensuring matching input array lengths in functions is crucial to prevent logical errors and inconsistencies in smart contracts. Mismatched array lengths can lead to incomplete operations or incorrect data handling, particularly if one array's length is assumed to reflect another's. For instance, iterating over a shorter array than intended could result in unprocessed elements from a longer array, potentially causing unintended consequences in contract execution. Validating that all input arrays have intended lengths before performing operations helps safeguard against such errors, ensuring that all elements are appropriately processed and the contract behaves as expected.

Num of instances: 5

Findings

Click to show findings

['241']

241:            for (uint256 i = 0; i < strategies.length; i++) { // <= FOUND
242:                 IStrategy[] memory singleStrategy = new IStrategy[](1);
243:                 uint256[] memory singleShare = new uint256[](1);
244:                 singleStrategy[0] = strategies[i];
245:                 singleShare[0] = shares[i]; // <= FOUND
246: 
247:                 withdrawalRoots[i] = _removeSharesAndQueueWithdrawal({
248:                     staker: staker,
249:                     operator: operator,
250:                     withdrawer: staker,
251:                     strategies: singleStrategy,
252:                     shares: singleShare
253:                 });
254:             }

['273']

273:        for (uint256 i = 0; i < queuedWithdrawalParams.length; i++) { // <= FOUND
274:             require(queuedWithdrawalParams[i].strategies.length == queuedWithdrawalParams[i].shares.length, "DelegationManager.queueWithdrawal: input length mismatch");
275:             require(queuedWithdrawalParams[i].withdrawer == msg.sender, "DelegationManager.queueWithdrawal: withdrawer must be staker");
276: 
277:             
278:             
279:             
280:             withdrawalRoots[i] = _removeSharesAndQueueWithdrawal({
281:                 staker: msg.sender,
282:                 operator: operator,
283:                 withdrawer: queuedWithdrawalParams[i].withdrawer,
284:                 strategies: queuedWithdrawalParams[i].strategies,
285:                 shares: queuedWithdrawalParams[i].shares
286:             });
287:         }

['610']

610:            for (uint256 i = 0; i < withdrawal.strategies.length; ) { // <= FOUND
611:                 require(
612:                     withdrawal.startBlock + strategyWithdrawalDelayBlocks[withdrawal.strategies[i]] <= block.number, 
613:                     "DelegationManager._completeQueuedWithdrawal: withdrawalDelayBlocks period has not yet passed for this strategy"
614:                 );
615: 
616:                 
617: 
619:                 if (withdrawal.strategies[i] == beaconChainETHStrategy) {
620:                     address staker = withdrawal.staker;
621:                     
622: 
625:                     uint256 increaseInDelegateableShares = eigenPodManager.addShares({
626:                         podOwner: staker,
627:                         shares: withdrawal.shares[i]
628:                     });
629:                     address podOwnerOperator = delegatedTo[staker];
630:                     
631:                     if (podOwnerOperator != address(0)) {
632:                         _increaseOperatorShares({
633:                             operator: podOwnerOperator,
634:                             
635:                             staker: staker,
636:                             strategy: withdrawal.strategies[i],
637:                             shares: increaseInDelegateableShares
638:                         });
639:                     }
640:                 } else {
641:                     strategyManager.addShares(msg.sender, tokens[i], withdrawal.strategies[i], withdrawal.shares[i]);
642:                     
643:                     if (currentOperator != address(0)) {
644:                         _increaseOperatorShares({
645:                             operator: currentOperator,
646:                             
647:                             staker: msg.sender,
648:                             strategy: withdrawal.strategies[i],
649:                             shares: withdrawal.shares[i]
650:                         });
651:                     }
652:                 }
653:                 unchecked { ++i; }
654:             }

['690']

690:        for (uint256 i = 0; i < strategies.length;) { // <= FOUND
691:             
692:             if (operator != address(0)) {
693:                 _decreaseOperatorShares({
694:                     operator: operator,
695:                     staker: staker,
696:                     strategy: strategies[i],
697:                     shares: shares[i]
698:                 });
699:             }
700: 
701:             
702:             if (strategies[i] == beaconChainETHStrategy) {
703:                 
704: 
709:                 eigenPodManager.removeShares(staker, shares[i]);
710:             } else {
711:                 require(
712:                     staker == withdrawer || !strategyManager.thirdPartyTransfersForbidden(strategies[i]),
713:                     "DelegationManager._removeSharesAndQueueWithdrawal: withdrawer must be same address as staker if thirdPartyTransfersForbidden are set"
714:                 );
715:                 
716:                 strategyManager.removeShares(staker, strategies[i], shares[i]);
717:             }
718: 
719:             unchecked { ++i; }
720:         }

['906']

906:            for (uint256 i = 0; i < strategyManagerStrats.length; ) { // <= FOUND
907:                 strategies[i] = strategyManagerStrats[i];
908:                 shares[i] = strategyManagerShares[i]; // <= FOUND
909: 
910:                 unchecked { ++i; }
911:             }

[NonCritical-6] It is standard for all external and public functions to be override from an interface

Resolution

This is to ensure the whole API is extracted in a interface

Num of instances: 130

Findings

Click to show findings

['41']

41:     function initialize(
42:         address initialOwner,
43:         IPauserRegistry _pauserRegistry,
44:         uint256 initialPausedStatus
45:     ) external initializer 

['61']

61:     function registerOperatorToAVS(
62:         address operator,
63:         ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature
64:     ) external onlyWhenNotPaused(PAUSED_OPERATOR_REGISTER_DEREGISTER_TO_AVS) 

['110']

110:     function deregisterOperatorFromAVS(address operator) external onlyWhenNotPaused(PAUSED_OPERATOR_REGISTER_DEREGISTER_TO_AVS) 

['126']

126:     function updateAVSMetadataURI(string calldata metadataURI) external 

['134']

134:     function cancelSalt(bytes32 salt) external 

['53']

53:     function initialize(
54:         address initOwner,
55:         IPauserRegistry _pauserRegistry,
56:         uint256 initPausedStatus,
57:         uint256 _withdrawalDelayBlocks
58:     ) external initializer 

['68']

68:     function createDelayedWithdrawal(
69:         address podOwner,
70:         address recipient
71:     ) external payable onlyEigenPod(podOwner) onlyWhenNotPaused(PAUSED_DELAYED_WITHDRAWAL_CLAIMS) 

['100']

100:     function claimDelayedWithdrawals(
101:         address recipient,
102:         uint256 maxNumberOfDelayedWithdrawalsToClaim
103:     ) external nonReentrant onlyWhenNotPaused(PAUSED_DELAYED_WITHDRAWAL_CLAIMS) 

['111']

111:     function claimDelayedWithdrawals(
112:         uint256 maxNumberOfDelayedWithdrawalsToClaim
113:     ) external nonReentrant onlyWhenNotPaused(PAUSED_DELAYED_WITHDRAWAL_CLAIMS) 

['118']

118:     function setWithdrawalDelayBlocks(uint256 newValue) external onlyOwner 

['123']

123:     function userWithdrawals(address user) external view returns (UserDelayedWithdrawals memory) 

['128']

128:     function getUserDelayedWithdrawals(address user) external view returns (DelayedWithdrawal[] memory) 

['140']

140:     function getClaimableUserDelayedWithdrawals(address user) external view returns (DelayedWithdrawal[] memory) 

['171']

171:     function userDelayedWithdrawalByIndex(
172:         address user,
173:         uint256 index
174:     ) external view returns (DelayedWithdrawal memory) 

['179']

179:     function userWithdrawalsLength(address user) external view returns (uint256) 

['184']

184:     function canClaimDelayedWithdrawal(address user, uint256 index) external view returns (bool) 

['69']

69:     function initialize(
70:         address initialOwner,
71:         IPauserRegistry _pauserRegistry,
72:         uint256 initialPausedStatus,
73:         uint256 _minWithdrawalDelayBlocks,
74:         IStrategy[] calldata _strategies,
75:         uint256[] calldata _withdrawalDelayBlocks
76:     ) external initializer 

['97']

97:     function registerAsOperator(
98:         OperatorDetails calldata registeringOperatorDetails,
99:         string calldata metadataURI
100:     ) external 

['121']

121:     function modifyOperatorDetails(OperatorDetails calldata newOperatorDetails) external 

['130']

130:     function updateOperatorMetadataURI(string calldata metadataURI) external 

['148']

148:     function delegateTo(
149:         address operator,
150:         SignatureWithExpiry memory approverSignatureAndExpiry,
151:         bytes32 approverSalt
152:     ) external 

['174']

174:     function delegateToBySignature(
175:         address staker,
176:         address operator,
177:         SignatureWithExpiry memory stakerSignatureAndExpiry,
178:         SignatureWithExpiry memory approverSignatureAndExpiry,
179:         bytes32 approverSalt
180:     ) external 

['211']

211:     function undelegate(address staker) external onlyWhenNotPaused(PAUSED_ENTER_WITHDRAWAL_QUEUE) returns (bytes32[] memory withdrawalRoots) 

['267']

267:     function queueWithdrawals(
268:         QueuedWithdrawalParams[] calldata queuedWithdrawalParams
269:     ) external onlyWhenNotPaused(PAUSED_ENTER_WITHDRAWAL_QUEUE) returns (bytes32[] memory) 

['305']

305:     function completeQueuedWithdrawal(
306:         Withdrawal calldata withdrawal,
307:         IERC20[] calldata tokens,
308:         uint256 middlewareTimesIndex,
309:         bool receiveAsTokens
310:     ) external onlyWhenNotPaused(PAUSED_EXIT_WITHDRAWAL_QUEUE) nonReentrant 

['323']

323:     function completeQueuedWithdrawals(
324:         Withdrawal[] calldata withdrawals,
325:         IERC20[][] calldata tokens,
326:         uint256[] calldata middlewareTimesIndexes,
327:         bool[] calldata receiveAsTokens
328:     ) external onlyWhenNotPaused(PAUSED_EXIT_WITHDRAWAL_QUEUE) nonReentrant 

['336']

336:     function migrateQueuedWithdrawals(IStrategyManager.DeprecatedStruct_QueuedWithdrawal[] memory withdrawalsToMigrate) external 

['384']

384:     function increaseDelegatedShares(
385:         address staker,
386:         IStrategy strategy,
387:         uint256 shares
388:     ) external onlyStrategyManagerOrEigenPodManager 

['407']

407:     function decreaseDelegatedShares(
408:         address staker,
409:         IStrategy strategy,
410:         uint256 shares
411:     ) external onlyStrategyManagerOrEigenPodManager 

['430']

430:     function setMinWithdrawalDelayBlocks(uint256 newMinWithdrawalDelayBlocks) external onlyOwner 

['441']

441:     function setStrategyWithdrawalDelayBlocks(
442:         IStrategy[] calldata strategies,
443:         uint256[] calldata withdrawalDelayBlocks
444:     ) external onlyOwner 

['838']

838:     function operatorDetails(address operator) external view returns (OperatorDetails memory) 

['844']

844:     function earningsReceiver(address operator) external view returns (address) 

['851']

851:     function delegationApprover(address operator) external view returns (address) 

['858']

858:     function stakerOptOutWindowBlocks(address operator) external view returns (uint256) 

['948']

948:     function calculateCurrentStakerDelegationDigestHash(
949:         address staker,
950:         address operator,
951:         uint256 expiry
952:     ) external view returns (bytes32) 

['156']

156:     function initialize(address _podOwner) external initializer 

['185']

185:     function verifyBalanceUpdates(
186:         uint64 oracleTimestamp,
187:         uint40[] calldata validatorIndices,
188:         BeaconChainProofs.StateRootProof calldata stateRootProof,
189:         bytes[] calldata validatorFieldsProofs,
190:         bytes32[][] calldata validatorFields
191:     ) external onlyWhenNotPaused(PAUSED_EIGENPODS_VERIFY_BALANCE_UPDATE) 

['232']

232:     function verifyAndProcessWithdrawals(
233:         uint64 oracleTimestamp,
234:         BeaconChainProofs.StateRootProof calldata stateRootProof,
235:         BeaconChainProofs.WithdrawalProof[] calldata withdrawalProofs,
236:         bytes[] calldata validatorFieldsProofs,
237:         bytes32[][] calldata validatorFields,
238:         bytes32[][] calldata withdrawalFields
239:     ) external onlyWhenNotPaused(PAUSED_EIGENPODS_VERIFY_WITHDRAWAL) 

['294']

294:     function verifyWithdrawalCredentials(
295:         uint64 oracleTimestamp,
296:         BeaconChainProofs.StateRootProof calldata stateRootProof,
297:         uint40[] calldata validatorIndices,
298:         bytes[] calldata validatorFieldsProofs,
299:         bytes32[][] calldata validatorFields
300:     )
301:         external
302:         onlyEigenPodOwner
303:         onlyWhenNotPaused(PAUSED_EIGENPODS_VERIFY_CREDENTIALS)
304:         
305:         proofIsForValidTimestamp(oracleTimestamp)
306:         
307:         hasEnabledRestaking
308:     

['348']

348:     function withdrawNonBeaconChainETHBalanceWei(
349:         address recipient,
350:         uint256 amountToWithdraw
351:     ) external onlyEigenPodOwner onlyWhenNotPaused(PAUSED_NON_PROOF_WITHDRAWALS) 

['362']

362:     function recoverTokens(
363:         IERC20[] memory tokenList,
364:         uint256[] memory amountsToWithdraw,
365:         address recipient
366:     ) external onlyEigenPodOwner onlyWhenNotPaused(PAUSED_NON_PROOF_WITHDRAWALS) 

['381']

381:     function activateRestaking()
382:         external
383:         onlyWhenNotPaused(PAUSED_EIGENPODS_VERIFY_CREDENTIALS)
384:         onlyEigenPodOwner
385:         hasNeverRestaked
386:     

['394']

394:     function withdrawBeforeRestaking() external onlyEigenPodOwner hasNeverRestaked 

['403']

403:     function stake(
404:         bytes calldata pubkey,
405:         bytes calldata signature,
406:         bytes32 depositDataRoot
407:     ) external payable onlyEigenPodManager 

['421']

421:     function withdrawRestakedBeaconChainETH(address recipient, uint256 amountWei) external onlyEigenPodManager 

['779']

779:     function validatorPubkeyHashToInfo(bytes32 validatorPubkeyHash) external view returns (ValidatorInfo memory) 

['784']

784:     function validatorPubkeyToInfo(bytes calldata validatorPubkey) external view returns (ValidatorInfo memory) 

['788']

788:     function validatorStatus(bytes32 pubkeyHash) external view returns (VALIDATOR_STATUS) 

['793']

793:     function validatorStatus(bytes calldata validatorPubkey) external view returns (VALIDATOR_STATUS) 

['57']

57:     function initialize(
58:         uint256 _maxPods,
59:         IBeaconChainOracle _beaconChainOracle,
60:         address initialOwner,
61:         IPauserRegistry _pauserRegistry,
62:         uint256 _initPausedStatus
63:     ) external initializer 

['75']

75:     function createPod() external onlyWhenNotPaused(PAUSED_NEW_EIGENPODS) returns (address) 

['90']

90:     function stake(
91:         bytes calldata pubkey, 
92:         bytes calldata signature, 
93:         bytes32 depositDataRoot
94:     ) external payable onlyWhenNotPaused(PAUSED_NEW_EIGENPODS) 

['111']

111:     function recordBeaconChainETHBalanceUpdate(
112:         address podOwner,
113:         int256 sharesDelta
114:     ) external onlyEigenPod(podOwner) nonReentrant 

['155']

155:     function removeShares(
156:         address podOwner, 
157:         uint256 shares
158:     ) external onlyDelegationManager 

['173']

173:     function addShares(
174:         address podOwner,
175:         uint256 shares
176:     ) external onlyDelegationManager returns (uint256) 

['196']

196:     function withdrawSharesAsTokens(
197:         address podOwner, 
198:         address destination, 
199:         uint256 shares
200:     ) external onlyDelegationManager 

['231']

231:     function setMaxPods(uint256 newMaxPods) external onlyUnpauser 

['240']

240:     function updateBeaconChainOracle(IBeaconChainOracle newBeaconChainOracle) external onlyOwner 

['248']

248:     function setDenebForkTimestamp(uint64 newDenebForkTimestamp) external onlyOwner 

['337']

337:     function getBlockRootAtTimestamp(uint64 timestamp) external view returns (bytes32) 

['73']

73:     function pause(uint256 newPausedStatus) external onlyPauser 

['83']

83:     function pauseAll() external onlyPauser 

['94']

94:     function unpause(uint256 newPausedStatus) external onlyUnpauser 

['116']

116:     function setPauserRegistry(IPauserRegistry newPauserRegistry) external onlyUnpauser 

['33']

33:     function setIsPauser(address newPauser, bool canPause) external onlyUnpauser 

['38']

38:     function setUnpauser(address newUnpauser) external onlyUnpauser 

['33']

33:     function initialize(
34:         address,
35:         IPauserRegistry,
36:         uint256
37:     ) external 

['39']

39:     function optIntoSlashing(address) external 

['41']

41:     function freezeOperator(address) external 

['43']

43:     function resetFrozenStatus(address[] calldata) external 

['45']

45:     function recordFirstStakeUpdate(address, uint32) external 

['47']

47:     function recordStakeUpdate(
48:         address,
49:         uint32,
50:         uint32,
51:         uint256
52:     ) external 

['54']

54:     function recordLastStakeUpdateAndRevokeSlashingAbility(address, uint32) external 

['56']

56:     function strategyManager() external view returns (IStrategyManager) 

['58']

58:     function delegation() external view returns (IDelegationManager) 

['60']

60:     function isFrozen(address) external view returns (bool) 

['62']

62:     function canSlash(address, address) external view returns (bool) 

['64']

64:     function contractCanSlashOperatorUntilBlock(
65:         address,
66:         address
67:     ) external view returns (uint32) 

['69']

69:     function latestUpdateBlock(address, address) external view returns (uint32) 

['71']

71:     function getCorrectValueForInsertAfter(address, uint32) external view returns (uint256) 

['73']

73:     function canWithdraw(
74:         address,
75:         uint32,
76:         uint256
77:     ) external returns (bool) 

['79']

79:     function operatorToMiddlewareTimes(
80:         address,
81:         uint256
82:     ) external view returns (MiddlewareTimes memory) 

['84']

84:     function middlewareTimesLength(address) external view returns (uint256) 

['86']

86:     function getMiddlewareTimesIndexStalestUpdateBlock(address, uint32) external view returns (uint32) 

['88']

88:     function getMiddlewareTimesIndexServeUntilBlock(address, uint32) external view returns (uint32) 

['90']

90:     function operatorWhitelistedContractsLinkedListSize(address) external view returns (uint256) 

['92']

92:     function operatorWhitelistedContractsLinkedListEntry(
93:         address,
94:         address
95:     ) external view returns (bool, uint256, uint256) 

['97']

97:     function whitelistedContractDetails(
98:         address,
99:         address
100:     ) external view returns (MiddlewareDetails memory) 

['252']

252:     function underlyingToShares(uint256 amountUnderlying) external view virtual returns (uint256) 

['260']

260:     function userUnderlyingView(address user) external view virtual returns (uint256) 

['268']

268:     function userUnderlying(address user) external virtual returns (uint256) 

['46']

46:     function setTVLLimits(uint256 newMaxPerDeposit, uint256 newMaxTotalDeposits) external onlyUnpauser 

['51']

51:     function getTVLLimits() external view returns (uint256, uint256) 

['82']

82:     function initialize(
83:         address initialOwner,
84:         address initialStrategyWhitelister,
85:         IPauserRegistry _pauserRegistry,
86:         uint256 initialPausedStatus
87:     ) external initializer 

['105']

105:     function depositIntoStrategy(
106:         IStrategy strategy,
107:         IERC20 token,
108:         uint256 amount
109:     ) external onlyWhenNotPaused(PAUSED_DEPOSITS) nonReentrant returns (uint256 shares) 

['134']

134:     function depositIntoStrategyWithSignature(
135:         IStrategy strategy,
136:         IERC20 token,
137:         uint256 amount,
138:         address staker,
139:         uint256 expiry,
140:         bytes memory signature
141:     ) external onlyWhenNotPaused(PAUSED_DEPOSITS) nonReentrant returns (uint256 shares) 

['170']

170:     function removeShares(
171:         address staker,
172:         IStrategy strategy,
173:         uint256 shares
174:     ) external onlyDelegationManager 

['179']

179:     function addShares(
180:         address staker,
181:         IERC20 token,
182:         IStrategy strategy,
183:         uint256 shares
184:     ) external onlyDelegationManager 

['189']

189:     function withdrawSharesAsTokens(
190:         address recipient,
191:         IStrategy strategy,
192:         uint256 shares,
193:         IERC20 token
194:     ) external onlyDelegationManager 

['200']

200:     function migrateQueuedWithdrawal(DeprecatedStruct_QueuedWithdrawal memory queuedWithdrawal) external onlyDelegationManager returns(bool, bytes32) 

['218']

218:     function setThirdPartyTransfersForbidden(
219:         IStrategy strategy,
220:         bool value
221:     ) external onlyStrategyWhitelister 

['229']

229:     function setStrategyWhitelister(address newStrategyWhitelister) external onlyOwner 

['238']

238:     function addStrategiesToDepositWhitelist(
239:         IStrategy[] calldata strategiesToWhitelist,
240:         bool[] calldata thirdPartyTransfersForbiddenValues
241:     ) external onlyStrategyWhitelister 

['264']

264:     function removeStrategiesFromDepositWhitelist(
265:         IStrategy[] calldata strategiesToRemoveFromWhitelist
266:     ) external onlyStrategyWhitelister 

['437']

437:     function getDeposits(address staker) external view returns (IStrategy[] memory, uint256[] memory) 

['451']

451:     function stakerStrategyListLength(address staker) external view returns (uint256) 

['150']

150:     function calculateOperatorAVSRegistrationDigestHash(
151:         address operator,
152:         address avs,
153:         bytes32 salt,
154:         uint256 expiry
155:     ) public view returns (bytes32) 

['171']

171:     function domainSeparator() public view returns (bytes32) 

['171']

171:     function domainSeparator() public view returns (bytes32) 

['824']

824:     function isDelegated(address staker) public view returns (bool) 

['831']

831:     function isOperator(address operator) public view returns (bool) 

['863']

863:     function getOperatorShares(
864:         address operator,
865:         IStrategy[] memory strategies
866:     ) public view returns (uint256[] memory) 

['878']

878:     function getDelegatableShares(address staker) public view returns (IStrategy[] memory, uint256[] memory) 

['926']

926:     function getWithdrawalDelay(IStrategy[] calldata strategies) public view returns (uint256) 

['938']

938:     function calculateWithdrawalRoot(Withdrawal memory withdrawal) public pure returns (bytes32) 

['966']

966:     function calculateStakerDelegationDigestHash(
967:         address staker,
968:         uint256 _stakerNonce,
969:         address operator,
970:         uint256 expiry
971:     ) public view returns (bytes32) 

['989']

989:     function calculateDelegationApprovalDigestHash(
990:         address staker,
991:         address operator,
992:         address _delegationApprover,
993:         bytes32 approverSalt,
994:         uint256 expiry
995:     ) public view returns (bytes32) 

['317']

317:     function getPod(address podOwner) public view returns (IEigenPod) 

['332']

332:     function hasPod(address podOwner) public view returns (bool) 

['350']

350:     function denebForkTimestamp() public view returns (uint64) 

['105']

105:     function paused() public view virtual returns (uint256) 

['110']

110:     function paused(uint8 index) public view virtual returns (bool) 

['70']

70:     function initialize(IERC20 _underlyingToken, IPauserRegistry _pauserRegistry) public virtual initializer 

['237']

237:     function underlyingToSharesView(uint256 amountUnderlying) public view virtual returns (uint256) 

['276']

276:     function shares(address user) public view virtual returns (uint256) 

['29']

29:     function initialize(
30:         uint256 _maxPerDeposit,
31:         uint256 _maxTotalDeposits,
32:         IERC20 _underlyingToken,
33:         IPauserRegistry _pauserRegistry
34:     ) public virtual initializer 

['171']

171:     function domainSeparator() public view returns (bytes32) 

['474']

474:     function calculateWithdrawalRoot(DeprecatedStruct_QueuedWithdrawal memory queuedWithdrawal) public pure returns (bytes32) 

['171']

171:     function domainSeparator() public view returns (bytes32) 

[NonCritical-7] Using abi.encodePacked can result in hash collision when used in hashing functions

Resolution

Consider using abi.encode as this pads data to 32 byte segments

Num of instances: 5

Findings

Click to show findings

['163']

161:         
162:         bytes32 digestHash = keccak256(
163:             abi.encodePacked("\x19\x01", domainSeparator(), structHash) // <= FOUND
164:         );

['978']

977:         
978:         bytes32 stakerDigestHash = keccak256(abi.encodePacked("\x19\x01", domainSeparator(), stakerStructHash)); // <= FOUND

['1002']

1001:         
1002:         bytes32 approverDigestHash = keccak256(abi.encodePacked("\x19\x01", domainSeparator(), approverStructHash)); // <= FOUND

['324']

321:             pod = IEigenPod(
322:                 Create2.computeAddress(
323:                     bytes32(uint256(uint160(podOwner))), 
324:                     keccak256(abi.encodePacked(beaconProxyBytecode, abi.encode(eigenPodBeacon, "")))  // <= FOUND
325:                 )
326:             );

['157']

155: 
156:         
157:         bytes32 digestHash = keccak256(abi.encodePacked("\x19\x01", domainSeparator(), structHash)); // <= FOUND

[NonCritical-8] Non constant/immutable state variables are missing a setter post deployment

Resolution

Non-constant or non-immutable state variables lacking a setter function can create inflexibility in contract operations. If there's no way to update these variables post-deployment, the contract might not adapt to changing conditions or requirements, which can be a significant drawback, especially in upgradable or long-lived contracts. To resolve this, implement setter functions guarded by appropriate access controls, like onlyOwner or similar modifiers, so that these variables can be updated as required while maintaining security. This enables smoother contract maintenance and feature upgrades.

Num of instances: 1

Findings

Click to show findings

['96']

96: address private __deprecated_stakeRegistry;

[NonCritical-9] Contracts do not use their OZ upgradable counterparts

Resolution

Using the upgradeable counterpart of the OpenZeppelin (OZ) library in Solidity is beneficial for creating contracts that can be updated in the future. OpenZeppelin's upgradeable contracts library is designed with proxy patterns in mind, which allow the logic of contracts to be upgraded while preserving the contract's state and address. This can be crucial for long-lived contracts where future requirements or improvements may not be fully known at the time of deployment. The upgradeable OZ contracts also include protection against a class of vulnerabilities related to initialization of storage variables in upgradeable contracts. Hence, it's a good idea to use them when developing contracts that may need to be upgraded in the future, as they provide a solid foundation for secure and upgradeable smart contracts.

Num of instances: 5

Findings

Click to show findings

['11']

11: contract AVSDirectory is
12:     Initializable,
13:     OwnableUpgradeable,
14:     Pausable, // <= FOUND 'Pausable,'
15:     AVSDirectoryStorage,
16:     ReentrancyGuardUpgradeable
17: 

['11']

11: contract DelayedWithdrawalRouter is
12:     Initializable,
13:     OwnableUpgradeable,
14:     ReentrancyGuardUpgradeable,
15:     Pausable, // <= FOUND 'Pausable,'
16:     IDelayedWithdrawalRouter
17: 

['21']

21: contract DelegationManager is Initializable, OwnableUpgradeable, Pausable, DelegationManagerStorage, ReentrancyGuardUpgradeable  // <= FOUND 'Pausable,'

['25']

25: contract EigenPodManager is
26:     Initializable,
27:     OwnableUpgradeable,
28:     Pausable, // <= FOUND 'Pausable,'
29:     EigenPodPausingConstants,
30:     EigenPodManagerStorage,
31:     ReentrancyGuardUpgradeable
32: 

['22']

22: contract StrategyManager is
23:     Initializable,
24:     OwnableUpgradeable,
25:     ReentrancyGuardUpgradeable,
26:     Pausable, // <= FOUND 'Pausable,'
27:     StrategyManagerStorage
28: 

[NonCritical-10] Default address(0) can be returned

Resolution

Allowing a function in Solidity to return the default address (address(0)) can be problematic as it can represent uninitialized or invalid addresses. If such an address is utilized in transfer operations or other sensitive actions, it could lead to loss of funds or unpredicted behavior. It's prudent to include checks in your functions to prevent the return of the zero address, enhancing contract security.

Num of instances: 2

Findings

Click to show findings

['844']

844:     function earningsReceiver(address operator) external view returns (address) {
845:         return _operatorDetails[operator].earningsReceiver;
846:     }

['851']

851:     function delegationApprover(address operator) external view returns (address) {
852:         return _operatorDetails[operator].delegationApprover;
853:     }

[NonCritical-11] Consider adding emergency-stop functionality

Resolution

In the event of a security breach or any unforeseen emergency, swiftly suspending all protocol operations becomes crucial. Having a mechanism in place to halt all functions collectively, instead of pausing individual contracts separately, substantially enhances the efficiency of mitigating ongoing attacks or vulnerabilities. This not only quickens the response time to potential threats but also reduces operational stress during these critical periods. Therefore, consider integrating a 'circuit breaker' or 'emergency stop' function into the smart contract system architecture. Such a feature would provide the capability to suspend the entire protocol instantly, which could prove invaluable during a time-sensitive crisis management situation.

Num of instances: 2

Findings

Click to show findings

['38']

38: contract EigenPod is IEigenPod, Initializable, ReentrancyGuardUpgradeable, EigenPodPausingConstants 

['13']

13: contract StrategyBaseTVLLimits is StrategyBase 

[NonCritical-12] Employ Explicit Casting to Bytes or Bytes32 for Enhanced Code Clarity and Meaning

Resolution

Smart contracts are complex entities, and clarity in their operations is fundamental to ensure that they function as intended. Casting a single argument instead of utilizing 'abi.encodePacked()' improves the transparency of the operation. It elucidates the intent of the code, reducing ambiguity and making it easier for auditors and developers to understand the code’s purpose. Such practices promote readability and maintainability, thus reducing the likelihood of errors and misunderstandings. Therefore, it's recommended to employ explicit casts for single arguments where possible, to increase the contract's comprehensibility and ensure a smoother review process.

Num of instances: 10

Findings

Click to show findings

['162']

150:     function calculateOperatorAVSRegistrationDigestHash(
151:         address operator,
152:         address avs,
153:         bytes32 salt,
154:         uint256 expiry
155:     ) public view returns (bytes32) {
156:         
157:         bytes32 structHash = keccak256(
158:             abi.encode(OPERATOR_AVS_REGISTRATION_TYPEHASH, operator, avs, salt, expiry)
159:         );
160:         
161:         bytes32 digestHash = keccak256(
162:             abi.encodePacked("\x19\x01", domainSeparator(), structHash) // <= FOUND
163:         );
164:         return digestHash;
165:     }

['326']

324:     function hashValidatorBLSPubkey(bytes memory validatorPubkey) internal pure returns (bytes32 pubkeyHash) {
325:         require(validatorPubkey.length == 48, "Input should be 48 bytes in length");
326:         return sha256(abi.encodePacked(validatorPubkey, bytes16(0))); // <= FOUND
327:     }

['977']

966:     function calculateStakerDelegationDigestHash(
967:         address staker,
968:         uint256 _stakerNonce,
969:         address operator,
970:         uint256 expiry
971:     ) public view returns (bytes32) {
972:         
973:         bytes32 stakerStructHash = keccak256(
974:             abi.encode(STAKER_DELEGATION_TYPEHASH, staker, operator, _stakerNonce, expiry)
975:         );
976:         
977:         bytes32 stakerDigestHash = keccak256(abi.encodePacked("\x19\x01", domainSeparator(), stakerStructHash)); // <= FOUND
978:         return stakerDigestHash;
979:     }

['1001']

989:     function calculateDelegationApprovalDigestHash(
990:         address staker,
991:         address operator,
992:         address _delegationApprover,
993:         bytes32 approverSalt,
994:         uint256 expiry
995:     ) public view returns (bytes32) {
996:         
997:         bytes32 approverStructHash = keccak256(
998:             abi.encode(DELEGATION_APPROVAL_TYPEHASH, _delegationApprover, staker, operator, approverSalt, expiry)
999:         );
1000:         
1001:         bytes32 approverDigestHash = keccak256(abi.encodePacked("\x19\x01", domainSeparator(), approverStructHash)); // <= FOUND
1002:         return approverDigestHash;
1003:     }

['748']

747:     function _podWithdrawalCredentials() internal view returns (bytes memory) {
748:         return abi.encodePacked(bytes1(uint8(1)), bytes11(0), address(this)); // <= FOUND
749:     }

['754']

752:     function _calculateValidatorPubkeyHash(bytes memory validatorPubkey) internal pure returns (bytes32){
753:         require(validatorPubkey.length == 48, "EigenPod._calculateValidatorPubkeyHash must be a 48-byte BLS public key");
754:         return sha256(abi.encodePacked(validatorPubkey, bytes16(0))); // <= FOUND
755:     }

['268']

258:     function _deployPod() internal returns (IEigenPod) {
259:         
260:         require(numPods + 1 <= maxPods, "EigenPodManager._deployPod: pod limit reached");
261:         ++numPods;
262:         
263:         IEigenPod pod = IEigenPod(
264:             Create2.deploy(
265:                 0,
266:                 bytes32(uint256(uint160(msg.sender))),
267:                 
268:                 abi.encodePacked(beaconProxyBytecode, abi.encode(eigenPodBeacon, "")) // <= FOUND
269:             )
270:         );
271:         pod.initialize(msg.sender);
272:         
273:         ownerToPod[msg.sender] = pod;
274:         emit PodDeployed(address(pod), msg.sender);
275:         return pod;
276:     }

['324']

317:     function getPod(address podOwner) public view returns (IEigenPod) {
318:         IEigenPod pod = ownerToPod[podOwner];
319:         
320:         if (address(pod) == address(0)) {
321:             pod = IEigenPod(
322:                 Create2.computeAddress(
323:                     bytes32(uint256(uint160(podOwner))), 
324:                     keccak256(abi.encodePacked(beaconProxyBytecode, abi.encode(eigenPodBeacon, "")))  // <= FOUND
325:                 )
326:             );
327:         }
328:         return pod;
329:     }

['156']

149:     function merkleizeSha256(bytes32[] memory leaves) internal pure returns (bytes32) {
150:         
151:         uint256 numNodesInLayer = leaves.length / 2;
152:         
153:         bytes32[] memory layer = new bytes32[](numNodesInLayer);
154:         
155:         for (uint256 i = 0; i < numNodesInLayer; i++) {
156:             layer[i] = sha256(abi.encodePacked(leaves[2 * i], leaves[2 * i + 1])); // <= FOUND
157:         }
158:         
159:         numNodesInLayer /= 2;
160:         
161:         while (numNodesInLayer != 0) {
162:             
163:             for (uint256 i = 0; i < numNodesInLayer; i++) {
164:                 layer[i] = sha256(abi.encodePacked(layer[2 * i], layer[2 * i + 1])); // <= FOUND
165:             }
166:             
167:             numNodesInLayer /= 2;
168:         }
169:         
170:         return layer[0];
171:     }

['155']

134:     function depositIntoStrategyWithSignature(
135:         IStrategy strategy,
136:         IERC20 token,
137:         uint256 amount,
138:         address staker,
139:         uint256 expiry,
140:         bytes memory signature
141:     ) external onlyWhenNotPaused(PAUSED_DEPOSITS) nonReentrant returns (uint256 shares) {
142:         require(
143:             !thirdPartyTransfersForbidden[strategy],
144:             "StrategyManager.depositIntoStrategyWithSignature: third transfers disabled"
145:         );
146:         require(expiry >= block.timestamp, "StrategyManager.depositIntoStrategyWithSignature: signature expired");
147:         
148:         uint256 nonce = nonces[staker];
149:         bytes32 structHash = keccak256(abi.encode(DEPOSIT_TYPEHASH, staker, strategy, token, amount, nonce, expiry));
150:         unchecked {
151:             nonces[staker] = nonce + 1;
152:         }
153: 
154:         
155:         bytes32 digestHash = keccak256(abi.encodePacked("\x19\x01", domainSeparator(), structHash)); // <= FOUND
156: 
157:         
158: 
163:         EIP1271SignatureUtils.checkSignature_EIP1271(staker, digestHash, signature);
164: 
165:         
166:         shares = _depositIntoStrategy(staker, strategy, token, amount);
167:     }

[NonCritical-13] It's not standard to end and begin a code object on the same line

Resolution

Placing the closing and opening brackets of distinct code blocks on the same line can impair code readability and clarity, leading to potential misinterpretations. To improve maintainability and reduce the chances of introducing errors, it is advisable to start each code block on a new line, making the boundaries between different code sections clearer and easier to discern unless it is an else statement.

Num of instances: 4

Findings

Click to show findings

['548']

548: 
549:             unchecked { ++i; } // <= FOUND

['605']

605:                 unchecked { ++i; } // <= FOUND

['605']

605: 
606:                 unchecked { ++i; } // <= FOUND

['403']

403:             unchecked { ++j; } // <= FOUND

[NonCritical-14] Use string.concat() on strings instead of abi.encodePacked() for clearer semantic meaning

Resolution

From Solidity 0.8.12 onwards, developers can utilize string.concat() to concatenate strings without additional padding. Opting for string.concat() over abi.encodePacked() offers clearer semantic interpretation of the code's intent, enhancing readability. This shift minimizes ambiguity, reducing the potential for misinterpretation by reviewers or future developers. Thus, for string concatenation tasks, it's recommended to transition to string.concat() for transparent, straightforward code that communicates its purpose distinctly.

Num of instances: 6

Findings

Click to show findings

['163']

161:         
162:         bytes32 digestHash = keccak256(
163:             abi.encodePacked("\x19\x01", domainSeparator(), structHash) // <= FOUND
164:         );

['978']

977:         
978:         bytes32 stakerDigestHash = keccak256(abi.encodePacked("\x19\x01", domainSeparator(), stakerStructHash)); // <= FOUND

['1002']

1001:         
1002:         bytes32 approverDigestHash = keccak256(abi.encodePacked("\x19\x01", domainSeparator(), approverStructHash)); // <= FOUND

['269']

263:         
264:         IEigenPod pod = IEigenPod(
265:             Create2.deploy(
266:                 0,
267:                 bytes32(uint256(uint160(msg.sender))),
268:                 
269:                 abi.encodePacked(beaconProxyBytecode, abi.encode(eigenPodBeacon, "")) // <= FOUND
270:             )
271:         );

['324']

321:             pod = IEigenPod(
322:                 Create2.computeAddress(
323:                     bytes32(uint256(uint160(podOwner))), 
324:                     keccak256(abi.encodePacked(beaconProxyBytecode, abi.encode(eigenPodBeacon, "")))  // <= FOUND
325:                 )
326:             );

['157']

155: 
156:         
157:         bytes32 digestHash = keccak256(abi.encodePacked("\x19\x01", domainSeparator(), structHash)); // <= FOUND

[NonCritical-15] Consider making private state variables internal to increase flexibility

Resolution

In Solidity, private state variables are strictly confined to the contract they are defined in and can't be accessed or modified by its derived contracts. While this offers strong encapsulation, it can limit contract extensibility and modification in inheritance chains. On the other hand, internal variables can be accessed and potentially overridden by child contracts, granting more flexibility in contract development and upgrades. Therefore, it's recommended to use private only when you explicitly want to prevent child contract access. Otherwise, prefer internal to maintain a balance between encapsulation and the flexibility offered by inheritance patterns in Solidity.

Num of instances: 2

Findings

Click to show findings

['28']

28: uint256 private _paused; // <= FOUND

['96']

96: address private __deprecated_stakeRegistry; // <= FOUND

[NonCritical-16] Contract can be bricked by the use of both 'Ownable' and 'Pausable' in the same contract

Resolution

In Solidity, the Ownable and Pausable contract inheritances add control mechanisms that can affect functionality. If a contract using Pausable is paused and ownership is then renounced through Ownable, the ability to resume operations is permanently lost, as only the owner could call the unpause function. To avoid this, developers should either disable the renounceOwnership function entirely or implement mechanisms to ensure unpause capability before renouncing ownership. This will help to prevent irreversible contract freezing and loss of use of functions using the whenNotPaused modifier and ensure sustained control over the contract's state.

Num of instances: 1

Findings

Click to show findings

['29']

29: contract Slasher is Initializable, OwnableUpgradeable, ISlasher, Pausable  // <= FOUND

[NonCritical-17] Floating pragma should be avoided

Num of instances: 3

Findings

Click to show findings

['2']

2: pragma solidity ^0.8.0; // <= FOUND

['8']

8: pragma solidity >=0.8.0 <0.9.0; // <= FOUND

['2']

2: pragma solidity >=0.5.0; // <= FOUND

[NonCritical-18] Empty function blocks

Resolution

Empty code blocks (i.e., {}) in a Solidity contract can be harmful as they can lead to ambiguity, misinterpretation, and unintended behavior. When developers encounter empty code blocks, it may be unclear whether the absence of code is intentional or the result of an oversight. This uncertainty can cause confusion during development, testing, and debugging, increasing the likelihood of introducing errors or vulnerabilities. Moreover, empty code blocks may give a false impression of implemented functionality or security measures, creating a misleading sense of assurance. To ensure clarity and maintainability, it is essential to avoid empty code blocks and explicitly document the intended behavior or any intentional omissions.

Num of instances: 22

Findings

Click to show findings

['33']

33:     function initialize(
34:         address,
35:         IPauserRegistry,
36:         uint256
37:     ) external {}

['39']

39:     function optIntoSlashing(address) external {}

['41']

41:     function freezeOperator(address) external {}

['43']

43:     function resetFrozenStatus(address[] calldata) external {}

['45']

45:     function recordFirstStakeUpdate(address, uint32) external {}

['47']

47:     function recordStakeUpdate(
48:         address,
49:         uint32,
50:         uint32,
51:         uint256
52:     ) external {}

['54']

54:     function recordLastStakeUpdateAndRevokeSlashingAbility(address, uint32) external {}

['56']

56:     function strategyManager() external view returns (IStrategyManager) {}

['58']

58:     function delegation() external view returns (IDelegationManager) {}

['60']

60:     function isFrozen(address) external view returns (bool) {}

['62']

62:     function canSlash(address, address) external view returns (bool) {}

['64']

64:     function contractCanSlashOperatorUntilBlock(
65:         address,
66:         address
67:     ) external view returns (uint32) {}

['69']

69:     function latestUpdateBlock(address, address) external view returns (uint32) {}

['71']

71:     function getCorrectValueForInsertAfter(address, uint32) external view returns (uint256) {}

['73']

73:     function canWithdraw(
74:         address,
75:         uint32,
76:         uint256
77:     ) external returns (bool) {}

['79']

79:     function operatorToMiddlewareTimes(
80:         address,
81:         uint256
82:     ) external view returns (MiddlewareTimes memory) {}

['84']

84:     function middlewareTimesLength(address) external view returns (uint256) {}

['86']

86:     function getMiddlewareTimesIndexStalestUpdateBlock(address, uint32) external view returns (uint32) {}

['88']

88:     function getMiddlewareTimesIndexServeUntilBlock(address, uint32) external view returns (uint32) {}

['90']

90:     function operatorWhitelistedContractsLinkedListSize(address) external view returns (uint256) {}

['92']

92:     function operatorWhitelistedContractsLinkedListEntry(
93:         address,
94:         address
95:     ) external view returns (bool, uint256, uint256) {}

['97']

97:     function whitelistedContractDetails(
98:         address,
99:         address
100:     ) external view returns (MiddlewareDetails memory) {}

[NonCritical-19] In functions which accept an address as a parameter, there should be a zero address check to prevent bugs

Resolution

In smart contract development, especially with Solidity, it's crucial to validate inputs to functions. When a function accepts an Ethereum address as a parameter, implementing a zero address check (i.e., ensuring the address is not 0x0) is a best practice to prevent potential bugs and vulnerabilities. The zero address (0x0) is a default value and generally indicates an uninitialized or invalid state. Passing the zero address to certain functions can lead to unintended behaviors, like funds getting locked permanently or transactions failing silently. By checking for and rejecting the zero address, developers can ensure that the function operates as intended and interacts only with valid Ethereum addresses. This check enhances the contract's robustness and security.

Num of instances: 81

Findings

Click to show findings

['41']

41:     function initialize(
42:         address initialOwner,
43:         IPauserRegistry _pauserRegistry,
44:         uint256 initialPausedStatus
45:     ) external initializer 

['61']

61:     function registerOperatorToAVS(
62:         address operator,
63:         ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature
64:     ) external onlyWhenNotPaused(PAUSED_OPERATOR_REGISTER_DEREGISTER_TO_AVS) 

['110']

110:     function deregisterOperatorFromAVS(address operator) external onlyWhenNotPaused(PAUSED_OPERATOR_REGISTER_DEREGISTER_TO_AVS) 

['150']

150:     function calculateOperatorAVSRegistrationDigestHash(
151:         address operator,
152:         address avs,
153:         bytes32 salt,
154:         uint256 expiry
155:     ) public view returns (bytes32) 

['53']

53:     function initialize(
54:         address initOwner,
55:         IPauserRegistry _pauserRegistry,
56:         uint256 initPausedStatus,
57:         uint256 _withdrawalDelayBlocks
58:     ) external initializer 

['100']

100:     function claimDelayedWithdrawals(
101:         address recipient,
102:         uint256 maxNumberOfDelayedWithdrawalsToClaim
103:     ) external nonReentrant onlyWhenNotPaused(PAUSED_DELAYED_WITHDRAWAL_CLAIMS) 

['123']

123:     function userWithdrawals(address user) external view returns (UserDelayedWithdrawals memory) 

['128']

128:     function getUserDelayedWithdrawals(address user) external view returns (DelayedWithdrawal[] memory) 

['140']

140:     function getClaimableUserDelayedWithdrawals(address user) external view returns (DelayedWithdrawal[] memory) 

['171']

171:     function userDelayedWithdrawalByIndex(
172:         address user,
173:         uint256 index
174:     ) external view returns (DelayedWithdrawal memory) 

['179']

179:     function userWithdrawalsLength(address user) external view returns (uint256) 

['184']

184:     function canClaimDelayedWithdrawal(address user, uint256 index) external view returns (bool) 

['190']

190:     function _claimDelayedWithdrawals(address recipient, uint256 maxNumberOfDelayedWithdrawalsToClaim) internal 

['69']

69:     function initialize(
70:         address initialOwner,
71:         IPauserRegistry _pauserRegistry,
72:         uint256 initialPausedStatus,
73:         uint256 _minWithdrawalDelayBlocks,
74:         IStrategy[] calldata _strategies,
75:         uint256[] calldata _withdrawalDelayBlocks
76:     ) external initializer 

['148']

148:     function delegateTo(
149:         address operator,
150:         SignatureWithExpiry memory approverSignatureAndExpiry,
151:         bytes32 approverSalt
152:     ) external 

['174']

174:     function delegateToBySignature(
175:         address staker,
176:         address operator,
177:         SignatureWithExpiry memory stakerSignatureAndExpiry,
178:         SignatureWithExpiry memory approverSignatureAndExpiry,
179:         bytes32 approverSalt
180:     ) external 

['384']

384:     function increaseDelegatedShares(
385:         address staker,
386:         IStrategy strategy,
387:         uint256 shares
388:     ) external onlyStrategyManagerOrEigenPodManager 

['407']

407:     function decreaseDelegatedShares(
408:         address staker,
409:         IStrategy strategy,
410:         uint256 shares
411:     ) external onlyStrategyManagerOrEigenPodManager 

['661']

661:     function _increaseOperatorShares(address operator, address staker, IStrategy strategy, uint256 shares) internal 

['667']

667:     function _decreaseOperatorShares(address operator, address staker, IStrategy strategy, uint256 shares) internal 

['749']

749:     function _withdrawSharesAsTokens(address staker, address withdrawer, IStrategy strategy, uint256 shares, IERC20 token) internal 

['838']

838:     function operatorDetails(address operator) external view returns (OperatorDetails memory) 

['844']

844:     function earningsReceiver(address operator) external view returns (address) 

['851']

851:     function delegationApprover(address operator) external view returns (address) 

['858']

858:     function stakerOptOutWindowBlocks(address operator) external view returns (uint256) 

['863']

863:     function getOperatorShares(
864:         address operator,
865:         IStrategy[] memory strategies
866:     ) public view returns (uint256[] memory) 

['878']

878:     function getDelegatableShares(address staker) public view returns (IStrategy[] memory, uint256[] memory) 

['948']

948:     function calculateCurrentStakerDelegationDigestHash(
949:         address staker,
950:         address operator,
951:         uint256 expiry
952:     ) external view returns (bytes32) 

['966']

966:     function calculateStakerDelegationDigestHash(
967:         address staker,
968:         uint256 _stakerNonce,
969:         address operator,
970:         uint256 expiry
971:     ) public view returns (bytes32) 

['989']

989:     function calculateDelegationApprovalDigestHash(
990:         address staker,
991:         address operator,
992:         address _delegationApprover,
993:         bytes32 approverSalt,
994:         uint256 expiry
995:     ) public view returns (bytes32) 

['348']

348:     function withdrawNonBeaconChainETHBalanceWei(
349:         address recipient,
350:         uint256 amountToWithdraw
351:     ) external onlyEigenPodOwner onlyWhenNotPaused(PAUSED_NON_PROOF_WITHDRAWALS) 

['362']

362:     function recoverTokens(
363:         IERC20[] memory tokenList,
364:         uint256[] memory amountsToWithdraw,
365:         address recipient
366:     ) external onlyEigenPodOwner onlyWhenNotPaused(PAUSED_NON_PROOF_WITHDRAWALS) 

['421']

421:     function withdrawRestakedBeaconChainETH(address recipient, uint256 amountWei) external onlyEigenPodManager 

['651']

651:     function _processFullWithdrawal(
652:         uint40 validatorIndex,
653:         bytes32 validatorPubkeyHash,
654:         uint64 withdrawalTimestamp,
655:         address recipient,
656:         uint64 withdrawalAmountGwei,
657:         ValidatorInfo memory validatorInfo
658:     ) internal returns (VerifiedWithdrawal memory) 

['710']

710:     function _processPartialWithdrawal(
711:         uint40 validatorIndex,
712:         uint64 withdrawalTimestamp,
713:         address recipient,
714:         uint64 partialWithdrawalAmountGwei
715:     ) internal returns (VerifiedWithdrawal memory) 

['733']

733:     function _processWithdrawalBeforeRestaking(address _podOwner) internal 

['739']

739:     function _sendETH(address recipient, uint256 amountWei) internal 

['743']

743:     function _sendETH_AsDelayedWithdrawal(address recipient, uint256 amountWei) internal 

['57']

57:     function initialize(
58:         uint256 _maxPods,
59:         IBeaconChainOracle _beaconChainOracle,
60:         address initialOwner,
61:         IPauserRegistry _pauserRegistry,
62:         uint256 _initPausedStatus
63:     ) external initializer 

['155']

155:     function removeShares(
156:         address podOwner, 
157:         uint256 shares
158:     ) external onlyDelegationManager 

['22']

22:     function checkSignature_EIP1271(address signer, bytes32 digestHash, bytes memory signature) internal view 

['33']

33:     function setIsPauser(address newPauser, bool canPause) external onlyUnpauser 

['38']

38:     function setUnpauser(address newUnpauser) external onlyUnpauser 

['33']

33:     function initialize(
34:         address,
35:         IPauserRegistry,
36:         uint256
37:     ) external 

['39']

39:     function optIntoSlashing(address) external 

['41']

41:     function freezeOperator(address) external 

['43']

43:     function resetFrozenStatus(address[] calldata) external 

['45']

45:     function recordFirstStakeUpdate(address, uint32) external 

['47']

47:     function recordStakeUpdate(
48:         address,
49:         uint32,
50:         uint32,
51:         uint256
52:     ) external 

['54']

54:     function recordLastStakeUpdateAndRevokeSlashingAbility(address, uint32) external 

['60']

60:     function isFrozen(address) external view returns (bool) 

['62']

62:     function canSlash(address, address) external view returns (bool) 

['64']

64:     function contractCanSlashOperatorUntilBlock(
65:         address,
66:         address
67:     ) external view returns (uint32) 

['69']

69:     function latestUpdateBlock(address, address) external view returns (uint32) 

['71']

71:     function getCorrectValueForInsertAfter(address, uint32) external view returns (uint256) 

['73']

73:     function canWithdraw(
74:         address,
75:         uint32,
76:         uint256
77:     ) external returns (bool) 

['79']

79:     function operatorToMiddlewareTimes(
80:         address,
81:         uint256
82:     ) external view returns (MiddlewareTimes memory) 

['84']

84:     function middlewareTimesLength(address) external view returns (uint256) 

['86']

86:     function getMiddlewareTimesIndexStalestUpdateBlock(address, uint32) external view returns (uint32) 

['88']

88:     function getMiddlewareTimesIndexServeUntilBlock(address, uint32) external view returns (uint32) 

['90']

90:     function operatorWhitelistedContractsLinkedListSize(address) external view returns (uint256) 

['92']

92:     function operatorWhitelistedContractsLinkedListEntry(
93:         address,
94:         address
95:     ) external view returns (bool, uint256, uint256) 

['97']

97:     function whitelistedContractDetails(
98:         address,
99:         address
100:     ) external view returns (MiddlewareDetails memory) 

['134']

134:     function withdraw(
135:         address recipient,
136:         IERC20 token,
137:         uint256 amountShares
138:     ) external virtual override onlyWhenNotPaused(PAUSED_WITHDRAWALS) onlyStrategyManager 

['181']

181:     function _beforeWithdrawal(address recipient, IERC20 token, uint256 amountShares) internal virtual 

['192']

192:     function _afterWithdrawal(address recipient, IERC20 token, uint256 amountToSend) internal virtual 

['260']

260:     function userUnderlyingView(address user) external view virtual returns (uint256) 

['268']

268:     function userUnderlying(address user) external virtual returns (uint256) 

['276']

276:     function shares(address user) public view virtual returns (uint256) 

['82']

82:     function initialize(
83:         address initialOwner,
84:         address initialStrategyWhitelister,
85:         IPauserRegistry _pauserRegistry,
86:         uint256 initialPausedStatus
87:     ) external initializer 

['134']

134:     function depositIntoStrategyWithSignature(
135:         IStrategy strategy,
136:         IERC20 token,
137:         uint256 amount,
138:         address staker,
139:         uint256 expiry,
140:         bytes memory signature
141:     ) external onlyWhenNotPaused(PAUSED_DEPOSITS) nonReentrant returns (uint256 shares) 

['170']

170:     function removeShares(
171:         address staker,
172:         IStrategy strategy,
173:         uint256 shares
174:     ) external onlyDelegationManager 

['179']

179:     function addShares(
180:         address staker,
181:         IERC20 token,
182:         IStrategy strategy,
183:         uint256 shares
184:     ) external onlyDelegationManager 

['189']

189:     function withdrawSharesAsTokens(
190:         address recipient,
191:         IStrategy strategy,
192:         uint256 shares,
193:         IERC20 token
194:     ) external onlyDelegationManager 

['229']

229:     function setStrategyWhitelister(address newStrategyWhitelister) external onlyOwner 

['323']

323:     function _depositIntoStrategy(
324:         address staker,
325:         IStrategy strategy,
326:         IERC20 token,
327:         uint256 amount
328:     ) internal onlyStrategiesWhitelistedForDeposit(strategy) returns (uint256 shares) 

['352']

352:     function _removeShares(
353:         address staker,
354:         IStrategy strategy,
355:         uint256 shareAmount
356:     ) internal returns (bool) 

['388']

388:     function _removeStrategyFromStakerStrategyList(
389:         address staker,
390:         IStrategy strategy
391:     ) internal 

['426']

426:     function _setStrategyWhitelister(address newStrategyWhitelister) internal 

['437']

437:     function getDeposits(address staker) external view returns (IStrategy[] memory, uint256[] memory) 

['451']

451:     function stakerStrategyListLength(address staker) external view returns (uint256) 

[NonCritical-20] Enum values should be used in place of constant array indexes

Resolution

Create a commented enum value to use in place of constant array indexes, this makes the code far easier to understand

Num of instances: 7

Findings

Click to show findings

['244']

244:                 singleStrategy[0] = strategies[i]; // <= FOUND

['245']

245:                 singleShare[0] = shares[i]; // <= FOUND

['896']

896:             strategies[0] = beaconChainETHStrategy; // <= FOUND

['897']

897:             shares[0] = uint256(podShares); // <= FOUND

['140']

140:         return computedHash[0]; // <= FOUND

['170']

170:         
171:         return layer[0]; // <= FOUND

['116']

116:         bytes32[1] memory computedHash = [leaf]; // <= FOUND

[NonCritical-21] Default bool values are manually set

Resolution

In instances where a new variable is defined, there is no need to set it to it's default value.

Num of instances: 1

Findings

Click to show findings

['15']

15: 
16:     bool private constant _PREV = false; // <= FOUND

[NonCritical-22] Default address values are manually set

Resolution

In instances where a new variable is defined, there is no need to set it to it's default value.

Num of instances: 1

Findings

Click to show findings

['234']

234:         delegatedTo[staker] = address(0); // <= FOUND

[NonCritical-23] Ownable2Step should be used in place of Ownable

Resolution

Ownable2Step further prevents risks posed by centralised privileges as there is a smaller likelihood of the owner being wrongfully changed

Num of instances: 6

Findings

Click to show findings

['11']

11: contract AVSDirectory is
12:     Initializable,
13:     OwnableUpgradeable, // <= FOUND
14:     Pausable,
15:     AVSDirectoryStorage,
16:     ReentrancyGuardUpgradeable
17: 

['11']

11: contract DelayedWithdrawalRouter is
12:     Initializable,
13:     OwnableUpgradeable, // <= FOUND
14:     ReentrancyGuardUpgradeable,
15:     Pausable,
16:     IDelayedWithdrawalRouter
17: 

['21']

21: contract DelegationManager is Initializable, OwnableUpgradeable, Pausable, DelegationManagerStorage, ReentrancyGuardUpgradeable  // <= FOUND

['25']

25: contract EigenPodManager is
26:     Initializable,
27:     OwnableUpgradeable, // <= FOUND
28:     Pausable,
29:     EigenPodPausingConstants,
30:     EigenPodManagerStorage,
31:     ReentrancyGuardUpgradeable
32: 

['29']

29: contract Slasher is Initializable, OwnableUpgradeable, ISlasher, Pausable  // <= FOUND

['22']

22: contract StrategyManager is
23:     Initializable,
24:     OwnableUpgradeable, // <= FOUND
25:     ReentrancyGuardUpgradeable,
26:     Pausable,
27:     StrategyManagerStorage
28: 

[NonCritical-24] Functions which are either private or internal should have a preceding _ in their name

Resolution

Add a preceding underscore to the function name, take care to refactor where there functions are called

Num of instances: 48

Findings

Click to show findings

['112']

112:     function verifyValidatorFields(
113:         bytes32 beaconStateRoot,
114:         bytes32[] calldata validatorFields,
115:         bytes calldata validatorFieldsProof,
116:         uint40 validatorIndex
117:     ) internal view 

['154']

154:     function verifyStateRootAgainstLatestBlockRoot(
155:         bytes32 latestBlockRoot,
156:         bytes32 beaconStateRoot,
157:         bytes calldata stateRootProof
158:     ) internal view 

['180']

180:     function verifyWithdrawal(
181:         bytes32 beaconStateRoot,
182:         bytes32[] calldata withdrawalFields,
183:         WithdrawalProof calldata withdrawalProof,
184:         uint64 denebForkTimestamp
185:     ) internal view 

['324']

324:     function hashValidatorBLSPubkey(bytes memory validatorPubkey) internal pure returns (bytes32 pubkeyHash) 

['332']

332:     function getWithdrawalTimestamp(WithdrawalProof memory withdrawalProof) internal pure returns (uint64) 

['340']

340:     function getWithdrawalEpoch(WithdrawalProof memory withdrawalProof) internal pure returns (uint64) 

['360']

360:     function getPubkeyHash(bytes32[] memory validatorFields) internal pure returns (bytes32) 

['365']

365:     function getWithdrawalCredentials(bytes32[] memory validatorFields) internal pure returns (bytes32) 

['373']

373:     function getEffectiveBalanceGwei(bytes32[] memory validatorFields) internal pure returns (uint64) 

['381']

381:     function getWithdrawableEpoch(bytes32[] memory validatorFields) internal pure returns (uint64) 

['397']

397:     function getValidatorIndex(bytes32[] memory withdrawalFields) internal pure returns (uint40) 

['405']

405:     function getWithdrawalAmountGwei(bytes32[] memory withdrawalFields) internal pure returns (uint64) 

['11']

11:     function concat(bytes memory _preBytes, bytes memory _postBytes) internal pure returns (bytes memory) 

['85']

85:     function concatStorage(bytes storage _preBytes, bytes memory _postBytes) internal 

['220']

220:     function slice(bytes memory _bytes, uint256 _start, uint256 _length) internal pure returns (bytes memory) 

['281']

281:     function toAddress(bytes memory _bytes, uint256 _start) internal pure returns (address) 

['292']

292:     function toUint8(bytes memory _bytes, uint256 _start) internal pure returns (uint8) 

['303']

303:     function toUint16(bytes memory _bytes, uint256 _start) internal pure returns (uint16) 

['314']

314:     function toUint32(bytes memory _bytes, uint256 _start) internal pure returns (uint32) 

['325']

325:     function toUint64(bytes memory _bytes, uint256 _start) internal pure returns (uint64) 

['336']

336:     function toUint96(bytes memory _bytes, uint256 _start) internal pure returns (uint96) 

['347']

347:     function toUint128(bytes memory _bytes, uint256 _start) internal pure returns (uint128) 

['358']

358:     function toUint256(bytes memory _bytes, uint256 _start) internal pure returns (uint256) 

['369']

369:     function toBytes32(bytes memory _bytes, uint256 _start) internal pure returns (bytes32) 

['380']

380:     function equal(bytes memory _preBytes, bytes memory _postBytes) internal pure returns (bool) 

['422']

422:     function equalStorage(bytes storage _preBytes, bytes memory _postBytes) internal view returns (bool) 

['22']

22:     function checkSignature_EIP1271(address signer, bytes32 digestHash, bytes memory signature) internal view 

['12']

12:     function fromLittleEndianUint64(bytes32 lenum) internal pure returns (uint64 n) 

['29']

29:     function verifyInclusionKeccak(
30:         bytes memory proof,
31:         bytes32 root,
32:         bytes32 leaf,
33:         uint256 index
34:     ) internal pure returns (bool) 

['48']

48:     function processInclusionProofKeccak(
49:         bytes memory proof,
50:         bytes32 leaf,
51:         uint256 index
52:     ) internal pure returns (bytes32) 

['88']

88:     function verifyInclusionSha256(
89:         bytes memory proof,
90:         bytes32 root,
91:         bytes32 leaf,
92:         uint256 index
93:     ) internal view returns (bool) 

['107']

107:     function processInclusionProofSha256(
108:         bytes memory proof,
109:         bytes32 leaf,
110:         uint256 index
111:     ) internal view returns (bytes32) 

['149']

149:     function merkleizeSha256(bytes32[] memory leaves) internal pure returns (bytes32) 

['28']

28:     function listExists(List storage self) internal view returns (bool) 

['43']

43:     function nodeExists(List storage self, uint256 _node) internal view returns (bool) 

['60']

60:     function sizeOf(List storage self) internal view returns (uint256) 

['69']

69:     function getHead(List storage self) internal view returns (uint256) 

['79']

79:     function getNode(List storage self, uint256 _node) internal view returns (bool, uint256, uint256) 

['94']

94:     function getAdjacent(List storage self, uint256 _node, bool _direction) internal view returns (bool, uint256) 

['109']

109:     function getNextNode(List storage self, uint256 _node) internal view returns (bool, uint256) 

['119']

119:     function getPreviousNode(List storage self, uint256 _node) internal view returns (bool, uint256) 

['130']

130:     function insertAfter(List storage self, uint256 _node, uint256 _new) internal returns (bool) 

['141']

141:     function insertBefore(List storage self, uint256 _node, uint256 _new) internal returns (bool) 

['151']

151:     function remove(List storage self, uint256 _node) internal returns (uint256) 

['170']

170:     function pushFront(List storage self, uint256 _node) internal returns (bool) 

['180']

180:     function pushBack(List storage self, uint256 _node) internal returns (bool) 

['189']

189:     function popFront(List storage self) internal returns (uint256) 

['198']

198:     function popBack(List storage self) internal returns (uint256) 

[NonCritical-25] Private and internal state variables should have a preceding _ in their name unless they are constants

Resolution

Add a preceding underscore to the state variable name, take care to refactor where there variables are read/wrote

Num of instances: 9

Findings

Click to show findings

['22']

22: uint256 internal immutable ORIGINAL_CHAIN_ID; // <= FOUND

['44']

44: uint256 internal withdrawalDelayBlocks; // <= FOUND

['18']

18: IDelayedWithdrawalRouter

['15']

15: keccak256("EIP712Domain(string name,uint256 chainId,address verifyingContract)");

['17']

17: keccak256("OperatorAVSRegistration(address operator,address avs,bytes32 salt,uint256 expiry)");

['22']

22: keccak256("StakerDelegation(address staker,address operator,uint256 nonce,uint256 expiry)");

['26']

26: keccak256("DelegationApproval(address delegationApprover,address staker,address operator,bytes32 salt,uint256 expiry)");

['35']

35: hex"608060405260405161090e38038061090e83398101604081905261002291610460565b61002e82826000610035565b505061058a565b61003e83610100565b6040516001600160a01b038416907f1cf3b03a6cf19fa2baba4df148e9dcabedea7f8a5c07840e207e5c089be95d3e90600090a260008251118061007f5750805b156100fb576100f9836001600160a01b0316635c60da1b6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156100c5573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906100e99190610520565b836102a360201b6100291760201c565b505b505050565b610113816102cf60201b6100551760201c565b6101725760405162461bcd60e51b815260206004820152602560248201527f455243313936373a206e657720626561636f6e206973206e6f74206120636f6e6044820152641d1c9858dd60da1b60648201526084015b60405180910390fd5b6101e6816001600160a01b0316635c60da1b6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156101b3573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906101d79190610520565b6102cf60201b6100551760201c565b61024b5760405162461bcd60e51b815260206004820152603060248201527f455243313936373a20626561636f6e20696d706c656d656e746174696f6e206960448201526f1cc81b9bdd08184818dbdb9d1c9858dd60821b6064820152608401610169565b806102827fa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d5060001b6102de60201b6100641760201c565b80546001600160a01b0319166001600160a01b039290921691909117905550565b60606102c883836040518060600160405280602781526020016108e7602791396102e1565b9392505050565b6001600160a01b03163b151590565b90565b6060600080856001600160a01b0316856040516102fe919061053b565b600060405180830381855af49150503d8060008114610339576040519150601f19603f3d011682016040523d82523d6000602084013e61033e565b606091505b5090925090506103508683838761035a565b9695505050505050565b606083156103c65782516103bf576001600160a01b0385163b6103bf5760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610169565b50816103d0565b6103d083836103d8565b949350505050565b8151156103e85781518083602001fd5b8060405162461bcd60e51b81526004016101699190610557565b80516001600160a01b038116811461041957600080fd5b919050565b634e487b7160e01b600052604160045260246000fd5b60005b8381101561044f578181015183820152602001610437565b838111156100f95750506000910152565b6000806040838503121561047357600080fd5b61047c83610402565b60208401519092506001600160401b038082111561049957600080fd5b818501915085601f8301126104ad57600080fd5b8151818111156104bf576104bf61041e565b604051601f8201601f19908116603f011681019083821181831017156104e7576104e761041e565b8160405282815288602084870101111561050057600080fd5b610511836020830160208801610434565b80955050505050509250929050565b60006020828403121561053257600080fd5b6102c882610402565b6000825161054d818460208701610434565b9190910192915050565b6020815260008251806020840152610576816040850160208701610434565b601f01601f19169190910160400192915050565b61034e806105996000396000f3fe60806040523661001357610011610017565b005b6100115b610027610022610067565b610100565b565b606061004e83836040518060600160405280602781526020016102f260279139610124565b9392505050565b6001600160a01b03163b151590565b90565b600061009a7fa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50546001600160a01b031690565b6001600160a01b0316635c60da1b6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156100d7573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906100fb9190610249565b905090565b3660008037600080366000845af43d6000803e80801561011f573d6000f35b3d6000fd5b6060600080856001600160a01b03168560405161014191906102a2565b600060405180830381855af49150503d806000811461017c576040519150601f19603f3d011682016040523d82523d6000602084013e610181565b606091505b50915091506101928683838761019c565b9695505050505050565b6060831561020d578251610206576001600160a01b0385163b6102065760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e747261637400000060448201526064015b60405180910390fd5b5081610217565b610217838361021f565b949350505050565b81511561022f5781518083602001fd5b8060405162461bcd60e51b81526004016101fd91906102be565b60006020828403121561025b57600080fd5b81516001600160a01b038116811461004e57600080fd5b60005b8381101561028d578181015183820152602001610275565b8381111561029c576000848401525b50505050565b600082516102b4818460208701610272565b9190910192915050565b60208152600082518060208401526102dd816040850160208701610272565b601f01601f1916919091016040019291505056fe416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a2646970667358221220d51e81d3bc5ed20a26aeb05dce7e825c503b2061aa78628027300c8d65b9d89a64736f6c634300080c0033416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564";

['22']

22: keccak256("Deposit(address staker,address strategy,address token,uint256 amount,uint256 nonce,uint256 expiry)");

[NonCritical-26] Contract lines should not be longer than 120 characters for readability

Resolution

Consider spreading these lines over multiple lines to aid in readability and the support of VIM users everywhere.

Num of instances: 297

Findings

Click to show findings

['15']

15:     /// @notice Unique address that holds the unpauser role. Capable of changing *both* the pauser and unpauser addresses. // <= FOUND

['30']

30:     /// @notice Sets new pauser - only callable by unpauser, as the unpauser is expected to be kept more secure, e.g. being a multisig with a higher threshold // <= FOUND

['37']

37:     /// @notice Sets new unpauser - only callable by unpauser, as the unpauser is expected to be kept more secure, e.g. being a multisig with a higher threshold // <= FOUND

['115']

115:         require(podOwner != address(0), "EigenPodManager.recordBeaconChainETHBalanceUpdate: podOwner cannot be zero address"); // <= FOUND

['149']

149:      * @dev This function reverts if it would result in `podOwnerShares[podOwner]` being less than zero, i.e. it is forbidden for this function to // <= FOUND

['150']

150:      * result in the `podOwner` incurring a "share deficit". This behavior prevents a Staker from queuing a withdrawal which improperly removes excessive // <= FOUND

['162']

162:         require(updatedPodOwnerShares >= 0, "EigenPodManager.removeShares: cannot result in pod owner having negative shares"); // <= FOUND

['169']

169:      * @dev Returns the number of shares added to `podOwnerShares[podOwner]` above zero, which will be less than the `shares` input // <= FOUND

['186']

186:         return uint256(_calculateChangeInDelegatableShares({sharesBefore: currentPodOwnerShares, sharesAfter: updatedPodOwnerShares})); // <= FOUND

['202']

202:         require(destination != address(0), "EigenPodManager.withdrawSharesAsTokens: destination cannot be zero address"); // <= FOUND

['204']

204:         require(shares % GWEI_TO_WEI == 0, "EigenPodManager.withdrawSharesAsTokens: shares must be a whole Gwei amount"); // <= FOUND

['215']

215:             // otherwise get rid of as much deficit as possible, and return early, since there is nothing left over to forward on // <= FOUND

['249']

249:         require(newDenebForkTimestamp != 0, "EigenPodManager.setDenebForkTimestamp: cannot set newDenebForkTimestamp to 0"); // <= FOUND

['250']

250:         require(_denebForkTimestamp == 0, "EigenPodManager.setDenebForkTimestamp: cannot set denebForkTimestamp more than once"); // <= FOUND

['291']

291:      * @notice Calculates the change in a pod owner's delegateable shares as a result of their beacon chain ETH shares changing // <= FOUND

['292']

292:      * from `sharesBefore` to `sharesAfter`. The key concept here is that negative/"deficit" shares are not delegateable. // <= FOUND

['294']

294:     function _calculateChangeInDelegatableShares(int256 sharesBefore, int256 sharesAfter) internal pure returns (int256) { // <= FOUND

['296']

296:             // if the shares started negative and stayed negative, then there cannot have been an increase in delegateable shares // <= FOUND

['299']

299:             // if the shares started negative and became positive, then the increase in delegateable shares is the ending share amount // <= FOUND

['304']

304:             // if the shares started positive and became negative, then the decrease in delegateable shares is the starting share amount // <= FOUND

['336']

336:     /// @notice Returns the Beacon block root at `timestamp`. Reverts if the Beacon block root at `timestamp` has not yet been finalized. // <= FOUND

['347']

347:      * @notice Wrapper around the `_denebForkTimestamp` storage variable that returns type(uint64).max if the storage variable is unset. // <= FOUND

['110']

110:     function deregisterOperatorFromAVS(address operator) external onlyWhenNotPaused(PAUSED_OPERATOR_REGISTER_DEREGISTER_TO_AVS) { // <= FOUND

['18']

18:  * - enabling any staker to delegate its stake to the operator of its choice (a given staker can only delegate to a single operator at a time) // <= FOUND

['19']

19:  * - enabling a staker to undelegate its assets from the operator it is delegated to (performed as part of the withdrawal process, initiated through the StrategyManager) // <= FOUND

['21']

21: contract DelegationManager is Initializable, OwnableUpgradeable, Pausable, DelegationManagerStorage, ReentrancyGuardUpgradeable { // <= FOUND

['40']

40:     // @notice Simple permission for functions that are only callable by the StrategyManager contract OR by the EigenPodManagerContract // <= FOUND

['93']

93:      * @dev Once an operator is registered, they cannot 'deregister' as an operator, and they will forever be considered "delegated to themself". // <= FOUND

['116']

116:      * @param newOperatorDetails is the updated `OperatorDetails` for the operator, to replace their current OperatorDetails`. // <= FOUND

['127']

127:      * @notice Called by an operator to emit an `OperatorMetadataURIUpdated` event indicating the information has updated. // <= FOUND

['137']

137:      * @param operator The account (`msg.sender`) is delegating its assets to for use in serving applications built on EigenLayer. // <= FOUND

['143']

143:      *          2) neither the operator nor their `delegationApprover` is the `msg.sender`, since in the event that the operator // <= FOUND

['145']

145:      * @dev In the event that `approverSignatureAndExpiry` is not checked, its content is ignored entirely; it's recommended to use an empty input // <= FOUND

['160']

160:      * @param operator The account (`staker`) is delegating its assets to for use in serving applications built on EigenLayer. // <= FOUND

['162']

162:      * @param approverSignatureAndExpiry is a parameter that will be used for verifying that the operator approves of this delegation action in the event that: // <= FOUND

['163']

163:      * @param approverSalt Is a salt used to help guarantee signature uniqueness. Each salt can only be used once by a given approver. // <= FOUND

['165']

165:      * @dev If `staker` is an EOA, then `stakerSignature` is verified to be a valid ECDSA stakerSignature from `staker`, indicating their intention for this action. // <= FOUND

['168']

168:      * @dev neither the operator nor their `delegationApprover` is the `msg.sender`, since in the event that the operator or their delegationApprover // <= FOUND

['171']

171:      * @dev In the case that `approverSignatureAndExpiry` is not checked, its content is ignored entirely; it's recommended to use an empty input // <= FOUND

['211']

211:     function undelegate(address staker) external onlyWhenNotPaused(PAUSED_ENTER_WITHDRAWAL_QUEUE) returns (bytes32[] memory withdrawalRoots) { // <= FOUND

['274']

274:             require(queuedWithdrawalParams[i].strategies.length == queuedWithdrawalParams[i].shares.length, "DelegationManager.queueWithdrawal: input length mismatch"); // <= FOUND

['275']

275:             require(queuedWithdrawalParams[i].withdrawer == msg.sender, "DelegationManager.queueWithdrawal: withdrawer must be staker"); // <= FOUND

['294']

294:      * @param tokens Array in which the i-th entry specifies the `token` input to the 'withdraw' function of the i-th Strategy in the `withdrawal.strategies` array. // <= FOUND

['295']

295:      * This input can be provided with zero length if `receiveAsTokens` is set to 'false' (since in that case, this input will be unused) // <= FOUND

['296']

296:      * @param middlewareTimesIndex is the index in the operator that the staker who triggered the withdrawal was delegated to's middleware times array // <= FOUND

['297']

297:      * @param receiveAsTokens If true, the shares specified in the withdrawal will be withdrawn from the specified strategies themselves // <= FOUND

['298']

298:      * and sent to the caller, through calls to `withdrawal.strategies[i].withdraw`. If false, then the shares in the specified strategies // <= FOUND

['301']

301:      * @dev beaconChainETHStrategy shares are non-transferrable, so if `receiveAsTokens = false` and `withdrawal.withdrawer != withdrawal.staker`, note that // <= FOUND

['302']

302:      * any beaconChainETHStrategy shares in the `withdrawal` will be _returned to the staker_, rather than transferred to the withdrawer, unlike shares in // <= FOUND

['318']

318:      * @param tokens Array of tokens for each Withdrawal. See `completeQueuedWithdrawal` for the usage of a single array. // <= FOUND

['319']

319:      * @param middlewareTimesIndexes One index to reference per Withdrawal. See `completeQueuedWithdrawal` for the usage of a single index. // <= FOUND

['320']

320:      * @param receiveAsTokens Whether or not to complete each withdrawal as tokens. See `completeQueuedWithdrawal` for the usage of a single boolean. // <= FOUND

['335']

335:     /// @dev This function is expected to be removed in the next upgrade, after all queued withdrawals have been migrated. // <= FOUND

['336']

336:     function migrateQueuedWithdrawals(IStrategyManager.DeprecatedStruct_QueuedWithdrawal[] memory withdrawalsToMigrate) external { // <= FOUND

['361']

361:                 require(!pendingWithdrawals[newRoot], "DelegationManager.migrateQueuedWithdrawals: withdrawal already exists"); // <= FOUND

['381']

381:      * @dev *If the staker is actively delegated*, then increases the `staker`'s delegated shares in `strategy` by `shares`. Otherwise does nothing. // <= FOUND

['404']

404:      * @dev *If the staker is actively delegated*, then decreases the `staker`'s delegated shares in `strategy` by `shares`. Otherwise does nothing. // <= FOUND

['466']

466:             "DelegationManager._setOperatorDetails: stakerOptOutWindowBlocks cannot be > MAX_STAKER_OPT_OUT_WINDOW_BLOCKS" // <= FOUND

['479']

479:      * @param operator The address to delegate *to* -- this address is being given power to place the `staker`'s assets at risk on services // <= FOUND

['496']

496:         // fetch the operator's `delegationApprover` address and store it in memory in case we need to use it multiple times // <= FOUND

['500']

500:          * If the `_delegationApprover` is the zero address, then the operator allows all stakers to delegate to them and this verification is skipped. // <= FOUND

['501']

501:          * If the `_delegationApprover` or the `operator` themselves is the caller, then approval is assumed and signature verification is skipped as well. // <= FOUND

['553']

553:      * @dev commented-out param (middlewareTimesIndex) is the index in the operator that the staker who triggered the withdrawal was delegated to's middleware times array // <= FOUND

['554']

554:      * This param is intended to be passed on to the Slasher contract, but is unused in the M2 release of these contracts, and is thus commented-out. // <= FOUND

['595']

595:                     "DelegationManager._completeQueuedWithdrawal: withdrawalDelayBlocks period has not yet passed for this strategy" // <= FOUND

['607']

607:         // Award shares back in StrategyManager/EigenPodManager. If withdrawer is delegated, increase the shares delegated to the operator // <= FOUND

['616']

616:                 /** When awarding podOwnerShares in EigenPodManager, we need to be sure to only give them back to the original podOwner. // <= FOUND

['623']

623:                     * The return value will be lower than the input value in the case where the staker has an existing share deficit // <= FOUND

['660']

660:     // @notice Increases `operator`s delegated shares in `strategy` by `shares` and emits an `OperatorSharesIncreased` event // <= FOUND

['666']

666:     // @notice Decreases `operator`s delegated shares in `strategy` by `shares` and emits an `OperatorSharesDecreased` event // <= FOUND

['674']

674:      * @notice Removes `shares` in `strategies` from `staker` who is currently delegated to `operator` and queues a withdrawal to the `withdrawer`. // <= FOUND

['675']

675:      * @dev If the `operator` is indeed an operator, then the operator's delegated shares in the `strategies` are also decreased appropriately. // <= FOUND

['676']

676:      * @dev If `withdrawer` is not the same address as `staker`, then thirdPartyTransfersForbidden[strategy] must be set to false in the StrategyManager. // <= FOUND

['685']

685:         require(staker != address(0), "DelegationManager._removeSharesAndQueueWithdrawal: staker cannot be zero address"); // <= FOUND

['686']

686:         require(strategies.length != 0, "DelegationManager._removeSharesAndQueueWithdrawal: strategies cannot be empty"); // <= FOUND

['713']

713:                     "DelegationManager._removeSharesAndQueueWithdrawal: withdrawer must be same address as staker if thirdPartyTransfersForbidden are set" // <= FOUND

['746']

746:      * @notice Withdraws `shares` in `strategy` to `withdrawer`. If the shares are virtual beaconChainETH shares, then a call is ultimately forwarded to the // <= FOUND

['749']

749:     function _withdrawSharesAsTokens(address staker, address withdrawer, IStrategy strategy, uint256 shares, IERC20 token) internal { // <= FOUND

['764']

764:             "DelegationManager._setMinWithdrawalDelayBlocks: _minWithdrawalDelayBlocks cannot be > MAX_WITHDRAWAL_DELAY_BLOCKS" // <= FOUND

['789']

789:                 "DelegationManager._setStrategyWithdrawalDelayBlocks: _withdrawalDelayBlocks cannot be > MAX_WITHDRAWAL_DELAY_BLOCKS" // <= FOUND

['810']

810:      * @dev By introducing a domain separator the DApp developers are guaranteed that there can be no signature collision. // <= FOUND

['924']

924:      * from all the inputted strategies. Return value is >= minWithdrawalDelayBlocks as this is the global min withdrawal delay. // <= FOUND

['963']

963:      * @param _stakerNonce The nonce of the staker. In practice we use the staker's current nonce, stored at `stakerNonce[staker]` // <= FOUND

['983']

983:      * @notice Calculates the digest hash to be signed by the operator's delegationApprove and used in the `delegateTo` and `delegateToBySignature` functions. // <= FOUND

['986']

986:      * @param _delegationApprover the operator's `delegationApprover` who will be signing the delegationHash (in general) // <= FOUND

['10']

10:     // @notice Struct that bundles together a signature and an expiration time for the signature. Used primarily for stack management. // <= FOUND

['18']

18:     // @notice Struct that bundles together a signature, a salt for uniqueness, and an expiration time for the signature. Used primarily for stack management. // <= FOUND

['36']

36:  *   to account balances in terms of gwei in the EigenPod contract and convert to wei when making calls to other contracts // <= FOUND

['48']

48:      * @notice Maximum "staleness" of a Beacon Chain state root against which `verifyBalanceUpdate` or `verifyWithdrawalCredentials` may be proven. // <= FOUND

['49']

49:      * We can't allow "stale" roots to be used for restaking as the validator may have been slashed in a more updated beacon state root.  // <= FOUND

['65']

65:     /// @notice This is the genesis time of the beacon state, to help us calculate conversions between slot and timestamp // <= FOUND

['73']

73:      * @notice The latest timestamp at which the pod owner withdrew the balance of the pod, via calling `withdrawBeforeRestaking`. // <= FOUND

['74']

74:      * @dev This variable is only updated when the `withdrawBeforeRestaking` function is called, which can only occur before `hasRestaked` is set to true for this pod. // <= FOUND

['75']

75:      * Proofs for this pod are only valid against Beacon Chain state roots corresponding to timestamps after the stored `mostRecentWithdrawalTimestamp`. // <= FOUND

['79']

79:     /// @notice the amount of execution layer ETH in this contract that is staked in EigenLayer (i.e. withdrawn from the Beacon Chain but not from EigenLayer), // <= FOUND

['82']

82:     /// @notice an indicator of whether or not the podOwner has ever "fully restaked" by successfully calling `verifyCorrectWithdrawalCredentials`. // <= FOUND

['85']

85:     /// @notice This is a mapping of validatorPubkeyHash to timestamp to whether or not they have proven a withdrawal for that timestamp // <= FOUND

['94']

94:      /// @notice This variable tracks the total amount of partial withdrawals claimed via merkle proofs prior to a switch to ZK proofs for claiming partial withdrawals // <= FOUND

['122']

122:             "EigenPod.proofIsForValidTimestamp: beacon chain proof must be for timestamp after mostRecentWithdrawalTimestamp" // <= FOUND

['128']

128:      * @notice Based on 'Pausable' code, but uses the storage of the EigenPodManager instead of this contract. This construction // <= FOUND

['130']

130:      * Modifier throws if the `indexed`th bit of `_paused` in the EigenPodManager is 1, i.e. if the `index`th pause switch is flipped. // <= FOUND

['155']

155:     /// @notice Used to initialize the pointers to addresses crucial to the pod's functionality. Called on construction by the EigenPodManager. // <= FOUND

['183']

183:      * @dev For more details on the Beacon Chain spec, see: https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#validator // <= FOUND

['193']

193:             (validatorIndices.length == validatorFieldsProofs.length) && (validatorFieldsProofs.length == validatorFields.length), // <= FOUND

['275']

275:             eigenPodManager.recordBeaconChainETHBalanceUpdate(podOwner, withdrawalSummary.sharesDeltaGwei * int256(GWEI_TO_WEI)); // <= FOUND

['284']

284:      * @notice This function verifies that the withdrawal credentials of validator(s) owned by the podOwner are pointed to // <= FOUND

['285']

285:      * this contract. It also verifies the effective balance  of the validator.  It verifies the provided proof of the ETH validator against the beacon chain state // <= FOUND

['316']

316:          * Withdrawal credential proof should not be "stale" (older than VERIFY_BALANCE_UPDATE_WINDOW_SECONDS) as we are doing a balance check here // <= FOUND

['317']

317:          * The validator container persists as the state evolves and even after the validator exits. So we can use a more "fresh" credential proof within // <= FOUND

['318']

318:          * the VERIFY_BALANCE_UPDATE_WINDOW_SECONDS window, not just the first proof where the validator container is registered in the state. // <= FOUND

['416']

416:      * @notice Called by EigenPodManager to withdrawBeaconChainETH that has been added to the EigenPod's balance due to a withdrawal from the beacon chain. // <= FOUND

['417']

417:      * @dev The podOwner must have already proved sufficient withdrawals, so that this pod's `withdrawableRestakedExecutionLayerGwei` exceeds the // <= FOUND

['422']

422:         require(amountWei % GWEI_TO_WEI == 0, "EigenPod.withdrawRestakedBeaconChainETH: amountWei must be a whole Gwei amount"); // <= FOUND

['424']

424:         require(amountGwei <= withdrawableRestakedExecutionLayerGwei, "EigenPod.withdrawRestakedBeaconChainETH: amountGwei exceeds withdrawableRestakedExecutionLayerGwei"); // <= FOUND

['438']

438:      * @param validatorFieldsProof is the bytes that prove the ETH validator's  withdrawal credentials against a beacon chain state root // <= FOUND

['464']

464:          * Deserialize the balance field from the Validator struct.  Note that this is the "effective" balance of the validator // <= FOUND

['465']

465:          * rather than the current balance.  Effective balance is generated via a hystersis function such that an effective // <= FOUND

['466']

466:          * balance, always a multiple of 1 ETH, will only lower to the next multiple of 1 ETH if the current balance is less // <= FOUND

['467']

467:          * than 0.25 ETH below their current effective balance.  For example, if the effective balance is 31ETH, it only falls to // <= FOUND

['468']

468:          * 30ETH when the true balance falls below 30.75ETH.  Thus in the worst case, the effective balance is overestimating the // <= FOUND

['576']

576:          * Without this check, there is an edge case where a user proves a past withdrawal for a validator whose funds they already withdrew, // <= FOUND

['578']

578:          * Note that this check is not made using the oracleTimestamp as in the `verifyWithdrawalCredentials` proof; instead this proof // <= FOUND

['579']

579:          * proof is made for the timestamp of the withdrawal, which may be within SLOTS_PER_HISTORICAL_ROOT slots of the oracleTimestamp. // <= FOUND

['580']

580:          * This difference in modifier usage is OK, since it is still not possible to `verifyAndProcessWithdrawal` against a slot that occurred // <= FOUND

['595']

595:             "EigenPod._verifyAndProcessWithdrawal: Validator never proven to have withdrawal credentials pointed to this contract" // <= FOUND

['753']

753:         require(validatorPubkey.length == 48, "EigenPod._calculateValidatorPubkeyHash must be a 48-byte BLS public key"); // <= FOUND

['768']

768:      * reference: https://github.com/ethereum/consensus-specs/blob/ce240ca795e257fc83059c4adfd591328c7a7f21/specs/bellatrix/beacon-chain.md#compute_timestamp_at_slot // <= FOUND

['9']

9:      * @dev Note that the input is formatted as a 'bytes32' type (i.e. 256 bits), but it is immediately truncated to a uint64 (i.e. 64 bits) // <= FOUND

['14']

14:  * @title Interface for factory that creates and manages solo staking pods that have their withdrawal credentials pointed to EigenLayer. // <= FOUND

['95']

95:     /// @notice Returns the beacon block root at `timestamp`. Reverts if the Beacon block root at `timestamp` has not yet been finalized. // <= FOUND

['115']

115:      * @dev The share amount can become negative. This is necessary to accommodate the fact that a pod owner's virtual beacon chain ETH shares can // <= FOUND

['118']

118:      * Likewise, when a withdrawal is completed, this "deficit" is decreased and the withdrawal amount is decreased; We can think of this // <= FOUND

['7']

7:  * @title A Strategy implementation inheriting from `StrategyBase` that limits the total amount of deposits it will accept. // <= FOUND

['8']

8:  * @dev Note that this implementation still converts between any amount of shares or underlying tokens in its view functions; // <= FOUND

['43']

43:      * @dev We note that there is a potential race condition between a call to this function that lowers either or both of these limits and call(s) // <= FOUND

['68']

68:      * @notice Called in the external `deposit` function, before any logic is executed. Makes sure that deposits don't exceed configured maximum. // <= FOUND

['70']

70:      * @dev Note that the `maxTotalDeposits` is purely checked against the current `_tokenBalance()`, since by this point in the deposit flow, the // <= FOUND

['73']

73:      * a) multiple simultaneous calls to `deposit` may result in some of these calls reverting due to `maxTotalDeposits` being reached. // <= FOUND

['74']

74:      * b) transferring funds directly to this Strategy (although not generally in someone's economic self interest) in order to reach `maxTotalDeposits` // <= FOUND

['76']

76:      * c) increases in the token balance of this contract through other effectsincluding token rebasingmay cause similar issues to (a) and (b). // <= FOUND

['33']

33:      * @notice Called in order to withdraw delayed withdrawals made to the `recipient` that have passed the `withdrawalDelayBlocks` period. // <= FOUND

['40']

40:      * @notice Called in order to withdraw delayed withdrawals made to the caller that have passed the `withdrawalDelayBlocks` period. // <= FOUND

['57']

57:     /// @notice Getter function for fetching the delayedWithdrawal at the `index`th entry from the `_userWithdrawals[user].delayedWithdrawals` array // <= FOUND

['63']

63:     /// @notice Convenience function for checking whether or not the delayedWithdrawal at the `index`th entry from the `_userWithdrawals[user].delayedWithdrawals` array is currently claimable // <= FOUND

['67']

67:      * @notice Delay enforced by this contract for completing any delayedWithdrawal. Measured in blocks, and adjustable by this contract's owner, // <= FOUND

['144']

144:      @notice this function returns the merkle root of a tree created from a set of leaves using sha256 as its hash function // <= FOUND

['147']

147:      @dev A pre-condition to this function is that leaves.length is a power of two.  If not, the function will merkleize the inputs incorrectly. // <= FOUND

['17']

17:  * @dev Note that some functions have their mutability restricted; developers inheriting from this contract cannot broaden // <= FOUND

['22']

22:  * To mitigate against the common "inflation attack" vector, we have chosen to use the 'virtual shares' mitigation route, // <= FOUND

['23']

23:  * similar to [OpenZeppelin](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/extensions/ERC4626.sol). // <= FOUND

['24']

24:  * We acknowledge that this mitigation has the known downside of the virtual shares causing some losses to users, which are pronounced // <= FOUND

['58']

58:     /// @notice Simply checks that the `msg.sender` is the `strategyManager`, which is an address stored immutably at construction. // <= FOUND

['64']

64:     /// @notice Since this contract is designed to be initializable, the constructor simply sets `strategyManager`, the only immutable variable. // <= FOUND

['87']

87:      * @dev This function is only callable by the strategyManager contract. It is invoked inside of the strategyManager's // <= FOUND

['89']

89:      * @dev Note that the assumption is made that `amount` of `token` has already been transferred directly to this contract // <= FOUND

['90']

90:      * (as performed in the StrategyManager's deposit functions). In particular, setting the `underlyingToken` of this contract // <= FOUND

['91']

91:      * to be a fee-on-transfer token will break the assumption that the amount this contract *received* of the token is equal to // <= FOUND

['92']

92:      * the amount that was input when the transfer was performed (i.e. the amount transferred 'out' of the depositor's balance). // <= FOUND

['107']

107:          * @notice calculation of newShares *mirrors* `underlyingToShares(amount)`, but is different since the balance of `underlyingToken` // <= FOUND

['108']

108:          * has already been increased due to the `strategyManager` transferring tokens to this strategy prior to calling this function // <= FOUND

['117']

117:         // extra check for correctness / against edge case where share rate can be massively inflated as a 'griefing' sort of attack // <= FOUND

['151']

151:          * @notice calculation of amountToSend *mirrors* `sharesToUnderlying(amountShares)`, but is different since the `totalShares` has already // <= FOUND

['167']

167:      * @notice Called in the external `deposit` function, before any logic is executed. Expected to be overridden if strategies want such logic. // <= FOUND

['176']

176:      * @notice Called in the external `withdraw` function, before any logic is executed.  Expected to be overridden if strategies want such logic. // <= FOUND

['10']

10: //BeaconBlockHeader Spec: https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#beaconblockheader // <= FOUND

['13']

13:     // constants are the number of fields and the heights of the different merkle trees used in merkleizing beacon chain containers // <= FOUND

['43']

43:     //in beacon block body https://github.com/ethereum/consensus-specs/blob/dev/specs/capella/beacon-chain.md#beaconblockbody // <= FOUND

['46']

46:     // in beacon block header https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#beaconblockheader // <= FOUND

['106']

106:      * @notice This function verifies merkle proofs of the fields of a certain validator against a beacon chain state root // <= FOUND

['205']

205:         //Note: post deneb hard fork, the number of exection payload header fields increased from 15->17, adding an extra level to the tree height // <= FOUND

['206']

206:         uint256 executionPayloadHeaderFieldTreeHeight = (getWithdrawalTimestamp(withdrawalProof) < denebForkTimestamp) ? EXECUTION_PAYLOAD_HEADER_FIELD_TREE_HEIGHT_CAPELLA : EXECUTION_PAYLOAD_HEADER_FIELD_TREE_HEIGHT_DENEB; // <= FOUND

['236']

236:          * Note: Here, the "1" in "1 + (BLOCK_ROOTS_TREE_HEIGHT)" signifies that extra step of choosing the "block_root_summary" within the individual // <= FOUND

['237']

237:          * "historical_summary". Everywhere else it signifies merkelize_with_mixin, where the length of an array is hashed with the root of the array, // <= FOUND

['299']

299:              * Note: Merkleization of the withdrawals root tree uses MerkleizeWithMixin, i.e., the length of the array is hashed with the root of // <= FOUND

['300']

300:              * the array.  Thus we shift the WITHDRAWALS_INDEX over by WITHDRAWALS_TREE_HEIGHT + 1 and not just WITHDRAWALS_TREE_HEIGHT. // <= FOUND

['8']

8:  * @title Adds pausability to a contract, with pausing & unpausing controlled by the `pauser` and `unpauser` of a PauserRegistry contract. // <= FOUND

['11']

11:  * @notice Contracts that inherit from this contract may define their own `pause` and `unpause` (and/or related) functions. // <= FOUND

['12']

12:  * These functions should be permissioned as "onlyPauser" which defers to a `PauserRegistry` for determining access control. // <= FOUND

['13']

13:  * @dev Pausability is implemented using a uint256, which allows up to 256 different single bit-flags; each bit can potentially pause different functionality. // <= FOUND

['14']

14:  * Inspiration for this was taken from the NearBridge design here https://etherscan.io/address/0x3FEFc5A4B1c02f21cBc8D3613643ba0635b9a873#code. // <= FOUND

['15']

15:  * For the `pause` and `unpause` functions we've implemented, if you pause, you can only flip (any number of) switches to on/1 (aka "paused"), and if you unpause, // <= FOUND

['20']

20:  * @dev We note as well that we have chosen to identify flags by their *bit index* as opposed to their numerical value, so, e.g. defining `DEPOSITS_PAUSED = 3` // <= FOUND

['21']

21:  * indicates specifically that if the *third bit* of `_paused` is flipped -- i.e. it is a '1' -- then deposits should be paused // <= FOUND

['24']

24:     /// @notice Address of the `PauserRegistry` contract that this contract defers to for determining access control (for pausing). // <= FOUND

['70']

70:      * @param newPausedStatus represents the new value for `_paused` to take, which means it may flip several bits at once. // <= FOUND

['74']

74:         // verify that the `newPausedStatus` does not *unflip* any bits (i.e. doesn't unpause anything, all 1 bits remain) // <= FOUND

['90']

90:      * It is permissioned to the `unpauser` address, which is expected to be a high threshold multisig or governance contract. // <= FOUND

['62']

62:     /// @notice Emitted when an ETH validator's withdrawal credentials are successfully verified to be pointed to this eigenPod // <= FOUND

['101']

101:     /// @notice the amount of execution layer ETH in this contract that is staked in EigenLayer (i.e. withdrawn from beaconchain but not EigenLayer), // <= FOUND

['107']

107:     /// @notice Used to initialize the pointers to contracts crucial to the pod's functionality, in beacon proxy construction from EigenPodManager // <= FOUND

['159']

159:      * @param withdrawalCredentialProofs is an array of proofs, where each proof proves each ETH validator's balance and withdrawal credentials // <= FOUND

['174']

174:      * @notice This function records an update (either increase or decrease) in the pod's balance in the StrategyManager.   // <= FOUND

['192']

192:      * @notice This function records full and partial withdrawals on behalf of one of the Ethereum validators for this EigenPod // <= FOUND

['194']

194:      * @param withdrawalProofs is the information needed to check the veracity of the block numbers and withdrawals being proven // <= FOUND

['75']

75:      * @notice Initializes the strategy manager contract. Sets the `pauserRegistry` (currently **not** modifiable after being set), // <= FOUND

['95']

95:      * @notice Deposits `amount` of `token` into the specified `strategy`, with the resultant shares credited to `msg.sender` // <= FOUND

['100']

100:      * @dev The `msg.sender` must have previously approved this contract to transfer at least `amount` of `token` on their behalf. // <= FOUND

['102']

102:      * WARNING: Depositing tokens that allow reentrancy (eg. ERC-777) into a strategy is not recommended.  This can lead to attack vectors // <= FOUND

['116']

116:      * Note that the assets are transferred out/from the `msg.sender`, not from the `staker`; this function is explicitly designed // <= FOUND

['123']

123:      * @param signature is a valid signature from the `staker`. either an ECDSA signature if the `staker` is an EOA, or data to forward // <= FOUND

['127']

127:      * @dev A signature is required for this function to eliminate the possibility of griefing attacks, specifically those // <= FOUND

['131']

131:      *  WARNING: Depositing tokens that allow reentrancy (eg. ERC-777) into a strategy is not recommended.  This can lead to attack vectors // <= FOUND

['169']

169:     /// @notice Used by the DelegationManager to remove a Staker's shares from a particular strategy when entering the withdrawal queue // <= FOUND

['178']

178:     /// @notice Used by the DelegationManager to award a Staker some shares that have passed through the withdrawal queue // <= FOUND

['198']

198:     /// @notice Function called by the DelegationManager as part of the process of transferring existing queued withdrawals from this contract to that contract. // <= FOUND

['200']

200:     function migrateQueuedWithdrawal(DeprecatedStruct_QueuedWithdrawal memory queuedWithdrawal) external onlyDelegationManager returns(bool, bytes32) { // <= FOUND

['234']

234:      * @notice Owner-only function that adds the provided Strategies to the 'whitelist' of strategies that stakers can deposit into // <= FOUND

['235']

235:      * @param strategiesToWhitelist Strategies that will be added to the `strategyIsWhitelistedForDeposit` mapping (if they aren't in it already) // <= FOUND

['261']

261:      * @notice Owner-only function that removes the provided Strategies from the 'whitelist' of strategies that stakers can deposit into // <= FOUND

['262']

262:      * @param strategiesToRemoveFromWhitelist Strategies that will be removed to the `strategyIsWhitelistedForDeposit` mapping (if they are in it) // <= FOUND

['285']

285:      * @notice This function adds `shares` for a given `strategy` to the `staker` and runs through the necessary update logic. // <= FOUND

['290']

290:      * @dev In particular, this function calls `delegation.increaseDelegatedShares(staker, strategy, shares)` to ensure that all // <= FOUND

['291']

291:      * delegated shares are tracked, increases the stored share amount in `stakerStrategyShares[staker][strategy]`, and adds `strategy` // <= FOUND

['315']

315:      * @notice Internal function in which `amount` of ERC20 `token` is transferred from `msg.sender` to the Strategy-type contract // <= FOUND

['350']

350:      * then the strategy is removed from stakerStrategyList[staker] and 'true' is returned. Otherwise 'false' is returned. // <= FOUND

['423']

423:      * @notice Internal function for modifying the `strategyWhitelister`. Used inside of the `setStrategyWhitelister` and `initialize` functions. // <= FOUND

['474']

474:     function calculateWithdrawalRoot(DeprecatedStruct_QueuedWithdrawal memory queuedWithdrawal) public pure returns (bytes32) { // <= FOUND

['14']

14:     // struct used to store information about the current state of an operator's obligations to middlewares they are serving // <= FOUND

['16']

16:         // The update block for the middleware whose most recent update was earliest, i.e. the 'stalest' update out of all middlewares the operator is serving // <= FOUND

['24']

24:         // the block at which the contract begins being able to finalize the operator's registration with the service via calling `recordFirstStakeUpdate` // <= FOUND

['43']

43:     /// @notice Emitted when `contractAddress` signals that it will no longer be able to slash `operator` after the `contractCanSlashOperatorUntilBlock`. // <= FOUND

['52']

52:      * @dev The `slashingContract` must have permission to slash the `slashedOperator`, i.e. `canSlash(slasherOperator, slashingContract)` must return 'true'. // <= FOUND

['56']

56:     /// @notice Emitted when `previouslySlashedAddress` is 'unfrozen', allowing them to again move deposited funds within EigenLayer. // <= FOUND

['68']

68:      * @dev Technically the operator is 'frozen' (hence the name of this function), and then subject to slashing pending a decision by a human-in-the-loop. // <= FOUND

['69']

69:      * @dev The operator must have previously given the caller (which should be a contract) the ability to slash them, through a call to `optIntoSlashing`. // <= FOUND

['80']

80:      * @notice this function is a called by middlewares during an operator's registration to make sure the operator's stake at registration // <= FOUND

['89']

89:      * @notice this function is a called by middlewares during a stake update for an operator (perhaps to free pending withdrawals) // <= FOUND

['94']

94:      * @param insertAfter the element of the operators linked list that the currently updating middleware should be inserted after // <= FOUND

['95']

95:      * @dev insertAfter should be calculated offchain before making the transaction that calls this. this is subject to race conditions, // <= FOUND

['106']

106:      * @notice this function is a called by middlewares during an operator's deregistration to make sure the operator's stake at deregistration // <= FOUND

['110']

110:      * @dev removes the middleware's slashing contract to the operator's linked list and revokes the middleware's (i.e. caller's) ability to // <= FOUND

['122']

122:      * @notice Used to determine whether `staker` is actively 'frozen'. If a staker is frozen, then they are potentially subject to // <= FOUND

['123']

123:      * slashing of their funds, and cannot cannot deposit or withdraw from the strategyManager until the slashing process is completed // <= FOUND

['143']

143:     /// @notice A search routine for finding the correct input value of `insertAfter` to `recordStakeUpdate` / `_updateMiddlewareList`. // <= FOUND

['147']

147:      * @notice Returns 'true' if `operator` can currently complete a withdrawal started at the `withdrawalStartBlock`, with `middlewareTimesIndex` used // <= FOUND

['148']

148:      * to specify the index of a `MiddlewareTimes` struct in the operator's list (i.e. an index in `operatorToMiddlewareTimes[operator]`). The specified // <= FOUND

['150']

150:      * This function will return 'false' if the operator cannot currently complete a withdrawal started at the `withdrawalStartBlock`, *or* in the event // <= FOUND

['152']

152:      * @param operator Either the operator who queued the withdrawal themselves, or if the withdrawing party is a staker who delegated to an operator, // <= FOUND

['155']

155:      * @param middlewareTimesIndex Indicates an index in `operatorToMiddlewareTimes[operator]` to consult as proof of the `operator`'s ability to withdraw // <= FOUND

['190']

190:     /// @notice Getter function for fetching a single node in the operator's linked list (`_operatorToWhitelistedContractsByUpdate[operator]`). // <= FOUND

['19']

19:      * If the `signer` contains no code -- i.e. it is not (yet, at least) a contract address, then checks using standard ECDSA logic // <= FOUND

['20']

20:      * Otherwise, passes on the signature to the signer to verify the signature and checks that it returns the `EIP1271_MAGICVALUE`. // <= FOUND

['32']

32:      * addresses of EigenPods that are pre-computed with Create2 to change, even upon upgrading this contract, changing compiler version, etc. // <= FOUND

['35']

35:         hex"608060405260405161090e38038061090e83398101604081905261002291610460565b61002e82826000610035565b505061058a565b61003e83610100565b6040516001600160a01b038416907f1cf3b03a6cf19fa2baba4df148e9dcabedea7f8a5c07840e207e5c089be95d3e90600090a260008251118061007f5750805b156100fb576100f9836001600160a01b0316635c60da1b6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156100c5573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906100e99190610520565b836102a360201b6100291760201c565b505b505050565b610113816102cf60201b6100551760201c565b6101725760405162461bcd60e51b815260206004820152602560248201527f455243313936373a206e657720626561636f6e206973206e6f74206120636f6e6044820152641d1c9858dd60da1b60648201526084015b60405180910390fd5b6101e6816001600160a01b0316635c60da1b6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156101b3573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906101d79190610520565b6102cf60201b6100551760201c565b61024b5760405162461bcd60e51b815260206004820152603060248201527f455243313936373a20626561636f6e20696d706c656d656e746174696f6e206960448201526f1cc81b9bdd08184818dbdb9d1c9858dd60821b6064820152608401610169565b806102827fa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d5060001b6102de60201b6100641760201c565b80546001600160a01b0319166001600160a01b039290921691909117905550565b60606102c883836040518060600160405280602781526020016108e7602791396102e1565b9392505050565b6001600160a01b03163b151590565b90565b6060600080856001600160a01b0316856040516102fe919061053b565b600060405180830381855af49150503d8060008114610339576040519150601f19603f3d011682016040523d82523d6000602084013e61033e565b606091505b5090925090506103508683838761035a565b9695505050505050565b606083156103c65782516103bf576001600160a01b0385163b6103bf5760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610169565b50816103d0565b6103d083836103d8565b949350505050565b8151156103e85781518083602001fd5b8060405162461bcd60e51b81526004016101699190610557565b80516001600160a01b038116811461041957600080fd5b919050565b634e487b7160e01b600052604160045260246000fd5b60005b8381101561044f578181015183820152602001610437565b838111156100f95750506000910152565b6000806040838503121561047357600080fd5b61047c83610402565b60208401519092506001600160401b038082111561049957600080fd5b818501915085601f8301126104ad57600080fd5b8151818111156104bf576104bf61041e565b604051601f8201601f19908116603f011681019083821181831017156104e7576104e761041e565b8160405282815288602084870101111561050057600080fd5b610511836020830160208801610434565b80955050505050509250929050565b60006020828403121561053257600080fd5b6102c882610402565b6000825161054d818460208701610434565b9190910192915050565b6020815260008251806020840152610576816040850160208701610434565b601f01601f19169190910160400192915050565b61034e806105996000396000f3fe60806040523661001357610011610017565b005b6100115b610027610022610067565b610100565b565b606061004e83836040518060600160405280602781526020016102f260279139610124565b9392505050565b6001600160a01b03163b151590565b90565b600061009a7fa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50546001600160a01b031690565b6001600160a01b0316635c60da1b6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156100d7573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906100fb9190610249565b905090565b3660008037600080366000845af43d6000803e80801561011f573d6000f35b3d6000fd5b6060600080856001600160a01b03168560405161014191906102a2565b600060405180830381855af49150503d806000811461017c576040519150601f19603f3d011682016040523d82523d6000602084013e610181565b606091505b50915091506101928683838761019c565b9695505050505050565b6060831561020d578251610206576001600160a01b0385163b6102065760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e747261637400000060448201526064015b60405180910390fd5b5081610217565b610217838361021f565b949350505050565b81511561022f5781518083602001fd5b8060405162461bcd60e51b81526004016101fd91906102be565b60006020828403121561025b57600080fd5b81516001600160a01b038116811461004e57600080fd5b60005b8381101561028d578181015183820152602001610275565b8381111561029c576000848401525b50505050565b600082516102b4818460208701610272565b9190910192915050565b60208152600082518060208401526102dd816040850160208701610272565b601f01601f1916919091016040019291505056fe416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a2646970667358221220d51e81d3bc5ed20a26aeb05dce7e825c503b2061aa78628027300c8d65b9d89a64736f6c634300080c0033416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564"; // <= FOUND

['26']

26:         keccak256("DelegationApproval(address delegationApprover,address staker,address operator,bytes32 salt,uint256 expiry)"); // <= FOUND

['68']

68:     /// @notice Mapping: staker => number of signed messages (used in `delegateToBySignature`) from the staker that this contract has already checked. // <= FOUND

['72']

72:      * @notice Mapping: delegationApprover => 32-byte salt => whether or not the salt has already been used by the delegationApprover. // <= FOUND

['73']

73:      * @dev Salts are used in the `delegateTo` and `delegateToBySignature` functions. Note that these functions only process the delegationApprover's // <= FOUND

['74']

74:      * signature + the provided salt if the operator being delegated to has specified a nonzero address as their `delegationApprover`. // <= FOUND

['82']

82:      * To withdraw from a strategy, max(minWithdrawalDelayBlocks, strategyWithdrawalDelayBlocks[strategy]) number of blocks must have passed.  // <= FOUND

['91']

91:     /// @dev This only increments (doesn't decrement), and is used to help ensure that otherwise identical withdrawals have unique hashes. // <= FOUND

['99']

99:      * @notice Minimum delay enforced by this contract per Strategy for completing queued withdrawals. Measured in blocks, and adjustable by this contract's owner, // <= FOUND

['9']

9:  * @notice Adapted from https://github.com/vittominacori/solidity-linked-list/blob/master/contracts/StructuredLinkedList.sol // <= FOUND

['39']

39:     /// @notice Permissioned role, which can be changed by the contract owner. Has the ability to edit the strategy whitelist // <= FOUND

['50']

50:     /// @notice *Deprecated* mapping: hash of withdrawal inputs, aka 'withdrawalRoot' => whether the withdrawal is pending // <= FOUND

['62']

62:      * Reserved space previously used by the deprecated mapping(address => uint256) beaconChainETHSharesToDecrementOnWithdrawal. // <= FOUND

['63']

63:      * This mapping tracked beaconChainETH "deficit" in cases where updates were made to shares retroactively.  However, this construction was // <= FOUND

['24']

24:          * @notice Address to verify signatures when a staker wishes to delegate to the operator, as well as controlling "forced undelegations". // <= FOUND

['26']

26:          * 1) If this address is left as address(0), then any staker will be free to delegate to the operator, i.e. no signature verification will be performed. // <= FOUND

['27']

27:          * 2) If this address is an EOA (i.e. it has no code), then we follow standard ECDSA signature verification for delegations to the operator. // <= FOUND

['28']

28:          * 3) If this address is a contract (i.e. it has code) then we forward a call to the contract and verify that it returns the correct EIP-1271 "magic value". // <= FOUND

['35']

35:          * 2) the operator completing registration for the service, via the service ultimately calling `Slasher.recordFirstStakeUpdate` // <= FOUND

['36']

36:          * @dev note that for a specific operator, this value *cannot decrease*, i.e. if the operator wishes to modify their OperatorDetails, // <= FOUND

['43']

43:      * @notice Abstract struct used in calculating an EIP712 signature for a staker to approve that they (the staker themselves) delegate to a specific operator. // <= FOUND

['44']

44:      * @dev Used in computing the `STAKER_DELEGATION_TYPEHASH` and as a reference in the computation of the stakerDigestHash in the `delegateToBySignature` function. // <= FOUND

['58']

58:      * @notice Abstract struct used in calculating an EIP712 signature for an operator's delegationApprover to approve that a specific staker delegate to the operator. // <= FOUND

['59']

59:      * @dev Used in computing the `DELEGATION_APPROVAL_TYPEHASH` and as a reference in the computation of the approverDigestHash in the `_delegate` function. // <= FOUND

['73']

73:      * Struct type used to specify an existing queued withdrawal. Rather than storing the entire struct, only a hash is stored. // <= FOUND

['74']

74:      * In functions that operate on existing queued withdrawals -- e.g. completeQueuedWithdrawal`, the data is resubmitted and the hash of the submitted // <= FOUND

['75']

75:      * data is computed by `calculateWithdrawalRoot` and checked against the stored hash in order to confirm the integrity of the submitted data. // <= FOUND

['111']

111:      * @dev Note that these strings are *never stored in storage* and are instead purely emitted in events for off-chain indexing // <= FOUND

['115']

115:     /// @notice Emitted whenever an operator's shares are increased for a given strategy. Note that shares is the delta in the operator's shares. // <= FOUND

['118']

118:     /// @notice Emitted whenever an operator's shares are decreased for a given strategy. Note that shares is the delta in the operator's shares. // <= FOUND

['146']

146:     /// @notice Emitted when the `strategyWithdrawalDelayBlocks` variable is modified from `previousValue` to `newValue`. // <= FOUND

['224']

224:      * @notice Undelegates the staker from the operator who they are delegated to. Puts the staker into the "undelegation limbo" mode of the EigenPodManager // <= FOUND

['227']

227:      * @return withdrawalRoot The root of the newly queued withdrawal, if a withdrawal was queued. Otherwise just bytes32(0). // <= FOUND

['230']

230:      * @dev Reverts if the caller is not the staker, nor the operator who the staker is delegated to, nor the operator's specified "delegationApprover" // <= FOUND

['255']

255:      * @dev middlewareTimesIndex should be calculated off chain before calling this function by finding the first index that satisfies `slasher.canWithdraw` // <= FOUND

['374']

374:     /// @notice Mapping: staker => number of signed delegation nonces (used in `delegateToBySignature`) from the staker that the contract has already checked // <= FOUND

['385']

385:      * @notice Minimum delay enforced by this contract for completing queued withdrawals. Measured in blocks, and adjustable by this contract's owner, // <= FOUND

['387']

387:      * Note that strategies each have a separate withdrawal delay, which can be greater than this value. So the minimum number of blocks that must pass // <= FOUND

['465']

465:     function migrateQueuedWithdrawals(IStrategyManager.DeprecatedStruct_QueuedWithdrawal[] memory withdrawalsToQueue) external; // <= FOUND

['32']

32:     /// @notice Mapping: user => struct storing all delayedWithdrawal info. Marked as internal with an external getter function named `userWithdrawals` // <= FOUND

['95']

95:      * @param maxNumberOfDelayedWithdrawalsToClaim Used to limit the maximum number of delayedWithdrawals to loop through claiming. // <= FOUND

['97']

97:      *      WARNING: Note that the caller of this function cannot control where the funds are sent, but they can control when the // <= FOUND

['151']

151:             // check if delayedWithdrawal can be claimed. break the loop as soon as a delayedWithdrawal cannot be claimed // <= FOUND

['206']

206:             // otherwise, the delayedWithdrawal can be claimed, in which case we increase the amountToSend and increment i // <= FOUND

['222']

222:     /// @notice internal function for changing the value of `withdrawalDelayBlocks`. Also performs sanity check and emits an event. // <= FOUND

['93']

93:     /// @notice Returns either a brief string explaining the strategy's goal & purpose, or a link to metadata that explains in more detail. // <= FOUND

['20']

20:     event OperatorAVSRegistrationStatusUpdated(address indexed operator, address indexed avs, OperatorAVSRegistrationStatus status); // <= FOUND

['145']

145:      * In functions that operate on existing queued withdrawals -- e.g. `startQueuedWithdrawalWaitingPeriod` or `completeQueuedWithdrawal`, // <= FOUND

['146']

146:      * the data is resubmitted and the hash of the submitted data is computed by `calculateWithdrawalRoot` and checked against the // <= FOUND

['158']

158:     function migrateQueuedWithdrawal(DeprecatedStruct_QueuedWithdrawal memory queuedWithdrawal) external returns (bool, bytes32); // <= FOUND

['160']

160:     function calculateWithdrawalRoot(DeprecatedStruct_QueuedWithdrawal memory queuedWithdrawal) external pure returns (bytes32); // <= FOUND

['18']

18:     /// @notice Index for flag that pauses the deposit related functions *of the EigenPods* when set. see EigenPod code for details. // <= FOUND

['20']

20:     /// @notice Index for flag that pauses the `verifyBalanceUpdate` function *of the EigenPods* when set. see EigenPod code for details. // <= FOUND

['22']

22:     /// @notice Index for flag that pauses the `verifyBeaconChainFullWithdrawal` function *of the EigenPods* when set. see EigenPod code for details. // <= FOUND

[NonCritical-27] Setters should prevent re-setting of the same value

Resolution

In Solidity, manipulating contract storage comes with significant gas costs. One can optimize gas usage by preventing unnecessary storage updates when the new value is the same as the existing one. If an existing value is the same as the new one, not reassigning it to the storage could potentially save substantial amounts of gas, notably 2900 gas for a 'Gsreset'. This saving may come at the expense of a cold storage load operation ('Gcoldsload'), which costs 2100 gas, or a warm storage access operation ('Gwarmaccess'), which costs 100 gas. Therefore, the gas efficiency of your contract can be significantly improved by adding a check that compares the new value with the current one before any storage update operation. If the values are the same, you can bypass the storage operation, thereby saving gas.

Num of instances: 5

Findings

Click to show findings

['118']

118:     function setWithdrawalDelayBlocks(uint256 newValue) external onlyOwner {
119:         _setWithdrawalDelayBlocks(newValue);
120:     }

['430']

430:     function setMinWithdrawalDelayBlocks(uint256 newMinWithdrawalDelayBlocks) external onlyOwner {
431:         _setMinWithdrawalDelayBlocks(newMinWithdrawalDelayBlocks);
432:     }

['441']

441:     function setStrategyWithdrawalDelayBlocks(
442:         IStrategy[] calldata strategies,
443:         uint256[] calldata withdrawalDelayBlocks
444:     ) external onlyOwner {
445:         _setStrategyWithdrawalDelayBlocks(strategies, withdrawalDelayBlocks);
446:     }

['229']

229:     function setStrategyWhitelister(address newStrategyWhitelister) external onlyOwner {
230:         _setStrategyWhitelister(newStrategyWhitelister);
231:     }

['240']

240:     function updateBeaconChainOracle(IBeaconChainOracle newBeaconChainOracle) external onlyOwner {
241:         _updateBeaconChainOracle(newBeaconChainOracle);
242:     }

[NonCritical-28] Specific imports should be used where possible so only used code is imported

Resolution

In many cases only some functionality is used from an import. In such cases it makes more sense to use {} to specify what to import and thus save gas whilst improving readability

Num of instances: 54

Findings

Click to show findings

['5']

5: import "@openzeppelin-upgrades/contracts/proxy/utils/Initializable.sol";

['6']

6: import "@openzeppelin-upgrades/contracts/access/OwnableUpgradeable.sol";

['7']

7: import "@openzeppelin-upgrades/contracts/security/ReentrancyGuardUpgradeable.sol";

['11']

11: import "../permissions/Pausable.sol";

['8']

8: import "../libraries/EIP1271SignatureUtils.sol";

['9']

9: import "./AVSDirectoryStorage.sol";

['4']

4: import "../interfaces/IAVSDirectory.sol";

['6']

6: import "../interfaces/IStrategyManager.sol";

['5']

5: import "../interfaces/IDelegationManager.sol";

['4']

4: import "../interfaces/ISlasher.sol";

['16']

16: import "../interfaces/IEigenPodManager.sol";

['5']

5: import "./Merkle.sol";

['13']

13: import "../libraries/Endian.sol";

['18']

18: import "../interfaces/IDelayedWithdrawalRouter.sol";

['9']

9: import "./DelegationManagerStorage.sol";

['7']

7: import "@openzeppelin-upgrades/contracts/utils/AddressUpgradeable.sol";

['8']

8: import "@openzeppelin-upgrades/contracts/utils/math/MathUpgradeable.sol";

['9']

9: import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

['11']

11: import "../libraries/BeaconChainProofs.sol";

['12']

12: import "../libraries/BytesLib.sol";

['15']

15: import "../interfaces/IETHPOSDeposit.sol";

['17']

17: import "../interfaces/IEigenPod.sol";

['19']

19: import "../interfaces/IPausable.sol";

['12']

12: import "./EigenPodPausingConstants.sol";

['4']

4: import "@openzeppelin/contracts/utils/Create2.sol";

['9']

9: import "../interfaces/IBeaconChainOracle.sol";

['13']

13: import "./EigenPodManagerStorage.sol";

['4']

4: import "@openzeppelin/contracts/proxy/beacon/IBeacon.sol";

['6']

6: import "../interfaces/IStrategy.sol";

['4']

4: import "@openzeppelin/contracts/interfaces/IERC1271.sol";

['5']

5: import "@openzeppelin/contracts/utils/Address.sol";

['6']

6: import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";

['5']

5: import "./ISignatureUtils.sol";

['4']

4: import "src/contracts/interfaces/IStrategyManager.sol";

['5']

5: import "src/contracts/interfaces/IDelegationManager.sol";

['9']

9: import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

['11']

11: import "./IStrategy.sol";

['6']

6: import "./IStrategyManager.sol";

['5']

5: import "./IEigenPodManager.sol";

['8']

8: import "./IBeaconChainOracle.sol";

['5']

5: import "./IETHPOSDeposit.sol";

['7']

7: import "./IEigenPod.sol";

['9']

9: import "./IPausable.sol";

['10']

10: import "./ISlasher.sol";

['4']

4: import "../interfaces/IPauserRegistry.sol";

['5']

5: import "./IDelegationManager.sol";

['4']

4: import "../../contracts/interfaces/IStrategyManager.sol";

['5']

5: import "../../contracts/interfaces/IStrategy.sol";

['6']

6: import "../../contracts/interfaces/IDelegationManager.sol";

['7']

7: import "../../../script/whitelist/Staker.sol";

['10']

10: import "@openzeppelin/contracts/access/Ownable.sol";

['7']

7: import "../libraries/StructuredLinkedList.sol";

['4']

4: import "./StrategyBase.sol";

['10']

10: import "./StrategyManagerStorage.sol";

[NonCritical-29] Use newer solidity versions

Resolution

Newer solidity versions have new functionality and are generally more gas efficient too (0.8.19) as such it makes sense to use them provided it is safe to do so

Num of instances: 4

Findings

Click to show findings

['2']

2: pragma solidity ^0.8.0; // <= FOUND

['8']

8: pragma solidity >=0.8.0 <0.9.0; // <= FOUND

['2']

2: pragma solidity =0.8.12; // <= FOUND

[]

2: pragma solidity >=0.5.0;

[NonCritical-30] Not all event definitions are utilizing indexed variables.

Resolution

Try to index as much as three variables in event declarations as this is more gas efficient when done on value type variables (uint, address etc) however not for bytes and string variables

Num of instances: 2

Findings

Click to show findings

['21']

21: event MaxPerDepositUpdated(uint256 previousValue, uint256 newValue); // <= FOUND

['24']

24: event MaxTotalDepositsUpdated(uint256 previousValue, uint256 newValue); // <= FOUND

[NonCritical-31] Function names should differ to make the code more readable

Resolution

In Solidity, while function overriding allows for functions with the same name to coexist, it is advisable to avoid this practice to enhance code readability and maintainability. Having multiple functions with the same name, even with different parameters or in inherited contracts, can cause confusion and increase the likelihood of errors during development, testing, and debugging. Using distinct and descriptive function names not only clarifies the purpose and behavior of each function, but also helps prevent unintended function calls or incorrect overriding. By adopting a clear and consistent naming convention, developers can create more comprehensible and maintainable smart contracts.

Num of instances: 20

Findings

Click to show findings

['100']

100:     function claimDelayedWithdrawals( // <= FOUND
101:         address recipient,
102:         uint256 maxNumberOfDelayedWithdrawalsToClaim
103:     ) external nonReentrant onlyWhenNotPaused(PAUSED_DELAYED_WITHDRAWAL_CLAIMS) 

['111']

111:     function claimDelayedWithdrawals( // <= FOUND
112:         uint256 maxNumberOfDelayedWithdrawalsToClaim
113:     ) external nonReentrant onlyWhenNotPaused(PAUSED_DELAYED_WITHDRAWAL_CLAIMS) 

['171']

171:     function domainSeparator() public view returns (bytes32)  // <= FOUND

['171']

171:     function domainSeparator() public view returns (bytes32)  // <= FOUND

['171']

171:     function domainSeparator() public view returns (bytes32)  // <= FOUND

['171']

171:     function domainSeparator() public view returns (bytes32)  // <= FOUND

['180']

180:     function _calculateDomainSeparator() internal view returns (bytes32)  // <= FOUND

['180']

180:     function _calculateDomainSeparator() internal view returns (bytes32)  // <= FOUND

['788']

788:     function validatorStatus(bytes32 pubkeyHash) external view returns (VALIDATOR_STATUS)  // <= FOUND

['793']

793:     function validatorStatus(bytes calldata validatorPubkey) external view returns (VALIDATOR_STATUS)  // <= FOUND

['403']

403:     function stake( // <= FOUND
404:         bytes calldata pubkey,
405:         bytes calldata signature,
406:         bytes32 depositDataRoot
407:     ) external payable onlyEigenPodManager 

['90']

90:     function stake( // <= FOUND
91:         bytes calldata pubkey, 
92:         bytes calldata signature, 
93:         bytes32 depositDataRoot
94:     ) external payable onlyWhenNotPaused(PAUSED_NEW_EIGENPODS) 

['79']

79:     function _beforeDeposit(IERC20 token, uint256 amount) internal virtual  // <= FOUND

['79']

79:     function _beforeDeposit(IERC20 token, uint256 amount) internal virtual override  // <= FOUND

['155']

155:     function removeShares( // <= FOUND
156:         address podOwner, 
157:         uint256 shares
158:     ) external onlyDelegationManager 

['170']

170:     function removeShares( // <= FOUND
171:         address staker,
172:         IStrategy strategy,
173:         uint256 shares
174:     ) external onlyDelegationManager 

['173']

173:     function addShares( // <= FOUND
174:         address podOwner,
175:         uint256 shares
176:     ) external onlyDelegationManager returns (uint256) 

['179']

179:     function addShares( // <= FOUND
180:         address staker,
181:         IERC20 token,
182:         IStrategy strategy,
183:         uint256 shares
184:     ) external onlyDelegationManager 

['938']

938:     function calculateWithdrawalRoot(Withdrawal memory withdrawal) public pure returns (bytes32)  // <= FOUND

['474']

474:     function calculateWithdrawalRoot(DeprecatedStruct_QueuedWithdrawal memory queuedWithdrawal) public pure returns (bytes32)  // <= FOUND

[NonCritical-32] It is convention to make the array size of __gap 50

Resolution

This is the recommendation made by OpenZeppelin and allows plenty of room for updatability

Num of instances: 4

Findings

Click to show findings

['91']

91: uint256[48] private __gap; // <= FOUND

['804']

804: uint256[44] private __gap; // <= FOUND

['45']

45: uint256[47] private __gap; // <= FOUND

['115']

115: uint256[39] private __gap; // <= FOUND

[NonCritical-33] Functions within contracts are not ordered according to the solidity style guide

Resolution

The following order should be used within contracts

constructor

receive function (if exists)

fallback function (if exists)

external

public

internal

private

Rearrange the contract functions and contructors to fit this ordering

Num of instances: 7

Findings

Click to show findings

['21']

21: contract DelegationManager is Initializable, OwnableUpgradeable, Pausable, DelegationManagerStorage, ReentrancyGuardUpgradeable  // <= FOUND

['38']

38: contract EigenPod is IEigenPod, Initializable, ReentrancyGuardUpgradeable, EigenPodPausingConstants  // <= FOUND

[]

25: contract EigenPodManager is
26:     Initializable,
27:     OwnableUpgradeable,
28:     Pausable,
29:     EigenPodPausingConstants,
30:     EigenPodManagerStorage,
31:     ReentrancyGuardUpgradeable
32: 

['23']

23: contract Pausable is IPausable  // <= FOUND

['30']

30: contract StrategyBase is Initializable, Pausable, IStrategy  // <= FOUND

['13']

13: contract StrategyBaseTVLLimits is StrategyBase  // <= FOUND

[]

22: contract StrategyManager is
23:     Initializable,
24:     OwnableUpgradeable,
25:     ReentrancyGuardUpgradeable,
26:     Pausable,
27:     StrategyManagerStorage
28: 

[NonCritical-34] Double type casts create complexity within the code

Resolution

Double type casting should be avoided in Solidity contracts to prevent unintended consequences and ensure accurate data representation. Performing multiple type casts in succession can lead to unexpected truncation, rounding errors, or loss of precision, potentially compromising the contract's functionality and reliability. Furthermore, double type casting can make the code less readable and harder to maintain, increasing the likelihood of errors and misunderstandings during development and debugging. To ensure precise and consistent data handling, developers should use appropriate data types and avoid unnecessary or excessive type casting, promoting a more robust and dependable contract execution.

Num of instances: 3

Findings

Click to show findings

['267']

263:         
264:         IEigenPod pod = IEigenPod(
265:             Create2.deploy(
266:                 0,
267:                 bytes32(uint256(uint160(msg.sender))), // <= FOUND
268:                 
269:                 abi.encodePacked(beaconProxyBytecode, abi.encode(eigenPodBeacon, ""))
270:             )
271:         );

['323']

321:             pod = IEigenPod(
322:                 Create2.computeAddress(
323:                     bytes32(uint256(uint160(podOwner))),  // <= FOUND
324:                     keccak256(abi.encodePacked(beaconProxyBytecode, abi.encode(eigenPodBeacon, ""))) 
325:                 )
326:             );

['15']

14:         
15:         n = uint64(uint256(lenum >> 192)); // <= FOUND

[NonCritical-35] Emits without msg.sender parameter

Resolution

In Solidity, when msg.sender plays a crucial role in a function's logic, it's important for transparency and auditability that any events emitted by this function include msg.sender as a parameter. This practice enhances the traceability and accountability of transactions, allowing users and external observers to easily track who initiated a particular action. Including msg.sender in event logs helps in creating a clear and verifiable record of interactions with the contract, thereby increasing user trust and facilitating easier debugging and analysis of contract behavior. It's a key aspect of writing clear, transparent, and user-friendly smart contracts.

Num of instances: 3

Findings

Click to show findings

['217']

211:     function undelegate(address staker) external onlyWhenNotPaused(PAUSED_ENTER_WITHDRAWAL_QUEUE) returns (bytes32[] memory withdrawalRoots) {
212:         require(isDelegated(staker), "DelegationManager.undelegate: staker must be delegated to undelegate");
213:         require(!isOperator(staker), "DelegationManager.undelegate: operators cannot be undelegated");
214:         require(staker != address(0), "DelegationManager.undelegate: cannot undelegate zero address");
215:         address operator = delegatedTo[staker];
216:         require(
217:             msg.sender == staker || // <= FOUND
218:                 msg.sender == operator || // <= FOUND
219:                 msg.sender == _operatorDetails[operator].delegationApprover, // <= FOUND
220:             "DelegationManager.undelegate: caller cannot undelegate staker"
221:         );
222: 
223:         
224:         
225:         (IStrategy[] memory strategies, uint256[] memory shares) = getDelegatableShares(staker);
226: 
227:         
228:         if (msg.sender != staker) { // <= FOUND
229:             emit StakerForceUndelegated(staker, operator); // <= FOUND
230:         }
231: 
232:         
233:         emit StakerUndelegated(staker, operator); // <= FOUND
234:         delegatedTo[staker] = address(0);
235: 
236:         
237:         if (strategies.length == 0) {
238:             withdrawalRoots = new bytes32[](0);
239:         } else {
240:             withdrawalRoots = new bytes32[](strategies.length);
241:             for (uint256 i = 0; i < strategies.length; i++) {
242:                 IStrategy[] memory singleStrategy = new IStrategy[](1);
243:                 uint256[] memory singleShare = new uint256[](1);
244:                 singleStrategy[0] = strategies[i];
245:                 singleShare[0] = shares[i];
246: 
247:                 withdrawalRoots[i] = _removeSharesAndQueueWithdrawal({
248:                     staker: staker,
249:                     operator: operator,
250:                     withdrawer: staker,
251:                     strategies: singleStrategy,
252:                     shares: singleShare
253:                 });
254:             }
255:         }
256: 
257:         return withdrawalRoots;
258:     }

['503']

487:     function _delegate(
488:         address staker,
489:         address operator,
490:         SignatureWithExpiry memory approverSignatureAndExpiry,
491:         bytes32 approverSalt
492:     ) internal onlyWhenNotPaused(PAUSED_NEW_DELEGATION) {
493:         require(!isDelegated(staker), "DelegationManager._delegate: staker is already actively delegated");
494:         require(isOperator(operator), "DelegationManager._delegate: operator is not registered in EigenLayer");
495: 
496:         
497:         address _delegationApprover = _operatorDetails[operator].delegationApprover;
498:         
499: 
503:         if (_delegationApprover != address(0) && msg.sender != _delegationApprover && msg.sender != operator) { // <= FOUND
504:             
505:             require(
506:                 approverSignatureAndExpiry.expiry >= block.timestamp,
507:                 "DelegationManager._delegate: approver signature expired"
508:             );
509:             
510:             require(
511:                 !delegationApproverSaltIsSpent[_delegationApprover][approverSalt],
512:                 "DelegationManager._delegate: approverSalt already spent"
513:             );
514:             delegationApproverSaltIsSpent[_delegationApprover][approverSalt] = true;
515: 
516:             
517:             bytes32 approverDigestHash = calculateDelegationApprovalDigestHash(
518:                 staker,
519:                 operator,
520:                 _delegationApprover,
521:                 approverSalt,
522:                 approverSignatureAndExpiry.expiry
523:             );
524: 
525:             
526:             EIP1271SignatureUtils.checkSignature_EIP1271(
527:                 _delegationApprover,
528:                 approverDigestHash,
529:                 approverSignatureAndExpiry.signature
530:             );
531:         }
532: 
533:         
534:         delegatedTo[staker] = operator;
535:         emit StakerDelegated(staker, operator); // <= FOUND
536: 
537:         (IStrategy[] memory strategies, uint256[] memory shares)
538:             = getDelegatableShares(staker);
539: 
540:         for (uint256 i = 0; i < strategies.length;) {
541:             _increaseOperatorShares({
542:                 operator: operator,
543:                 staker: staker,
544:                 strategy: strategies[i],
545:                 shares: shares[i]
546:             });
547: 
548:             unchecked { ++i; }
549:         }
550:     }

['575']

556:     function _completeQueuedWithdrawal(
557:         Withdrawal calldata withdrawal,
558:         IERC20[] calldata tokens,
559:         uint256 ,
560:         bool receiveAsTokens
561:     ) internal {
562:         bytes32 withdrawalRoot = calculateWithdrawalRoot(withdrawal);
563: 
564:         require(
565:             pendingWithdrawals[withdrawalRoot], 
566:             "DelegationManager._completeQueuedWithdrawal: action is not in queue"
567:         );
568: 
569:         require(
570:             withdrawal.startBlock + minWithdrawalDelayBlocks <= block.number, 
571:             "DelegationManager._completeQueuedWithdrawal: minWithdrawalDelayBlocks period has not yet passed"
572:         );
573: 
574:         require(
575:             msg.sender == withdrawal.withdrawer,  // <= FOUND
576:             "DelegationManager._completeQueuedWithdrawal: only withdrawer can complete action"
577:         );
578: 
579:         if (receiveAsTokens) {
580:             require(
581:                 tokens.length == withdrawal.strategies.length, 
582:                 "DelegationManager._completeQueuedWithdrawal: input length mismatch"
583:             );
584:         }
585: 
586:         
587:         delete pendingWithdrawals[withdrawalRoot];
588: 
589:         
590:         
591:         if (receiveAsTokens) {
592:             for (uint256 i = 0; i < withdrawal.strategies.length; ) {
593:                 require(
594:                     withdrawal.startBlock + strategyWithdrawalDelayBlocks[withdrawal.strategies[i]] <= block.number,
595:                     "DelegationManager._completeQueuedWithdrawal: withdrawalDelayBlocks period has not yet passed for this strategy"
596:                 );
597: 
598:                 _withdrawSharesAsTokens({
599:                     staker: withdrawal.staker,
600:                     withdrawer: msg.sender, // <= FOUND
601:                     strategy: withdrawal.strategies[i],
602:                     shares: withdrawal.shares[i],
603:                     token: tokens[i]
604:                 });
605:                 unchecked { ++i; }
606:             }
607:         
608:         } else {
609:             address currentOperator = delegatedTo[msg.sender]; // <= FOUND
610:             for (uint256 i = 0; i < withdrawal.strategies.length; ) {
611:                 require(
612:                     withdrawal.startBlock + strategyWithdrawalDelayBlocks[withdrawal.strategies[i]] <= block.number, 
613:                     "DelegationManager._completeQueuedWithdrawal: withdrawalDelayBlocks period has not yet passed for this strategy"
614:                 );
615: 
616:                 
617: 
619:                 if (withdrawal.strategies[i] == beaconChainETHStrategy) {
620:                     address staker = withdrawal.staker;
621:                     
622: 
625:                     uint256 increaseInDelegateableShares = eigenPodManager.addShares({
626:                         podOwner: staker,
627:                         shares: withdrawal.shares[i]
628:                     });
629:                     address podOwnerOperator = delegatedTo[staker];
630:                     
631:                     if (podOwnerOperator != address(0)) {
632:                         _increaseOperatorShares({
633:                             operator: podOwnerOperator,
634:                             
635:                             staker: staker,
636:                             strategy: withdrawal.strategies[i],
637:                             shares: increaseInDelegateableShares
638:                         });
639:                     }
640:                 } else {
641:                     strategyManager.addShares(msg.sender, tokens[i], withdrawal.strategies[i], withdrawal.shares[i]); // <= FOUND
642:                     
643:                     if (currentOperator != address(0)) {
644:                         _increaseOperatorShares({
645:                             operator: currentOperator,
646:                             
647:                             staker: msg.sender, // <= FOUND
648:                             strategy: withdrawal.strategies[i],
649:                             shares: withdrawal.shares[i]
650:                         });
651:                     }
652:                 }
653:                 unchecked { ++i; }
654:             }
655:         }
656: 
657:         emit WithdrawalCompleted(withdrawalRoot); // <= FOUND
658:     }

[NonCritical-36] Functions with array parameters should have length checks in place

Resolution

Functions in Solidity that accept array parameters should incorporate length checks as a security measure. This is to prevent potential overflow errors, unwanted gas consumption, and manipulation attempts. Without length checks, an attacker could pass excessively large arrays as input, causing excessive computation and potentially causing the function to exceed the block gas limit, leading to a denial-of-service. Additionally, unexpected array sizes could lead to logic errors within the function. As a resolution, always validate array length at the start of functions handling array inputs, ensuring it aligns with the expectations of the function logic. This makes the code more robust and predictable.

Num of instances: 13

Findings

Click to show findings

['360']

360:     function getPubkeyHash(bytes32[] memory validatorFields) internal pure returns (bytes32) { // <= FOUND
361:         return 
362:             validatorFields[VALIDATOR_PUBKEY_INDEX];
363:     }

['365']

365:     function getWithdrawalCredentials(bytes32[] memory validatorFields) internal pure returns (bytes32) { // <= FOUND
366:         return
367:             validatorFields[VALIDATOR_WITHDRAWAL_CREDENTIALS_INDEX];
368:     }

['373']

373:     function getEffectiveBalanceGwei(bytes32[] memory validatorFields) internal pure returns (uint64) { // <= FOUND
374:         return 
375:             Endian.fromLittleEndianUint64(validatorFields[VALIDATOR_BALANCE_INDEX]);
376:     }

['381']

381:     function getWithdrawableEpoch(bytes32[] memory validatorFields) internal pure returns (uint64) { // <= FOUND
382:         return 
383:             Endian.fromLittleEndianUint64(validatorFields[VALIDATOR_WITHDRAWABLE_EPOCH_INDEX]);
384:     }

['397']

397:     function getValidatorIndex(bytes32[] memory withdrawalFields) internal pure returns (uint40) { // <= FOUND
398:         return 
399:             uint40(Endian.fromLittleEndianUint64(withdrawalFields[WITHDRAWAL_VALIDATOR_INDEX_INDEX]));
400:     }

['405']

405:     function getWithdrawalAmountGwei(bytes32[] memory withdrawalFields) internal pure returns (uint64) { // <= FOUND
406:         return
407:             Endian.fromLittleEndianUint64(withdrawalFields[WITHDRAWAL_VALIDATOR_AMOUNT_INDEX]);
408:     }

['69']

69:     function initialize(
70:         address initialOwner,
71:         IPauserRegistry _pauserRegistry,
72:         uint256 initialPausedStatus,
73:         uint256 _minWithdrawalDelayBlocks,
74:         IStrategy[] calldata _strategies, // <= FOUND
75:         uint256[] calldata _withdrawalDelayBlocks // <= FOUND
76:     ) external initializer {
77:         _initializePauser(_pauserRegistry, initialPausedStatus);
78:         _DOMAIN_SEPARATOR = _calculateDomainSeparator();
79:         _transferOwnership(initialOwner);
80:         _setMinWithdrawalDelayBlocks(_minWithdrawalDelayBlocks);
81:         _setStrategyWithdrawalDelayBlocks(_strategies, _withdrawalDelayBlocks);
82:     }

['305']

305:     function completeQueuedWithdrawal(
306:         Withdrawal calldata withdrawal,
307:         IERC20[] calldata tokens, // <= FOUND
308:         uint256 middlewareTimesIndex,
309:         bool receiveAsTokens
310:     ) external onlyWhenNotPaused(PAUSED_EXIT_WITHDRAWAL_QUEUE) nonReentrant {
311:         _completeQueuedWithdrawal(withdrawal, tokens, middlewareTimesIndex, receiveAsTokens);
312:     }

['441']

441:     function setStrategyWithdrawalDelayBlocks(
442:         IStrategy[] calldata strategies, // <= FOUND
443:         uint256[] calldata withdrawalDelayBlocks // <= FOUND
444:     ) external onlyOwner {
445:         _setStrategyWithdrawalDelayBlocks(strategies, withdrawalDelayBlocks);
446:     }

['441']

441:     function _verifyWithdrawalCredentials(
442:         uint64 oracleTimestamp,
443:         bytes32 beaconStateRoot,
444:         uint40 validatorIndex,
445:         bytes calldata validatorFieldsProof,
446:         bytes32[] calldata validatorFields // <= FOUND
447:     ) internal returns (uint256) {
448:         bytes32 validatorPubkeyHash = validatorFields.getPubkeyHash();
449:         ValidatorInfo memory validatorInfo = _validatorPubkeyHashToInfo[validatorPubkeyHash];
450: 
451:         
452:         require(
453:             validatorInfo.status == VALIDATOR_STATUS.INACTIVE,
454:             "EigenPod.verifyCorrectWithdrawalCredentials: Validator must be inactive to prove withdrawal credentials"
455:         );
456: 
457:         
458:         require(
459:             validatorFields.getWithdrawalCredentials() == bytes32(_podWithdrawalCredentials()),
460:             "EigenPod.verifyCorrectWithdrawalCredentials: Proof is not for this EigenPod"
461:         );
462: 
463:         
464: 
471:         uint64 validatorEffectiveBalanceGwei = validatorFields.getEffectiveBalanceGwei();
472: 
473:         
474:         BeaconChainProofs.verifyValidatorFields({
475:             beaconStateRoot: beaconStateRoot,
476:             validatorFields: validatorFields,
477:             validatorFieldsProof: validatorFieldsProof,
478:             validatorIndex: validatorIndex
479:         });
480: 
481:         
482:         validatorInfo.status = VALIDATOR_STATUS.ACTIVE;
483:         validatorInfo.validatorIndex = validatorIndex;
484:         validatorInfo.mostRecentBalanceUpdateTimestamp = oracleTimestamp;
485: 
486:         if (validatorEffectiveBalanceGwei > MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR) {
487:             validatorInfo.restakedBalanceGwei = MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR;
488:         } else {
489:             validatorInfo.restakedBalanceGwei = validatorEffectiveBalanceGwei;
490:         }
491:         _validatorPubkeyHashToInfo[validatorPubkeyHash] = validatorInfo;
492: 
493:         emit ValidatorRestaked(validatorIndex);
494:         emit ValidatorBalanceUpdated(validatorIndex, oracleTimestamp, validatorInfo.restakedBalanceGwei);
495: 
496:         return validatorInfo.restakedBalanceGwei * GWEI_TO_WEI;

['499']

499:     function _verifyBalanceUpdate(
500:         uint64 oracleTimestamp,
501:         uint40 validatorIndex,
502:         bytes32 beaconStateRoot,
503:         bytes calldata validatorFieldsProof,
504:         bytes32[] calldata validatorFields // <= FOUND
505:     ) internal returns(int256 sharesDeltaGwei){
506:         uint64 validatorEffectiveBalanceGwei = validatorFields.getEffectiveBalanceGwei();
507:         bytes32 validatorPubkeyHash = validatorFields.getPubkeyHash();
508:         ValidatorInfo memory validatorInfo = _validatorPubkeyHashToInfo[validatorPubkeyHash];
509: 
510:         
511:         require(
512:             validatorInfo.mostRecentBalanceUpdateTimestamp < oracleTimestamp,
513:             "EigenPod.verifyBalanceUpdate: Validators balance has already been updated for this timestamp"
514:         );
515: 
516:         
517:         require(
518:             validatorInfo.status == VALIDATOR_STATUS.ACTIVE, 
519:             "EigenPod.verifyBalanceUpdate: Validator not active"
520:         );
521: 
522:         
523:         
524:         
525:         if (validatorFields.getWithdrawableEpoch() <= _timestampToEpoch(oracleTimestamp)) {
526:             require(
527:                 validatorEffectiveBalanceGwei > 0,
528:                 "EigenPod.verifyBalanceUpdate: validator is withdrawable but has not withdrawn"
529:             );
530:         }
531: 
532:         
533:         BeaconChainProofs.verifyValidatorFields({
534:             beaconStateRoot: beaconStateRoot,
535:             validatorFields: validatorFields,
536:             validatorFieldsProof: validatorFieldsProof,
537:             validatorIndex: validatorIndex
538:         });
539: 
540:         
541: 
542:         uint64 currentRestakedBalanceGwei = validatorInfo.restakedBalanceGwei;
543:         uint64 newRestakedBalanceGwei;
544:         if (validatorEffectiveBalanceGwei > MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR) {
545:             newRestakedBalanceGwei = MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR;
546:         } else {
547:             newRestakedBalanceGwei = validatorEffectiveBalanceGwei;
548:         }

['566']

566:     function _verifyAndProcessWithdrawal(
567:         bytes32 beaconStateRoot,
568:         BeaconChainProofs.WithdrawalProof calldata withdrawalProof,
569:         bytes calldata validatorFieldsProof,
570:         bytes32[] calldata validatorFields, // <= FOUND
571:         bytes32[] calldata withdrawalFields // <= FOUND
572:     )
573:         internal
574:         
575: 
583:         proofIsForValidTimestamp(withdrawalProof.getWithdrawalTimestamp())
584:         returns (VerifiedWithdrawal memory)
585:     {
586:         uint64 withdrawalTimestamp = withdrawalProof.getWithdrawalTimestamp();
587:         bytes32 validatorPubkeyHash = validatorFields.getPubkeyHash();
588: 
589:         
590: 
593:         require(
594:             _validatorPubkeyHashToInfo[validatorPubkeyHash].status != VALIDATOR_STATUS.INACTIVE,
595:             "EigenPod._verifyAndProcessWithdrawal: Validator never proven to have withdrawal credentials pointed to this contract"
596:         );
597: 
598:         
599:         require(
600:             !provenWithdrawal[validatorPubkeyHash][withdrawalTimestamp],
601:             "EigenPod._verifyAndProcessWithdrawal: withdrawal has already been proven for this timestamp"
602:         );
603: 
604:         provenWithdrawal[validatorPubkeyHash][withdrawalTimestamp] = true;
605: 
606:         
607:         BeaconChainProofs.verifyWithdrawal({
608:             beaconStateRoot: beaconStateRoot, 
609:             withdrawalFields: withdrawalFields, 
610:             withdrawalProof: withdrawalProof,
611:             denebForkTimestamp: eigenPodManager.denebForkTimestamp()
612:         });
613: 
614:         uint40 validatorIndex = withdrawalFields.getValidatorIndex();
615: 
616:         
617:         BeaconChainProofs.verifyValidatorFields({
618:             beaconStateRoot: beaconStateRoot,
619:             validatorFields: validatorFields,
620:             validatorFieldsProof: validatorFieldsProof,
621:             validatorIndex: validatorIndex
622:         });
623: 
624:         uint64 withdrawalAmountGwei = withdrawalFields.getWithdrawalAmountGwei();

['43']

43:     function resetFrozenStatus(address[] calldata) external {} // <= FOUND

[NonCritical-37] Interface imports should be declared first

Resolution

Amend the ordering of imports to import interfaces first followed by other imports

Num of instances: 8

Findings

Click to show findings

['5']

2: 
3: pragma solidity =0.8.12;
4: 
5: import "@openzeppelin/contracts/utils/Create2.sol"; // <= FOUND
6: import "@openzeppelin-upgrades/contracts/proxy/utils/Initializable.sol"; // <= FOUND
7: import "@openzeppelin-upgrades/contracts/access/OwnableUpgradeable.sol"; // <= FOUND
8: import "@openzeppelin-upgrades/contracts/security/ReentrancyGuardUpgradeable.sol"; // <= FOUND
9: 
10: import "../interfaces/IBeaconChainOracle.sol"; // <= FOUND
11: 
12: import "../permissions/Pausable.sol"; // <= FOUND
13: import "./EigenPodPausingConstants.sol"; // <= FOUND
14: import "./EigenPodManagerStorage.sol"; // <= FOUND
15: 
26: contract EigenPodManager is
27:     Initializable,
28:     OwnableUpgradeable,
29:     Pausable,
30:     EigenPodPausingConstants,
31:     EigenPodManagerStorage,
32:     ReentrancyGuardUpgradeable
33: {
34:     
35:     modifier onlyEigenPod(address podOwner) {
36:         require(address(ownerToPod[podOwner]) == msg.sender, "EigenPodManager.onlyEigenPod: not a pod");
37:         _;
38:     }
39: 
40:     modifier onlyDelegationManager() {
41:         require(
42:             msg.sender == address(delegationManager),
43:             "EigenPodManager.onlyDelegationManager: not the DelegationManager"
44:         );
45:         _;
46:     }
47: 
48:     constructor(
49:         IETHPOSDeposit _ethPOS,
50:         IBeacon _eigenPodBeacon,
51:         IStrategyManager _strategyManager,
52:         ISlasher _slasher,
53:         IDelegationManager _delegationManager
54:     ) EigenPodManagerStorage(_ethPOS, _eigenPodBeacon, _strategyManager, _slasher, _delegationManager) {
55:         _disableInitializers();
56:     }
57: 
58:     function initialize(
59:         uint256 _maxPods,
60:         IBeaconChainOracle _beaconChainOracle,
61:         address initialOwner,
62:         IPauserRegistry _pauserRegistry,
63:         uint256 _initPausedStatus
64:     ) external initializer {
65:         _setMaxPods(_maxPods);
66:         _updateBeaconChainOracle(_beaconChainOracle);
67:         _transferOwnership(initialOwner);
68:         _initializePauser(_pauserRegistry, _initPausedStatus);
69:     }
70: 
76:     function createPod() external onlyWhenNotPaused(PAUSED_NEW_EIGENPODS) returns (address) {
77:         require(!hasPod(msg.sender), "EigenPodManager.createPod: Sender already has a pod");
78:         

['5']

2: 
3: pragma solidity >=0.5.0;
4: 
5: import "../../contracts/interfaces/IStrategyManager.sol"; // <= FOUND
6: import "../../contracts/interfaces/IStrategy.sol"; // <= FOUND
7: import "../../contracts/interfaces/IDelegationManager.sol"; // <= FOUND
8: import "../../../script/whitelist/Staker.sol"; // <= FOUND
9: 
10: import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; // <= FOUND
11: import "@openzeppelin/contracts/access/Ownable.sol"; // <= FOUND
12: import "@openzeppelin/contracts/utils/Create2.sol"; // <= FOUND
13: 
14: interface IWhitelister {
15:     function whitelist(address operator) external;
16: 
17:     function getStaker(address operator) external returns (address);
18: 
19:     function depositIntoStrategy(
20:         address staker,
21:         IStrategy strategy,
22:         IERC20 token,
23:         uint256 amount
24:     ) external returns (bytes memory);
25: 
26:     function queueWithdrawal(
27:         address staker,
28:         IDelegationManager.QueuedWithdrawalParams[] calldata queuedWithdrawalParams
29:     ) external returns (bytes memory);
30: 
31:     function completeQueuedWithdrawal(
32:         address staker,
33:         IDelegationManager.Withdrawal calldata queuedWithdrawal,
34:         IERC20[] calldata tokens,
35:         uint256 middlewareTimesIndex,
36:         bool receiveAsTokens
37:     ) external returns (bytes memory);
38: 
39:     function transfer(address staker, address token, address to, uint256 amount) external returns (bytes memory);
40: 
41:     function callAddress(address to, bytes memory data) external payable returns (bytes memory);
42: }
43: 

['5']

2: 
3: pragma solidity =0.8.12;
4: 
5: import "@openzeppelin-upgrades/contracts/proxy/utils/Initializable.sol"; // <= FOUND
6: import "@openzeppelin-upgrades/contracts/access/OwnableUpgradeable.sol"; // <= FOUND
7: import "@openzeppelin-upgrades/contracts/security/ReentrancyGuardUpgradeable.sol"; // <= FOUND
8: import "@openzeppelin-upgrades/contracts/utils/AddressUpgradeable.sol"; // <= FOUND
9: import "@openzeppelin-upgrades/contracts/utils/math/MathUpgradeable.sol"; // <= FOUND
10: import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; // <= FOUND
11: 
12: import "../libraries/BeaconChainProofs.sol"; // <= FOUND
13: import "../libraries/BytesLib.sol"; // <= FOUND
14: import "../libraries/Endian.sol"; // <= FOUND
15: 
16: import "../interfaces/IETHPOSDeposit.sol"; // <= FOUND
17: import "../interfaces/IEigenPodManager.sol"; // <= FOUND
18: import "../interfaces/IEigenPod.sol"; // <= FOUND
19: import "../interfaces/IDelayedWithdrawalRouter.sol"; // <= FOUND
20: import "../interfaces/IPausable.sol"; // <= FOUND
21: 
22: import "./EigenPodPausingConstants.sol"; // <= FOUND
23: 
39: contract EigenPod is IEigenPod, Initializable, ReentrancyGuardUpgradeable, EigenPodPausingConstants {
40:     using BytesLib for bytes;
41:     using SafeERC20 for IERC20;
42:     using BeaconChainProofs for *;
43: 
46:     uint256 internal constant GWEI_TO_WEI = 1e9;
47: 
52:     uint256 internal constant VERIFY_BALANCE_UPDATE_WINDOW_SECONDS = 4.5 hours;
53: 
55:     IETHPOSDeposit public immutable ethPOS;
56: 
58:     IDelayedWithdrawalRouter public immutable delayedWithdrawalRouter;
59: 
61:     IEigenPodManager public immutable eigenPodManager;
62: 
64:     uint64 public immutable MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR;
65: 
67:     uint64 public immutable GENESIS_TIME;
68: 
71:     address public podOwner;
72: 
78:     uint64 public mostRecentWithdrawalTimestamp;
79: 
81:     uint64 public withdrawableRestakedExecutionLayerGwei;
82: 
84:     bool public hasRestaked;
85: 
87:     mapping(bytes32 => mapping(uint64 => bool)) public provenWithdrawal;
88: 
90:     mapping(bytes32 => ValidatorInfo) internal _validatorPubkeyHashToInfo;
91: 
93:     uint256 public nonBeaconChainETHBalanceWei;
94: 
95:      
96:     uint64 public sumOfPartialWithdrawalsClaimedGwei;
97: 
98:     modifier onlyEigenPodManager() {
99:         require(msg.sender == address(eigenPodManager), "EigenPod.onlyEigenPodManager: not eigenPodManager");
100:         _;
101:     }
102: 
103:     modifier onlyEigenPodOwner() {
104:         require(msg.sender == podOwner, "EigenPod.onlyEigenPodOwner: not podOwner");
105:         _;
106:     }
107: 
108:     modifier hasNeverRestaked() {
109:         require(!hasRestaked, "EigenPod.hasNeverRestaked: restaking is enabled");

['5']

2: 
3: pragma solidity =0.8.12;
4: 
5: import "../interfaces/ISlasher.sol"; // <= FOUND
6: import "../interfaces/IDelegationManager.sol"; // <= FOUND
7: import "../interfaces/IStrategyManager.sol"; // <= FOUND
8: import "../libraries/StructuredLinkedList.sol"; // <= FOUND
9: import "../permissions/Pausable.sol"; // <= FOUND
10: import "@openzeppelin-upgrades/contracts/access/OwnableUpgradeable.sol"; // <= FOUND
11: import "@openzeppelin-upgrades/contracts/proxy/utils/Initializable.sol"; // <= FOUND
12: 
30: contract Slasher is Initializable, OwnableUpgradeable, ISlasher, Pausable {
31:      
32:     constructor(IStrategyManager, IDelegationManager) {}
33: 
34:     function initialize(
35:         address,
36:         IPauserRegistry,
37:         uint256
38:     ) external {}
39: 
40:     function optIntoSlashing(address) external {}
41: 
42:     function freezeOperator(address) external {}
43: 
44:     function resetFrozenStatus(address[] calldata) external {}
45: 
46:     function recordFirstStakeUpdate(address, uint32) external {}
47: 
48:     function recordStakeUpdate(
49:         address,
50:         uint32,
51:         uint32,
52:         uint256
53:     ) external {}
54: 
55:     function recordLastStakeUpdateAndRevokeSlashingAbility(address, uint32) external {}
56: 
57:     function strategyManager() external view returns (IStrategyManager) {}
58: 
59:     function delegation() external view returns (IDelegationManager) {}
60: 
61:     function isFrozen(address) external view returns (bool) {}
62: 
63:     function canSlash(address, address) external view returns (bool) {}
64: 
65:     function contractCanSlashOperatorUntilBlock(
66:         address,
67:         address
68:     ) external view returns (uint32) {}
69: 
70:     function latestUpdateBlock(address, address) external view returns (uint32) {}
71: 
72:     function getCorrectValueForInsertAfter(address, uint32) external view returns (uint256) {}
73: 
74:     function canWithdraw(
75:         address,
76:         uint32,
77:         uint256

['5']

2: 
3: pragma solidity =0.8.12;
4: 
5: import "../interfaces/IStrategyManager.sol"; // <= FOUND
6: import "../permissions/Pausable.sol"; // <= FOUND
7: import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; // <= FOUND
8: import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; // <= FOUND
9: import "@openzeppelin-upgrades/contracts/proxy/utils/Initializable.sol"; // <= FOUND
10: 
31: contract StrategyBase is Initializable, Pausable, IStrategy {
32:     using SafeERC20 for IERC20;
33: 
34:     uint8 internal constant PAUSED_DEPOSITS = 0;
35:     uint8 internal constant PAUSED_WITHDRAWALS = 1;
36: 
42:     uint256 internal constant SHARES_OFFSET = 1e3;
43:     
48:     uint256 internal constant BALANCE_OFFSET = 1e3;
49: 
51:     IStrategyManager public immutable strategyManager;
52: 
54:     IERC20 public underlyingToken;
55: 
57:     uint256 public totalShares;
58: 
60:     modifier onlyStrategyManager() {
61:         require(msg.sender == address(strategyManager), "StrategyBase.onlyStrategyManager");
62:         _;
63:     }
64: 
66:     constructor(IStrategyManager _strategyManager) {
67:         strategyManager = _strategyManager;
68:         _disableInitializers();
69:     }
70: 
71:     function initialize(IERC20 _underlyingToken, IPauserRegistry _pauserRegistry) public virtual initializer {
72:         _initializeStrategyBase(_underlyingToken, _pauserRegistry);
73:     }
74: 
76:     function _initializeStrategyBase(
77:         IERC20 _underlyingToken,
78:         IPauserRegistry _pauserRegistry
79:     ) internal onlyInitializing {
80:         underlyingToken = _underlyingToken;
81:         _initializePauser(_pauserRegistry, UNPAUSE_ALL);
82:     }
83: 
97:     function deposit(
98:         IERC20 token,
99:         uint256 amount
100:     ) external virtual override onlyWhenNotPaused(PAUSED_DEPOSITS) onlyStrategyManager returns (uint256 newShares) {
101:         
102:         _beforeDeposit(token, amount);
103: 
104:         
105:         uint256 priorTotalShares = totalShares;
106: 

['5']

2: 
3: pragma solidity >=0.5.0;
4: 
5: import "../libraries/BeaconChainProofs.sol"; // <= FOUND
6: import "./IEigenPodManager.sol"; // <= FOUND
7: import "./IBeaconChainOracle.sol"; // <= FOUND
8: import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; // <= FOUND
9: 
24: interface IEigenPod {
25:     enum VALIDATOR_STATUS {
26:         INACTIVE, 
27:         ACTIVE, 
28:         WITHDRAWN 
29:     }
30: 
31:     struct ValidatorInfo {
32:         
33:         uint64 validatorIndex;
34:         
35:         uint64 restakedBalanceGwei;
36:         
37:         uint64 mostRecentBalanceUpdateTimestamp;
38:         
39:         VALIDATOR_STATUS status;
40:     }
41: 
46:     struct VerifiedWithdrawal {
47:         
48:         uint256 amountToSendGwei;
49:         
50:         int256 sharesDeltaGwei;
51:     }
52: 
54:     enum PARTIAL_WITHDRAWAL_CLAIM_STATUS {
55:         REDEEMED,
56:         PENDING,
57:         FAILED
58:     }
59: 
61:     event EigenPodStaked(bytes pubkey);
62: 
64:     event ValidatorRestaked(uint40 validatorIndex);
65: 
68:     event ValidatorBalanceUpdated(uint40 validatorIndex, uint64 balanceTimestamp, uint64 newValidatorBalanceGwei);
69: 
71:     event FullWithdrawalRedeemed(
72:         uint40 validatorIndex,
73:         uint64 withdrawalTimestamp,
74:         address indexed recipient,
75:         uint64 withdrawalAmountGwei
76:     );
77: 
79:     event PartialWithdrawalRedeemed(
80:         uint40 validatorIndex,
81:         uint64 withdrawalTimestamp,
82:         address indexed recipient,

['5']

2: 
3: pragma solidity =0.8.12;
4: 
5: import "@openzeppelin-upgrades/contracts/proxy/utils/Initializable.sol"; // <= FOUND
6: import "@openzeppelin-upgrades/contracts/access/OwnableUpgradeable.sol"; // <= FOUND
7: import "@openzeppelin-upgrades/contracts/security/ReentrancyGuardUpgradeable.sol"; // <= FOUND
8: import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; // <= FOUND
9: import "../interfaces/IEigenPodManager.sol"; // <= FOUND
10: import "../permissions/Pausable.sol"; // <= FOUND
11: import "./StrategyManagerStorage.sol"; // <= FOUND
12: import "../libraries/EIP1271SignatureUtils.sol"; // <= FOUND
13: 
23: contract StrategyManager is
24:     Initializable,
25:     OwnableUpgradeable,
26:     ReentrancyGuardUpgradeable,
27:     Pausable,
28:     StrategyManagerStorage
29: {
30:     using SafeERC20 for IERC20;
31: 
33:     uint8 internal constant PAUSED_DEPOSITS = 0;
34: 
36:     uint256 internal immutable ORIGINAL_CHAIN_ID;
37: 
38:     modifier onlyStrategyWhitelister() {
39:         require(
40:             msg.sender == strategyWhitelister,
41:             "StrategyManager.onlyStrategyWhitelister: not the strategyWhitelister"
42:         );
43:         _;
44:     }
45: 
46:     modifier onlyStrategiesWhitelistedForDeposit(IStrategy strategy) {
47:         require(
48:             strategyIsWhitelistedForDeposit[strategy],
49:             "StrategyManager.onlyStrategiesWhitelistedForDeposit: strategy not whitelisted"
50:         );
51:         _;
52:     }
53: 
54:     modifier onlyDelegationManager() {
55:         require(msg.sender == address(delegation), "StrategyManager.onlyDelegationManager: not the DelegationManager");
56:         _;
57:     }
58: 
64:     constructor(
65:         IDelegationManager _delegation,
66:         IEigenPodManager _eigenPodManager,
67:         ISlasher _slasher
68:     ) StrategyManagerStorage(_delegation, _eigenPodManager, _slasher) {
69:         _disableInitializers();
70:         ORIGINAL_CHAIN_ID = block.chainid;
71:     }
72: 
83:     function initialize(
84:         address initialOwner,
85:         address initialStrategyWhitelister,
86:         IPauserRegistry _pauserRegistry,
87:         uint256 initialPausedStatus

['5']

2: 
3: pragma solidity =0.8.12;
4: 
5: import "@openzeppelin-upgrades/contracts/proxy/utils/Initializable.sol"; // <= FOUND
6: import "@openzeppelin-upgrades/contracts/access/OwnableUpgradeable.sol"; // <= FOUND
7: import "@openzeppelin-upgrades/contracts/security/ReentrancyGuardUpgradeable.sol"; // <= FOUND
8: import "../interfaces/IEigenPodManager.sol"; // <= FOUND
9: import "../interfaces/IDelayedWithdrawalRouter.sol"; // <= FOUND
10: import "../permissions/Pausable.sol"; // <= FOUND
11: 
12: contract DelayedWithdrawalRouter is
13:     Initializable,
14:     OwnableUpgradeable,
15:     ReentrancyGuardUpgradeable,
16:     Pausable,
17:     IDelayedWithdrawalRouter
18: {
19:     
20:     uint8 internal constant PAUSED_DELAYED_WITHDRAWAL_CLAIMS = 0;
21: 
26:     uint256 public withdrawalDelayBlocks;
27:     
28:     uint256 public constant MAX_WITHDRAWAL_DELAY_BLOCKS = 216000;
29: 
31:     IEigenPodManager public immutable eigenPodManager;
32: 
34:     mapping(address => UserDelayedWithdrawals) internal _userWithdrawals;
35: 
37:     modifier onlyEigenPod(address podOwner) {
38:         require(
39:             address(eigenPodManager.getPod(podOwner)) == msg.sender,
40:             "DelayedWithdrawalRouter.onlyEigenPod: not podOwner's EigenPod"
41:         );
42:         _;
43:     }
44: 
45:     constructor(IEigenPodManager _eigenPodManager) {
46:         require(
47:             address(_eigenPodManager) != address(0),
48:             "DelayedWithdrawalRouter.constructor: _eigenPodManager cannot be zero address"
49:         );
50:         eigenPodManager = _eigenPodManager;
51:         _disableInitializers();
52:     }
53: 
54:     function initialize(
55:         address initOwner,
56:         IPauserRegistry _pauserRegistry,
57:         uint256 initPausedStatus,
58:         uint256 _withdrawalDelayBlocks
59:     ) external initializer {
60:         _transferOwnership(initOwner);
61:         _initializePauser(_pauserRegistry, initPausedStatus);
62:         _setWithdrawalDelayBlocks(_withdrawalDelayBlocks);
63:     }
64: 
69:     function createDelayedWithdrawal(
70:         address podOwner,

[NonCritical-38] Upgradable contract constructor should have the initialize modifier

Num of instances: 8

Findings

Click to show findings

['32']

11: contract AVSDirectory is
12:     Initializable,
13:     OwnableUpgradeable,
14:     Pausable,
15:     AVSDirectoryStorage,
16:     ReentrancyGuardUpgradeable
17: {
18:     
19:     uint8 internal constant PAUSED_OPERATOR_REGISTER_DEREGISTER_TO_AVS = 0;
20: 
22:     uint256 internal immutable ORIGINAL_CHAIN_ID;
23: 
32:     constructor(IDelegationManager _delegation) AVSDirectoryStorage(_delegation) { // <= FOUND
33:         _disableInitializers();
34:         ORIGINAL_CHAIN_ID = block.chainid;
35:     }
36: 
41:     function initialize(
42:         address initialOwner,
43:         IPauserRegistry _pauserRegistry,
44:         uint256 initialPausedStatus
45:     ) external initializer {
46:         _initializePauser(_pauserRegistry, initialPausedStatus);
47:         _DOMAIN_SEPARATOR = _calculateDomainSeparator();
48:         _transferOwnership(initialOwner);
49:     }
50: 
61:     function registerOperatorToAVS(
62:         address operator,
63:         ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature
64:     ) external onlyWhenNotPaused(PAUSED_OPERATOR_REGISTER_DEREGISTER_TO_AVS) {
65: 
66:         require(
67:             operatorSignature.expiry >= block.timestamp,
68:             "AVSDirectory.registerOperatorToAVS: operator signature expired"
69:         );
70:         require(
71:             avsOperatorStatus[msg.sender][operator] != OperatorAVSRegistrationStatus.REGISTERED,
72:             "AVSDirectory.registerOperatorToAVS: operator already registered"
73:         );
74:         require(
75:             !operatorSaltIsSpent[operator][operatorSignature.salt],
76:             "AVSDirectory.registerOperatorToAVS: salt already spent"
77:         );
78:         require(
79:             delegation.isOperator(operator),
80:             "AVSDirectory.registerOperatorToAVS: operator not registered to EigenLayer yet");
81: 
82:         
83:         bytes32 operatorRegistrationDigestHash = calculateOperatorAVSRegistrationDigestHash({
84:             operator: operator,
85:             avs: msg.sender,
86:             salt: operatorSignature.salt,
87:             expiry: operatorSignature.expiry
88:         });
89: 
90:         
91:         EIP1271SignatureUtils.checkSignature_EIP1271(
92:             operator,
93:             operatorRegistrationDigestHash,
94:             operatorSignature.signature
95:         );
96: 
97:         
98:         avsOperatorStatus[msg.sender][operator] = OperatorAVSRegistrationStatus.REGISTERED;
99: 
100:         
101:         operatorSaltIsSpent[operator][operatorSignature.salt] = true;
102: 
103:         emit OperatorAVSRegistrationStatusUpdated(operator, msg.sender, OperatorAVSRegistrationStatus.REGISTERED);
104:     }

['44']

11: contract DelayedWithdrawalRouter is
12:     Initializable,
13:     OwnableUpgradeable,
14:     ReentrancyGuardUpgradeable,
15:     Pausable,
16:     IDelayedWithdrawalRouter
17: {
18:     
19:     uint8 internal constant PAUSED_DELAYED_WITHDRAWAL_CLAIMS = 0;
20: 
25:     uint256 public withdrawalDelayBlocks;
26:     
27:     uint256 public constant MAX_WITHDRAWAL_DELAY_BLOCKS = 216000;
28: 
30:     IEigenPodManager public immutable eigenPodManager;
31: 
33:     mapping(address => UserDelayedWithdrawals) internal _userWithdrawals;
34: 
36:     modifier onlyEigenPod(address podOwner) {
37:         require(
38:             address(eigenPodManager.getPod(podOwner)) == msg.sender,
39:             "DelayedWithdrawalRouter.onlyEigenPod: not podOwner's EigenPod"
40:         );
41:         _;
42:     }
43: 
44:     constructor(IEigenPodManager _eigenPodManager) { // <= FOUND
45:         require(
46:             address(_eigenPodManager) != address(0),
47:             "DelayedWithdrawalRouter.constructor: _eigenPodManager cannot be zero address" // <= FOUND
48:         );
49:         eigenPodManager = _eigenPodManager;
50:         _disableInitializers();
51:     }
52: 
53:     function initialize(
54:         address initOwner,
55:         IPauserRegistry _pauserRegistry,
56:         uint256 initPausedStatus,
57:         uint256 _withdrawalDelayBlocks
58:     ) external initializer {
59:         _transferOwnership(initOwner);
60:         _initializePauser(_pauserRegistry, initPausedStatus);
61:         _setWithdrawalDelayBlocks(_withdrawalDelayBlocks);
62:     }
63: 
68:     function createDelayedWithdrawal(
69:         address podOwner,
70:         address recipient
71:     ) external payable onlyEigenPod(podOwner) onlyWhenNotPaused(PAUSED_DELAYED_WITHDRAWAL_CLAIMS) {
72:         require(
73:             recipient != address(0),
74:             "DelayedWithdrawalRouter.createDelayedWithdrawal: recipient cannot be zero address"
75:         );
76:         uint224 withdrawalAmount = uint224(msg.value);
77:         if (withdrawalAmount != 0) {
78:             DelayedWithdrawal memory delayedWithdrawal = DelayedWithdrawal({
79:                 amount: withdrawalAmount,
80:                 blockCreated: uint32(block.number)
81:             });
82:             _userWithdrawals[recipient].delayedWithdrawals.push(delayedWithdrawal);
83:             emit DelayedWithdrawalCreated(
84:                 podOwner,
85:                 recipient,
86:                 withdrawalAmount,
87:                 _userWithdrawals[recipient].delayedWithdrawals.length - 1
88:             );
89:         }
90:     }
91: 
100:     function claimDelayedWithdrawals(
101:         address recipient,
102:         uint256 maxNumberOfDelayedWithdrawalsToClaim
103:     ) external nonReentrant onlyWhenNotPaused(PAUSED_DELAYED_WITHDRAWAL_CLAIMS) {
104:         _claimDelayedWithdrawals(recipient, maxNumberOfDelayedWithdrawalsToClaim);
105:     }
106: 
111:     function claimDelayedWithdrawals(
112:         uint256 maxNumberOfDelayedWithdrawalsToClaim
113:     ) external nonReentrant onlyWhenNotPaused(PAUSED_DELAYED_WITHDRAWAL_CLAIMS) {
114:         _claimDelayedWithdrawals(msg.sender, maxNumberOfDelayedWithdrawalsToClaim);
115:     }
116: 
118:     function setWithdrawalDelayBlocks(uint256 newValue) external onlyOwner {
119:         _setWithdrawalDelayBlocks(newValue);
120:     }

['56']

21: contract DelegationManager is Initializable, OwnableUpgradeable, Pausable, DelegationManagerStorage, ReentrancyGuardUpgradeable {
22:     
23:     uint8 internal constant PAUSED_NEW_DELEGATION = 0;
24: 
26:     uint8 internal constant PAUSED_ENTER_WITHDRAWAL_QUEUE = 1;
27: 
29:     uint8 internal constant PAUSED_EXIT_WITHDRAWAL_QUEUE = 2;
30: 
32:     uint256 internal immutable ORIGINAL_CHAIN_ID;
33: 
35:     uint256 public constant MAX_STAKER_OPT_OUT_WINDOW_BLOCKS = (180 days) / 12;
36: 
38:     IStrategy public constant beaconChainETHStrategy = IStrategy(0xbeaC0eeEeeeeEEeEeEEEEeeEEeEeeeEeeEEBEaC0);
39: 
41:     modifier onlyStrategyManagerOrEigenPodManager() {
42:         require(
43:             msg.sender == address(strategyManager) || msg.sender == address(eigenPodManager),
44:             "DelegationManager: onlyStrategyManagerOrEigenPodManager"
45:         );
46:         _;
47:     }
48: 
56:     constructor( // <= FOUND
57:         IStrategyManager _strategyManager,
58:         ISlasher _slasher,
59:         IEigenPodManager _eigenPodManager
60:     ) DelegationManagerStorage(_strategyManager, _slasher, _eigenPodManager) {
61:         _disableInitializers();
62:         ORIGINAL_CHAIN_ID = block.chainid;
63:     }
64: 
69:     function initialize(
70:         address initialOwner,
71:         IPauserRegistry _pauserRegistry,
72:         uint256 initialPausedStatus,
73:         uint256 _minWithdrawalDelayBlocks,
74:         IStrategy[] calldata _strategies,
75:         uint256[] calldata _withdrawalDelayBlocks
76:     ) external initializer {
77:         _initializePauser(_pauserRegistry, initialPausedStatus);
78:         _DOMAIN_SEPARATOR = _calculateDomainSeparator();
79:         _transferOwnership(initialOwner);
80:         _setMinWithdrawalDelayBlocks(_minWithdrawalDelayBlocks);
81:         _setStrategyWithdrawalDelayBlocks(_strategies, _withdrawalDelayBlocks);
82:     }
83: 
97:     function registerAsOperator(
98:         OperatorDetails calldata registeringOperatorDetails,
99:         string calldata metadataURI
100:     ) external {
101:         require(
102:             _operatorDetails[msg.sender].earningsReceiver == address(0),
103:             "DelegationManager.registerAsOperator: operator has already registered"
104:         );
105:         _setOperatorDetails(msg.sender, registeringOperatorDetails);
106:         SignatureWithExpiry memory emptySignatureAndExpiry;
107:         
108:         _delegate(msg.sender, msg.sender, emptySignatureAndExpiry, bytes32(0));
109:         
110:         emit OperatorRegistered(msg.sender, registeringOperatorDetails);
111:         emit OperatorMetadataURIUpdated(msg.sender, metadataURI);
112:     }
113: 
121:     function modifyOperatorDetails(OperatorDetails calldata newOperatorDetails) external {
122:         require(isOperator(msg.sender), "DelegationManager.modifyOperatorDetails: caller must be an operator");
123:         _setOperatorDetails(msg.sender, newOperatorDetails);
124:     }
125: 
130:     function updateOperatorMetadataURI(string calldata metadataURI) external {
131:         require(isOperator(msg.sender), "DelegationManager.updateOperatorMetadataURI: caller must be an operator");
132:         emit OperatorMetadataURIUpdated(msg.sender, metadataURI);
133:     }
134: 
148:     function delegateTo(
149:         address operator,
150:         SignatureWithExpiry memory approverSignatureAndExpiry,
151:         bytes32 approverSalt
152:     ) external {
153:         
154:         _delegate(msg.sender, operator, approverSignatureAndExpiry, approverSalt);
155:     }
156: 
174:     function delegateToBySignature(
175:         address staker,
176:         address operator,

['140']

38: contract EigenPod is IEigenPod, Initializable, ReentrancyGuardUpgradeable, EigenPodPausingConstants {
39:     using BytesLib for bytes;
40:     using SafeERC20 for IERC20;
41:     using BeaconChainProofs for *;
42: 
45:     uint256 internal constant GWEI_TO_WEI = 1e9;
46: 
51:     uint256 internal constant VERIFY_BALANCE_UPDATE_WINDOW_SECONDS = 4.5 hours;
52: 
54:     IETHPOSDeposit public immutable ethPOS;
55: 
57:     IDelayedWithdrawalRouter public immutable delayedWithdrawalRouter;
58: 
60:     IEigenPodManager public immutable eigenPodManager;
61: 
63:     uint64 public immutable MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR;
64: 
66:     uint64 public immutable GENESIS_TIME;
67: 
70:     address public podOwner;
71: 
77:     uint64 public mostRecentWithdrawalTimestamp;
78: 
80:     uint64 public withdrawableRestakedExecutionLayerGwei;
81: 
83:     bool public hasRestaked;
84: 
86:     mapping(bytes32 => mapping(uint64 => bool)) public provenWithdrawal;
87: 
89:     mapping(bytes32 => ValidatorInfo) internal _validatorPubkeyHashToInfo;
90: 
92:     uint256 public nonBeaconChainETHBalanceWei;
93: 
94:      
95:     uint64 public sumOfPartialWithdrawalsClaimedGwei;
96: 
97:     modifier onlyEigenPodManager() {
98:         require(msg.sender == address(eigenPodManager), "EigenPod.onlyEigenPodManager: not eigenPodManager");
99:         _;
100:     }
101: 
102:     modifier onlyEigenPodOwner() {
103:         require(msg.sender == podOwner, "EigenPod.onlyEigenPodOwner: not podOwner");
104:         _;
105:     }
106: 
107:     modifier hasNeverRestaked() {
108:         require(!hasRestaked, "EigenPod.hasNeverRestaked: restaking is enabled");
109:         _;
110:     }
111: 
113:     modifier hasEnabledRestaking() {
114:         require(hasRestaked, "EigenPod.hasEnabledRestaking: restaking is not enabled");
115:         _;
116:     }
117: 
119:     modifier proofIsForValidTimestamp(uint64 timestamp) {
120:         require(
121:             timestamp > mostRecentWithdrawalTimestamp,
122:             "EigenPod.proofIsForValidTimestamp: beacon chain proof must be for timestamp after mostRecentWithdrawalTimestamp"
123:         );
124:         _;
125:     }
126: 
132:     modifier onlyWhenNotPaused(uint8 index) {
133:         require(
134:             !IPausable(address(eigenPodManager)).paused(index),
135:             "EigenPod.onlyWhenNotPaused: index is paused in EigenPodManager"
136:         );
137:         _;
138:     }
139: 
140:     constructor( // <= FOUND
141:         IETHPOSDeposit _ethPOS,
142:         IDelayedWithdrawalRouter _delayedWithdrawalRouter,
143:         IEigenPodManager _eigenPodManager,
144:         uint64 _MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR,
145:         uint64 _GENESIS_TIME
146:     ) {
147:         ethPOS = _ethPOS;
148:         delayedWithdrawalRouter = _delayedWithdrawalRouter;
149:         eigenPodManager = _eigenPodManager;
150:         MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR = _MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR;
151:         GENESIS_TIME = _GENESIS_TIME;
152:         _disableInitializers();
153:     }
154: 
156:     function initialize(address _podOwner) external initializer {
157:         require(_podOwner != address(0), "EigenPod.initialize: podOwner cannot be zero address");
158:         podOwner = _podOwner;
159:         
160: 
165:         hasRestaked = true;
166:         emit RestakingActivated(podOwner);
167:     }
168: 
170:     receive() external payable {
171:         nonBeaconChainETHBalanceWei += msg.value;
172:         emit NonBeaconChainETHReceived(msg.value);
173:     }
174: 
185:     function verifyBalanceUpdates(
186:         uint64 oracleTimestamp,
187:         uint40[] calldata validatorIndices,
188:         BeaconChainProofs.StateRootProof calldata stateRootProof,
189:         bytes[] calldata validatorFieldsProofs,
190:         bytes32[][] calldata validatorFields
191:     ) external onlyWhenNotPaused(PAUSED_EIGENPODS_VERIFY_BALANCE_UPDATE) {
192:         require(
193:             (validatorIndices.length == validatorFieldsProofs.length) && (validatorFieldsProofs.length == validatorFields.length),
194:             "EigenPod.verifyBalanceUpdates: validatorIndices and proofs must be same length"
195:         );
196: 
197:         
198:         require(
199:             oracleTimestamp + VERIFY_BALANCE_UPDATE_WINDOW_SECONDS >= block.timestamp,
200:             "EigenPod.verifyBalanceUpdates: specified timestamp is too far in past"
201:         );
202: 
203:         
204:         BeaconChainProofs.verifyStateRootAgainstLatestBlockRoot({
205:             latestBlockRoot: eigenPodManager.getBlockRootAtTimestamp(oracleTimestamp),
206:             beaconStateRoot: stateRootProof.beaconStateRoot,
207:             stateRootProof: stateRootProof.proof
208:         });
209: 
210:         int256 sharesDeltaGwei;
211:         for (uint256 i = 0; i < validatorIndices.length; i++) {
212:             sharesDeltaGwei += _verifyBalanceUpdate(
213:                 oracleTimestamp,
214:                 validatorIndices[i],
215:                 stateRootProof.beaconStateRoot,
216:                 validatorFieldsProofs[i], 
217:                 validatorFields[i]
218:             );
219:         }
220:         eigenPodManager.recordBeaconChainETHBalanceUpdate(podOwner, sharesDeltaGwei * int256(GWEI_TO_WEI));
221:     }
222: 
232:     function verifyAndProcessWithdrawals(
233:         uint64 oracleTimestamp,
234:         BeaconChainProofs.StateRootProof calldata stateRootProof,
235:         BeaconChainProofs.WithdrawalProof[] calldata withdrawalProofs,
236:         bytes[] calldata validatorFieldsProofs,
237:         bytes32[][] calldata validatorFields,
238:         bytes32[][] calldata withdrawalFields
239:     ) external onlyWhenNotPaused(PAUSED_EIGENPODS_VERIFY_WITHDRAWAL) {
240:         require(
241:             (validatorFields.length == validatorFieldsProofs.length) &&
242:                 (validatorFieldsProofs.length == withdrawalProofs.length) &&
243:                 (withdrawalProofs.length == withdrawalFields.length),
244:             "EigenPod.verifyAndProcessWithdrawals: inputs must be same length"

['47']

25: contract EigenPodManager is
26:     Initializable,
27:     OwnableUpgradeable,
28:     Pausable,
29:     EigenPodPausingConstants,
30:     EigenPodManagerStorage,
31:     ReentrancyGuardUpgradeable
32: {
33:     
34:     modifier onlyEigenPod(address podOwner) {
35:         require(address(ownerToPod[podOwner]) == msg.sender, "EigenPodManager.onlyEigenPod: not a pod");
36:         _;
37:     }
38: 
39:     modifier onlyDelegationManager() {
40:         require(
41:             msg.sender == address(delegationManager),
42:             "EigenPodManager.onlyDelegationManager: not the DelegationManager"
43:         );
44:         _;
45:     }
46: 
47:     constructor( // <= FOUND
48:         IETHPOSDeposit _ethPOS,
49:         IBeacon _eigenPodBeacon,
50:         IStrategyManager _strategyManager,
51:         ISlasher _slasher,
52:         IDelegationManager _delegationManager
53:     ) EigenPodManagerStorage(_ethPOS, _eigenPodBeacon, _strategyManager, _slasher, _delegationManager) {
54:         _disableInitializers();
55:     }
56: 
57:     function initialize(
58:         uint256 _maxPods,
59:         IBeaconChainOracle _beaconChainOracle,
60:         address initialOwner,
61:         IPauserRegistry _pauserRegistry,
62:         uint256 _initPausedStatus
63:     ) external initializer {
64:         _setMaxPods(_maxPods);
65:         _updateBeaconChainOracle(_beaconChainOracle);
66:         _transferOwnership(initialOwner);
67:         _initializePauser(_pauserRegistry, _initPausedStatus);
68:     }
69: 
75:     function createPod() external onlyWhenNotPaused(PAUSED_NEW_EIGENPODS) returns (address) {
76:         require(!hasPod(msg.sender), "EigenPodManager.createPod: Sender already has a pod");
77:         
78:         IEigenPod pod = _deployPod();
79: 
80:         return address(pod);
81:     }
82: 
90:     function stake(
91:         bytes calldata pubkey, 
92:         bytes calldata signature, 
93:         bytes32 depositDataRoot
94:     ) external payable onlyWhenNotPaused(PAUSED_NEW_EIGENPODS) {
95:         IEigenPod pod = ownerToPod[msg.sender];
96:         if (address(pod) == address(0)) {
97:             
98:             pod = _deployPod();
99:         }
100:         pod.stake{value: msg.value}(pubkey, signature, depositDataRoot);
101:     }
102: 
111:     function recordBeaconChainETHBalanceUpdate(
112:         address podOwner,
113:         int256 sharesDelta
114:     ) external onlyEigenPod(podOwner) nonReentrant {
115:         require(podOwner != address(0), "EigenPodManager.recordBeaconChainETHBalanceUpdate: podOwner cannot be zero address");
116:         require(sharesDelta % int256(GWEI_TO_WEI) == 0,

['31']

29: contract Slasher is Initializable, OwnableUpgradeable, ISlasher, Pausable {
30:      
31:     constructor(IStrategyManager, IDelegationManager) {} // <= FOUND
32: 
33:     function initialize(
34:         address,
35:         IPauserRegistry,
36:         uint256
37:     ) external {}
38: 
39:     function optIntoSlashing(address) external {}
40: 
41:     function freezeOperator(address) external {}
42: 
43:     function resetFrozenStatus(address[] calldata) external {}
44: 
45:     function recordFirstStakeUpdate(address, uint32) external {}
46: 
47:     function recordStakeUpdate(
48:         address,
49:         uint32,
50:         uint32,
51:         uint256
52:     ) external {}
53: 
54:     function recordLastStakeUpdateAndRevokeSlashingAbility(address, uint32) external {}
55: 
56:     function strategyManager() external view returns (IStrategyManager) {}
57: 
58:     function delegation() external view returns (IDelegationManager) {}
59: 
60:     function isFrozen(address) external view returns (bool) {}
61: 
62:     function canSlash(address, address) external view returns (bool) {}
63: 
64:     function contractCanSlashOperatorUntilBlock(
65:         address,
66:         address
67:     ) external view returns (uint32) {}
68: 
69:     function latestUpdateBlock(address, address) external view returns (uint32) {}
70: 
71:     function getCorrectValueForInsertAfter(address, uint32) external view returns (uint256) {}
72: 
73:     function canWithdraw(
74:         address,
75:         uint32,
76:         uint256
77:     ) external returns (bool) {}
78: 
79:     function operatorToMiddlewareTimes(
80:         address,

['65']

30: contract StrategyBase is Initializable, Pausable, IStrategy {
31:     using SafeERC20 for IERC20;
32: 
33:     uint8 internal constant PAUSED_DEPOSITS = 0;
34:     uint8 internal constant PAUSED_WITHDRAWALS = 1;
35: 
41:     uint256 internal constant SHARES_OFFSET = 1e3;
42:     
47:     uint256 internal constant BALANCE_OFFSET = 1e3;
48: 
50:     IStrategyManager public immutable strategyManager;
51: 
53:     IERC20 public underlyingToken;
54: 
56:     uint256 public totalShares;
57: 
59:     modifier onlyStrategyManager() {
60:         require(msg.sender == address(strategyManager), "StrategyBase.onlyStrategyManager");
61:         _;
62:     }
63: 
65:     constructor(IStrategyManager _strategyManager) { // <= FOUND
66:         strategyManager = _strategyManager;
67:         _disableInitializers();
68:     }
69: 
70:     function initialize(IERC20 _underlyingToken, IPauserRegistry _pauserRegistry) public virtual initializer {
71:         _initializeStrategyBase(_underlyingToken, _pauserRegistry);
72:     }
73: 
75:     function _initializeStrategyBase(
76:         IERC20 _underlyingToken,
77:         IPauserRegistry _pauserRegistry
78:     ) internal onlyInitializing {
79:         underlyingToken = _underlyingToken;
80:         _initializePauser(_pauserRegistry, UNPAUSE_ALL);
81:     }
82: 
96:     function deposit(
97:         IERC20 token,
98:         uint256 amount
99:     ) external virtual override onlyWhenNotPaused(PAUSED_DEPOSITS) onlyStrategyManager returns (uint256 newShares) {
100:         
101:         _beforeDeposit(token, amount);
102: 
103:         
104:         uint256 priorTotalShares = totalShares;
105: 
106:         
107: 
110:         
111:         uint256 virtualShareAmount = priorTotalShares + SHARES_OFFSET;
112:         uint256 virtualTokenBalance = _tokenBalance() + BALANCE_OFFSET;
113:         
114:         uint256 virtualPriorTokenBalance = virtualTokenBalance - amount;
115:         newShares = (amount * virtualShareAmount) / virtualPriorTokenBalance;
116: 
117:         
118:         require(newShares != 0, "StrategyBase.deposit: newShares cannot be zero");
119: 
120:         
121:         totalShares = (priorTotalShares + newShares);
122:         return newShares;
123:     }
124: 
134:     function withdraw(
135:         address recipient,
136:         IERC20 token,
137:         uint256 amountShares
138:     ) external virtual override onlyWhenNotPaused(PAUSED_WITHDRAWALS) onlyStrategyManager {
139:         
140:         _beforeWithdrawal(recipient, token, amountShares);
141: 
142:         
143:         uint256 priorTotalShares = totalShares;
144: 
145:         require(
146:             amountShares <= priorTotalShares,
147:             "StrategyBase.withdraw: amountShares must be less than or equal to totalShares"
148:         );
149: 
150:         
151: 
154:         
155:         uint256 virtualPriorTotalShares = priorTotalShares + SHARES_OFFSET;

['63']

22: contract StrategyManager is
23:     Initializable,
24:     OwnableUpgradeable,
25:     ReentrancyGuardUpgradeable,
26:     Pausable,
27:     StrategyManagerStorage
28: {
29:     using SafeERC20 for IERC20;
30: 
32:     uint8 internal constant PAUSED_DEPOSITS = 0;
33: 
35:     uint256 internal immutable ORIGINAL_CHAIN_ID;
36: 
37:     modifier onlyStrategyWhitelister() {
38:         require(
39:             msg.sender == strategyWhitelister,
40:             "StrategyManager.onlyStrategyWhitelister: not the strategyWhitelister"
41:         );
42:         _;
43:     }
44: 
45:     modifier onlyStrategiesWhitelistedForDeposit(IStrategy strategy) {
46:         require(
47:             strategyIsWhitelistedForDeposit[strategy],
48:             "StrategyManager.onlyStrategiesWhitelistedForDeposit: strategy not whitelisted"
49:         );
50:         _;
51:     }
52: 
53:     modifier onlyDelegationManager() {
54:         require(msg.sender == address(delegation), "StrategyManager.onlyDelegationManager: not the DelegationManager");
55:         _;
56:     }
57: 
63:     constructor( // <= FOUND
64:         IDelegationManager _delegation,
65:         IEigenPodManager _eigenPodManager,
66:         ISlasher _slasher
67:     ) StrategyManagerStorage(_delegation, _eigenPodManager, _slasher) {
68:         _disableInitializers();
69:         ORIGINAL_CHAIN_ID = block.chainid;
70:     }
71: 
82:     function initialize(
83:         address initialOwner,
84:         address initialStrategyWhitelister,
85:         IPauserRegistry _pauserRegistry,
86:         uint256 initialPausedStatus
87:     ) external initializer {
88:         _DOMAIN_SEPARATOR = _calculateDomainSeparator();
89:         _initializePauser(_pauserRegistry, initialPausedStatus);
90:         _transferOwnership(initialOwner);
91:         _setStrategyWhitelister(initialStrategyWhitelister);
92:     }
93: 
105:     function depositIntoStrategy(
106:         IStrategy strategy,
107:         IERC20 token,
108:         uint256 amount
109:     ) external onlyWhenNotPaused(PAUSED_DEPOSITS) nonReentrant returns (uint256 shares) {
110:         shares = _depositIntoStrategy(msg.sender, strategy, token, amount);
111:     }
112: 
134:     function depositIntoStrategyWithSignature(
135:         IStrategy strategy,
136:         IERC20 token,
137:         uint256 amount,
138:         address staker,
139:         uint256 expiry,
140:         bytes memory signature
141:     ) external onlyWhenNotPaused(PAUSED_DEPOSITS) nonReentrant returns (uint256 shares) {
142:         require(
143:             !thirdPartyTransfersForbidden[strategy],
144:             "StrategyManager.depositIntoStrategyWithSignature: third transfers disabled"
145:         );
146:         require(expiry >= block.timestamp, "StrategyManager.depositIntoStrategyWithSignature: signature expired");
147:         
148:         uint256 nonce = nonces[staker];
149:         bytes32 structHash = keccak256(abi.encode(DEPOSIT_TYPEHASH, staker, strategy, token, amount, nonce, expiry));
150:         unchecked {
151:             nonces[staker] = nonce + 1;
152:         }
153: 
154:         
155:         bytes32 digestHash = keccak256(abi.encodePacked("\x19\x01", domainSeparator(), structHash));
156: 
157:         
158: 
163:         EIP1271SignatureUtils.checkSignature_EIP1271(staker, digestHash, signature);
164: 
165:         

[NonCritical-39] All interfaces used within a project should be imported

Num of instances: 3

Findings

Click to show findings

['13']

13: interface IWhitelister  // <= FOUND

['8']

8: interface ISocketUpdater  // <= FOUND

['9']

9: interface IDelegationFaucet  // <= FOUND

[NonCritical-40] SPDX identifier should be the in the first line of a solidity file

Resolution

Ensure the SPDX identifier is defined in the top line of the solidity file

Num of instances: 1

Findings

Click to show findings

['1']

1: // ┏━━━┓━┏┓━┏┓━━┏━━━┓━━┏━━━┓━━━━┏━━━┓━━━━━━━━━━━━━━━━━━━┏┓━━━━━┏━━━┓━━━━━━━━━┏┓━━━━━━━━━━━━━━┏┓━ // <= FOUND
2: // ┃┏━━┛┏┛┗┓┃┃━━┃┏━┓┃━━┃┏━┓┃━━━━┗┓┏┓┃━━━━━━━━━━━━━━━━━━┏┛┗┓━━━━┃┏━┓┃━━━━━━━━┏┛┗┓━━━━━━━━━━━━┏┛┗┓
3: // ┃┗━━┓┗┓┏┛┃┗━┓┗┛┏┛┃━━┃┃━┃┃━━━━━┃┃┃┃┏━━┓┏━━┓┏━━┓┏━━┓┏┓┗┓┏┛━━━━┃┃━┗┛┏━━┓┏━┓━┗┓┏┛┏━┓┏━━┓━┏━━┓┗┓┏┛
4: // ┃┏━━┛━┃┃━┃┏┓┃┏━┛┏┛━━┃┃━┃┃━━━━━┃┃┃┃┃┏┓┃┃┏┓┃┃┏┓┃┃━━┫┣┫━┃┃━━━━━┃┃━┏┓┃┏┓┃┃┏┓┓━┃┃━┃┏┛┗━┓┃━┃┏━┛━┃┃━
5: // ┃┗━━┓━┃┗┓┃┃┃┃┃┃┗━┓┏┓┃┗━┛┃━━━━┏┛┗┛┃┃┃━┫┃┗┛┃┃┗┛┃┣━━┃┃┃━┃┗┓━━━━┃┗━┛┃┃┗┛┃┃┃┃┃━┃┗┓┃┃━┃┗┛┗┓┃┗━┓━┃┗┓
6: // ┗━━━┛━┗━┛┗┛┗┛┗━━━┛┗┛┗━━━┛━━━━┗━━━┛┗━━┛┃┏━┛┗━━┛┗━━┛┗┛━┗━┛━━━━┗━━━┛┗━━┛┗┛┗┛━┗━┛┗┛━┗━━━┛┗━━┛━┗━┛
7: // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┃┃━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
8: // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┗┛━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
9: 
10: // SPDX-License-Identifier: CC0-1.0
11: 
12: pragma solidity >=0.5.0;
13: 
14: // This interface is designed to be compatible with the Vyper version.
15: /// @notice This is the Ethereum 2.0 deposit contract interface.
16: /// For more information see the Phase 0 specification under https://github.com/ethereum/eth2.0-specs
17: interface IETHPOSDeposit {
18:     /// @notice A processed deposit event.
19:     event DepositEvent(bytes pubkey, bytes withdrawal_credentials, bytes amount, bytes signature, bytes index);
20: 
21:     /// @notice Submit a Phase 0 DepositData object.
22:     /// @param pubkey A BLS12-381 public key.
23:     /// @param withdrawal_credentials Commitment to a public key for withdrawals.
24:     /// @param signature A BLS12-381 signature.
25:     /// @param deposit_data_root The SHA-256 hash of the SSZ-encoded DepositData object.
26:     /// Used as a protection against malformed input.
27:     function deposit(
28:         bytes calldata pubkey,
29:         bytes calldata withdrawal_credentials,
30:         bytes calldata signature,
31:         bytes32 deposit_data_root
32:     ) external payable;
33: 
34:     /// @notice Query the current deposit root hash.
35:     /// @return The deposit root hash.
36:     function get_deposit_root() external view returns (bytes32);
37: 
38:     /// @notice Query the current deposit count.
39:     /// @return The deposit count encoded as a little endian 64-bit number.
40:     function get_deposit_count() external view returns (bytes memory);
41: }
42: 

[NonCritical-41] Use allowlist/denylist rather than whitelist/blacklist

Num of instances: 44

Findings

Click to show findings

['7']

7: import "../../../script/whitelist/Staker.sol"; // <= FOUND

['14']

14:     function whitelist(address operator) external; // <= FOUND

['98']

97: 
98:     function whitelistedContractDetails( // <= FOUND
99:         address,
100:         address
101:     ) external view returns (MiddlewareDetails memory) {}

['47']

46:         require(
47:             strategyIsWhitelistedForDeposit[strategy], // <= FOUND
48:             "StrategyManager.onlyStrategiesWhitelistedForDeposit: strategy not whitelisted" // <= FOUND
49:         );

['190']

188: 
190:     function operatorWhitelistedContractsLinkedListSize(address operator) external view returns (uint256); // <= FOUND

['193']

191: 
193:     function operatorWhitelistedContractsLinkedListEntry( // <= FOUND
194:         address operator,
195:         address node
196:     ) external view returns (bool, uint256, uint256);

['31']

29: 
31:     event StrategyWhitelisterChanged(address previousAddress, address newAddress); // <= FOUND

['34']

32: 
34:     event StrategyAddedToDepositWhitelist(IStrategy strategy); // <= FOUND

['37']

35: 
37:     event StrategyRemovedFromDepositWhitelist(IStrategy strategy); // <= FOUND

['113']

107: 
113:     function addStrategiesToDepositWhitelist( // <= FOUND
114:         IStrategy[] calldata strategiesToWhitelist, // <= FOUND
115:         bool[] calldata thirdPartyTransfersForbiddenValues
116:     ) external;

['121']

116: 
121:     function removeStrategiesFromDepositWhitelist(IStrategy[] calldata strategiesToRemoveFromWhitelist) external; // <= FOUND

['130']

128: 
130:     function strategyWhitelister() external view returns (address); // <= FOUND

['14']

13: 
14: interface IWhitelister { // <= FOUND

['91']

90: 
91:     function operatorWhitelistedContractsLinkedListSize(address) external view returns (uint256) {} // <= FOUND

['93']

92: 
93:     function operatorWhitelistedContractsLinkedListEntry( // <= FOUND
94:         address,
95:         address
96:     ) external view returns (bool, uint256, uint256) {}

['38']

37: 
38:     modifier onlyStrategyWhitelister() { // <= FOUND

['39']

38:         require(
39:             msg.sender == strategyWhitelister, // <= FOUND
40:             "StrategyManager.onlyStrategyWhitelister: not the strategyWhitelister" // <= FOUND
41:         );

['46']

45: 
46:     modifier onlyStrategiesWhitelistedForDeposit(IStrategy strategy) { // <= FOUND

['95']

82: 
93:     function initialize(
94:         address initialOwner,
95:         address initialStrategyWhitelister, // <= FOUND
96:         IPauserRegistry _pauserRegistry,
97:         uint256 initialPausedStatus
98:     ) external initializer {

['91']

91:         _setStrategyWhitelister(initialStrategyWhitelister); // <= FOUND

['229']

218: 
226:     function setThirdPartyTransfersForbidden(
227:         IStrategy strategy,
228:         bool value
229:     ) external onlyStrategyWhitelister { // <= FOUND

['234']

229: 
234:     function setStrategyWhitelister(address newStrategyWhitelister) external onlyOwner { // <= FOUND

['230']

230:         _setStrategyWhitelister(newStrategyWhitelister); // <= FOUND

['244']

238: 
244:     function addStrategiesToDepositWhitelist( // <= FOUND
245:         IStrategy[] calldata strategiesToWhitelist, // <= FOUND
246:         bool[] calldata thirdPartyTransfersForbiddenValues
247:     ) external onlyStrategyWhitelister { // <= FOUND

['243']

242:         require(
243:             strategiesToWhitelist.length == thirdPartyTransfersForbiddenValues.length, // <= FOUND
244:             "StrategyManager.addStrategiesToDepositWhitelist: array lengths do not match" // <= FOUND
245:         );

['246']

246:         uint256 strategiesToWhitelistLength = strategiesToWhitelist.length; // <= FOUND

['247']

247:         for (uint256 i = 0; i < strategiesToWhitelistLength; ) { // <= FOUND

['250']

249:             
250:             if (!strategyIsWhitelistedForDeposit[strategiesToWhitelist[i]]) { // <= FOUND

['250']

250:                 strategyIsWhitelistedForDeposit[strategiesToWhitelist[i]] = true; // <= FOUND

['251']

251:                 emit StrategyAddedToDepositWhitelist(strategiesToWhitelist[i]); // <= FOUND

['252']

252:                 _setThirdPartyTransfersForbidden(strategiesToWhitelist[i], thirdPartyTransfersForbiddenValues[i]); // <= FOUND

['269']

264: 
269:     function removeStrategiesFromDepositWhitelist( // <= FOUND
270:         IStrategy[] calldata strategiesToRemoveFromWhitelist // <= FOUND
271:     ) external onlyStrategyWhitelister { // <= FOUND

['267']

267:         uint256 strategiesToRemoveFromWhitelistLength = strategiesToRemoveFromWhitelist.length; // <= FOUND

['268']

268:         for (uint256 i = 0; i < strategiesToRemoveFromWhitelistLength; ) { // <= FOUND

['271']

270:             
271:             if (strategyIsWhitelistedForDeposit[strategiesToRemoveFromWhitelist[i]]) { // <= FOUND

['271']

271:                 strategyIsWhitelistedForDeposit[strategiesToRemoveFromWhitelist[i]] = false; // <= FOUND

['272']

272:                 emit StrategyRemovedFromDepositWhitelist(strategiesToRemoveFromWhitelist[i]); // <= FOUND

['275']

274:                 
275:                 _setThirdPartyTransfersForbidden(strategiesToRemoveFromWhitelist[i], false); // <= FOUND

['338']

323: 
333:     function _depositIntoStrategy(
334:         address staker,
335:         IStrategy strategy,
336:         IERC20 token,
337:         uint256 amount
338:     ) internal onlyStrategiesWhitelistedForDeposit(strategy) returns (uint256 shares) { // <= FOUND

['431']

426: 
431:     function _setStrategyWhitelister(address newStrategyWhitelister) internal { // <= FOUND

['427']

427:         emit StrategyWhitelisterChanged(strategyWhitelister, newStrategyWhitelister); // <= FOUND

['428']

428:         strategyWhitelister = newStrategyWhitelister; // <= FOUND

['41']

40:     
41:     address public strategyWhitelister; // <= FOUND

['59']

58:     
59:     mapping(IStrategy => bool) public strategyIsWhitelistedForDeposit; // <= FOUND

[NonCritical-42] Multiple mappings can be replaced with a single struct mapping

Resolution

Using a single struct mapping in place of multiple defined mappings in a Solidity contract can lead to improved code organization, better readability, and easier maintainability. By consolidating related data into a single struct, developers can create a more cohesive data structure that logically groups together relevant pieces of information, thus reducing redundancy and clutter. This approach simplifies the codebase, making it easier to understand, navigate, and modify. Additionally, it can result in more efficient gas usage when accessing or updating multiple related data points simultaneously.

Num of instances: 1

Findings

Click to show findings

['86']

38: contract EigenPod is IEigenPod, Initializable, ReentrancyGuardUpgradeable, EigenPodPausingConstants {
39:     using BytesLib for bytes;
40:     using SafeERC20 for IERC20;
41:     using BeaconChainProofs for *;
42: 
45:     uint256 internal constant GWEI_TO_WEI = 1e9;
46: 
51:     uint256 internal constant VERIFY_BALANCE_UPDATE_WINDOW_SECONDS = 4.5 hours;
52: 
54:     IETHPOSDeposit public immutable ethPOS;
55: 
57:     IDelayedWithdrawalRouter public immutable delayedWithdrawalRouter;
58: 
60:     IEigenPodManager public immutable eigenPodManager;
61: 
63:     uint64 public immutable MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR;
64: 
66:     uint64 public immutable GENESIS_TIME;
67: 
70:     address public podOwner;
71: 
77:     uint64 public mostRecentWithdrawalTimestamp;
78: 
80:     uint64 public withdrawableRestakedExecutionLayerGwei;
81: 
83:     bool public hasRestaked;
84: 
86:     mapping(bytes32 => mapping(uint64 => bool)) public provenWithdrawal; // <= FOUND
87: 
89:     mapping(bytes32 => ValidatorInfo) internal _validatorPubkeyHashToInfo; // <= FOUND
90: 
92:     uint256 public nonBeaconChainETHBalanceWei;
93: 
94:      
95:     uint64 public sumOfPartialWithdrawalsClaimedGwei;
96: 
250:         
251: 
259:     uint256[44] private __gap;
260: }

[NonCritical-43] Unused state variables present

Resolution

If these serve no purpose, they should be safely removed

Num of instances: 2

Findings

Click to show findings

['96']

96: address private __deprecated_stakeRegistry; // <= FOUND

['16']

16: uint8 internal constant PAUSED_WITHDRAW_RESTAKED_ETH = 1; // <= FOUND

[NonCritical-44] Unused mappings present

Resolution

If these serve no purpose, they should be safely removed

Num of instances: 2

Findings

Click to show findings

['56']

56:     mapping(address => uint256) internal numWithdrawalsQueued; // <= FOUND

['63']

63:     mapping(address => uint256) internal beaconChainETHSharesToDecrementOnWithdrawal; // <= FOUND

[NonCritical-45] Unused modifiers present

Resolution

If these serve no purpose, they should be safely removed

Num of instances: 1

Findings

Click to show findings

['45']

45:     modifier whenNotPaused() { // <= FOUND
46:         require(_paused == 0, "Pausable: contract is paused");
47:         _;
48:     }

[NonCritical-46] Constants should be on the left side of the

Resolution

Putting constants on the left side of a comparison operator like == or < is a best practice known as "Yoda conditions", which can help prevent accidental assignment instead of comparison. In some programming languages, if a variable is mistakenly put on the left with a single = instead of ==, it assigns the constant's value to the variable without any compiler error. However, doing this with the constant on the left would generate an error, as constants cannot be assigned values. Although Solidity's static typing system prevents accidental assignments within conditionals, adopting this practice enhances code readability and consistency, especially when developers are working across multiple languages that support this convention.

Num of instances: 17

Findings

Click to show findings

['77']

77:         if (withdrawalAmount != 0)  // <= FOUND

['216']

216:         if (amountToSend != 0)  // <= FOUND

['237']

237:         if (strategies.length == 0)  // <= FOUND

['885']

885:         if (podShares <= 0)  // <= FOUND

['892']

892:         if (strategyManagerStrats.length == 0)  // <= FOUND

['270']

270:         if (withdrawalSummary.amountToSendGwei != 0)  // <= FOUND

['274']

274:         if (withdrawalSummary.sharesDeltaGwei != 0)  // <= FOUND

['295']

295:        if (sharesBefore <= 0)  // <= FOUND

['297']

297:             if (sharesAfter <= 0)  // <= FOUND

['352']

352:         if (timestamp == 0)  // <= FOUND

['300']

300:         if (stakerStrategyShares[staker][strategy] == 0)  // <= FOUND

['373']

373:         if (userShares == 0)  // <= FOUND

['160']

160:         if (numberOfClaimableWithdrawals != 0)  // <= FOUND

['297']

297:             if (sharesAfter <= 0)  // <= FOUND

['128']

128:         if (changeInDelegatableShares != 0)  // <= FOUND

['129']

129:            if (changeInDelegatableShares < 0)  // <= FOUND

['208']

208:         if (currentPodOwnerShares < 0)  // <= FOUND

[NonCritical-47] Defined named returns not used within function

Resolution

Such instances can be replaced with unnamed returns

Num of instances: 1

Findings

Click to show findings

['324']

324:     function hashValidatorBLSPubkey(bytes memory validatorPubkey) internal pure returns (bytes32 pubkeyHash) { // <= FOUND
325:         require(validatorPubkey.length == 48, "Input should be 48 bytes in length");
326:         return sha256(abi.encodePacked(validatorPubkey, bytes16(0)));
327:     }

[NonCritical-48] Initialize functions do not emit an event

Resolution

Emitting an event within initializer functions in Solidity is a best practice for providing transparency and traceability. Initializer functions set the initial state and values of an upgradeable contract. Emitting an event during initialization allows anyone to verify and audit the initial state of the contract via the transaction logs. This can be particularly useful for verifying the parameters set during initialization, tracking the contract's deployment, and troubleshooting or debugging. Therefore, developers should include an event emission in their initializer functions, providing a clear record of the contract's initialization and enhancing the contract's transparency and security.

Num of instances: 8

Findings

Click to show findings

['41']

41:     function initialize( // <= FOUND
42:         address initialOwner,
43:         IPauserRegistry _pauserRegistry,
44:         uint256 initialPausedStatus
45:     ) external initializer {
46:         _initializePauser(_pauserRegistry, initialPausedStatus);
47:         _DOMAIN_SEPARATOR = _calculateDomainSeparator();
48:         _transferOwnership(initialOwner);
49:     }

['53']

53:     function initialize( // <= FOUND
54:         address initOwner,
55:         IPauserRegistry _pauserRegistry,
56:         uint256 initPausedStatus,
57:         uint256 _withdrawalDelayBlocks
58:     ) external initializer {
59:         _transferOwnership(initOwner);
60:         _initializePauser(_pauserRegistry, initPausedStatus);
61:         _setWithdrawalDelayBlocks(_withdrawalDelayBlocks);
62:     }

['69']

69:     function initialize( // <= FOUND
70:         address initialOwner,
71:         IPauserRegistry _pauserRegistry,
72:         uint256 initialPausedStatus,
73:         uint256 _minWithdrawalDelayBlocks,
74:         IStrategy[] calldata _strategies,
75:         uint256[] calldata _withdrawalDelayBlocks
76:     ) external initializer {
77:         _initializePauser(_pauserRegistry, initialPausedStatus);
78:         _DOMAIN_SEPARATOR = _calculateDomainSeparator();
79:         _transferOwnership(initialOwner);
80:         _setMinWithdrawalDelayBlocks(_minWithdrawalDelayBlocks);
81:         _setStrategyWithdrawalDelayBlocks(_strategies, _withdrawalDelayBlocks);
82:     }

['57']

57:     function initialize( // <= FOUND
58:         uint256 _maxPods,
59:         IBeaconChainOracle _beaconChainOracle,
60:         address initialOwner,
61:         IPauserRegistry _pauserRegistry,
62:         uint256 _initPausedStatus
63:     ) external initializer {
64:         _setMaxPods(_maxPods);
65:         _updateBeaconChainOracle(_beaconChainOracle);
66:         _transferOwnership(initialOwner);
67:         _initializePauser(_pauserRegistry, _initPausedStatus);
68:     }

['33']

33:     function initialize( // <= FOUND
34:         address,
35:         IPauserRegistry,
36:         uint256
37:     ) external {}

['70']

70:     function initialize(IERC20 _underlyingToken, IPauserRegistry _pauserRegistry) public virtual initializer { // <= FOUND
71:         _initializeStrategyBase(_underlyingToken, _pauserRegistry);
72:     }

['29']

29:     function initialize( // <= FOUND
30:         uint256 _maxPerDeposit,
31:         uint256 _maxTotalDeposits,
32:         IERC20 _underlyingToken,
33:         IPauserRegistry _pauserRegistry
34:     ) public virtual initializer {
35:         _setTVLLimits(_maxPerDeposit, _maxTotalDeposits);
36:         _initializeStrategyBase(_underlyingToken, _pauserRegistry);
37:     }

['82']

82:     function initialize( // <= FOUND
83:         address initialOwner,
84:         address initialStrategyWhitelister,
85:         IPauserRegistry _pauserRegistry,
86:         uint256 initialPausedStatus
87:     ) external initializer {
88:         _DOMAIN_SEPARATOR = _calculateDomainSeparator();
89:         _initializePauser(_pauserRegistry, initialPausedStatus);
90:         _transferOwnership(initialOwner);
91:         _setStrategyWhitelister(initialStrategyWhitelister);
92:     }

[NonCritical-49] Both immutable and constant state variables should be CONSTANT_CASE

Resolution

Make found instants CAPITAL_CASE

Num of instances: 10

Findings

Click to show findings

['38']

38: IStrategy public constant beaconChainETHStrategy = IStrategy(0xbeaC0eeEeeeeEEeEeEEEEeeEEeEeeeEeeEEBEaC0); // <= FOUND

['34']

34: bytes internal constant beaconProxyBytecode = // <= FOUND

['60']

60: IEigenPodManager public immutable eigenPodManager; // <= FOUND

['57']

57: IDelayedWithdrawalRouter public immutable delayedWithdrawalRouter; // <= FOUND

['50']

50: IStrategyManager public immutable strategyManager; // <= FOUND

['20']

20: IDelegationManager public immutable delegation; // <= FOUND

['24']

24: ISlasher public immutable slasher; // <= FOUND

['18']

18: IBeacon public immutable eigenPodBeacon; // <= FOUND

['27']

27: IDelegationManager public immutable delegationManager; // <= FOUND

['54']

54: IETHPOSDeposit public immutable ethPOS; // <= FOUND

[NonCritical-50] Consider using named mappings

Resolution

In Solidity version 0.8.18 and beyond mapping parameters can be named. This makes the purpose and function of a given mapping far clearer which in turn improves readability.

Num of instances: 18

Findings

Click to show findings

['86']

86:     mapping(bytes32 => mapping(uint64 => bool)) public provenWithdrawal; // <= FOUND

['13']

13:     mapping(address => bool) public isPauser; // <= FOUND

['34']

34:     mapping(address => mapping(bytes32 => bool)) public operatorSaltIsSpent; // <= FOUND

['76']

76:     mapping(address => mapping(bytes32 => bool)) public delegationApproverSaltIsSpent; // <= FOUND

['88']

88:     mapping(bytes32 => bool) public pendingWithdrawals; // <= FOUND

['51']

51:     mapping(bytes32 => bool) public withdrawalRootPending; // <= FOUND

['58']

58:     mapping(IStrategy => bool) public strategyIsWhitelistedForDeposit; // <= FOUND

['70']

70:     mapping(IStrategy => bool) public thirdPartyTransfersForbidden; // <= FOUND

['66']

66:     mapping(address => address) public delegatedTo; // <= FOUND

['54']

54:     mapping(address => mapping(IStrategy => uint256)) public operatorShares; // <= FOUND

['69']

69:     mapping(address => uint256) public stakerNonce; // <= FOUND

['92']

92:     mapping(address => uint256) public cumulativeWithdrawalsQueued; // <= FOUND

['102']

102:     mapping(IStrategy => uint256) public strategyWithdrawalDelayBlocks; // <= FOUND

['65']

65:     mapping(address => int256) public podOwnerShares; // <= FOUND

['38']

38:     mapping(address => uint256) public nonces; // <= FOUND

['46']

46:     mapping(address => mapping(IStrategy => uint256)) public stakerStrategyShares; // <= FOUND

['56']

56:     mapping(address => uint256) internal numWithdrawalsQueued; // <= FOUND

['63']

63:     mapping(address => uint256) internal beaconChainETHSharesToDecrementOnWithdrawal; // <= FOUND

[NonCritical-51] Contract inherits Pausable but does not expose pausing/unpausing functionality

Resolution

When a contract inherits from the Pausable contract in Solidity, it gains the ability to pause and resume certain actions, which can be crucial in mitigating potential risks or issues. However, the _pause and _unpause functions are internal and can only be called from within the contract or derived contracts. If the functionality to pause and unpause isn't exposed through public or external functions, it can't be called by the contract owner or designated pauser account.

Resolution: To take advantage of the Pausable contract, create public or external functions that call _pause and _unpause.

Num of instances: 1

Findings

Click to show findings

['29']

29: contract Slasher is Initializable, OwnableUpgradeable, ISlasher, Pausable 

[NonCritical-52] Uses of EIP712 does not include a salt

Resolution

It is standard for uses of EIP712 to include a salt, not doing so can cause future incompatibilities and in this instance cause hash collisions do to no salting

Num of instances: 1

Findings

Click to show findings

['14']

14:     
15:     bytes32 public constant DOMAIN_TYPEHASH =
16:         keccak256("EIP712Domain(string name,uint256 chainId,address verifyingContract)"); // <= FOUND

[NonCritical-53] Use a single contract or library for system wide constants

Num of instances: 7

Findings

Click to show findings

['19']

11: contract AVSDirectory is
12:     Initializable,
13:     OwnableUpgradeable,
14:     Pausable,
15:     AVSDirectoryStorage,
16:     ReentrancyGuardUpgradeable
17: {
18:     
19:     uint8 internal constant PAUSED_OPERATOR_REGISTER_DEREGISTER_TO_AVS = 0; // <= FOUND
20: 
22:     uint256 internal immutable ORIGINAL_CHAIN_ID;
23: 
91: }

['19']

11: contract DelayedWithdrawalRouter is
12:     Initializable,
13:     OwnableUpgradeable,
14:     ReentrancyGuardUpgradeable,
15:     Pausable,
16:     IDelayedWithdrawalRouter
17: {
18:     
19:     uint8 internal constant PAUSED_DELAYED_WITHDRAWAL_CLAIMS = 0; // <= FOUND
20: 
25:     uint256 public withdrawalDelayBlocks;
26:     
27:     uint256 public constant MAX_WITHDRAWAL_DELAY_BLOCKS = 216000; // <= FOUND
28: 
30:     IEigenPodManager public immutable eigenPodManager;
31: 
33:     mapping(address => UserDelayedWithdrawals) internal _userWithdrawals;
34: 
96:     uint256[48] private __gap;
97: }

['23']

21: contract DelegationManager is Initializable, OwnableUpgradeable, Pausable, DelegationManagerStorage, ReentrancyGuardUpgradeable {
22:     
23:     uint8 internal constant PAUSED_NEW_DELEGATION = 0; // <= FOUND
24: 
26:     uint8 internal constant PAUSED_ENTER_WITHDRAWAL_QUEUE = 1; // <= FOUND
27: 
29:     uint8 internal constant PAUSED_EXIT_WITHDRAWAL_QUEUE = 2; // <= FOUND
30: 
32:     uint256 internal immutable ORIGINAL_CHAIN_ID;
33: 
35:     uint256 public constant MAX_STAKER_OPT_OUT_WINDOW_BLOCKS = (180 days) / 12; // <= FOUND
36: 
38:     IStrategy public constant beaconChainETHStrategy = IStrategy(0xbeaC0eeEeeeeEEeEeEEEEeeEEeEeeeEeeEEBEaC0); // <= FOUND
39: 
357: }

['45']

38: contract EigenPod is IEigenPod, Initializable, ReentrancyGuardUpgradeable, EigenPodPausingConstants {
39:     using BytesLib for bytes;
40:     using SafeERC20 for IERC20;
41:     using BeaconChainProofs for *;
42: 
45:     uint256 internal constant GWEI_TO_WEI = 1e9; // <= FOUND
46: 
51:     uint256 internal constant VERIFY_BALANCE_UPDATE_WINDOW_SECONDS = 4.5 hours; // <= FOUND
52: 
54:     IETHPOSDeposit public immutable ethPOS;
55: 
57:     IDelayedWithdrawalRouter public immutable delayedWithdrawalRouter;
58: 
60:     IEigenPodManager public immutable eigenPodManager;
61: 
63:     uint64 public immutable MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR;
64: 
66:     uint64 public immutable GENESIS_TIME;
67: 
70:     address public podOwner;
71: 
77:     uint64 public mostRecentWithdrawalTimestamp;
78: 
80:     uint64 public withdrawableRestakedExecutionLayerGwei;
81: 
83:     bool public hasRestaked;
84: 
86:     mapping(bytes32 => mapping(uint64 => bool)) public provenWithdrawal;
87: 
89:     mapping(bytes32 => ValidatorInfo) internal _validatorPubkeyHashToInfo;
90: 
92:     uint256 public nonBeaconChainETHBalanceWei;
93: 
94:      
95:     uint64 public sumOfPartialWithdrawalsClaimedGwei;
96: 
250:         
251: 
259:     uint256[44] private __gap;
260: }

['30']

23: contract Pausable is IPausable {
24:     
25:     IPauserRegistry public pauserRegistry;
26: 
28:     uint256 private _paused;
29: 
30:     uint256 internal constant UNPAUSE_ALL = 0; // <= FOUND
31:     uint256 internal constant PAUSE_ALL = type(uint256).max; // <= FOUND
32: 
85:     uint256[48] private __gap;
86: }

['33']

30: contract StrategyBase is Initializable, Pausable, IStrategy {
31:     using SafeERC20 for IERC20;
32: 
33:     uint8 internal constant PAUSED_DEPOSITS = 0; // <= FOUND
34:     uint8 internal constant PAUSED_WITHDRAWALS = 1; // <= FOUND
35: 
41:     uint256 internal constant SHARES_OFFSET = 1e3; // <= FOUND
42:     
47:     uint256 internal constant BALANCE_OFFSET = 1e3; // <= FOUND
48: 
50:     IStrategyManager public immutable strategyManager;
51: 
53:     IERC20 public underlyingToken;
54: 
56:     uint256 public totalShares;
57: 
188:     uint256[48] private __gap;
189: }

['32']

22: contract StrategyManager is
23:     Initializable,
24:     OwnableUpgradeable,
25:     ReentrancyGuardUpgradeable,
26:     Pausable,
27:     StrategyManagerStorage
28: {
29:     using SafeERC20 for IERC20;
30: 
32:     uint8 internal constant PAUSED_DEPOSITS = 0; // <= FOUND
33: 
35:     uint256 internal immutable ORIGINAL_CHAIN_ID;
36: 
218: }

[NonCritical-54] Consider using modifiers for address control

Resolution

Modifiers in Solidity can improve code readability and modularity by encapsulating repetitive checks, such as address validity checks, into a reusable construct. For example, an onlyOwner modifier can be used to replace repetitive require(msg.sender == owner) checks across several functions, reducing code redundancy and enhancing maintainability. To implement, define a modifier with the check, then apply the modifier to relevant functions.

Num of instances: 14

Findings

Click to show findings

['42']

42:         require(
43:             msg.sender == address(strategyManager) || msg.sender == address(eigenPodManager), // <= FOUND
44:             "DelegationManager: onlyStrategyManagerOrEigenPodManager"
45:         );

['216']

216:         require(
217:             msg.sender == staker || // <= FOUND
218:                 msg.sender == operator || // <= FOUND
219:                 msg.sender == _operatorDetails[operator].delegationApprover, // <= FOUND
220:             "DelegationManager.undelegate: caller cannot undelegate staker"
221:         );

['574']

574: 
575:         require(
576:             msg.sender == withdrawal.withdrawer,  // <= FOUND
577:             "DelegationManager._completeQueuedWithdrawal: only withdrawer can complete action"
578:         );

['98']

98:         require(msg.sender == address(eigenPodManager), "EigenPod.onlyEigenPodManager: not eigenPodManager"); // <= FOUND

['103']

103:         require(msg.sender == podOwner, "EigenPod.onlyEigenPodOwner: not podOwner"); // <= FOUND

['40']

40:         require(
41:             msg.sender == address(delegationManager), // <= FOUND
42:             "EigenPodManager.onlyDelegationManager: not the DelegationManager"
43:         );

['40']

40:         require(msg.sender == pauserRegistry.unpauser(), "msg.sender is not permissioned as unpauser"); // <= FOUND

['19']

19:         require(msg.sender == unpauser, "msg.sender is not permissioned as unpauser"); // <= FOUND

['60']

60:         require(msg.sender == address(strategyManager), "StrategyBase.onlyStrategyManager"); // <= FOUND

['38']

38:         require(
39:             msg.sender == strategyWhitelister, // <= FOUND
40:             "StrategyManager.onlyStrategyWhitelister: not the strategyWhitelister"
41:         );

['54']

54:         require(msg.sender == address(delegation), "StrategyManager.onlyDelegationManager: not the DelegationManager"); // <= FOUND

['37']

37:         require(
38:             address(eigenPodManager.getPod(podOwner)) == msg.sender, // <= FOUND
39:             "DelayedWithdrawalRouter.onlyEigenPod: not podOwner's EigenPod"
40:         );

['275']

275:             require(queuedWithdrawalParams[i].withdrawer == msg.sender, "DelegationManager.queueWithdrawal: withdrawer must be staker"); // <= FOUND

['35']

35:         require(address(ownerToPod[podOwner]) == msg.sender, "EigenPodManager.onlyEigenPod: not a pod"); // <= FOUND

[NonCritical-55] Off-by-one timestamp error

Resolution

In Solidity, using >= or <= to compare against block.timestamp (alias now) may introduce off-by-one errors due to the fact that block.timestamp is only updated once per block and its value remains constant throughout the block's execution. If an operation happens at the exact second when block.timestamp changes, it could result in unexpected behavior. To avoid this, it's safer to use strict inequality operators (> or <). For instance, if a condition should only be met after a certain time, use block.timestamp > time rather than block.timestamp >= time. This way, potential off-by-one errors due to the exact timing of block mining are mitigated, leading to safer, more predictable contract behavior.

Num of instances: 6

Findings

Click to show findings

['61']

61:     function registerOperatorToAVS(
62:         address operator,
63:         ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature
64:     ) external onlyWhenNotPaused(PAUSED_OPERATOR_REGISTER_DEREGISTER_TO_AVS) {
65: 
66:         require(
67:             operatorSignature.expiry >= block.timestamp,
68:             "AVSDirectory.registerOperatorToAVS: operator signature expired"
69:         );
70:         require(
71:             avsOperatorStatus[msg.sender][operator] != OperatorAVSRegistrationStatus.REGISTERED,
72:             "AVSDirectory.registerOperatorToAVS: operator already registered"
73:         );
74:         require(
75:             !operatorSaltIsSpent[operator][operatorSignature.salt],
76:             "AVSDirectory.registerOperatorToAVS: salt already spent"
77:         );
78:         require(
79:             delegation.isOperator(operator),
80:             "AVSDirectory.registerOperatorToAVS: operator not registered to EigenLayer yet");
81: 
82:         
83:         bytes32 operatorRegistrationDigestHash = calculateOperatorAVSRegistrationDigestHash({
84:             operator: operator,
85:             avs: msg.sender,
86:             salt: operatorSignature.salt,
87:             expiry: operatorSignature.expiry
88:         });
89: 
90:         
91:         EIP1271SignatureUtils.checkSignature_EIP1271(
92:             operator,
93:             operatorRegistrationDigestHash,
94:             operatorSignature.signature
95:         );
96: 
97:         
98:         avsOperatorStatus[msg.sender][operator] = OperatorAVSRegistrationStatus.REGISTERED;
99: 
100:         
101:         operatorSaltIsSpent[operator][operatorSignature.salt] = true;
102: 
103:         emit OperatorAVSRegistrationStatusUpdated(operator, msg.sender, OperatorAVSRegistrationStatus.REGISTERED);
104:     }

['174']

174:     function delegateToBySignature(
175:         address staker,
176:         address operator,
177:         SignatureWithExpiry memory stakerSignatureAndExpiry,
178:         SignatureWithExpiry memory approverSignatureAndExpiry,
179:         bytes32 approverSalt
180:     ) external {
181:         
182:         require(
183:             stakerSignatureAndExpiry.expiry >= block.timestamp,
184:             "DelegationManager.delegateToBySignature: staker signature expired"
185:         );
186: 
187:         
188:         uint256 currentStakerNonce = stakerNonce[staker];
189:         bytes32 stakerDigestHash = calculateStakerDelegationDigestHash(
190:             staker,
191:             currentStakerNonce,
192:             operator,
193:             stakerSignatureAndExpiry.expiry
194:         );
195:         unchecked {
196:             stakerNonce[staker] = currentStakerNonce + 1;
197:         }
198: 
199:         
200:         EIP1271SignatureUtils.checkSignature_EIP1271(staker, stakerDigestHash, stakerSignatureAndExpiry.signature);
201: 
202:         
203:         _delegate(staker, operator, approverSignatureAndExpiry, approverSalt);
204:     }

['487']

487:     function _delegate(
488:         address staker,
489:         address operator,
490:         SignatureWithExpiry memory approverSignatureAndExpiry,
491:         bytes32 approverSalt
492:     ) internal onlyWhenNotPaused(PAUSED_NEW_DELEGATION) {
493:         require(!isDelegated(staker), "DelegationManager._delegate: staker is already actively delegated");
494:         require(isOperator(operator), "DelegationManager._delegate: operator is not registered in EigenLayer");
495: 
496:         
497:         address _delegationApprover = _operatorDetails[operator].delegationApprover;
498:         
499: 
503:         if (_delegationApprover != address(0) && msg.sender != _delegationApprover && msg.sender != operator) {
504:             
505:             require(
506:                 approverSignatureAndExpiry.expiry >= block.timestamp,
507:                 "DelegationManager._delegate: approver signature expired"
508:             );
509:             
510:             require(
511:                 !delegationApproverSaltIsSpent[_delegationApprover][approverSalt],
512:                 "DelegationManager._delegate: approverSalt already spent"
513:             );
514:             delegationApproverSaltIsSpent[_delegationApprover][approverSalt] = true;
515: 
516:             
517:             bytes32 approverDigestHash = calculateDelegationApprovalDigestHash(
518:                 staker,
519:                 operator,
520:                 _delegationApprover,
521:                 approverSalt,
522:                 approverSignatureAndExpiry.expiry
523:             );
524: 
525:             
526:             EIP1271SignatureUtils.checkSignature_EIP1271(
527:                 _delegationApprover,
528:                 approverDigestHash,
529:                 approverSignatureAndExpiry.signature
530:             );
531:         }
532: 
533:         
534:         delegatedTo[staker] = operator;
535:         emit StakerDelegated(staker, operator);
536: 
537:         (IStrategy[] memory strategies, uint256[] memory shares)
538:             = getDelegatableShares(staker);
539: 
540:         for (uint256 i = 0; i < strategies.length;) {
541:             _increaseOperatorShares({
542:                 operator: operator,
543:                 staker: staker,
544:                 strategy: strategies[i],
545:                 shares: shares[i]
546:             });
547: 
548:             unchecked { ++i; }
549:         }
550:     }

['185']

185:     function verifyBalanceUpdates(
186:         uint64 oracleTimestamp,
187:         uint40[] calldata validatorIndices,
188:         BeaconChainProofs.StateRootProof calldata stateRootProof,
189:         bytes[] calldata validatorFieldsProofs,
190:         bytes32[][] calldata validatorFields
191:     ) external onlyWhenNotPaused(PAUSED_EIGENPODS_VERIFY_BALANCE_UPDATE) {
192:         require(
193:             (validatorIndices.length == validatorFieldsProofs.length) && (validatorFieldsProofs.length == validatorFields.length),
194:             "EigenPod.verifyBalanceUpdates: validatorIndices and proofs must be same length"
195:         );
196: 
197:         
198:         require(
199:             oracleTimestamp + VERIFY_BALANCE_UPDATE_WINDOW_SECONDS >= block.timestamp,
200:             "EigenPod.verifyBalanceUpdates: specified timestamp is too far in past"
201:         );
202: 
203:         
204:         BeaconChainProofs.verifyStateRootAgainstLatestBlockRoot({
205:             latestBlockRoot: eigenPodManager.getBlockRootAtTimestamp(oracleTimestamp),
206:             beaconStateRoot: stateRootProof.beaconStateRoot,
207:             stateRootProof: stateRootProof.proof
208:         });
209: 
210:         int256 sharesDeltaGwei;
211:         for (uint256 i = 0; i < validatorIndices.length; i++) {
212:             sharesDeltaGwei += _verifyBalanceUpdate(
213:                 oracleTimestamp,
214:                 validatorIndices[i],
215:                 stateRootProof.beaconStateRoot,
216:                 validatorFieldsProofs[i], 
217:                 validatorFields[i]
218:             );
219:         }
220:         eigenPodManager.recordBeaconChainETHBalanceUpdate(podOwner, sharesDeltaGwei * int256(GWEI_TO_WEI));
221:     }

['294']

294:     function verifyWithdrawalCredentials(
295:         uint64 oracleTimestamp,
296:         BeaconChainProofs.StateRootProof calldata stateRootProof,
297:         uint40[] calldata validatorIndices,
298:         bytes[] calldata validatorFieldsProofs,
299:         bytes32[][] calldata validatorFields
300:     )
301:         external
302:         onlyEigenPodOwner
303:         onlyWhenNotPaused(PAUSED_EIGENPODS_VERIFY_CREDENTIALS)
304:         
305:         proofIsForValidTimestamp(oracleTimestamp)
306:         
307:         hasEnabledRestaking
308:     {
309:         require(
310:             (validatorIndices.length == validatorFieldsProofs.length) &&
311:                 (validatorFieldsProofs.length == validatorFields.length),
312:             "EigenPod.verifyWithdrawalCredentials: validatorIndices and proofs must be same length"
313:         );
314: 
315:         
316: 
320:         require(
321:             oracleTimestamp + VERIFY_BALANCE_UPDATE_WINDOW_SECONDS >= block.timestamp,
322:             "EigenPod.verifyWithdrawalCredentials: specified timestamp is too far in past"
323:         );
324: 
325:         
326:         BeaconChainProofs.verifyStateRootAgainstLatestBlockRoot({
327:             latestBlockRoot: eigenPodManager.getBlockRootAtTimestamp(oracleTimestamp),
328:             beaconStateRoot: stateRootProof.beaconStateRoot,
329:             stateRootProof: stateRootProof.proof
330:         });
331: 
332:         uint256 totalAmountToBeRestakedWei;
333:         for (uint256 i = 0; i < validatorIndices.length; i++) {
334:             totalAmountToBeRestakedWei += _verifyWithdrawalCredentials(
335:                 oracleTimestamp,
336:                 stateRootProof.beaconStateRoot,
337:                 validatorIndices[i],
338:                 validatorFieldsProofs[i],
339:                 validatorFields[i]
340:             );
341:         }
342: 
343:         
344:         eigenPodManager.recordBeaconChainETHBalanceUpdate(podOwner, int256(totalAmountToBeRestakedWei));
345:     }

['134']

134:     function depositIntoStrategyWithSignature(
135:         IStrategy strategy,
136:         IERC20 token,
137:         uint256 amount,
138:         address staker,
139:         uint256 expiry,
140:         bytes memory signature
141:     ) external onlyWhenNotPaused(PAUSED_DEPOSITS) nonReentrant returns (uint256 shares) {
142:         require(
143:             !thirdPartyTransfersForbidden[strategy],
144:             "StrategyManager.depositIntoStrategyWithSignature: third transfers disabled"
145:         );
146:         require(expiry >= block.timestamp, "StrategyManager.depositIntoStrategyWithSignature: signature expired"); // <= FOUND
147:         
148:         uint256 nonce = nonces[staker];
149:         bytes32 structHash = keccak256(abi.encode(DEPOSIT_TYPEHASH, staker, strategy, token, amount, nonce, expiry));
150:         unchecked {
151:             nonces[staker] = nonce + 1;
152:         }
153: 
154:         
155:         bytes32 digestHash = keccak256(abi.encodePacked("\x19\x01", domainSeparator(), structHash));
156: 
157:         
158: 
163:         EIP1271SignatureUtils.checkSignature_EIP1271(staker, digestHash, signature);
164: 
165:         
166:         shares = _depositIntoStrategy(staker, strategy, token, amount);
167:     }

[NonCritical-56] Variables should be used in place of magic numbers to improve readability

Resolution

Magic numbers should be avoided in Solidity code to enhance readability, maintainability, and reduce the likelihood of errors. Magic numbers are hard-coded values with no clear meaning or context, which can create confusion and make the code harder to understand for developers. Using well-defined constants or variables with descriptive names instead of magic numbers not only clarifies the purpose and significance of the value but also simplifies code updates and modifications.

Num of instances: 28

Findings

Click to show findings

['393']

393:                 
394:                 
395:                 
396:                 
397:                 let cb := 1 // <= FOUND
398: 
399:                 let mc := add(_preBytes, 0x20)
400:                 let end := add(mc, length)
401: 
402:                 for {

['454']

454:                         
455:                         
456:                         
457:                         
458:                         let cb := 1 // <= FOUND
459: 
460:                         
461:                         mstore(0x0, _preBytes.slot)
462:                         let sc := keccak256(0x0, 0x20)
463: 
464:                         let mc := add(_postBytes, 0x20)
465:                         let end := add(mc, mlength)
466: 
467:                         
468:                         
469:                         
470:                         for {

['83']

83:             emit DelayedWithdrawalCreated(
84:                 podOwner,
85:                 recipient,
86:                 withdrawalAmount,
87:                 _userWithdrawals[recipient].delayedWithdrawals.length - 1 // <= FOUND
88:             );

['35']

35: 
37:     uint256 public constant MAX_STAKER_OPT_OUT_WINDOW_BLOCKS = (180 days) / 12; // <= FOUND

['14']

14:         
15:         n = uint64(uint256(lenum >> 192)); // <= FOUND

['398']

398:                 
399:                 stakerStrategyList[staker][j] = stakerStrategyList[staker][
400:                     stakerStrategyList[staker].length - 1 // <= FOUND
401:                 ];

['90']

90:             
91:             
92:             
93:             let fslot := sload(_preBytes.slot)
94:             
95:             
96:             
97:             
98:             
99:             
100:             
101:             let slength := div(and(fslot, sub(mul(0x100, iszero(and(fslot, 1))), 1)), 2) // <= FOUND
102:             let mlength := mload(_postBytes)
103:             let newlength := add(slength, mlength)
104:             
105:             
106:             
107:             switch add(lt(slength, 32), lt(newlength, 32)) // <= FOUND
108:             case 2 { // <= FOUND

['109']

109:                 
110:                 
111:                 
112:                 sstore(
113:                     _preBytes.slot,
114:                     
115:                     
116:                     add(
117:                         
118:                         
119:                         fslot,
120:                         add(
121:                             mul(
122:                                 div(
123:                                     
124:                                     mload(add(_postBytes, 0x20)),
125:                                     
126:                                     exp(0x100, sub(32, mlength))
127:                                 ),
128:                                 
129:                                 
130:                                 exp(0x100, sub(32, newlength))
131:                             ),
132:                             
133:                             
134:                             mul(mlength, 2) // <= FOUND
135:                         )
136:                     )
137:                 )
138:             }

['140']

140:                 
141:                 
142:                 
143:                 mstore(0x0, _preBytes.slot)
144:                 let sc := add(keccak256(0x0, 0x20), div(slength, 32)) // <= FOUND
145: 
146:                 
147:                 sstore(_preBytes.slot, add(mul(newlength, 2), 1)) // <= FOUND
148: 
149:                 
150:                 
151:                 
152:                 
153:                 
154:                 
155:                 
156:                 
157: 
158:                 let submod := sub(32, slength)
159:                 let mc := add(_postBytes, submod)
160:                 let end := add(_postBytes, mlength)
161:                 let mask := sub(exp(0x100, submod), 1) // <= FOUND
162: 
163:                 sstore(
164:                     sc,
165:                     add(
166:                         and(fslot, 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00),
167:                         and(mload(mc), mask)
168:                     )
169:                 )
170: 
171:                 for {

['184']

184:                 
185:                 mstore(0x0, _preBytes.slot)
186:                 
187:                 let sc := add(keccak256(0x0, 0x20), div(slength, 32)) // <= FOUND
188: 
189:                 
190:                 sstore(_preBytes.slot, add(mul(newlength, 2), 1)) // <= FOUND
191: 
192:                 
193:                 
194:                 let slengthmod := mod(slength, 32) // <= FOUND
195:                 
196:                 let mlengthmod := mod(mlength, 32) // <= FOUND
197:                 let submod := sub(32, slengthmod)
198:                 let mc := add(_postBytes, submod)
199:                 let end := add(_postBytes, mlength)
200:                 let mask := sub(exp(0x100, submod), 1) // <= FOUND
201: 
202:                 sstore(sc, add(sload(sc), and(mload(mc), mask)))
203: 
204:                 for {

['401']

401:                 eq(add(lt(mc, end), cb), 2) { // <= FOUND

['427']

427:             
428:             let fslot := sload(_preBytes.slot)
429:             
430:             let slength := div(and(fslot, sub(mul(0x100, iszero(and(fslot, 1))), 1)), 2) // <= FOUND
431:             let mlength := mload(_postBytes)
432: 
433:             
434:             switch eq(slength, mlength)
435:             case 1 { // <= FOUND

['468']

468: 
469:                         } eq(add(lt(mc, end), cb), 2) { // <= FOUND

['15']

15:         return
16:             (n >> 56) | // <= FOUND
17:             ((0x00FF000000000000 & n) >> 40) | // <= FOUND
18:             ((0x0000FF0000000000 & n) >> 24) | // <= FOUND
19:             ((0x000000FF00000000 & n) >> 8) | // <= FOUND
20:             ((0x00000000FF000000 & n) << 8) | // <= FOUND
21:             ((0x0000000000FF0000 & n) << 24) | // <= FOUND
22:             ((0x000000000000FF00 & n) << 40) | // <= FOUND
23:             ((0x00000000000000FF & n) << 56); // <= FOUND

['59']

59:             if (index % 2 == 0) { // <= FOUND

['62']

62:                     mstore(0x00, computedHash)
63:                     mstore(0x20, mload(add(proof, i)))
64:                     computedHash := keccak256(0x00, 0x40)
65:                     index := div(index, 2) // <= FOUND
66:                 }

['70']

70:                     mstore(0x00, mload(add(proof, i)))
71:                     mstore(0x20, computedHash)
72:                     computedHash := keccak256(0x00, 0x40)
73:                     index := div(index, 2) // <= FOUND
74:                 }

['121']

121:                     mstore(0x00, mload(computedHash))
122:                     mstore(0x20, mload(add(proof, i)))
123:                     if iszero(staticcall(sub(gas(), 2000), 2, 0x00, 0x40, computedHash, 0x20)) { // <= FOUND

['65']

65:                     index := div(index, 2) // <= FOUND
66:                 }

['131']

131:                     mstore(0x00, mload(add(proof, i)))
132:                     mstore(0x20, mload(computedHash))
133:                     if iszero(staticcall(sub(gas(), 2000), 2, 0x00, 0x40, computedHash, 0x20)) { // <= FOUND

['151']

151:         
152:         uint256 numNodesInLayer = leaves.length / 2; // <= FOUND

['159']

159:         
160:         numNodesInLayer /= 2; // <= FOUND

['167']

167:             
168:             numNodesInLayer /= 2; // <= FOUND

['73']

73: 
74:             
75:             
76:             
77:             
78:             
79:             mstore(
80:                 0x40,
81:                 and(
82:                     add(add(end, iszero(add(length, mload(_preBytes)))), 31), // <= FOUND
83:                     not(31) 
84:                 )
85:             )
86:         }

['231']

231:                 
232:                 
233:                 tempBytes := mload(0x40)
234: 
235:                 
236:                 
237:                 
238:                 
239:                 
240:                 
241:                 
242:                 
243:                 let lengthmod := and(_length, 31) // <= FOUND
244: 
245:                 
246:                 
247:                 
248:                 
249:                 let mc := add(add(tempBytes, lengthmod), mul(0x20, iszero(lengthmod)))
250:                 let end := add(mc, _length)
251: 
252:                 for {

['261']

261: 
262:                 mstore(tempBytes, _length)
263: 
264:                 
265:                 
266:                 mstore(0x40, and(add(mc, 31), not(31))) // <= FOUND
267:             }

['439']

439:                     switch lt(slength, 32) // <= FOUND
440:                     case 1 { // <= FOUND

['410']

410:         ethPOS.deposit{value: 32 ether}(pubkey, _podWithdrawalCredentials(), signature, depositDataRoot); // <= FOUND

[NonCritical-57] Redundant else statement

Num of instances: 12

Findings

Click to show findings

['171']

171:     function domainSeparator() public view returns (bytes32) { // <= FOUND
172:         if (block.chainid == ORIGINAL_CHAIN_ID) {
173:             return _DOMAIN_SEPARATOR;
174:         } else {
175:             return _calculateDomainSeparator();
176:         }
177:     }

['171']

171:     function domainSeparator() public view returns (bytes32) { // <= FOUND
172:         if (block.chainid == ORIGINAL_CHAIN_ID) {
173:             return _DOMAIN_SEPARATOR;
174:         } else {
175:             return _calculateDomainSeparator();
176:         }
177:     }

['171']

171:     function domainSeparator() public view returns (bytes32) { // <= FOUND
172:         if (block.chainid == ORIGINAL_CHAIN_ID) {
173:             return _DOMAIN_SEPARATOR;
174:         } else {
175:             return _calculateDomainSeparator();
176:         }
177:     }

['171']

171:     function domainSeparator() public view returns (bytes32) { // <= FOUND
172:         if (block.chainid == ORIGINAL_CHAIN_ID) {
173:             return _DOMAIN_SEPARATOR;
174:         } else {
175:             return _calculateDomainSeparator();
176:         }
177:     }

['878']

878:     function getDelegatableShares(address staker) public view returns (IStrategy[] memory, uint256[] memory) { // <= FOUND
879:         
880:         int256 podShares = eigenPodManager.podOwnerShares(staker);
881:         (IStrategy[] memory strategyManagerStrats, uint256[] memory strategyManagerShares) 
882:             = strategyManager.getDeposits(staker);
883: 
884:         
885:         if (podShares <= 0) {
886:             return (strategyManagerStrats, strategyManagerShares);
887:         }
888: 
889:         IStrategy[] memory strategies;
890:         uint256[] memory shares;
891: 
892:         if (strategyManagerStrats.length == 0) {
893:             
894:             strategies = new IStrategy[](1);
895:             shares = new uint256[](1);
896:             strategies[0] = beaconChainETHStrategy;
897:             shares[0] = uint256(podShares);
898:         } else {
899:             
900:             
901:             
902:             strategies = new IStrategy[](strategyManagerStrats.length + 1);
903:             shares = new uint256[](strategies.length);
904:             
905:             
906:             for (uint256 i = 0; i < strategyManagerStrats.length; ) {
907:                 strategies[i] = strategyManagerStrats[i];
908:                 shares[i] = strategyManagerShares[i];
909: 
910:                 unchecked { ++i; }
911:             }
912: 
913:             
914:             strategies[strategies.length - 1] = beaconChainETHStrategy;
915:             shares[strategies.length - 1] = uint256(podShares);
916:         }
917: 
918:         return (strategies, shares);
919:     }

[]

566:     function _verifyAndProcessWithdrawal(
567:         bytes32 beaconStateRoot,
568:         BeaconChainProofs.WithdrawalProof calldata withdrawalProof,
569:         bytes calldata validatorFieldsProof,
570:         bytes32[] calldata validatorFields,
571:         bytes32[] calldata withdrawalFields
572:     )
573:         internal
574:         
575: 
583:         proofIsForValidTimestamp(withdrawalProof.getWithdrawalTimestamp())
584:         returns (VerifiedWithdrawal memory)
585:     {
586:         uint64 withdrawalTimestamp = withdrawalProof.getWithdrawalTimestamp();
587:         bytes32 validatorPubkeyHash = validatorFields.getPubkeyHash();
588: 
589:         
590: 
593:         require(
594:             _validatorPubkeyHashToInfo[validatorPubkeyHash].status != VALIDATOR_STATUS.INACTIVE,
595:             "EigenPod._verifyAndProcessWithdrawal: Validator never proven to have withdrawal credentials pointed to this contract"
596:         );
597: 
598:         
599:         require(
600:             !provenWithdrawal[validatorPubkeyHash][withdrawalTimestamp],
601:             "EigenPod._verifyAndProcessWithdrawal: withdrawal has already been proven for this timestamp"
602:         );
603: 
604:         provenWithdrawal[validatorPubkeyHash][withdrawalTimestamp] = true;
605: 
606:         
607:         BeaconChainProofs.verifyWithdrawal({
608:             beaconStateRoot: beaconStateRoot, 
609:             withdrawalFields: withdrawalFields, 
610:             withdrawalProof: withdrawalProof,
611:             denebForkTimestamp: eigenPodManager.denebForkTimestamp()
612:         });
613: 
614:         uint40 validatorIndex = withdrawalFields.getValidatorIndex();
615: 
616:         
617:         BeaconChainProofs.verifyValidatorFields({
618:             beaconStateRoot: beaconStateRoot,
619:             validatorFields: validatorFields,
620:             validatorFieldsProof: validatorFieldsProof,
621:             validatorIndex: validatorIndex
622:         });
623: 
624:         uint64 withdrawalAmountGwei = withdrawalFields.getWithdrawalAmountGwei();
625:         
626:         
627: 
630:         if (withdrawalProof.getWithdrawalEpoch() >= validatorFields.getWithdrawableEpoch()) {
631:             return
632:                 _processFullWithdrawal(
633:                     validatorIndex,
634:                     validatorPubkeyHash,
635:                     withdrawalTimestamp,
636:                     podOwner,
637:                     withdrawalAmountGwei,
638:                     _validatorPubkeyHashToInfo[validatorPubkeyHash]
639:                 );
640:         } else {
641:             return
642:                 _processPartialWithdrawal(
643:                     validatorIndex,
644:                     withdrawalTimestamp,
645:                     podOwner,
646:                     withdrawalAmountGwei
647:                 );
648:         }
649:     }

['294']

294:     function _calculateChangeInDelegatableShares(int256 sharesBefore, int256 sharesAfter) internal pure returns (int256) { // <= FOUND
295:         if (sharesBefore <= 0) {
296:             
297:             if (sharesAfter <= 0) {
298:                 return 0;
299:             
300:             } else {
301:                 return sharesAfter;
302:             }
303:         } else {
304:             
305:             if (sharesAfter <= 0) {
306:                 return (-sharesBefore);
307:             
308:             
309:             } else {
310:                 return (sharesAfter - sharesBefore);
311:             }
312:         }
313:     }

['350']

350:     function denebForkTimestamp() public view returns (uint64) { // <= FOUND
351:         uint64 timestamp = _denebForkTimestamp;
352:         if (timestamp == 0) {
353:             return type(uint64).max;
354:         } else {
355:             return timestamp;
356:         }
357:     }

['28']

28:     function listExists(List storage self) internal view returns (bool) { // <= FOUND
29:         
30:         if (self.list[_HEAD][_PREV] != _HEAD || self.list[_HEAD][_NEXT] != _HEAD) {
31:             return true;
32:         } else {
33:             return false;
34:         }
35:     }

['43']

43:     function nodeExists(List storage self, uint256 _node) internal view returns (bool) { // <= FOUND
44:         if (self.list[_node][_PREV] == _HEAD && self.list[_node][_NEXT] == _HEAD) {
45:             if (self.list[_HEAD][_NEXT] == _node) {
46:                 return true;
47:             } else {
48:                 return false;
49:             }
50:         } else {
51:             return true;
52:         }
53:     }

['79']

79:     function getNode(List storage self, uint256 _node) internal view returns (bool, uint256, uint256) { // <= FOUND
80:         if (!nodeExists(self, _node)) {
81:             return (false, 0, 0);
82:         } else {
83:             return (true, self.list[_node][_PREV], self.list[_node][_NEXT]);
84:         }
85:     }

['94']

94:     function getAdjacent(List storage self, uint256 _node, bool _direction) internal view returns (bool, uint256) { // <= FOUND
95:         if (!nodeExists(self, _node)) {
96:             return (false, 0);
97:         } else {
98:             uint256 adjacent = self.list[_node][_direction];
99:             return (adjacent != _HEAD, adjacent);
100:         }
101:     }

[NonCritical-58] Use EIP-5767 to manage EIP712 domains

Num of instances: 1

Findings

Click to show findings

['16']

14:     
15:     bytes32 public constant DOMAIN_TYPEHASH =
16:         keccak256("EIP712Domain(string name,uint256 chainId,address verifyingContract)"); // <= FOUND

[NonCritical-59] Constant array index within iteration

Resolution

Using constant array indexes within for loops is error prone as typically [i] is used instead as by using a constant index such as [0] means that only one value will be used/modified within an array, historically many bugs have been introduced through using a constant index rather than the array iteration index i.e i,j. Provided this is intentional and not a typo consider using enum values to make this distinction clear.

Num of instances: 1

Findings

Click to show findings

['244']

241:            for (uint256 i = 0; i < strategies.length; i++) {
242:                 IStrategy[] memory singleStrategy = new IStrategy[](1);
243:                 uint256[] memory singleShare = new uint256[](1);
244:                 singleStrategy[0] = strategies[i]; // <= FOUND
245:                 singleShare[0] = shares[i]; // <= FOUND
246: 
247:                 withdrawalRoots[i] = _removeSharesAndQueueWithdrawal({
248:                     staker: staker,
249:                     operator: operator,
250:                     withdrawer: staker,
251:                     strategies: singleStrategy,
252:                     shares: singleShare
253:                 });
254:             }

[NonCritical-60] Empty bytes check is missing

Resolution

When developing smart contracts in Solidity, it's crucial to validate the inputs of your functions. This includes ensuring that the bytes parameters are not empty, especially when they represent crucial data such as addresses, identifiers, or raw data that the contract needs to process.

Missing empty bytes checks can lead to unexpected behaviour in your contract. For instance, certain operations might fail, produce incorrect results, or consume unnecessary gas when performed with empty bytes. Moreover, missing input validation can potentially expose your contract to malicious activity, including exploitation of unhandled edge cases.

To mitigate these issues, always validate that bytes parameters are not empty when the logic of your contract requires it.

Num of instances: 29

Findings

Click to show findings

['134']

134:     function cancelSalt(bytes32 salt) external {
135:         require(!operatorSaltIsSpent[msg.sender][salt], "AVSDirectory.cancelSalt: cannot cancel spent salt");
136:         operatorSaltIsSpent[msg.sender][salt] = true;
137:     }

['150']

150:     function calculateOperatorAVSRegistrationDigestHash(
151:         address operator,
152:         address avs,
153:         bytes32 salt,
154:         uint256 expiry
155:     ) public view returns (bytes32) {
156:         
157:         bytes32 structHash = keccak256(
158:             abi.encode(OPERATOR_AVS_REGISTRATION_TYPEHASH, operator, avs, salt, expiry)
159:         );
160:         
161:         bytes32 digestHash = keccak256(
162:             abi.encodePacked("\x19\x01", domainSeparator(), structHash)
163:         );
164:         return digestHash;
165:     }

['360']

360:     function getPubkeyHash(bytes32[] memory validatorFields) internal pure returns (bytes32) {
361:         return 
362:             validatorFields[VALIDATOR_PUBKEY_INDEX];
363:     }

['365']

365:     function getWithdrawalCredentials(bytes32[] memory validatorFields) internal pure returns (bytes32) {
366:         return
367:             validatorFields[VALIDATOR_WITHDRAWAL_CREDENTIALS_INDEX];
368:     }

['373']

373:     function getEffectiveBalanceGwei(bytes32[] memory validatorFields) internal pure returns (uint64) {
374:         return 
375:             Endian.fromLittleEndianUint64(validatorFields[VALIDATOR_BALANCE_INDEX]);
376:     }

['381']

381:     function getWithdrawableEpoch(bytes32[] memory validatorFields) internal pure returns (uint64) {
382:         return 
383:             Endian.fromLittleEndianUint64(validatorFields[VALIDATOR_WITHDRAWABLE_EPOCH_INDEX]);
384:     }

['397']

397:     function getValidatorIndex(bytes32[] memory withdrawalFields) internal pure returns (uint40) {
398:         return 
399:             uint40(Endian.fromLittleEndianUint64(withdrawalFields[WITHDRAWAL_VALIDATOR_INDEX_INDEX]));
400:     }

['405']

405:     function getWithdrawalAmountGwei(bytes32[] memory withdrawalFields) internal pure returns (uint64) {
406:         return
407:             Endian.fromLittleEndianUint64(withdrawalFields[WITHDRAWAL_VALIDATOR_AMOUNT_INDEX]);
408:     }

['11']

11:     function concat(bytes memory _preBytes, bytes memory _postBytes) internal pure returns (bytes memory) {
12:         bytes memory tempBytes;
13: 
14:         assembly {
15:             
16:             
17:             tempBytes := mload(0x40)
18: 
19:             
20:             
21:             let length := mload(_preBytes)
22:             mstore(tempBytes, length)
23: 
24:             
25:             
26:             
27:             let mc := add(tempBytes, 0x20)
28:             
29:             
30:             let end := add(mc, length)
31: 
32:             for {
33:                 
34:                 
35:                 let cc := add(_preBytes, 0x20)
36:             } lt(mc, end) {
37:                 
38:                 mc := add(mc, 0x20)
39:                 cc := add(cc, 0x20)
40:             } {
41:                 
42:                 
43:                 mstore(mc, mload(cc))
44:             }
45: 
46:             
47:             
48:             
49:             length := mload(_postBytes)
50:             mstore(tempBytes, add(length, mload(tempBytes)))
51: 
52:             
53:             
54:             mc := end
55:             
56:             
57:             end := add(mc, length)
58: 
59:             for {
60:                 let cc := add(_postBytes, 0x20)
61:             } lt(mc, end) {
62:                 mc := add(mc, 0x20)
63:                 cc := add(cc, 0x20)
64:             } {
65:                 mstore(mc, mload(cc))
66:             }
67: 
68:             
69:             
70:             
71:             
72:             
73:             mstore(
74:                 0x40,
75:                 and(
76:                     add(add(end, iszero(add(length, mload(_preBytes)))), 31),
77:                     not(31) 
78:                 )
79:             )
80:         }
81: 
82:         return tempBytes;
83:     }

['380']

380:     function equal(bytes memory _preBytes, bytes memory _postBytes) internal pure returns (bool) {
381:         bool success = true;
382: 
383:         assembly {
384:             let length := mload(_preBytes)
385: 
386:             
387:             switch eq(length, mload(_postBytes))
388:             case 1 {
389:                 
390:                 
391:                 
392:                 
393:                 let cb := 1
394: 
395:                 let mc := add(_preBytes, 0x20)
396:                 let end := add(mc, length)
397: 
398:                 for {
399:                     let cc := add(_postBytes, 0x20)
400:                 } 
401:                 eq(add(lt(mc, end), cb), 2) {
402:                     mc := add(mc, 0x20)
403:                     cc := add(cc, 0x20)
404:                 } {
405:                     
406:                     if iszero(eq(mload(mc), mload(cc))) {
407:                         
408:                         success := 0
409:                         cb := 0
410:                     }
411:                 }
412:             }
413:             default {
414:                 
415:                 success := 0
416:             }
417:         }
418: 
419:         return success;
420:     }

['422']

422:     function equalStorage(bytes storage _preBytes, bytes memory _postBytes) internal view returns (bool) {
423:         bool success = true;
424: 
425:         assembly {
426:             
427:             let fslot := sload(_preBytes.slot)
428:             
429:             let slength := div(and(fslot, sub(mul(0x100, iszero(and(fslot, 1))), 1)), 2)
430:             let mlength := mload(_postBytes)
431: 
432:             
433:             switch eq(slength, mlength)
434:             case 1 {
435:                 
436:                 
437:                 
438:                 if iszero(iszero(slength)) {
439:                     switch lt(slength, 32)
440:                     case 1 {
441:                         
442:                         fslot := mul(div(fslot, 0x100), 0x100)
443: 
444:                         if iszero(eq(fslot, mload(add(_postBytes, 0x20)))) {
445:                             
446:                             success := 0
447:                         }
448:                     }
449:                     default {
450:                         
451:                         
452:                         
453:                         
454:                         let cb := 1
455: 
456:                         
457:                         mstore(0x0, _preBytes.slot)
458:                         let sc := keccak256(0x0, 0x20)
459: 
460:                         let mc := add(_postBytes, 0x20)
461:                         let end := add(mc, mlength)
462: 
463:                         
464:                         
465:                         
466:                         for {
467: 
468:                         } eq(add(lt(mc, end), cb), 2) {
469:                             sc := add(sc, 1)
470:                             mc := add(mc, 0x20)
471:                         } {
472:                             if iszero(eq(sload(sc), mload(mc))) {
473:                                 
474:                                 success := 0
475:                                 cb := 0
476:                             }
477:                         }
478:                     }
479:                 }
480:             }
481:             default {
482:                 
483:                 success := 0
484:             }
485:         }
486: 
487:         return success;
488:     }

['148']

148:     function delegateTo(
149:         address operator,
150:         SignatureWithExpiry memory approverSignatureAndExpiry,
151:         bytes32 approverSalt
152:     ) external {
153:         
154:         _delegate(msg.sender, operator, approverSignatureAndExpiry, approverSalt);
155:     }

['174']

174:     function delegateToBySignature(
175:         address staker,
176:         address operator,
177:         SignatureWithExpiry memory stakerSignatureAndExpiry,
178:         SignatureWithExpiry memory approverSignatureAndExpiry,
179:         bytes32 approverSalt
180:     ) external {
181:         
182:         require(
183:             stakerSignatureAndExpiry.expiry >= block.timestamp,
184:             "DelegationManager.delegateToBySignature: staker signature expired"
185:         );
186: 
187:         
188:         uint256 currentStakerNonce = stakerNonce[staker];
189:         bytes32 stakerDigestHash = calculateStakerDelegationDigestHash(
190:             staker,
191:             currentStakerNonce,
192:             operator,
193:             stakerSignatureAndExpiry.expiry
194:         );
195:         unchecked {
196:             stakerNonce[staker] = currentStakerNonce + 1;
197:         }
198: 
199:         
200:         EIP1271SignatureUtils.checkSignature_EIP1271(staker, stakerDigestHash, stakerSignatureAndExpiry.signature);
201: 
202:         
203:         _delegate(staker, operator, approverSignatureAndExpiry, approverSalt);
204:     }

['989']

989:     function calculateDelegationApprovalDigestHash(
990:         address staker,
991:         address operator,
992:         address _delegationApprover,
993:         bytes32 approverSalt,
994:         uint256 expiry
995:     ) public view returns (bytes32) {
996:         
997:         bytes32 approverStructHash = keccak256(
998:             abi.encode(DELEGATION_APPROVAL_TYPEHASH, _delegationApprover, staker, operator, approverSalt, expiry)
999:         );
1000:         
1001:         bytes32 approverDigestHash = keccak256(abi.encodePacked("\x19\x01", domainSeparator(), approverStructHash));
1002:         return approverDigestHash;
1003:     }

['403']

403:     function stake(
404:         bytes calldata pubkey,
405:         bytes calldata signature,
406:         bytes32 depositDataRoot
407:     ) external payable onlyEigenPodManager {
408:         
409:         require(msg.value == 32 ether, "EigenPod.stake: must initially stake for any validator with 32 ether");
410:         ethPOS.deposit{value: 32 ether}(pubkey, _podWithdrawalCredentials(), signature, depositDataRoot);
411:         emit EigenPodStaked(pubkey);
412:     }

['441']

441:     function _verifyWithdrawalCredentials(
442:         uint64 oracleTimestamp,
443:         bytes32 beaconStateRoot,
444:         uint40 validatorIndex,
445:         bytes calldata validatorFieldsProof,
446:         bytes32[] calldata validatorFields
447:     ) internal returns (uint256) {
448:         bytes32 validatorPubkeyHash = validatorFields.getPubkeyHash();
449:         ValidatorInfo memory validatorInfo = _validatorPubkeyHashToInfo[validatorPubkeyHash];
450: 
451:         
452:         require(
453:             validatorInfo.status == VALIDATOR_STATUS.INACTIVE,
454:             "EigenPod.verifyCorrectWithdrawalCredentials: Validator must be inactive to prove withdrawal credentials"
455:         );
456: 
457:         
458:         require(
459:             validatorFields.getWithdrawalCredentials() == bytes32(_podWithdrawalCredentials()),
460:             "EigenPod.verifyCorrectWithdrawalCredentials: Proof is not for this EigenPod"
461:         );
462: 
463:         
464: 
471:         uint64 validatorEffectiveBalanceGwei = validatorFields.getEffectiveBalanceGwei();
472: 
473:         
474:         BeaconChainProofs.verifyValidatorFields({
475:             beaconStateRoot: beaconStateRoot,
476:             validatorFields: validatorFields,
477:             validatorFieldsProof: validatorFieldsProof,
478:             validatorIndex: validatorIndex
479:         });
480: 
481:         
482:         validatorInfo.status = VALIDATOR_STATUS.ACTIVE;
483:         validatorInfo.validatorIndex = validatorIndex;
484:         validatorInfo.mostRecentBalanceUpdateTimestamp = oracleTimestamp;
485: 
486:         if (validatorEffectiveBalanceGwei > MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR) {
487:             validatorInfo.restakedBalanceGwei = MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR;
488:         } else {
489:             validatorInfo.restakedBalanceGwei = validatorEffectiveBalanceGwei;
490:         }
491:         _validatorPubkeyHashToInfo[validatorPubkeyHash] = validatorInfo;
492: 
493:         emit ValidatorRestaked(validatorIndex);
494:         emit ValidatorBalanceUpdated(validatorIndex, oracleTimestamp, validatorInfo.restakedBalanceGwei);
495: 
496:         return validatorInfo.restakedBalanceGwei * GWEI_TO_WEI;
497:     }

['499']

499:     function _verifyBalanceUpdate(
500:         uint64 oracleTimestamp,
501:         uint40 validatorIndex,
502:         bytes32 beaconStateRoot,
503:         bytes calldata validatorFieldsProof,
504:         bytes32[] calldata validatorFields
505:     ) internal returns(int256 sharesDeltaGwei){
506:         uint64 validatorEffectiveBalanceGwei = validatorFields.getEffectiveBalanceGwei();
507:         bytes32 validatorPubkeyHash = validatorFields.getPubkeyHash();
508:         ValidatorInfo memory validatorInfo = _validatorPubkeyHashToInfo[validatorPubkeyHash];
509: 
510:         
511:         require(
512:             validatorInfo.mostRecentBalanceUpdateTimestamp < oracleTimestamp,
513:             "EigenPod.verifyBalanceUpdate: Validators balance has already been updated for this timestamp"
514:         );
515: 
516:         
517:         require(
518:             validatorInfo.status == VALIDATOR_STATUS.ACTIVE, 
519:             "EigenPod.verifyBalanceUpdate: Validator not active"
520:         );
521: 
522:         
523:         
524:         
525:         if (validatorFields.getWithdrawableEpoch() <= _timestampToEpoch(oracleTimestamp)) {
526:             require(
527:                 validatorEffectiveBalanceGwei > 0,
528:                 "EigenPod.verifyBalanceUpdate: validator is withdrawable but has not withdrawn"
529:             );
530:         }
531: 
532:         
533:         BeaconChainProofs.verifyValidatorFields({
534:             beaconStateRoot: beaconStateRoot,
535:             validatorFields: validatorFields,
536:             validatorFieldsProof: validatorFieldsProof,
537:             validatorIndex: validatorIndex
538:         });
539: 
540:         
541: 
542:         uint64 currentRestakedBalanceGwei = validatorInfo.restakedBalanceGwei;
543:         uint64 newRestakedBalanceGwei;
544:         if (validatorEffectiveBalanceGwei > MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR) {
545:             newRestakedBalanceGwei = MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR;
546:         } else {
547:             newRestakedBalanceGwei = validatorEffectiveBalanceGwei;
548:         }
549:         
550:         
551:         validatorInfo.restakedBalanceGwei = newRestakedBalanceGwei;
552:         validatorInfo.mostRecentBalanceUpdateTimestamp = oracleTimestamp;
553:         _validatorPubkeyHashToInfo[validatorPubkeyHash] = validatorInfo;
554: 
555:         
556:         if (newRestakedBalanceGwei != currentRestakedBalanceGwei) {
557:             emit ValidatorBalanceUpdated(validatorIndex, oracleTimestamp, newRestakedBalanceGwei);
558: 
559:             sharesDeltaGwei = _calculateSharesDelta({
560:                 newAmountGwei: newRestakedBalanceGwei,
561:                 previousAmountGwei: currentRestakedBalanceGwei
562:             });
563:         }
564:     }

['566']

566:     function _verifyAndProcessWithdrawal(
567:         bytes32 beaconStateRoot,
568:         BeaconChainProofs.WithdrawalProof calldata withdrawalProof,
569:         bytes calldata validatorFieldsProof,
570:         bytes32[] calldata validatorFields,
571:         bytes32[] calldata withdrawalFields
572:     )
573:         internal
574:         
575: 
583:         proofIsForValidTimestamp(withdrawalProof.getWithdrawalTimestamp())
584:         returns (VerifiedWithdrawal memory)
585:     {
586:         uint64 withdrawalTimestamp = withdrawalProof.getWithdrawalTimestamp();
587:         bytes32 validatorPubkeyHash = validatorFields.getPubkeyHash();
588: 
589:         
590: 
593:         require(
594:             _validatorPubkeyHashToInfo[validatorPubkeyHash].status != VALIDATOR_STATUS.INACTIVE,
595:             "EigenPod._verifyAndProcessWithdrawal: Validator never proven to have withdrawal credentials pointed to this contract"
596:         );
597: 
598:         
599:         require(
600:             !provenWithdrawal[validatorPubkeyHash][withdrawalTimestamp],
601:             "EigenPod._verifyAndProcessWithdrawal: withdrawal has already been proven for this timestamp"
602:         );
603: 
604:         provenWithdrawal[validatorPubkeyHash][withdrawalTimestamp] = true;
605: 
606:         
607:         BeaconChainProofs.verifyWithdrawal({
608:             beaconStateRoot: beaconStateRoot, 
609:             withdrawalFields: withdrawalFields, 
610:             withdrawalProof: withdrawalProof,
611:             denebForkTimestamp: eigenPodManager.denebForkTimestamp()
612:         });
613: 
614:         uint40 validatorIndex = withdrawalFields.getValidatorIndex();
615: 
616:         
617:         BeaconChainProofs.verifyValidatorFields({
618:             beaconStateRoot: beaconStateRoot,
619:             validatorFields: validatorFields,
620:             validatorFieldsProof: validatorFieldsProof,
621:             validatorIndex: validatorIndex
622:         });
623: 
624:         uint64 withdrawalAmountGwei = withdrawalFields.getWithdrawalAmountGwei();
625:         
626:         
627: 
630:         if (withdrawalProof.getWithdrawalEpoch() >= validatorFields.getWithdrawableEpoch()) {
631:             return
632:                 _processFullWithdrawal(
633:                     validatorIndex,
634:                     validatorPubkeyHash,
635:                     withdrawalTimestamp,
636:                     podOwner,
637:                     withdrawalAmountGwei,
638:                     _validatorPubkeyHashToInfo[validatorPubkeyHash]
639:                 );
640:         } else {
641:             return
642:                 _processPartialWithdrawal(
643:                     validatorIndex,
644:                     withdrawalTimestamp,
645:                     podOwner,
646:                     withdrawalAmountGwei
647:                 );
648:         }
649:     }

['651']

651:     function _processFullWithdrawal(
652:         uint40 validatorIndex,
653:         bytes32 validatorPubkeyHash,
654:         uint64 withdrawalTimestamp,
655:         address recipient,
656:         uint64 withdrawalAmountGwei,
657:         ValidatorInfo memory validatorInfo
658:     ) internal returns (VerifiedWithdrawal memory) {
659: 
660:         
661: 
666:         uint64 amountToQueueGwei;
667: 
668:         if (withdrawalAmountGwei > MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR) {
669:             amountToQueueGwei = MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR;
670:         } else {
671:             amountToQueueGwei = withdrawalAmountGwei;
672:         }
673: 
674:         
675: 
679:         VerifiedWithdrawal memory verifiedWithdrawal;
680:         verifiedWithdrawal.amountToSendGwei = uint256(withdrawalAmountGwei - amountToQueueGwei);
681:         withdrawableRestakedExecutionLayerGwei += amountToQueueGwei;
682:         
683:         
684: 
691:         verifiedWithdrawal.sharesDeltaGwei = _calculateSharesDelta({
692:             newAmountGwei: amountToQueueGwei,
693:             previousAmountGwei: validatorInfo.restakedBalanceGwei
694:         });
695: 
696:         
697: 
700:         validatorInfo.restakedBalanceGwei = 0;
701:         validatorInfo.status = VALIDATOR_STATUS.WITHDRAWN;
702: 
703:         _validatorPubkeyHashToInfo[validatorPubkeyHash] = validatorInfo;
704: 
705:         emit FullWithdrawalRedeemed(validatorIndex, withdrawalTimestamp, recipient, withdrawalAmountGwei);
706: 
707:         return verifiedWithdrawal;
708:     }

['779']

779:     function validatorPubkeyHashToInfo(bytes32 validatorPubkeyHash) external view returns (ValidatorInfo memory) {
780:         return _validatorPubkeyHashToInfo[validatorPubkeyHash];
781:     }

['784']

784:     function validatorPubkeyToInfo(bytes calldata validatorPubkey) external view returns (ValidatorInfo memory) {
785:         return _validatorPubkeyHashToInfo[_calculateValidatorPubkeyHash(validatorPubkey)];
786:     }

['788']

788:     function validatorStatus(bytes32 pubkeyHash) external view returns (VALIDATOR_STATUS) {
789:         return _validatorPubkeyHashToInfo[pubkeyHash].status;
790:     }

['793']

793:     function validatorStatus(bytes calldata validatorPubkey) external view returns (VALIDATOR_STATUS) {
794:         bytes32 validatorPubkeyHash = _calculateValidatorPubkeyHash(validatorPubkey);
795:         return _validatorPubkeyHashToInfo[validatorPubkeyHash].status;
796:     }

['90']

90:     function stake(
91:         bytes calldata pubkey, 
92:         bytes calldata signature, 
93:         bytes32 depositDataRoot
94:     ) external payable onlyWhenNotPaused(PAUSED_NEW_EIGENPODS) {
95:         IEigenPod pod = ownerToPod[msg.sender];
96:         if (address(pod) == address(0)) {
97:             
98:             pod = _deployPod();
99:         }
100:         pod.stake{value: msg.value}(pubkey, signature, depositDataRoot);
101:     }

['22']

22:     function checkSignature_EIP1271(address signer, bytes32 digestHash, bytes memory signature) internal view {
23:         
24: 
29:         if (Address.isContract(signer)) {
30:             require(
31:                 IERC1271(signer).isValidSignature(digestHash, signature) == EIP1271_MAGICVALUE,
32:                 "EIP1271SignatureUtils.checkSignature_EIP1271: ERC1271 signature verification failed"
33:             );
34:         } else {
35:             require(
36:                 ECDSA.recover(digestHash, signature) == signer,
37:                 "EIP1271SignatureUtils.checkSignature_EIP1271: signature not from signer"
38:             );
39:         }
40:     }

['12']

12:     function fromLittleEndianUint64(bytes32 lenum) internal pure returns (uint64 n) {
13:         
14:         n = uint64(uint256(lenum >> 192));
15:         return
16:             (n >> 56) |
17:             ((0x00FF000000000000 & n) >> 40) |
18:             ((0x0000FF0000000000 & n) >> 24) |
19:             ((0x000000FF00000000 & n) >> 8) |
20:             ((0x00000000FF000000 & n) << 8) |
21:             ((0x0000000000FF0000 & n) << 24) |
22:             ((0x000000000000FF00 & n) << 40) |
23:             ((0x00000000000000FF & n) << 56);
24:     }

['29']

29:     function verifyInclusionKeccak(
30:         bytes memory proof,
31:         bytes32 root,
32:         bytes32 leaf,
33:         uint256 index
34:     ) internal pure returns (bool) {
35:         return processInclusionProofKeccak(proof, leaf, index) == root;
36:     }

['88']

88:     function verifyInclusionSha256(
89:         bytes memory proof,
90:         bytes32 root,
91:         bytes32 leaf,
92:         uint256 index
93:     ) internal view returns (bool) {
94:         return processInclusionProofSha256(proof, leaf, index) == root;
95:     }

['134']

134:     function depositIntoStrategyWithSignature(
135:         IStrategy strategy,
136:         IERC20 token,
137:         uint256 amount,
138:         address staker,
139:         uint256 expiry,
140:         bytes memory signature
141:     ) external onlyWhenNotPaused(PAUSED_DEPOSITS) nonReentrant returns (uint256 shares) {
142:         require(
143:             !thirdPartyTransfersForbidden[strategy],
144:             "StrategyManager.depositIntoStrategyWithSignature: third transfers disabled"
145:         );
146:         require(expiry >= block.timestamp, "StrategyManager.depositIntoStrategyWithSignature: signature expired");
147:         
148:         uint256 nonce = nonces[staker];
149:         bytes32 structHash = keccak256(abi.encode(DEPOSIT_TYPEHASH, staker, strategy, token, amount, nonce, expiry));
150:         unchecked {
151:             nonces[staker] = nonce + 1;
152:         }
153: 
154:         
155:         bytes32 digestHash = keccak256(abi.encodePacked("\x19\x01", domainSeparator(), structHash));
156: 
157:         
158: 
163:         EIP1271SignatureUtils.checkSignature_EIP1271(staker, digestHash, signature);
164: 
165:         
166:         shares = _depositIntoStrategy(staker, strategy, token, amount);
167:     }

[NonCritical-61] Use max instead of 0xfff...

Resolution

In Solidity, it's common to use a maximum value to indicate "unlimited" or "all," especially when dealing with token allowances in the context of ERC20 tokens. However, using a manually inserted large value such as 0xfff... is prone to errors and decreases the readability and maintainability of the code.

A better approach is to use a predefined maximum value constant. In Solidity, the type(uint256).max constant represents the largest number a uint256 can hold. This constant clearly communicates the intention of representing the maximum possible value, and is less prone to errors than typing out a long series of fs.

Num of instances: 2

Findings

Click to show findings

['82']

81: 
82:     bytes8 internal constant UINT64_MASK = 0xffffffffffffffff; // <= FOUND

['166']

140:                 
141:                 
142:                 
143:                 mstore(0x0, _preBytes.slot)
144:                 let sc := add(keccak256(0x0, 0x20), div(slength, 32))
145: 
146:                 
147:                 sstore(_preBytes.slot, add(mul(newlength, 2), 1))
148: 
149:                 
150:                 
151:                 
152:                 
153:                 
154:                 
155:                 
156:                 
157: 
158:                 let submod := sub(32, slength)
159:                 let mc := add(_postBytes, submod)
160:                 let end := add(_postBytes, mlength)
161:                 let mask := sub(exp(0x100, submod), 1)
162: 
163:                 sstore(
164:                     sc,
165:                     add(
166:                         and(fslot, 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00), // <= FOUND
167:                         and(mload(mc), mask)
168:                     )
169:                 )
170: 
171:                 for {

[NonCritical-62] Use scopes sparingly

Resolution

In Solidity programming, the use of scoped blocks, denoted by {} without a preceding control structure like if, for, etc., allows for the creation of isolated scopes within a function. While this can be useful for managing memory and preventing naming conflicts, it should be used sparingly. Excessive use of these scope blocks can obscure the code's logic flow and make it more difficult to understand, impeding code maintainability. As a best practice, only employ scoped blocks when necessary for memory management or to avoid clear naming conflicts. Otherwise, aim for clarity and simplicity in your code structure for optimal readability and maintainability.

Num of instances: 7

Findings

Click to show findings

['267']

267:         { // <= FOUND
268:             
269:             uint256 executionPayloadIndex = (BODY_ROOT_INDEX << (BEACON_BLOCK_BODY_FIELD_TREE_HEIGHT)) |
270:                 EXECUTION_PAYLOAD_INDEX;
271:             require(
272:                 Merkle.verifyInclusionSha256({ // <= FOUND
273:                     proof: withdrawalProof.executionPayloadProof,
274:                     root: withdrawalProof.blockRoot,
275:                     leaf: withdrawalProof.executionPayloadRoot,
276:                     index: executionPayloadIndex
277:                 }

['293']

293:         { // <= FOUND
294:             
295: 
302:             uint256 withdrawalIndex = (WITHDRAWALS_INDEX << (WITHDRAWALS_TREE_HEIGHT + 1)) |
303:                 uint256(withdrawalProof.withdrawalIndex);
304:             bytes32 withdrawalRoot = Merkle.merkleizeSha256(withdrawalFields);
305:             require(
306:                 Merkle.verifyInclusionSha256({ // <= FOUND
307:                     proof: withdrawalProof.withdrawalProof,
308:                     root: withdrawalProof.executionPayloadRoot,
309:                     leaf: withdrawalRoot,
310:                     index: withdrawalIndex
311:                 }

['40']

40: { // <= FOUND
41:                 
42:                 
43:                 mstore(mc, mload(cc))
44:             }

['64']

64: { // <= FOUND
65:                 mstore(mc, mload(cc))
66:             }

['174']

174: { // <= FOUND
175:                     sstore(sc, mload(mc))
176:                 }

['257']

257: { // <= FOUND
258:                     mstore(mc, mload(cc))
259:                 }

['404']

404: { // <= FOUND
405:                     
406:                     if iszero(eq(mload(mc), mload(cc))) { // <= FOUND
407:                         
408:                         success := 0
409:                         cb := 0
410:                     }
411:                 }

[NonCritical-63] Remove unnecessary solhint-disable

Num of instances: 3

Findings

Click to show findings

['26']

26:     // solhint-disable-next-line no-empty-blocks // <= FOUND

['195']

195:                 // solhint-disable-next-line no-unused-vars // <= FOUND

['466']

466:                         // solhint-disable-next-line no-empty-blocks // <= FOUND

[NonCritical-64] Consider using SMTChecker

Resolution

The SMTChecker is a valuable tool for Solidity developers as it helps detect potential vulnerabilities and logical errors in the contract's code. By utilizing Satisfiability Modulo Theories (SMT) solvers, it can reason about the potential states a contract can be in, and therefore, identify conditions that could lead to undesirable behavior. This automatic formal verification can catch issues that might otherwise be missed in manual code reviews or standard testing, enhancing the overall contract's security and reliability.

Num of instances: 39

Findings

Click to show findings

['1']

1: // SPDX-License-Identifier: BUSL-1.1
2: pragma solidity =0.8.12;
3: 
4: import "../interfaces/IPauserRegistry.sol";
5: 
6: /**
7:  * @title Defines pauser & unpauser roles + modifiers to be used elsewhere.
8:  * @author Layr Labs, Inc.
9:  * @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service
10:  */
11: contract PauserRegistry is IPauserRegistry {
12:     /// @notice Mapping of addresses to whether they hold the pauser role.
13:     mapping(address => bool) public isPauser;
14: 
15:     /// @notice Unique address that holds the unpauser role. Capable of changing *both* the pauser and unpauser addresses.
16:     address public unpauser;
17: 
18:     modifier onlyUnpauser() {
19:         require(msg.sender == unpauser, "msg.sender is not permissioned as unpauser");
20:         _;
21:     }
22: 
23:     constructor(address[] memory _pausers, address _unpauser) {
24:         for (uint256 i = 0; i < _pausers.length; i++) {
25:             _setIsPauser(_pausers[i], true);
26:         }
27:         _setUnpauser(_unpauser);
28:     }
29: 
30:     /// @notice Sets new pauser - only callable by unpauser, as the unpauser is expected to be kept more secure, e.g. being a multisig with a higher threshold
31:     /// @param newPauser Address to be added/removed as pauser
32:     /// @param canPause Whether the address should be added or removed as pauser
33:     function setIsPauser(address newPauser, bool canPause) external onlyUnpauser {
34:         _setIsPauser(newPauser, canPause);
35:     }
36: 
37:     /// @notice Sets new unpauser - only callable by unpauser, as the unpauser is expected to be kept more secure, e.g. being a multisig with a higher threshold
38:     function setUnpauser(address newUnpauser) external onlyUnpauser {
39:         _setUnpauser(newUnpauser);
40:     }
41: 
42:     function _setIsPauser(address pauser, bool canPause) internal {
43:         require(pauser != address(0), "PauserRegistry._setPauser: zero address input");
44:         isPauser[pauser] = canPause;
45:         emit PauserStatusChanged(pauser, canPause);
46:     }
47: 
48:     function _setUnpauser(address newUnpauser) internal {
49:         require(newUnpauser != address(0), "PauserRegistry._setUnpauser: zero address input");
50:         emit UnpauserChanged(unpauser, newUnpauser);
51:         unpauser = newUnpauser;
52:     }
53: }
54: 

['1']

1: // SPDX-License-Identifier: BUSL-1.1
2: pragma solidity =0.8.12;
3: 
4: import "@openzeppelin/contracts/utils/Create2.sol";
5: import "@openzeppelin-upgrades/contracts/proxy/utils/Initializable.sol";
6: import "@openzeppelin-upgrades/contracts/access/OwnableUpgradeable.sol";
7: import "@openzeppelin-upgrades/contracts/security/ReentrancyGuardUpgradeable.sol";
8: 
9: import "../interfaces/IBeaconChainOracle.sol";
10: 
11: import "../permissions/Pausable.sol";
12: import "./EigenPodPausingConstants.sol";
13: import "./EigenPodManagerStorage.sol";
14: 
15: /**
16:  * @title The contract used for creating and managing EigenPods
17:  * @author Layr Labs, Inc.
18:  * @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service
19:  * @notice The main functionalities are:
20:  * - creating EigenPods
21:  * - staking for new validators on EigenPods
22:  * - keeping track of the restaked balances of all EigenPod owners
23:  * - withdrawing eth when withdrawals are completed
24:  */
25: contract EigenPodManager is
26:     Initializable,
27:     OwnableUpgradeable,
28:     Pausable,
29:     EigenPodPausingConstants,
30:     EigenPodManagerStorage,
31:     ReentrancyGuardUpgradeable
32: {
33:     
34:     modifier onlyEigenPod(address podOwner) {
35:         require(address(ownerToPod[podOwner]) == msg.sender, "EigenPodManager.onlyEigenPod: not a pod");
36:         _;
37:     }
38: 
39:     modifier onlyDelegationManager() {
40:         require(
41:             msg.sender == address(delegationManager),
42:             "EigenPodManager.onlyDelegationManager: not the DelegationManager"
43:         );
44:         _;
45:     }
46: 
47:     constructor(
48:         IETHPOSDeposit _ethPOS,
49:         IBeacon _eigenPodBeacon,
50:         IStrategyManager _strategyManager,
51:         ISlasher _slasher,
52:         IDelegationManager _delegationManager
53:     ) EigenPodManagerStorage(_ethPOS, _eigenPodBeacon, _strategyManager, _slasher, _delegationManager) {
54:         _disableInitializers();
55:     }
56: 
57:     function initialize(
58:         uint256 _maxPods,
59:         IBeaconChainOracle _beaconChainOracle,
60:         address initialOwner,
61:         IPauserRegistry _pauserRegistry,
62:         uint256 _initPausedStatus
63:     ) external initializer {
64:         _setMaxPods(_maxPods);
65:         _updateBeaconChainOracle(_beaconChainOracle);
66:         _transferOwnership(initialOwner);
67:         _initializePauser(_pauserRegistry, _initPausedStatus);
68:     }
69: 
70:     /**
71:      * @notice Creates an EigenPod for the sender.
72:      * @dev Function will revert if the `msg.sender` already has an EigenPod.
73:      * @dev Returns EigenPod address 
74:      */
75:     function createPod() external onlyWhenNotPaused(PAUSED_NEW_EIGENPODS) returns (address) {
76:         require(!hasPod(msg.sender), "EigenPodManager.createPod: Sender already has a pod");
77:         // deploy a pod if the sender doesn't have one already
78:         IEigenPod pod = _deployPod();
79: 
80:         return address(pod);
81:     }
82: 
83:     /**
84:      * @notice Stakes for a new beacon chain validator on the sender's EigenPod.
85:      * Also creates an EigenPod for the sender if they don't have one already.
86:      * @param pubkey The 48 bytes public key of the beacon chain validator.
87:      * @param signature The validator's signature of the deposit data.
88:      * @param depositDataRoot The root/hash of the deposit data for the validator's deposit.
89:      */
90:     function stake(
91:         bytes calldata pubkey, 
92:         bytes calldata signature, 
93:         bytes32 depositDataRoot
94:     ) external payable onlyWhenNotPaused(PAUSED_NEW_EIGENPODS) {
95:         IEigenPod pod = ownerToPod[msg.sender];
96:         if (address(pod) == address(0)) {
97:             //deploy a pod if the sender doesn't have one already
98:             pod = _deployPod();
99:         }
100:         pod.stake{value: msg.value}(pubkey, signature, depositDataRoot);
101:     }
102: 
103:     /**
104:      * @notice Changes the `podOwner`'s shares by `sharesDelta` and performs a call to the DelegationManager
105:      * to ensure that delegated shares are also tracked correctly
106:      * @param podOwner is the pod owner whose balance is being updated.
107:      * @param sharesDelta is the change in podOwner's beaconChainETHStrategy shares
108:      * @dev Callable only by the podOwner's EigenPod contract.
109:      * @dev Reverts if `sharesDelta` is not a whole Gwei amount
110:      */
111:     function recordBeaconChainETHBalanceUpdate(
112:         address podOwner,
113:         int256 sharesDelta
114:     ) external onlyEigenPod(podOwner) nonReentrant {
115:         require(podOwner != address(0), "EigenPodManager.recordBeaconChainETHBalanceUpdate: podOwner cannot be zero address");
116:         require(sharesDelta % int256(GWEI_TO_WEI) == 0,
117:             "EigenPodManager.recordBeaconChainETHBalanceUpdate: sharesDelta must be a whole Gwei amount");
118:         int256 currentPodOwnerShares = podOwnerShares[podOwner];
119:         int256 updatedPodOwnerShares = currentPodOwnerShares + sharesDelta;
120:         podOwnerShares[podOwner] = updatedPodOwnerShares;
121: 
122:         // inform the DelegationManager of the change in delegateable shares
123:         int256 changeInDelegatableShares = _calculateChangeInDelegatableShares({
124:             sharesBefore: currentPodOwnerShares,
125:             sharesAfter: updatedPodOwnerShares
126:         });
127:         // skip making a call to the DelegationManager if there is no change in delegateable shares
128:         if (changeInDelegatableShares != 0) {
129:             if (changeInDelegatableShares < 0) {
130:                 delegationManager.decreaseDelegatedShares({
131:                     staker: podOwner,
132:                     strategy: beaconChainETHStrategy,
133:                     shares: uint256(-changeInDelegatableShares)
134:                 });
135:             } else {
136:                 delegationManager.increaseDelegatedShares({
137:                     staker: podOwner,
138:                     strategy: beaconChainETHStrategy,
139:                     shares: uint256(changeInDelegatableShares)
140:                 });
141:             }
142:         }
143:         emit PodSharesUpdated(podOwner, sharesDelta);
144:     }
145: 
146:     /**
147:      * @notice Used by the DelegationManager to remove a pod owner's shares while they're in the withdrawal queue.
148:      * Simply decreases the `podOwner`'s shares by `shares`, down to a minimum of zero.
149:      * @dev This function reverts if it would result in `podOwnerShares[podOwner]` being less than zero, i.e. it is forbidden for this function to
150:      * result in the `podOwner` incurring a "share deficit". This behavior prevents a Staker from queuing a withdrawal which improperly removes excessive
151:      * shares from the operator to whom the staker is delegated.
152:      * @dev Reverts if `shares` is not a whole Gwei amount
153:      * @dev The delegation manager validates that the podOwner is not address(0)
154:      */
155:     function removeShares(
156:         address podOwner, 
157:         uint256 shares
158:     ) external onlyDelegationManager {
159:         require(int256(shares) >= 0, "EigenPodManager.removeShares: shares cannot be negative");
160:         require(shares % GWEI_TO_WEI == 0, "EigenPodManager.removeShares: shares must be a whole Gwei amount");
161:         int256 updatedPodOwnerShares = podOwnerShares[podOwner] - int256(shares);
162:         require(updatedPodOwnerShares >= 0, "EigenPodManager.removeShares: cannot result in pod owner having negative shares");
163:         podOwnerShares[podOwner] = updatedPodOwnerShares;
164:     }
165: 
166:     /**
167:      * @notice Increases the `podOwner`'s shares by `shares`, paying off deficit if possible.
168:      * Used by the DelegationManager to award a pod owner shares on exiting the withdrawal queue
169:      * @dev Returns the number of shares added to `podOwnerShares[podOwner]` above zero, which will be less than the `shares` input
170:      * in the event that the podOwner has an existing shares deficit (i.e. `podOwnerShares[podOwner]` starts below zero)
171:      * @dev Reverts if `shares` is not a whole Gwei amount
172:      */
173:     function addShares(
174:         address podOwner,
175:         uint256 shares
176:     ) external onlyDelegationManager returns (uint256) {
177:         require(podOwner != address(0), "EigenPodManager.addShares: podOwner cannot be zero address");
178:         require(int256(shares) >= 0, "EigenPodManager.addShares: shares cannot be negative");
179:         require(shares % GWEI_TO_WEI == 0, "EigenPodManager.addShares: shares must be a whole Gwei amount");
180:         int256 currentPodOwnerShares = podOwnerShares[podOwner];
181:         int256 updatedPodOwnerShares = currentPodOwnerShares + int256(shares);
182:         podOwnerShares[podOwner] = updatedPodOwnerShares;
183: 
184:         emit PodSharesUpdated(podOwner, int256(shares));
185: 
186:         return uint256(_calculateChangeInDelegatableShares({sharesBefore: currentPodOwnerShares, sharesAfter: updatedPodOwnerShares}));
187:     }
188: 
189:     /**
190:      * @notice Used by the DelegationManager to complete a withdrawal, sending tokens to some destination address
191:      * @dev Prioritizes decreasing the podOwner's share deficit, if they have one
192:      * @dev Reverts if `shares` is not a whole Gwei amount
193:      * @dev This function assumes that `removeShares` has already been called by the delegationManager, hence why
194:      *      we do not need to update the podOwnerShares if `currentPodOwnerShares` is positive
195:      */
196:     function withdrawSharesAsTokens(
197:         address podOwner, 
198:         address destination, 
199:         uint256 shares
200:     ) external onlyDelegationManager {
201:         require(podOwner != address(0), "EigenPodManager.withdrawSharesAsTokens: podOwner cannot be zero address");
202:         require(destination != address(0), "EigenPodManager.withdrawSharesAsTokens: destination cannot be zero address");
203:         require(int256(shares) >= 0, "EigenPodManager.withdrawSharesAsTokens: shares cannot be negative");
204:         require(shares % GWEI_TO_WEI == 0, "EigenPodManager.withdrawSharesAsTokens: shares must be a whole Gwei amount");
205:         int256 currentPodOwnerShares = podOwnerShares[podOwner];
206: 
207:         // if there is an existing shares deficit, prioritize decreasing the deficit first
208:         if (currentPodOwnerShares < 0) {
209:             uint256 currentShareDeficit = uint256(-currentPodOwnerShares);
210:             // get rid of the whole deficit if possible, and pass any remaining shares onto destination
211:             if (shares > currentShareDeficit) {
212:                 podOwnerShares[podOwner] = 0;
213:                 shares -= currentShareDeficit;
214:                 emit PodSharesUpdated(podOwner, int256(currentShareDeficit));
215:             // otherwise get rid of as much deficit as possible, and return early, since there is nothing left over to forward on
216:             } else {
217:                 podOwnerShares[podOwner] += int256(shares);
218:                 emit PodSharesUpdated(podOwner, int256(shares));
219:                 return;
220:             }
221:         }
222:         // Actually withdraw to the destination
223:         ownerToPod[podOwner].withdrawRestakedBeaconChainETH(destination, shares);
224:     }
225: 
226:     /**
227:      * Sets the maximum number of pods that can be deployed
228:      * @param newMaxPods The new maximum number of pods that can be deployed
229:      * @dev Callable by the unpauser of this contract
230:      */
231:     function setMaxPods(uint256 newMaxPods) external onlyUnpauser {
232:         _setMaxPods(newMaxPods);
233:     }
234: 
235:     /**
236:      * @notice Updates the oracle contract that provides the beacon chain state root
237:      * @param newBeaconChainOracle is the new oracle contract being pointed to
238:      * @dev Callable only by the owner of this contract (i.e. governance)
239:      */
240:     function updateBeaconChainOracle(IBeaconChainOracle newBeaconChainOracle) external onlyOwner {
241:         _updateBeaconChainOracle(newBeaconChainOracle);
242:     }
243: 
244:     /**
245:      * @notice Sets the timestamp of the Deneb fork.
246:      * @param newDenebForkTimestamp is the new timestamp of the Deneb fork
247:      */
248:     function setDenebForkTimestamp(uint64 newDenebForkTimestamp) external onlyOwner {
249:         require(newDenebForkTimestamp != 0, "EigenPodManager.setDenebForkTimestamp: cannot set newDenebForkTimestamp to 0");
250:         require(_denebForkTimestamp == 0, "EigenPodManager.setDenebForkTimestamp: cannot set denebForkTimestamp more than once");
251:         
252:         _denebForkTimestamp = newDenebForkTimestamp;
253:         emit DenebForkTimestampUpdated(newDenebForkTimestamp);
254:     }
255: 
256:     // INTERNAL FUNCTIONS
257: 
258:     function _deployPod() internal returns (IEigenPod) {
259:         // check that the limit of EigenPods has not been hit, and increment the EigenPod count
260:         require(numPods + 1 <= maxPods, "EigenPodManager._deployPod: pod limit reached");
261:         ++numPods;
262:         // create the pod
263:         IEigenPod pod = IEigenPod(
264:             Create2.deploy(
265:                 0,
266:                 bytes32(uint256(uint160(msg.sender))),
267:                 // set the beacon address to the eigenPodBeacon and initialize it
268:                 abi.encodePacked(beaconProxyBytecode, abi.encode(eigenPodBeacon, ""))
269:             )
270:         );
271:         pod.initialize(msg.sender);
272:         // store the pod in the mapping
273:         ownerToPod[msg.sender] = pod;
274:         emit PodDeployed(address(pod), msg.sender);
275:         return pod;
276:     }
277: 
278:     /// @notice Internal setter for `beaconChainOracle` that also emits an event
279:     function _updateBeaconChainOracle(IBeaconChainOracle newBeaconChainOracle) internal {
280:         beaconChainOracle = newBeaconChainOracle;
281:         emit BeaconOracleUpdated(address(newBeaconChainOracle));
282:     }
283: 
284:     /// @notice Internal setter for `maxPods` that also emits an event
285:     function _setMaxPods(uint256 _maxPods) internal {
286:         emit MaxPodsUpdated(maxPods, _maxPods);
287:         maxPods = _maxPods;
288:     }
289: 
290:     /**
291:      * @notice Calculates the change in a pod owner's delegateable shares as a result of their beacon chain ETH shares changing
292:      * from `sharesBefore` to `sharesAfter`. The key concept here is that negative/"deficit" shares are not delegateable.
293:      */
294:     function _calculateChangeInDelegatableShares(int256 sharesBefore, int256 sharesAfter) internal pure returns (int256) {
295:         if (sharesBefore <= 0) {
296:             // if the shares started negative and stayed negative, then there cannot have been an increase in delegateable shares
297:             if (sharesAfter <= 0) {
298:                 return 0;
299:             // if the shares started negative and became positive, then the increase in delegateable shares is the ending share amount
300:             } else {
301:                 return sharesAfter;
302:             }
303:         } else {
304:             // if the shares started positive and became negative, then the decrease in delegateable shares is the starting share amount
305:             if (sharesAfter <= 0) {
306:                 return (-sharesBefore);
307:             // if the shares started positive and stayed positive, then the change in delegateable shares
308:             // is the difference between starting and ending amounts
309:             } else {
310:                 return (sharesAfter - sharesBefore);
311:             }
312:         }
313:     }
314: 
315:     // VIEW FUNCTIONS
316:     /// @notice Returns the address of the `podOwner`'s EigenPod (whether it is deployed yet or not).
317:     function getPod(address podOwner) public view returns (IEigenPod) {
318:         IEigenPod pod = ownerToPod[podOwner];
319:         // if pod does not exist already, calculate what its address *will be* once it is deployed
320:         if (address(pod) == address(0)) {
321:             pod = IEigenPod(
322:                 Create2.computeAddress(
323:                     bytes32(uint256(uint160(podOwner))), //salt
324:                     keccak256(abi.encodePacked(beaconProxyBytecode, abi.encode(eigenPodBeacon, ""))) //bytecode
325:                 )
326:             );
327:         }
328:         return pod;
329:     }
330: 
331:     /// @notice Returns 'true' if the `podOwner` has created an EigenPod, and 'false' otherwise.
332:     function hasPod(address podOwner) public view returns (bool) {
333:         return address(ownerToPod[podOwner]) != address(0);
334:     }
335: 
336:     /// @notice Returns the Beacon block root at `timestamp`. Reverts if the Beacon block root at `timestamp` has not yet been finalized.
337:     function getBlockRootAtTimestamp(uint64 timestamp) external view returns (bytes32) {
338:         bytes32 stateRoot = beaconChainOracle.timestampToBlockRoot(timestamp);
339:         require(
340:             stateRoot != bytes32(0),
341:             "EigenPodManager.getBlockRootAtTimestamp: state root at timestamp not yet finalized"
342:         );
343:         return stateRoot;
344:     }
345: 
346:     /**
347:      * @notice Wrapper around the `_denebForkTimestamp` storage variable that returns type(uint64).max if the storage variable is unset.
348:      * @dev This allows restricting the storage variable to be set once and only once.
349:      */
350:     function denebForkTimestamp() public view returns (uint64) {
351:         uint64 timestamp = _denebForkTimestamp;
352:         if (timestamp == 0) {
353:             return type(uint64).max;
354:         } else {
355:             return timestamp;
356:         }
357:     }
358: }

['1']

1: // SPDX-License-Identifier: BUSL-1.1
2: pragma solidity >=0.5.0;
3: 
4: import "../../contracts/interfaces/IStrategyManager.sol";
5: import "../../contracts/interfaces/IStrategy.sol";
6: import "../../contracts/interfaces/IDelegationManager.sol";
7: import "../../../script/whitelist/Staker.sol";
8: 
9: import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
10: import "@openzeppelin/contracts/access/Ownable.sol";
11: import "@openzeppelin/contracts/utils/Create2.sol";
12: 
13: interface IWhitelister {
14:     function whitelist(address operator) external;
15: 
16:     function getStaker(address operator) external returns (address);
17: 
18:     function depositIntoStrategy(
19:         address staker,
20:         IStrategy strategy,
21:         IERC20 token,
22:         uint256 amount
23:     ) external returns (bytes memory);
24: 
25:     function queueWithdrawal(
26:         address staker,
27:         IDelegationManager.QueuedWithdrawalParams[] calldata queuedWithdrawalParams
28:     ) external returns (bytes memory);
29: 
30:     function completeQueuedWithdrawal(
31:         address staker,
32:         IDelegationManager.Withdrawal calldata queuedWithdrawal,
33:         IERC20[] calldata tokens,
34:         uint256 middlewareTimesIndex,
35:         bool receiveAsTokens
36:     ) external returns (bytes memory);
37: 
38:     function transfer(address staker, address token, address to, uint256 amount) external returns (bytes memory);
39: 
40:     function callAddress(address to, bytes memory data) external payable returns (bytes memory);
41: }
42: 

['1']

1: // SPDX-License-Identifier: BUSL-1.1
2: pragma solidity =0.8.12;
3: 
4: import "@openzeppelin-upgrades/contracts/proxy/utils/Initializable.sol";
5: import "@openzeppelin-upgrades/contracts/access/OwnableUpgradeable.sol";
6: import "@openzeppelin-upgrades/contracts/security/ReentrancyGuardUpgradeable.sol";
7: import "../permissions/Pausable.sol";
8: import "../libraries/EIP1271SignatureUtils.sol";
9: import "./AVSDirectoryStorage.sol";
10: 
11: contract AVSDirectory is
12:     Initializable,
13:     OwnableUpgradeable,
14:     Pausable,
15:     AVSDirectoryStorage,
16:     ReentrancyGuardUpgradeable
17: {
18:     // @dev Index for flag that pauses operator register/deregister to avs when set.
19:     uint8 internal constant PAUSED_OPERATOR_REGISTER_DEREGISTER_TO_AVS = 0;
20: 
21:     // @dev Chain ID at the time of contract deployment
22:     uint256 internal immutable ORIGINAL_CHAIN_ID;
23: 
24:     /*******************************************************************************
25:                             INITIALIZING FUNCTIONS
26:     *******************************************************************************/
27: 
28:     /**
29:      * @dev Initializes the immutable addresses of the strategy mananger, delegationManager, slasher, 
30:      * and eigenpodManager contracts
31:      */
32:     constructor(IDelegationManager _delegation) AVSDirectoryStorage(_delegation) {
33:         _disableInitializers();
34:         ORIGINAL_CHAIN_ID = block.chainid;
35:     }
36: 
37:     /**
38:      * @dev Initializes the addresses of the initial owner, pauser registry, and paused status.
39:      * minWithdrawalDelayBlocks is set only once here
40:      */
41:     function initialize(
42:         address initialOwner,
43:         IPauserRegistry _pauserRegistry,
44:         uint256 initialPausedStatus
45:     ) external initializer {
46:         _initializePauser(_pauserRegistry, initialPausedStatus);
47:         _DOMAIN_SEPARATOR = _calculateDomainSeparator();
48:         _transferOwnership(initialOwner);
49:     }
50: 
51:     /*******************************************************************************
52:                             EXTERNAL FUNCTIONS 
53:     *******************************************************************************/
54: 
56:     /**
57:      * @notice Called by the AVS's service manager contract to register an operator with the avs.
58:      * @param operator The address of the operator to register.
59:      * @param operatorSignature The signature, salt, and expiry of the operator's signature.
60:      */
61:     function registerOperatorToAVS(
62:         address operator,
63:         ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature
64:     ) external onlyWhenNotPaused(PAUSED_OPERATOR_REGISTER_DEREGISTER_TO_AVS) {
65: 
66:         require(
67:             operatorSignature.expiry >= block.timestamp,
68:             "AVSDirectory.registerOperatorToAVS: operator signature expired"
69:         );
70:         require(
71:             avsOperatorStatus[msg.sender][operator] != OperatorAVSRegistrationStatus.REGISTERED,
72:             "AVSDirectory.registerOperatorToAVS: operator already registered"
73:         );
74:         require(
75:             !operatorSaltIsSpent[operator][operatorSignature.salt],
76:             "AVSDirectory.registerOperatorToAVS: salt already spent"
77:         );
78:         require(
79:             delegation.isOperator(operator),
80:             "AVSDirectory.registerOperatorToAVS: operator not registered to EigenLayer yet");
81: 
82:         // Calculate the digest hash
83:         bytes32 operatorRegistrationDigestHash = calculateOperatorAVSRegistrationDigestHash({
84:             operator: operator,
85:             avs: msg.sender,
86:             salt: operatorSignature.salt,
87:             expiry: operatorSignature.expiry
88:         });
89: 
90:         // Check that the signature is valid
91:         EIP1271SignatureUtils.checkSignature_EIP1271(
92:             operator,
93:             operatorRegistrationDigestHash,
94:             operatorSignature.signature
95:         );
96: 
97:         // Set the operator as registered
98:         avsOperatorStatus[msg.sender][operator] = OperatorAVSRegistrationStatus.REGISTERED;
99: 
100:         // Mark the salt as spent
101:         operatorSaltIsSpent[operator][operatorSignature.salt] = true;
102: 
103:         emit OperatorAVSRegistrationStatusUpdated(operator, msg.sender, OperatorAVSRegistrationStatus.REGISTERED);
104:     }
105: 
106:     /**
107:      * @notice Called by an avs to deregister an operator with the avs.
108:      * @param operator The address of the operator to deregister.
109:      */
110:     function deregisterOperatorFromAVS(address operator) external onlyWhenNotPaused(PAUSED_OPERATOR_REGISTER_DEREGISTER_TO_AVS) {
111:         require(
112:             avsOperatorStatus[msg.sender][operator] == OperatorAVSRegistrationStatus.REGISTERED,
113:             "AVSDirectory.deregisterOperatorFromAVS: operator not registered"
114:         );
115: 
116:         // Set the operator as deregistered
117:         avsOperatorStatus[msg.sender][operator] = OperatorAVSRegistrationStatus.UNREGISTERED;
118: 
119:         emit OperatorAVSRegistrationStatusUpdated(operator, msg.sender, OperatorAVSRegistrationStatus.UNREGISTERED);
120:     }
121: 
122:     /**
123:      * @notice Called by an avs to emit an `AVSMetadataURIUpdated` event indicating the information has updated.
124:      * @param metadataURI The URI for metadata associated with an avs
125:      */
126:     function updateAVSMetadataURI(string calldata metadataURI) external {
127:         emit AVSMetadataURIUpdated(msg.sender, metadataURI);
128:     }
129: 
130:     /**
131:      * @notice Called by an operator to cancel a salt that has been used to register with an AVS.
132:      * @param salt A unique and single use value associated with the approver signature.
133:      */
134:     function cancelSalt(bytes32 salt) external {
135:         require(!operatorSaltIsSpent[msg.sender][salt], "AVSDirectory.cancelSalt: cannot cancel spent salt");
136:         operatorSaltIsSpent[msg.sender][salt] = true;
137:     }
138: 
139:     /*******************************************************************************
140:                             VIEW FUNCTIONS
141:     *******************************************************************************/
142: 
143:     /**
144:      * @notice Calculates the digest hash to be signed by an operator to register with an AVS
145:      * @param operator The account registering as an operator
146:      * @param avs The address of the service manager contract for the AVS that the operator is registering to
147:      * @param salt A unique and single use value associated with the approver signature.
148:      * @param expiry Time after which the approver's signature becomes invalid
149:      */
150:     function calculateOperatorAVSRegistrationDigestHash(
151:         address operator,
152:         address avs,
153:         bytes32 salt,
154:         uint256 expiry
155:     ) public view returns (bytes32) {
156:         // calculate the struct hash
157:         bytes32 structHash = keccak256(
158:             abi.encode(OPERATOR_AVS_REGISTRATION_TYPEHASH, operator, avs, salt, expiry)
159:         );
160:         // calculate the digest hash
161:         bytes32 digestHash = keccak256(
162:             abi.encodePacked("\x19\x01", domainSeparator(), structHash)
163:         );
164:         return digestHash;
165:     }
166: 
167:     /**
168:      * @notice Getter function for the current EIP-712 domain separator for this contract.
169:      * @dev The domain separator will change in the event of a fork that changes the ChainID.
170:      */
171:     function domainSeparator() public view returns (bytes32) {
172:         if (block.chainid == ORIGINAL_CHAIN_ID) {
173:             return _DOMAIN_SEPARATOR;
174:         } else {
175:             return _calculateDomainSeparator();
176:         }
177:     }
178: 
179:     // @notice Internal function for calculating the current domain separator of this contract
180:     function _calculateDomainSeparator() internal view returns (bytes32) {
181:         return keccak256(abi.encode(DOMAIN_TYPEHASH, keccak256(bytes("EigenLayer")), block.chainid, address(this)));
182:     }
183: }

['1']

1: // ┏━━━┓━┏┓━┏┓━━┏━━━┓━━┏━━━┓━━━━┏━━━┓━━━━━━━━━━━━━━━━━━━┏┓━━━━━┏━━━┓━━━━━━━━━┏┓━━━━━━━━━━━━━━┏┓━
2: // ┃┏━━┛┏┛┗┓┃┃━━┃┏━┓┃━━┃┏━┓┃━━━━┗┓┏┓┃━━━━━━━━━━━━━━━━━━┏┛┗┓━━━━┃┏━┓┃━━━━━━━━┏┛┗┓━━━━━━━━━━━━┏┛┗┓
3: // ┃┗━━┓┗┓┏┛┃┗━┓┗┛┏┛┃━━┃┃━┃┃━━━━━┃┃┃┃┏━━┓┏━━┓┏━━┓┏━━┓┏┓┗┓┏┛━━━━┃┃━┗┛┏━━┓┏━┓━┗┓┏┛┏━┓┏━━┓━┏━━┓┗┓┏┛
4: // ┃┏━━┛━┃┃━┃┏┓┃┏━┛┏┛━━┃┃━┃┃━━━━━┃┃┃┃┃┏┓┃┃┏┓┃┃┏┓┃┃━━┫┣┫━┃┃━━━━━┃┃━┏┓┃┏┓┃┃┏┓┓━┃┃━┃┏┛┗━┓┃━┃┏━┛━┃┃━
5: // ┃┗━━┓━┃┗┓┃┃┃┃┃┃┗━┓┏┓┃┗━┛┃━━━━┏┛┗┛┃┃┃━┫┃┗┛┃┃┗┛┃┣━━┃┃┃━┃┗┓━━━━┃┗━┛┃┃┗┛┃┃┃┃┃━┃┗┓┃┃━┃┗┛┗┓┃┗━┓━┃┗┓
6: // ┗━━━┛━┗━┛┗┛┗┛┗━━━┛┗┛┗━━━┛━━━━┗━━━┛┗━━┛┃┏━┛┗━━┛┗━━┛┗┛━┗━┛━━━━┗━━━┛┗━━┛┗┛┗┛━┗━┛┗┛━┗━━━┛┗━━┛━┗━┛
7: // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┃┃━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
8: // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┗┛━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
9: 
10: // SPDX-License-Identifier: CC0-1.0
11: 
12: pragma solidity >=0.5.0;
13: 
14: // This interface is designed to be compatible with the Vyper version.
15: /// @notice This is the Ethereum 2.0 deposit contract interface.
16: /// For more information see the Phase 0 specification under https://github.com/ethereum/eth2.0-specs
17: interface IETHPOSDeposit {
18:     /// @notice A processed deposit event.
19:     event DepositEvent(bytes pubkey, bytes withdrawal_credentials, bytes amount, bytes signature, bytes index);
20: 
21:     /// @notice Submit a Phase 0 DepositData object.
22:     /// @param pubkey A BLS12-381 public key.
23:     /// @param withdrawal_credentials Commitment to a public key for withdrawals.
24:     /// @param signature A BLS12-381 signature.
25:     /// @param deposit_data_root The SHA-256 hash of the SSZ-encoded DepositData object.
26:     /// Used as a protection against malformed input.
27:     function deposit(
28:         bytes calldata pubkey,
29:         bytes calldata withdrawal_credentials,
30:         bytes calldata signature,
31:         bytes32 deposit_data_root
32:     ) external payable;
33: 
34:     /// @notice Query the current deposit root hash.
35:     /// @return The deposit root hash.
36:     function get_deposit_root() external view returns (bytes32);
37: 
38:     /// @notice Query the current deposit count.
39:     /// @return The deposit count encoded as a little endian 64-bit number.
40:     function get_deposit_count() external view returns (bytes memory);
41: }
42: 

['1']

1: // SPDX-License-Identifier: BUSL-1.1
2: pragma solidity =0.8.12;
3: 
4: import "@openzeppelin-upgrades/contracts/proxy/utils/Initializable.sol";
5: import "@openzeppelin-upgrades/contracts/access/OwnableUpgradeable.sol";
6: import "@openzeppelin-upgrades/contracts/security/ReentrancyGuardUpgradeable.sol";
7: import "../permissions/Pausable.sol";
8: import "../libraries/EIP1271SignatureUtils.sol";
9: import "./DelegationManagerStorage.sol";
10: 
11: /**
12:  * @title DelegationManager
13:  * @author Layr Labs, Inc.
14:  * @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service
15:  * @notice  This is the contract for delegation in EigenLayer. The main functionalities of this contract are
16:  * - enabling anyone to register as an operator in EigenLayer
17:  * - allowing operators to specify parameters related to stakers who delegate to them
18:  * - enabling any staker to delegate its stake to the operator of its choice (a given staker can only delegate to a single operator at a time)
19:  * - enabling a staker to undelegate its assets from the operator it is delegated to (performed as part of the withdrawal process, initiated through the StrategyManager)
20:  */
21: contract DelegationManager is Initializable, OwnableUpgradeable, Pausable, DelegationManagerStorage, ReentrancyGuardUpgradeable {
22:     // @dev Index for flag that pauses new delegations when set
23:     uint8 internal constant PAUSED_NEW_DELEGATION = 0;
24: 
25:     // @dev Index for flag that pauses queuing new withdrawals when set.
26:     uint8 internal constant PAUSED_ENTER_WITHDRAWAL_QUEUE = 1;
27: 
28:     // @dev Index for flag that pauses completing existing withdrawals when set.
29:     uint8 internal constant PAUSED_EXIT_WITHDRAWAL_QUEUE = 2;
30: 
31:     // @dev Chain ID at the time of contract deployment
32:     uint256 internal immutable ORIGINAL_CHAIN_ID;
33: 
34:     // @dev Maximum Value for `stakerOptOutWindowBlocks`. Approximately equivalent to 6 months in blocks.
35:     uint256 public constant MAX_STAKER_OPT_OUT_WINDOW_BLOCKS = (180 days) / 12;
36: 
37:     /// @notice Canonical, virtual beacon chain ETH strategy
38:     IStrategy public constant beaconChainETHStrategy = IStrategy(0xbeaC0eeEeeeeEEeEeEEEEeeEEeEeeeEeeEEBEaC0);
39: 
40:     // @notice Simple permission for functions that are only callable by the StrategyManager contract OR by the EigenPodManagerContract
41:     modifier onlyStrategyManagerOrEigenPodManager() {
42:         require(
43:             msg.sender == address(strategyManager) || msg.sender == address(eigenPodManager),
44:             "DelegationManager: onlyStrategyManagerOrEigenPodManager"
45:         );
46:         _;
47:     }
48: 
49:     /*******************************************************************************
50:                             INITIALIZING FUNCTIONS
51:     *******************************************************************************/
52: 
53:     /**
54:      * @dev Initializes the immutable addresses of the strategy mananger and slasher.
55:      */
56:     constructor(
57:         IStrategyManager _strategyManager,
58:         ISlasher _slasher,
59:         IEigenPodManager _eigenPodManager
60:     ) DelegationManagerStorage(_strategyManager, _slasher, _eigenPodManager) {
61:         _disableInitializers();
62:         ORIGINAL_CHAIN_ID = block.chainid;
63:     }
64: 
65:     /**
66:      * @dev Initializes the addresses of the initial owner, pauser registry, and paused status.
67:      * minWithdrawalDelayBlocks is set only once here
68:      */
69:     function initialize(
70:         address initialOwner,
71:         IPauserRegistry _pauserRegistry,
72:         uint256 initialPausedStatus,
73:         uint256 _minWithdrawalDelayBlocks,
74:         IStrategy[] calldata _strategies,
75:         uint256[] calldata _withdrawalDelayBlocks
76:     ) external initializer {
77:         _initializePauser(_pauserRegistry, initialPausedStatus);
78:         _DOMAIN_SEPARATOR = _calculateDomainSeparator();
79:         _transferOwnership(initialOwner);
80:         _setMinWithdrawalDelayBlocks(_minWithdrawalDelayBlocks);
81:         _setStrategyWithdrawalDelayBlocks(_strategies, _withdrawalDelayBlocks);
82:     }
83: 
84:     /*******************************************************************************
85:                             EXTERNAL FUNCTIONS 
86:     *******************************************************************************/
87: 
88:     /**
89:      * @notice Registers the caller as an operator in EigenLayer.
90:      * @param registeringOperatorDetails is the `OperatorDetails` for the operator.
91:      * @param metadataURI is a URI for the operator's metadata, i.e. a link providing more details on the operator.
92:      *
93:      * @dev Once an operator is registered, they cannot 'deregister' as an operator, and they will forever be considered "delegated to themself".
94:      * @dev This function will revert if the caller attempts to set their `earningsReceiver` to address(0).
95:      * @dev Note that the `metadataURI` is *never stored * and is only emitted in the `OperatorMetadataURIUpdated` event
96:      */
97:     function registerAsOperator(
98:         OperatorDetails calldata registeringOperatorDetails,
99:         string calldata metadataURI
100:     ) external {
101:         require(
102:             _operatorDetails[msg.sender].earningsReceiver == address(0),
103:             "DelegationManager.registerAsOperator: operator has already registered"
104:         );
105:         _setOperatorDetails(msg.sender, registeringOperatorDetails);
106:         SignatureWithExpiry memory emptySignatureAndExpiry;
107:         // delegate from the operator to themselves
108:         _delegate(msg.sender, msg.sender, emptySignatureAndExpiry, bytes32(0));
109:         // emit events
110:         emit OperatorRegistered(msg.sender, registeringOperatorDetails);
111:         emit OperatorMetadataURIUpdated(msg.sender, metadataURI);
112:     }
113: 
114:     /**
115:      * @notice Updates an operator's stored `OperatorDetails`.
116:      * @param newOperatorDetails is the updated `OperatorDetails` for the operator, to replace their current OperatorDetails`.
117:      *
118:      * @dev The caller must have previously registered as an operator in EigenLayer.
119:      * @dev This function will revert if the caller attempts to set their `earningsReceiver` to address(0).
120:      */
121:     function modifyOperatorDetails(OperatorDetails calldata newOperatorDetails) external {
122:         require(isOperator(msg.sender), "DelegationManager.modifyOperatorDetails: caller must be an operator");
123:         _setOperatorDetails(msg.sender, newOperatorDetails);
124:     }
125: 
126:     /**
127:      * @notice Called by an operator to emit an `OperatorMetadataURIUpdated` event indicating the information has updated.
128:      * @param metadataURI The URI for metadata associated with an operator
129:      */
130:     function updateOperatorMetadataURI(string calldata metadataURI) external {
131:         require(isOperator(msg.sender), "DelegationManager.updateOperatorMetadataURI: caller must be an operator");
132:         emit OperatorMetadataURIUpdated(msg.sender, metadataURI);
133:     }
134: 
135:     /**
136:      * @notice Caller delegates their stake to an operator.
137:      * @param operator The account (`msg.sender`) is delegating its assets to for use in serving applications built on EigenLayer.
138:      * @param approverSignatureAndExpiry Verifies the operator approves of this delegation
139:      * @param approverSalt A unique single use value tied to an individual signature.
140:      * @dev The approverSignatureAndExpiry is used in the event that:
141:      *          1) the operator's `delegationApprover` address is set to a non-zero value.
142:      *                  AND
143:      *          2) neither the operator nor their `delegationApprover` is the `msg.sender`, since in the event that the operator
144:      *             or their delegationApprover is the `msg.sender`, then approval is assumed.
145:      * @dev In the event that `approverSignatureAndExpiry` is not checked, its content is ignored entirely; it's recommended to use an empty input
146:      * in this case to save on complexity + gas costs
147:      */
148:     function delegateTo(
149:         address operator,
150:         SignatureWithExpiry memory approverSignatureAndExpiry,
151:         bytes32 approverSalt
152:     ) external {
153:         // go through the internal delegation flow, checking the `approverSignatureAndExpiry` if applicable
154:         _delegate(msg.sender, operator, approverSignatureAndExpiry, approverSalt);
155:     }
156: 
157:     /**
158:      * @notice Caller delegates a staker's stake to an operator with valid signatures from both parties.
159:      * @param staker The account delegating stake to an `operator` account
160:      * @param operator The account (`staker`) is delegating its assets to for use in serving applications built on EigenLayer.
161:      * @param stakerSignatureAndExpiry Signed data from the staker authorizing delegating stake to an operator
162:      * @param approverSignatureAndExpiry is a parameter that will be used for verifying that the operator approves of this delegation action in the event that:
163:      * @param approverSalt Is a salt used to help guarantee signature uniqueness. Each salt can only be used once by a given approver.
164:      *
165:      * @dev If `staker` is an EOA, then `stakerSignature` is verified to be a valid ECDSA stakerSignature from `staker`, indicating their intention for this action.
166:      * @dev If `staker` is a contract, then `stakerSignature` will be checked according to EIP-1271.
167:      * @dev the operator's `delegationApprover` address is set to a non-zero value.
168:      * @dev neither the operator nor their `delegationApprover` is the `msg.sender`, since in the event that the operator or their delegationApprover
169:      * is the `msg.sender`, then approval is assumed.
170:      * @dev This function will revert if the current `block.timestamp` is equal to or exceeds the expiry
171:      * @dev In the case that `approverSignatureAndExpiry` is not checked, its content is ignored entirely; it's recommended to use an empty input
172:      * in this case to save on complexity + gas costs
173:      */
174:     function delegateToBySignature(
175:         address staker,
176:         address operator,
177:         SignatureWithExpiry memory stakerSignatureAndExpiry,
178:         SignatureWithExpiry memory approverSignatureAndExpiry,
179:         bytes32 approverSalt
180:     ) external {
181:         // check the signature expiry
182:         require(
183:             stakerSignatureAndExpiry.expiry >= block.timestamp,
184:             "DelegationManager.delegateToBySignature: staker signature expired"
185:         );
186: 
187:         // calculate the digest hash, then increment `staker`'s nonce
188:         uint256 currentStakerNonce = stakerNonce[staker];
189:         bytes32 stakerDigestHash = calculateStakerDelegationDigestHash(
190:             staker,
191:             currentStakerNonce,
192:             operator,
193:             stakerSignatureAndExpiry.expiry
194:         );
195:         unchecked {
196:             stakerNonce[staker] = currentStakerNonce + 1;
197:         }
198: 
199:         // actually check that the signature is valid
200:         EIP1271SignatureUtils.checkSignature_EIP1271(staker, stakerDigestHash, stakerSignatureAndExpiry.signature);
201: 
202:         // go through the internal delegation flow, checking the `approverSignatureAndExpiry` if applicable
203:         _delegate(staker, operator, approverSignatureAndExpiry, approverSalt);
204:     }
205: 
206:     /**
207:      * Allows the staker, the staker's operator, or that operator's delegationApprover to undelegate
208:      * a staker from their operator. Undelegation immediately removes ALL active shares/strategies from
209:      * both the staker and operator, and places the shares and strategies in the withdrawal queue
210:      */
211:     function undelegate(address staker) external onlyWhenNotPaused(PAUSED_ENTER_WITHDRAWAL_QUEUE) returns (bytes32[] memory withdrawalRoots) {
212:         require(isDelegated(staker), "DelegationManager.undelegate: staker must be delegated to undelegate");
213:         require(!isOperator(staker), "DelegationManager.undelegate: operators cannot be undelegated");
214:         require(staker != address(0), "DelegationManager.undelegate: cannot undelegate zero address");
215:         address operator = delegatedTo[staker];
216:         require(
217:             msg.sender == staker ||
218:                 msg.sender == operator ||
219:                 msg.sender == _operatorDetails[operator].delegationApprover,
220:             "DelegationManager.undelegate: caller cannot undelegate staker"
221:         );
222: 
223:         // Gather strategies and shares to remove from staker/operator during undelegation
224:         // Undelegation removes ALL currently-active strategies and shares
225:         (IStrategy[] memory strategies, uint256[] memory shares) = getDelegatableShares(staker);
226: 
227:         // emit an event if this action was not initiated by the staker themselves
228:         if (msg.sender != staker) {
229:             emit StakerForceUndelegated(staker, operator);
230:         }
231: 
232:         // undelegate the staker
233:         emit StakerUndelegated(staker, operator);
234:         delegatedTo[staker] = address(0);
235: 
236:         // if no delegatable shares, return an empty array, and don't queue a withdrawal
237:         if (strategies.length == 0) {
238:             withdrawalRoots = new bytes32[](0);
239:         } else {
240:             withdrawalRoots = new bytes32[](strategies.length);
241:             for (uint256 i = 0; i < strategies.length; i++) {
242:                 IStrategy[] memory singleStrategy = new IStrategy[](1);
243:                 uint256[] memory singleShare = new uint256[](1);
244:                 singleStrategy[0] = strategies[i];
245:                 singleShare[0] = shares[i];
246: 
247:                 withdrawalRoots[i] = _removeSharesAndQueueWithdrawal({
248:                     staker: staker,
249:                     operator: operator,
250:                     withdrawer: staker,
251:                     strategies: singleStrategy,
252:                     shares: singleShare
253:                 });
254:             }
255:         }
256: 
257:         return withdrawalRoots;
258:     }
259: 
260:     /**
261:      * Allows a staker to withdraw some shares. Withdrawn shares/strategies are immediately removed
262:      * from the staker. If the staker is delegated, withdrawn shares/strategies are also removed from
263:      * their operator.
264:      *
265:      * All withdrawn shares/strategies are placed in a queue and can be fully withdrawn after a delay.
266:      */
267:     function queueWithdrawals(
268:         QueuedWithdrawalParams[] calldata queuedWithdrawalParams
269:     ) external onlyWhenNotPaused(PAUSED_ENTER_WITHDRAWAL_QUEUE) returns (bytes32[] memory) {
270:         bytes32[] memory withdrawalRoots = new bytes32[](queuedWithdrawalParams.length);
271:         address operator = delegatedTo[msg.sender];
272: 
273:         for (uint256 i = 0; i < queuedWithdrawalParams.length; i++) {
274:             require(queuedWithdrawalParams[i].strategies.length == queuedWithdrawalParams[i].shares.length, "DelegationManager.queueWithdrawal: input length mismatch");
275:             require(queuedWithdrawalParams[i].withdrawer == msg.sender, "DelegationManager.queueWithdrawal: withdrawer must be staker");
276: 
277:             // Remove shares from staker's strategies and place strategies/shares in queue.
278:             // If the staker is delegated to an operator, the operator's delegated shares are also reduced
279:             // NOTE: This will fail if the staker doesn't have the shares implied by the input parameters
280:             withdrawalRoots[i] = _removeSharesAndQueueWithdrawal({
281:                 staker: msg.sender,
282:                 operator: operator,
283:                 withdrawer: queuedWithdrawalParams[i].withdrawer,
284:                 strategies: queuedWithdrawalParams[i].strategies,
285:                 shares: queuedWithdrawalParams[i].shares
286:             });
287:         }
288:         return withdrawalRoots;
289:     }
290: 
291:     /**
292:      * @notice Used to complete the specified `withdrawal`. The caller must match `withdrawal.withdrawer`
293:      * @param withdrawal The Withdrawal to complete.
294:      * @param tokens Array in which the i-th entry specifies the `token` input to the 'withdraw' function of the i-th Strategy in the `withdrawal.strategies` array.
295:      * This input can be provided with zero length if `receiveAsTokens` is set to 'false' (since in that case, this input will be unused)
296:      * @param middlewareTimesIndex is the index in the operator that the staker who triggered the withdrawal was delegated to's middleware times array
297:      * @param receiveAsTokens If true, the shares specified in the withdrawal will be withdrawn from the specified strategies themselves
298:      * and sent to the caller, through calls to `withdrawal.strategies[i].withdraw`. If false, then the shares in the specified strategies
299:      * will simply be transferred to the caller directly.
300:      * @dev middlewareTimesIndex is unused, but will be used in the Slasher eventually
301:      * @dev beaconChainETHStrategy shares are non-transferrable, so if `receiveAsTokens = false` and `withdrawal.withdrawer != withdrawal.staker`, note that
302:      * any beaconChainETHStrategy shares in the `withdrawal` will be _returned to the staker_, rather than transferred to the withdrawer, unlike shares in
303:      * any other strategies, which will be transferred to the withdrawer.
304:      */
305:     function completeQueuedWithdrawal(
306:         Withdrawal calldata withdrawal,
307:         IERC20[] calldata tokens,
308:         uint256 middlewareTimesIndex,
309:         bool receiveAsTokens
310:     ) external onlyWhenNotPaused(PAUSED_EXIT_WITHDRAWAL_QUEUE) nonReentrant {
311:         _completeQueuedWithdrawal(withdrawal, tokens, middlewareTimesIndex, receiveAsTokens);
312:     }
313: 
314:     /**
315:      * @notice Array-ified version of `completeQueuedWithdrawal`.
316:      * Used to complete the specified `withdrawals`. The function caller must match `withdrawals[...].withdrawer`
317:      * @param withdrawals The Withdrawals to complete.
318:      * @param tokens Array of tokens for each Withdrawal. See `completeQueuedWithdrawal` for the usage of a single array.
319:      * @param middlewareTimesIndexes One index to reference per Withdrawal. See `completeQueuedWithdrawal` for the usage of a single index.
320:      * @param receiveAsTokens Whether or not to complete each withdrawal as tokens. See `completeQueuedWithdrawal` for the usage of a single boolean.
321:      * @dev See `completeQueuedWithdrawal` for relevant dev tags
322:      */
323:     function completeQueuedWithdrawals(
324:         Withdrawal[] calldata withdrawals,
325:         IERC20[][] calldata tokens,
326:         uint256[] calldata middlewareTimesIndexes,
327:         bool[] calldata receiveAsTokens
328:     ) external onlyWhenNotPaused(PAUSED_EXIT_WITHDRAWAL_QUEUE) nonReentrant {
329:         for (uint256 i = 0; i < withdrawals.length; ++i) {
330:             _completeQueuedWithdrawal(withdrawals[i], tokens[i], middlewareTimesIndexes[i], receiveAsTokens[i]);
331:         }
332:     }
333: 
334:     /// @notice Migrates an array of queued withdrawals from the StrategyManager contract to this contract.
335:     /// @dev This function is expected to be removed in the next upgrade, after all queued withdrawals have been migrated.
336:     function migrateQueuedWithdrawals(IStrategyManager.DeprecatedStruct_QueuedWithdrawal[] memory withdrawalsToMigrate) external {
337:         for(uint256 i = 0; i < withdrawalsToMigrate.length;) {
338:             IStrategyManager.DeprecatedStruct_QueuedWithdrawal memory withdrawalToMigrate = withdrawalsToMigrate[i];
339:             // Delete withdrawal root from strateyManager
340:             (bool isDeleted, bytes32 oldWithdrawalRoot) = strategyManager.migrateQueuedWithdrawal(withdrawalToMigrate);
341:             // If old storage is deleted from strategyManager
342:             if (isDeleted) {
343:                 address staker = withdrawalToMigrate.staker;
344:                 // Create queue entry and increment withdrawal nonce
345:                 uint256 nonce = cumulativeWithdrawalsQueued[staker];
346:                 cumulativeWithdrawalsQueued[staker]++;
347: 
348:                 Withdrawal memory migratedWithdrawal = Withdrawal({
349:                     staker: staker,
350:                     delegatedTo: withdrawalToMigrate.delegatedAddress,
351:                     withdrawer: withdrawalToMigrate.withdrawerAndNonce.withdrawer,
352:                     nonce: nonce,
353:                     startBlock: withdrawalToMigrate.withdrawalStartBlock,
354:                     strategies: withdrawalToMigrate.strategies,
355:                     shares: withdrawalToMigrate.shares
356:                 });
357: 
358:                 // create the new storage
359:                 bytes32 newRoot = calculateWithdrawalRoot(migratedWithdrawal);
360:                 // safety check to ensure that root doesn't exist already -- this should *never* be hit
361:                 require(!pendingWithdrawals[newRoot], "DelegationManager.migrateQueuedWithdrawals: withdrawal already exists");
362:                 pendingWithdrawals[newRoot] = true;
363: 
364:                 emit WithdrawalQueued(newRoot, migratedWithdrawal);
365: 
366:                 emit WithdrawalMigrated(oldWithdrawalRoot, newRoot);
367:             }
368:             unchecked {
369:                 ++i;
370:             }
371:         }
372:         
373:     }
374: 
375:     /**
376:      * @notice Increases a staker's delegated share balance in a strategy.
377:      * @param staker The address to increase the delegated shares for their operator.
378:      * @param strategy The strategy in which to increase the delegated shares.
379:      * @param shares The number of shares to increase.
380:      *
381:      * @dev *If the staker is actively delegated*, then increases the `staker`'s delegated shares in `strategy` by `shares`. Otherwise does nothing.
382:      * @dev Callable only by the StrategyManager or EigenPodManager.
383:      */
384:     function increaseDelegatedShares(
385:         address staker,
386:         IStrategy strategy,
387:         uint256 shares
388:     ) external onlyStrategyManagerOrEigenPodManager {
389:         // if the staker is delegated to an operator
390:         if (isDelegated(staker)) {
391:             address operator = delegatedTo[staker];
392: 
393:             // add strategy shares to delegate's shares
394:             _increaseOperatorShares({operator: operator, staker: staker, strategy: strategy, shares: shares});
395:         }
396:     }
397: 
398:     /**
399:      * @notice Decreases a staker's delegated share balance in a strategy.
400:      * @param staker The address to increase the delegated shares for their operator.
401:      * @param strategy The strategy in which to decrease the delegated shares.
402:      * @param shares The number of shares to decrease.
403:      *
404:      * @dev *If the staker is actively delegated*, then decreases the `staker`'s delegated shares in `strategy` by `shares`. Otherwise does nothing.
405:      * @dev Callable only by the StrategyManager or EigenPodManager.
406:      */
407:     function decreaseDelegatedShares(
408:         address staker,
409:         IStrategy strategy,
410:         uint256 shares
411:     ) external onlyStrategyManagerOrEigenPodManager {
412:         // if the staker is delegated to an operator
413:         if (isDelegated(staker)) {
414:             address operator = delegatedTo[staker];
415: 
416:             // subtract strategy shares from delegate's shares
417:             _decreaseOperatorShares({
418:                 operator: operator,
419:                 staker: staker,
420:                 strategy: strategy,
421:                 shares: shares
422:             });
423:         }
424:     }
425: 
426:     /**
427:      * @notice Owner-only function for modifying the value of the `minWithdrawalDelayBlocks` variable.
428:      * @param newMinWithdrawalDelayBlocks new value of `minWithdrawalDelayBlocks`.
429:      */
430:     function setMinWithdrawalDelayBlocks(uint256 newMinWithdrawalDelayBlocks) external onlyOwner {
431:         _setMinWithdrawalDelayBlocks(newMinWithdrawalDelayBlocks);
432:     }
433: 
434:     /**
435:      * @notice Called by owner to set the minimum withdrawal delay blocks for each passed in strategy
436:      * Note that the min number of blocks to complete a withdrawal of a strategy is 
437:      * MAX(minWithdrawalDelayBlocks, strategyWithdrawalDelayBlocks[strategy])
438:      * @param strategies The strategies to set the minimum withdrawal delay blocks for
439:      * @param withdrawalDelayBlocks The minimum withdrawal delay blocks to set for each strategy
440:      */
441:     function setStrategyWithdrawalDelayBlocks(
442:         IStrategy[] calldata strategies,
443:         uint256[] calldata withdrawalDelayBlocks
444:     ) external onlyOwner {
445:         _setStrategyWithdrawalDelayBlocks(strategies, withdrawalDelayBlocks);
446:     }
447: 
448:     /*******************************************************************************
449:                             INTERNAL FUNCTIONS
450:     *******************************************************************************/
451: 
452:     /**
453:      * @notice Sets operator parameters in the `_operatorDetails` mapping.
454:      * @param operator The account registered as an operator updating their operatorDetails
455:      * @param newOperatorDetails The new parameters for the operator
456:      *
457:      * @dev This function will revert if the operator attempts to set their `earningsReceiver` to address(0).
458:      */
459:     function _setOperatorDetails(address operator, OperatorDetails calldata newOperatorDetails) internal {
460:         require(
461:             newOperatorDetails.earningsReceiver != address(0),
462:             "DelegationManager._setOperatorDetails: cannot set `earningsReceiver` to zero address"
463:         );
464:         require(
465:             newOperatorDetails.stakerOptOutWindowBlocks <= MAX_STAKER_OPT_OUT_WINDOW_BLOCKS,
466:             "DelegationManager._setOperatorDetails: stakerOptOutWindowBlocks cannot be > MAX_STAKER_OPT_OUT_WINDOW_BLOCKS"
467:         );
468:         require(
469:             newOperatorDetails.stakerOptOutWindowBlocks >= _operatorDetails[operator].stakerOptOutWindowBlocks,
470:             "DelegationManager._setOperatorDetails: stakerOptOutWindowBlocks cannot be decreased"
471:         );
472:         _operatorDetails[operator] = newOperatorDetails;
473:         emit OperatorDetailsModified(msg.sender, newOperatorDetails);
474:     }
475: 
476:     /**
477:      * @notice Delegates *from* a `staker` *to* an `operator`.
478:      * @param staker The address to delegate *from* -- this address is delegating control of its own assets.
479:      * @param operator The address to delegate *to* -- this address is being given power to place the `staker`'s assets at risk on services
480:      * @param approverSignatureAndExpiry Verifies the operator approves of this delegation
481:      * @param approverSalt Is a salt used to help guarantee signature uniqueness. Each salt can only be used once by a given approver.
482:      * @dev Ensures that:
483:      *          1) the `staker` is not already delegated to an operator
484:      *          2) the `operator` has indeed registered as an operator in EigenLayer
485:      *          3) if applicable, that the approver signature is valid and non-expired
486:      */
487:     function _delegate(
488:         address staker,
489:         address operator,
490:         SignatureWithExpiry memory approverSignatureAndExpiry,
491:         bytes32 approverSalt
492:     ) internal onlyWhenNotPaused(PAUSED_NEW_DELEGATION) {
493:         require(!isDelegated(staker), "DelegationManager._delegate: staker is already actively delegated");
494:         require(isOperator(operator), "DelegationManager._delegate: operator is not registered in EigenLayer");
495: 
496:         // fetch the operator's `delegationApprover` address and store it in memory in case we need to use it multiple times
497:         address _delegationApprover = _operatorDetails[operator].delegationApprover;
498:         /**
499:          * Check the `_delegationApprover`'s signature, if applicable.
500:          * If the `_delegationApprover` is the zero address, then the operator allows all stakers to delegate to them and this verification is skipped.
501:          * If the `_delegationApprover` or the `operator` themselves is the caller, then approval is assumed and signature verification is skipped as well.
502:          */
503:         if (_delegationApprover != address(0) && msg.sender != _delegationApprover && msg.sender != operator) {
504:             // check the signature expiry
505:             require(
506:                 approverSignatureAndExpiry.expiry >= block.timestamp,
507:                 "DelegationManager._delegate: approver signature expired"
508:             );
509:             // check that the salt hasn't been used previously, then mark the salt as spent
510:             require(
511:                 !delegationApproverSaltIsSpent[_delegationApprover][approverSalt],
512:                 "DelegationManager._delegate: approverSalt already spent"
513:             );
514:             delegationApproverSaltIsSpent[_delegationApprover][approverSalt] = true;
515: 
516:             // calculate the digest hash
517:             bytes32 approverDigestHash = calculateDelegationApprovalDigestHash(
518:                 staker,
519:                 operator,
520:                 _delegationApprover,
521:                 approverSalt,
522:                 approverSignatureAndExpiry.expiry
523:             );
524: 
525:             // actually check that the signature is valid
526:             EIP1271SignatureUtils.checkSignature_EIP1271(
527:                 _delegationApprover,
528:                 approverDigestHash,
529:                 approverSignatureAndExpiry.signature
530:             );
531:         }
532: 
533:         // record the delegation relation between the staker and operator, and emit an event
534:         delegatedTo[staker] = operator;
535:         emit StakerDelegated(staker, operator);
536: 
537:         (IStrategy[] memory strategies, uint256[] memory shares)
538:             = getDelegatableShares(staker);
539: 
540:         for (uint256 i = 0; i < strategies.length;) {
541:             _increaseOperatorShares({
542:                 operator: operator,
543:                 staker: staker,
544:                 strategy: strategies[i],
545:                 shares: shares[i]
546:             });
547: 
548:             unchecked { ++i; }
549:         }
550:     }
551: 
552:     /**
553:      * @dev commented-out param (middlewareTimesIndex) is the index in the operator that the staker who triggered the withdrawal was delegated to's middleware times array
554:      * This param is intended to be passed on to the Slasher contract, but is unused in the M2 release of these contracts, and is thus commented-out.
555:      */
556:     function _completeQueuedWithdrawal(
557:         Withdrawal calldata withdrawal,
558:         IERC20[] calldata tokens,
559:         uint256 /*middlewareTimesIndex*/,
560:         bool receiveAsTokens
561:     ) internal {
562:         bytes32 withdrawalRoot = calculateWithdrawalRoot(withdrawal);
563: 
564:         require(
565:             pendingWithdrawals[withdrawalRoot], 
566:             "DelegationManager._completeQueuedWithdrawal: action is not in queue"
567:         );
568: 
569:         require(
570:             withdrawal.startBlock + minWithdrawalDelayBlocks <= block.number, 
571:             "DelegationManager._completeQueuedWithdrawal: minWithdrawalDelayBlocks period has not yet passed"
572:         );
573: 
574:         require(
575:             msg.sender == withdrawal.withdrawer, 
576:             "DelegationManager._completeQueuedWithdrawal: only withdrawer can complete action"
577:         );
578: 
579:         if (receiveAsTokens) {
580:             require(
581:                 tokens.length == withdrawal.strategies.length, 
582:                 "DelegationManager._completeQueuedWithdrawal: input length mismatch"
583:             );
584:         }
585: 
586:         // Remove `withdrawalRoot` from pending roots
587:         delete pendingWithdrawals[withdrawalRoot];
588: 
589:         // Finalize action by converting shares to tokens for each strategy, or
590:         // by re-awarding shares in each strategy.
591:         if (receiveAsTokens) {
592:             for (uint256 i = 0; i < withdrawal.strategies.length; ) {
593:                 require(
594:                     withdrawal.startBlock + strategyWithdrawalDelayBlocks[withdrawal.strategies[i]] <= block.number,
595:                     "DelegationManager._completeQueuedWithdrawal: withdrawalDelayBlocks period has not yet passed for this strategy"
596:                 );
597: 
598:                 _withdrawSharesAsTokens({
599:                     staker: withdrawal.staker,
600:                     withdrawer: msg.sender,
601:                     strategy: withdrawal.strategies[i],
602:                     shares: withdrawal.shares[i],
603:                     token: tokens[i]
604:                 });
605:                 unchecked { ++i; }
606:             }
607:         // Award shares back in StrategyManager/EigenPodManager. If withdrawer is delegated, increase the shares delegated to the operator
608:         } else {
609:             address currentOperator = delegatedTo[msg.sender];
610:             for (uint256 i = 0; i < withdrawal.strategies.length; ) {
611:                 require(
612:                     withdrawal.startBlock + strategyWithdrawalDelayBlocks[withdrawal.strategies[i]] <= block.number, 
613:                     "DelegationManager._completeQueuedWithdrawal: withdrawalDelayBlocks period has not yet passed for this strategy"
614:                 );
615: 
616:                 /** When awarding podOwnerShares in EigenPodManager, we need to be sure to only give them back to the original podOwner.
617:                  * Other strategy shares can + will be awarded to the withdrawer.
618:                  */
619:                 if (withdrawal.strategies[i] == beaconChainETHStrategy) {
620:                     address staker = withdrawal.staker;
621:                     /**
622:                     * Update shares amount depending upon the returned value.
623:                     * The return value will be lower than the input value in the case where the staker has an existing share deficit
624:                     */
625:                     uint256 increaseInDelegateableShares = eigenPodManager.addShares({
626:                         podOwner: staker,
627:                         shares: withdrawal.shares[i]
628:                     });
629:                     address podOwnerOperator = delegatedTo[staker];
630:                     // Similar to `isDelegated` logic
631:                     if (podOwnerOperator != address(0)) {
632:                         _increaseOperatorShares({
633:                             operator: podOwnerOperator,
634:                             // the 'staker' here is the address receiving new shares
635:                             staker: staker,
636:                             strategy: withdrawal.strategies[i],
637:                             shares: increaseInDelegateableShares
638:                         });
639:                     }
640:                 } else {
641:                     strategyManager.addShares(msg.sender, tokens[i], withdrawal.strategies[i], withdrawal.shares[i]);
642:                     // Similar to `isDelegated` logic
643:                     if (currentOperator != address(0)) {
644:                         _increaseOperatorShares({
645:                             operator: currentOperator,
646:                             // the 'staker' here is the address receiving new shares
647:                             staker: msg.sender,
648:                             strategy: withdrawal.strategies[i],
649:                             shares: withdrawal.shares[i]
650:                         });
651:                     }
652:                 }
653:                 unchecked { ++i; }
654:             }
655:         }
656: 
657:         emit WithdrawalCompleted(withdrawalRoot);
658:     }
659: 
660:     // @notice Increases `operator`s delegated shares in `strategy` by `shares` and emits an `OperatorSharesIncreased` event
661:     function _increaseOperatorShares(address operator, address staker, IStrategy strategy, uint256 shares) internal {
662:         operatorShares[operator][strategy] += shares;
663:         emit OperatorSharesIncreased(operator, staker, strategy, shares);
664:     }
665: 
666:     // @notice Decreases `operator`s delegated shares in `strategy` by `shares` and emits an `OperatorSharesDecreased` event
667:     function _decreaseOperatorShares(address operator, address staker, IStrategy strategy, uint256 shares) internal {
668:         // This will revert on underflow, so no check needed
669:         operatorShares[operator][strategy] -= shares;
670:         emit OperatorSharesDecreased(operator, staker, strategy, shares);
671:     }
672: 
673:     /**
674:      * @notice Removes `shares` in `strategies` from `staker` who is currently delegated to `operator` and queues a withdrawal to the `withdrawer`.
675:      * @dev If the `operator` is indeed an operator, then the operator's delegated shares in the `strategies` are also decreased appropriately.
676:      * @dev If `withdrawer` is not the same address as `staker`, then thirdPartyTransfersForbidden[strategy] must be set to false in the StrategyManager.
677:      */
678:     function _removeSharesAndQueueWithdrawal(
679:         address staker, 
680:         address operator,
681:         address withdrawer,
682:         IStrategy[] memory strategies, 
683:         uint256[] memory shares
684:     ) internal returns (bytes32) {
685:         require(staker != address(0), "DelegationManager._removeSharesAndQueueWithdrawal: staker cannot be zero address");
686:         require(strategies.length != 0, "DelegationManager._removeSharesAndQueueWithdrawal: strategies cannot be empty");
687:     
688:         // Remove shares from staker and operator
689:         // Each of these operations fail if we attempt to remove more shares than exist
690:         for (uint256 i = 0; i < strategies.length;) {
691:             // Similar to `isDelegated` logic
692:             if (operator != address(0)) {
693:                 _decreaseOperatorShares({
694:                     operator: operator,
695:                     staker: staker,
696:                     strategy: strategies[i],
697:                     shares: shares[i]
698:                 });
699:             }
700: 
701:             // Remove active shares from EigenPodManager/StrategyManager
702:             if (strategies[i] == beaconChainETHStrategy) {
703:                 /**
704:                  * This call will revert if it would reduce the Staker's virtual beacon chain ETH shares below zero.
705:                  * This behavior prevents a Staker from queuing a withdrawal which improperly removes excessive
706:                  * shares from the operator to whom the staker is delegated.
707:                  * It will also revert if the share amount being withdrawn is not a whole Gwei amount.
708:                  */
709:                 eigenPodManager.removeShares(staker, shares[i]);
710:             } else {
711:                 require(
712:                     staker == withdrawer || !strategyManager.thirdPartyTransfersForbidden(strategies[i]),
713:                     "DelegationManager._removeSharesAndQueueWithdrawal: withdrawer must be same address as staker if thirdPartyTransfersForbidden are set"
714:                 );
715:                 // this call will revert if `shares[i]` exceeds the Staker's current shares in `strategies[i]`
716:                 strategyManager.removeShares(staker, strategies[i], shares[i]);
717:             }
718: 
719:             unchecked { ++i; }
720:         }
721: 
722:         // Create queue entry and increment withdrawal nonce
723:         uint256 nonce = cumulativeWithdrawalsQueued[staker];
724:         cumulativeWithdrawalsQueued[staker]++;
725: 
726:         Withdrawal memory withdrawal = Withdrawal({
727:             staker: staker,
728:             delegatedTo: operator,
729:             withdrawer: withdrawer,
730:             nonce: nonce,
731:             startBlock: uint32(block.number),
732:             strategies: strategies,
733:             shares: shares
734:         });
735: 
736:         bytes32 withdrawalRoot = calculateWithdrawalRoot(withdrawal);
737: 
738:         // Place withdrawal in queue
739:         pendingWithdrawals[withdrawalRoot] = true;
740: 
741:         emit WithdrawalQueued(withdrawalRoot, withdrawal);
742:         return withdrawalRoot;
743:     }
744: 
745:     /**
746:      * @notice Withdraws `shares` in `strategy` to `withdrawer`. If the shares are virtual beaconChainETH shares, then a call is ultimately forwarded to the
747:      * `staker`s EigenPod; otherwise a call is ultimately forwarded to the `strategy` with info on the `token`.
748:      */
749:     function _withdrawSharesAsTokens(address staker, address withdrawer, IStrategy strategy, uint256 shares, IERC20 token) internal {
750:         if (strategy == beaconChainETHStrategy) {
751:             eigenPodManager.withdrawSharesAsTokens({
752:                 podOwner: staker,
753:                 destination: withdrawer,
754:                 shares: shares
755:             });
756:         } else {
757:             strategyManager.withdrawSharesAsTokens(withdrawer, strategy, shares, token);
758:         }
759:     }
760: 
761:     function _setMinWithdrawalDelayBlocks(uint256 _minWithdrawalDelayBlocks) internal {
762:         require(
763:             _minWithdrawalDelayBlocks <= MAX_WITHDRAWAL_DELAY_BLOCKS,
764:             "DelegationManager._setMinWithdrawalDelayBlocks: _minWithdrawalDelayBlocks cannot be > MAX_WITHDRAWAL_DELAY_BLOCKS"
765:         );
766:         emit MinWithdrawalDelayBlocksSet(minWithdrawalDelayBlocks, _minWithdrawalDelayBlocks);
767:         minWithdrawalDelayBlocks = _minWithdrawalDelayBlocks;
768:     }
769: 
770:     /**
771:      * @notice Sets the withdrawal delay blocks for each strategy in `_strategies` to `_withdrawalDelayBlocks`.
772:      * gets called when initializing contract or by calling `setStrategyWithdrawalDelayBlocks`
773:      */
774:     function _setStrategyWithdrawalDelayBlocks(
775:         IStrategy[] calldata _strategies,
776:         uint256[] calldata _withdrawalDelayBlocks
777:     ) internal {
778:         require(
779:             _strategies.length == _withdrawalDelayBlocks.length,
780:             "DelegationManager._setStrategyWithdrawalDelayBlocks: input length mismatch"
781:         );
782:         uint256 numStrats = _strategies.length;
783:         for (uint256 i = 0; i < numStrats; ++i) {
784:             IStrategy strategy = _strategies[i];
785:             uint256 prevStrategyWithdrawalDelayBlocks = strategyWithdrawalDelayBlocks[strategy];
786:             uint256 newStrategyWithdrawalDelayBlocks = _withdrawalDelayBlocks[i];
787:             require(
788:                 newStrategyWithdrawalDelayBlocks <= MAX_WITHDRAWAL_DELAY_BLOCKS,
789:                 "DelegationManager._setStrategyWithdrawalDelayBlocks: _withdrawalDelayBlocks cannot be > MAX_WITHDRAWAL_DELAY_BLOCKS"
790:             );
791: 
792:             // set the new withdrawal delay blocks
793:             strategyWithdrawalDelayBlocks[strategy] = newStrategyWithdrawalDelayBlocks;
794:             emit StrategyWithdrawalDelayBlocksSet(
795:                 strategy,
796:                 prevStrategyWithdrawalDelayBlocks,
797:                 newStrategyWithdrawalDelayBlocks
798:             );
799:         }
800:     }
801: 
802:     /*******************************************************************************
803:                             VIEW FUNCTIONS
804:     *******************************************************************************/
805: 
806:     /**
807:      * @notice Getter function for the current EIP-712 domain separator for this contract.
808:      *
809:      * @dev The domain separator will change in the event of a fork that changes the ChainID.
810:      * @dev By introducing a domain separator the DApp developers are guaranteed that there can be no signature collision.
811:      * for more detailed information please read EIP-712.
812:      */
813:     function domainSeparator() public view returns (bytes32) {
814:         if (block.chainid == ORIGINAL_CHAIN_ID) {
815:             return _DOMAIN_SEPARATOR;
816:         } else {
817:             return _calculateDomainSeparator();
818:         }
819:     }
820: 
821:     /**
822:      * @notice Returns 'true' if `staker` *is* actively delegated, and 'false' otherwise.
823:      */
824:     function isDelegated(address staker) public view returns (bool) {
825:         return (delegatedTo[staker] != address(0));
826:     }
827: 
828:     /**
829:      * @notice Returns true is an operator has previously registered for delegation.
830:      */
831:     function isOperator(address operator) public view returns (bool) {
832:         return (_operatorDetails[operator].earningsReceiver != address(0));
833:     }
834: 
835:     /**
836:      * @notice Returns the OperatorDetails struct associated with an `operator`.
837:      */
838:     function operatorDetails(address operator) external view returns (OperatorDetails memory) {
839:         return _operatorDetails[operator];
840:     }
841: 
842:     /*
843:      * @notice Returns the earnings receiver address for an operator
844:      */
845:     function earningsReceiver(address operator) external view returns (address) {
846:         return _operatorDetails[operator].earningsReceiver;
847:     }
848: 
849:     /**
850:      * @notice Returns the delegationApprover account for an operator
851:      */
852:     function delegationApprover(address operator) external view returns (address) {
853:         return _operatorDetails[operator].delegationApprover;
854:     }
855: 
856:     /**
857:      * @notice Returns the stakerOptOutWindowBlocks for an operator
858:      */
859:     function stakerOptOutWindowBlocks(address operator) external view returns (uint256) {
860:         return _operatorDetails[operator].stakerOptOutWindowBlocks;
861:     }
862: 
863:     /// @notice Given array of strategies, returns array of shares for the operator
864:     function getOperatorShares(
865:         address operator,
866:         IStrategy[] memory strategies
867:     ) public view returns (uint256[] memory) {
868:         uint256[] memory shares = new uint256[](strategies.length);
869:         for (uint256 i = 0; i < strategies.length; ++i) {
870:             shares[i] = operatorShares[operator][strategies[i]];
871:         }
872:         return shares;
873:     }
874: 
875:     /**
876:      * @notice Returns the number of actively-delegatable shares a staker has across all strategies.
877:      * @dev Returns two empty arrays in the case that the Staker has no actively-delegateable shares.
878:      */
879:     function getDelegatableShares(address staker) public view returns (IStrategy[] memory, uint256[] memory) {
880:         // Get currently active shares and strategies for `staker`
881:         int256 podShares = eigenPodManager.podOwnerShares(staker);
882:         (IStrategy[] memory strategyManagerStrats, uint256[] memory strategyManagerShares) 
883:             = strategyManager.getDeposits(staker);
884: 
885:         // Has no shares in EigenPodManager, but potentially some in StrategyManager
886:         if (podShares <= 0) {
887:             return (strategyManagerStrats, strategyManagerShares);
888:         }
889: 
890:         IStrategy[] memory strategies;
891:         uint256[] memory shares;
892: 
893:         if (strategyManagerStrats.length == 0) {
894:             // Has shares in EigenPodManager, but not in StrategyManager
895:             strategies = new IStrategy[](1);
896:             shares = new uint256[](1);
897:             strategies[0] = beaconChainETHStrategy;
898:             shares[0] = uint256(podShares);
899:         } else {
900:             // Has shares in both
901:             
902:             // 1. Allocate return arrays
903:             strategies = new IStrategy[](strategyManagerStrats.length + 1);
904:             shares = new uint256[](strategies.length);
905:             
906:             // 2. Place StrategyManager strats/shares in return arrays
907:             for (uint256 i = 0; i < strategyManagerStrats.length; ) {
908:                 strategies[i] = strategyManagerStrats[i];
909:                 shares[i] = strategyManagerShares[i];
910: 
911:                 unchecked { ++i; }
912:             }
913: 
914:             // 3. Place EigenPodManager strat/shares in return arrays
915:             strategies[strategies.length - 1] = beaconChainETHStrategy;
916:             shares[strategies.length - 1] = uint256(podShares);
917:         }
918: 
919:         return (strategies, shares);
920:     }
921: 
922:     /**
923:      * @notice Given a list of strategies, return the minimum number of blocks that must pass to withdraw
924:      * from all the inputted strategies. Return value is >= minWithdrawalDelayBlocks as this is the global min withdrawal delay.
925:      * @param strategies The strategies to check withdrawal delays for
926:      */
927:     function getWithdrawalDelay(IStrategy[] calldata strategies) public view returns (uint256) {
928:         uint256 withdrawalDelay = minWithdrawalDelayBlocks;
929:         for (uint256 i = 0; i < strategies.length; ++i) {
930:             uint256 currWithdrawalDelay = strategyWithdrawalDelayBlocks[strategies[i]];
931:             if (currWithdrawalDelay > withdrawalDelay) {
932:                 withdrawalDelay = currWithdrawalDelay;
933:             }
934:         }
935:         return withdrawalDelay;
936:     }
937: 
938:     /// @notice Returns the keccak256 hash of `withdrawal`.
939:     function calculateWithdrawalRoot(Withdrawal memory withdrawal) public pure returns (bytes32) {
940:         return keccak256(abi.encode(withdrawal));
941:     }
942: 
943:     /**
944:      * @notice Calculates the digestHash for a `staker` to sign to delegate to an `operator`
945:      * @param staker The signing staker
946:      * @param operator The operator who is being delegated to
947:      * @param expiry The desired expiry time of the staker's signature
948:      */
949:     function calculateCurrentStakerDelegationDigestHash(
950:         address staker,
951:         address operator,
952:         uint256 expiry
953:     ) external view returns (bytes32) {
954:         // fetch the staker's current nonce
955:         uint256 currentStakerNonce = stakerNonce[staker];
956:         // calculate the digest hash
957:         return calculateStakerDelegationDigestHash(staker, currentStakerNonce, operator, expiry);
958:     }
959: 
960:     /**
961:      * @notice Calculates the digest hash to be signed and used in the `delegateToBySignature` function
962:      * @param staker The signing staker
963:      * @param _stakerNonce The nonce of the staker. In practice we use the staker's current nonce, stored at `stakerNonce[staker]`
964:      * @param operator The operator who is being delegated to
965:      * @param expiry The desired expiry time of the staker's signature
966:      */
967:     function calculateStakerDelegationDigestHash(
968:         address staker,
969:         uint256 _stakerNonce,
970:         address operator,
971:         uint256 expiry
972:     ) public view returns (bytes32) {
973:         // calculate the struct hash
974:         bytes32 stakerStructHash = keccak256(
975:             abi.encode(STAKER_DELEGATION_TYPEHASH, staker, operator, _stakerNonce, expiry)
976:         );
977:         // calculate the digest hash
978:         bytes32 stakerDigestHash = keccak256(abi.encodePacked("\x19\x01", domainSeparator(), stakerStructHash));
979:         return stakerDigestHash;
980:     }
981: 
982:     /**
983:      * @notice Calculates the digest hash to be signed by the operator's delegationApprove and used in the `delegateTo` and `delegateToBySignature` functions.
984:      * @param staker The account delegating their stake
985:      * @param operator The account receiving delegated stake
986:      * @param _delegationApprover the operator's `delegationApprover` who will be signing the delegationHash (in general)
987:      * @param approverSalt A unique and single use value associated with the approver signature.
988:      * @param expiry Time after which the approver's signature becomes invalid
989:      */
990:     function calculateDelegationApprovalDigestHash(
991:         address staker,
992:         address operator,
993:         address _delegationApprover,
994:         bytes32 approverSalt,
995:         uint256 expiry
996:     ) public view returns (bytes32) {
997:         // calculate the struct hash
998:         bytes32 approverStructHash = keccak256(
999:             abi.encode(DELEGATION_APPROVAL_TYPEHASH, _delegationApprover, staker, operator, approverSalt, expiry)
1000:         );
1001:         // calculate the digest hash
1002:         bytes32 approverDigestHash = keccak256(abi.encodePacked("\x19\x01", domainSeparator(), approverStructHash));
1003:         return approverDigestHash;
1004:     }
1005: 
1006:     /**
1007:      * @dev Recalculates the domain separator when the chainid changes due to a fork.
1008:      */
1009:     function _calculateDomainSeparator() internal view returns (bytes32) {
1010:         return keccak256(abi.encode(DOMAIN_TYPEHASH, keccak256(bytes("EigenLayer")), block.chainid, address(this)));
1011:     }
1012: }
1013: 

['1']

1: // SPDX-License-Identifier: BUSL-1.1
2: pragma solidity >=0.5.0;
3: 
4: /**
5:  * @title The interface for common signature utilities.
6:  * @author Layr Labs, Inc.
7:  * @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service
8:  */
9: interface ISignatureUtils {
10:     // @notice Struct that bundles together a signature and an expiration time for the signature. Used primarily for stack management.
11:     struct SignatureWithExpiry {
12:         // the signature itself, formatted as a single bytes object
13:         bytes signature;
14:         // the expiration timestamp (UTC) of the signature
15:         uint256 expiry;
16:     }
17: 
18:     // @notice Struct that bundles together a signature, a salt for uniqueness, and an expiration time for the signature. Used primarily for stack management.
19:     struct SignatureWithSaltAndExpiry {
20:         // the signature itself, formatted as a single bytes object
21:         bytes signature;
22:         // the salt used to generate the signature
23:         bytes32 salt;
24:         // the expiration timestamp (UTC) of the signature
25:         uint256 expiry;
26:     }
27: }

['1']

1: // SPDX-License-Identifier: BUSL-1.1
2: pragma solidity =0.8.12;
3: 
4: import "@openzeppelin-upgrades/contracts/proxy/utils/Initializable.sol";
5: import "@openzeppelin-upgrades/contracts/access/OwnableUpgradeable.sol";
6: import "@openzeppelin-upgrades/contracts/security/ReentrancyGuardUpgradeable.sol";
7: import "@openzeppelin-upgrades/contracts/utils/AddressUpgradeable.sol";
8: import "@openzeppelin-upgrades/contracts/utils/math/MathUpgradeable.sol";
9: import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
10: 
11: import "../libraries/BeaconChainProofs.sol";
12: import "../libraries/BytesLib.sol";
13: import "../libraries/Endian.sol";
14: 
15: import "../interfaces/IETHPOSDeposit.sol";
16: import "../interfaces/IEigenPodManager.sol";
17: import "../interfaces/IEigenPod.sol";
18: import "../interfaces/IDelayedWithdrawalRouter.sol";
19: import "../interfaces/IPausable.sol";
20: 
21: import "./EigenPodPausingConstants.sol";
22: 
23: /**
24:  * @title The implementation contract used for restaking beacon chain ETH on EigenLayer
25:  * @author Layr Labs, Inc.
26:  * @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service
27:  * @notice The main functionalities are:
28:  * - creating new ETH validators with their withdrawal credentials pointed to this contract
29:  * - proving from beacon chain state roots that withdrawal credentials are pointed to this contract
30:  * - proving from beacon chain state roots the balances of ETH validators with their withdrawal credentials
31:  *   pointed to this contract
32:  * - updating aggregate balances in the EigenPodManager
33:  * - withdrawing eth when withdrawals are initiated
34:  * @notice This EigenPod Beacon Proxy implementation adheres to the current Capella consensus specs
35:  * @dev Note that all beacon chain balances are stored as gwei within the beacon chain datastructures. We choose
36:  *   to account balances in terms of gwei in the EigenPod contract and convert to wei when making calls to other contracts
37:  */
38: contract EigenPod is IEigenPod, Initializable, ReentrancyGuardUpgradeable, EigenPodPausingConstants {
39:     using BytesLib for bytes;
40:     using SafeERC20 for IERC20;
41:     using BeaconChainProofs for *;
42: 
43:     // CONSTANTS + IMMUTABLES
44:     // @notice Internal constant used in calculations, since the beacon chain stores balances in Gwei rather than wei
45:     uint256 internal constant GWEI_TO_WEI = 1e9;
46: 
47:     /**
48:      * @notice Maximum "staleness" of a Beacon Chain state root against which `verifyBalanceUpdate` or `verifyWithdrawalCredentials` may be proven.
49:      * We can't allow "stale" roots to be used for restaking as the validator may have been slashed in a more updated beacon state root. 
50:      */
51:     uint256 internal constant VERIFY_BALANCE_UPDATE_WINDOW_SECONDS = 4.5 hours;
52: 
53:     /// @notice This is the beacon chain deposit contract
54:     IETHPOSDeposit public immutable ethPOS;
55: 
56:     /// @notice Contract used for withdrawal routing, to provide an extra "safety net" mechanism
57:     IDelayedWithdrawalRouter public immutable delayedWithdrawalRouter;
58: 
59:     /// @notice The single EigenPodManager for EigenLayer
60:     IEigenPodManager public immutable eigenPodManager;
61: 
62:     ///@notice The maximum amount of ETH, in gwei, a validator can have restaked in the eigenlayer
63:     uint64 public immutable MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR;
64: 
65:     /// @notice This is the genesis time of the beacon state, to help us calculate conversions between slot and timestamp
66:     uint64 public immutable GENESIS_TIME;
67: 
68:     // STORAGE VARIABLES
69:     /// @notice The owner of this EigenPod
70:     address public podOwner;
71: 
72:     /**
73:      * @notice The latest timestamp at which the pod owner withdrew the balance of the pod, via calling `withdrawBeforeRestaking`.
74:      * @dev This variable is only updated when the `withdrawBeforeRestaking` function is called, which can only occur before `hasRestaked` is set to true for this pod.
75:      * Proofs for this pod are only valid against Beacon Chain state roots corresponding to timestamps after the stored `mostRecentWithdrawalTimestamp`.
76:      */
77:     uint64 public mostRecentWithdrawalTimestamp;
78: 
79:     /// @notice the amount of execution layer ETH in this contract that is staked in EigenLayer (i.e. withdrawn from the Beacon Chain but not from EigenLayer),
80:     uint64 public withdrawableRestakedExecutionLayerGwei;
81: 
82:     /// @notice an indicator of whether or not the podOwner has ever "fully restaked" by successfully calling `verifyCorrectWithdrawalCredentials`.
83:     bool public hasRestaked;
84: 
85:     /// @notice This is a mapping of validatorPubkeyHash to timestamp to whether or not they have proven a withdrawal for that timestamp
86:     mapping(bytes32 => mapping(uint64 => bool)) public provenWithdrawal;
87: 
88:     /// @notice This is a mapping that tracks a validator's information by their pubkey hash
89:     mapping(bytes32 => ValidatorInfo) internal _validatorPubkeyHashToInfo;
90: 
91:     /// @notice This variable tracks any ETH deposited into this contract via the `receive` fallback function
92:     uint256 public nonBeaconChainETHBalanceWei;
93: 
94:      /// @notice This variable tracks the total amount of partial withdrawals claimed via merkle proofs prior to a switch to ZK proofs for claiming partial withdrawals
95:     uint64 public sumOfPartialWithdrawalsClaimedGwei;
96: 
97:     modifier onlyEigenPodManager() {
98:         require(msg.sender == address(eigenPodManager), "EigenPod.onlyEigenPodManager: not eigenPodManager");
99:         _;
100:     }
101: 
102:     modifier onlyEigenPodOwner() {
103:         require(msg.sender == podOwner, "EigenPod.onlyEigenPodOwner: not podOwner");
104:         _;
105:     }
106: 
107:     modifier hasNeverRestaked() {
108:         require(!hasRestaked, "EigenPod.hasNeverRestaked: restaking is enabled");
109:         _;
110:     }
111: 
112:     /// @notice checks that hasRestaked is set to true by calling activateRestaking()
113:     modifier hasEnabledRestaking() {
114:         require(hasRestaked, "EigenPod.hasEnabledRestaking: restaking is not enabled");
115:         _;
116:     }
117: 
118:     /// @notice Checks that `timestamp` is strictly greater than the value stored in `mostRecentWithdrawalTimestamp`
119:     modifier proofIsForValidTimestamp(uint64 timestamp) {
120:         require(
121:             timestamp > mostRecentWithdrawalTimestamp,
122:             "EigenPod.proofIsForValidTimestamp: beacon chain proof must be for timestamp after mostRecentWithdrawalTimestamp"
123:         );
124:         _;
125:     }
126: 
127:     /**
128:      * @notice Based on 'Pausable' code, but uses the storage of the EigenPodManager instead of this contract. This construction
129:      * is necessary for enabling pausing all EigenPods at the same time (due to EigenPods being Beacon Proxies).
130:      * Modifier throws if the `indexed`th bit of `_paused` in the EigenPodManager is 1, i.e. if the `index`th pause switch is flipped.
131:      */
132:     modifier onlyWhenNotPaused(uint8 index) {
133:         require(
134:             !IPausable(address(eigenPodManager)).paused(index),
135:             "EigenPod.onlyWhenNotPaused: index is paused in EigenPodManager"
136:         );
137:         _;
138:     }
139: 
140:     constructor(
141:         IETHPOSDeposit _ethPOS,
142:         IDelayedWithdrawalRouter _delayedWithdrawalRouter,
143:         IEigenPodManager _eigenPodManager,
144:         uint64 _MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR,
145:         uint64 _GENESIS_TIME
146:     ) {
147:         ethPOS = _ethPOS;
148:         delayedWithdrawalRouter = _delayedWithdrawalRouter;
149:         eigenPodManager = _eigenPodManager;
150:         MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR = _MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR;
151:         GENESIS_TIME = _GENESIS_TIME;
152:         _disableInitializers();
153:     }
154: 
155:     /// @notice Used to initialize the pointers to addresses crucial to the pod's functionality. Called on construction by the EigenPodManager.
156:     function initialize(address _podOwner) external initializer {
157:         require(_podOwner != address(0), "EigenPod.initialize: podOwner cannot be zero address");
158:         podOwner = _podOwner;
159:         /**
160:          * From the M2 deployment onwards, we are requiring that pods deployed are by default enabled with restaking
161:          * In prior deployments without proofs, EigenPods could be deployed with restaking disabled so as to allow
162:          * simple (proof-free) withdrawals.  However, this is no longer the case.  Thus going forward, all pods are
163:          * initialized with hasRestaked set to true.
164:          */
165:         hasRestaked = true;
166:         emit RestakingActivated(podOwner);
167:     }
168: 
169:     /// @notice payable fallback function that receives ether deposited to the eigenpods contract
170:     receive() external payable {
171:         nonBeaconChainETHBalanceWei += msg.value;
172:         emit NonBeaconChainETHReceived(msg.value);
173:     }
174: 
175:     /**
176:      * @notice This function records an update (either increase or decrease) in a validator's balance.
177:      * @param oracleTimestamp The oracleTimestamp whose state root the proof will be proven against.
178:      *        Must be within `VERIFY_BALANCE_UPDATE_WINDOW_SECONDS` of the current block.
179:      * @param validatorIndices is the list of indices of the validators being proven, refer to consensus specs 
180:      * @param stateRootProof proves a `beaconStateRoot` against a block root fetched from the oracle
181:      * @param validatorFieldsProofs proofs against the `beaconStateRoot` for each validator in `validatorFields`
182:      * @param validatorFields are the fields of the "Validator Container", refer to consensus specs
183:      * @dev For more details on the Beacon Chain spec, see: https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#validator
184:      */
185:     function verifyBalanceUpdates(
186:         uint64 oracleTimestamp,
187:         uint40[] calldata validatorIndices,
188:         BeaconChainProofs.StateRootProof calldata stateRootProof,
189:         bytes[] calldata validatorFieldsProofs,
190:         bytes32[][] calldata validatorFields
191:     ) external onlyWhenNotPaused(PAUSED_EIGENPODS_VERIFY_BALANCE_UPDATE) {
192:         require(
193:             (validatorIndices.length == validatorFieldsProofs.length) && (validatorFieldsProofs.length == validatorFields.length),
194:             "EigenPod.verifyBalanceUpdates: validatorIndices and proofs must be same length"
195:         );
196: 
197:         // Balance updates should not be "stale" (older than VERIFY_BALANCE_UPDATE_WINDOW_SECONDS)
198:         require(
199:             oracleTimestamp + VERIFY_BALANCE_UPDATE_WINDOW_SECONDS >= block.timestamp,
200:             "EigenPod.verifyBalanceUpdates: specified timestamp is too far in past"
201:         );
202: 
203:         // Verify passed-in beaconStateRoot against oracle-provided block root:
204:         BeaconChainProofs.verifyStateRootAgainstLatestBlockRoot({
205:             latestBlockRoot: eigenPodManager.getBlockRootAtTimestamp(oracleTimestamp),
206:             beaconStateRoot: stateRootProof.beaconStateRoot,
207:             stateRootProof: stateRootProof.proof
208:         });
209: 
210:         int256 sharesDeltaGwei;
211:         for (uint256 i = 0; i < validatorIndices.length; i++) {
212:             sharesDeltaGwei += _verifyBalanceUpdate(
213:                 oracleTimestamp,
214:                 validatorIndices[i],
215:                 stateRootProof.beaconStateRoot,
216:                 validatorFieldsProofs[i], // Use validator fields proof because contains the effective balance
217:                 validatorFields[i]
218:             );
219:         }
220:         eigenPodManager.recordBeaconChainETHBalanceUpdate(podOwner, sharesDeltaGwei * int256(GWEI_TO_WEI));
221:     }
222: 
223:     /**
224:      * @notice This function records full and partial withdrawals on behalf of one or more of this EigenPod's validators
225:      * @param oracleTimestamp is the timestamp of the oracle slot that the withdrawal is being proven against
226:      * @param stateRootProof proves a `beaconStateRoot` against a block root fetched from the oracle
227:      * @param withdrawalProofs proves several withdrawal-related values against the `beaconStateRoot`
228:      * @param validatorFieldsProofs proves `validatorFields` against the `beaconStateRoot`
229:      * @param withdrawalFields are the fields of the withdrawals being proven
230:      * @param validatorFields are the fields of the validators being proven
231:      */
232:     function verifyAndProcessWithdrawals(
233:         uint64 oracleTimestamp,
234:         BeaconChainProofs.StateRootProof calldata stateRootProof,
235:         BeaconChainProofs.WithdrawalProof[] calldata withdrawalProofs,
236:         bytes[] calldata validatorFieldsProofs,
237:         bytes32[][] calldata validatorFields,
238:         bytes32[][] calldata withdrawalFields
239:     ) external onlyWhenNotPaused(PAUSED_EIGENPODS_VERIFY_WITHDRAWAL) {
240:         require(
241:             (validatorFields.length == validatorFieldsProofs.length) &&
242:                 (validatorFieldsProofs.length == withdrawalProofs.length) &&
243:                 (withdrawalProofs.length == withdrawalFields.length),
244:             "EigenPod.verifyAndProcessWithdrawals: inputs must be same length"
245:         );
246: 
247:         // Verify passed-in beaconStateRoot against oracle-provided block root:
248:         BeaconChainProofs.verifyStateRootAgainstLatestBlockRoot({
249:             latestBlockRoot: eigenPodManager.getBlockRootAtTimestamp(oracleTimestamp),
250:             beaconStateRoot: stateRootProof.beaconStateRoot,
251:             stateRootProof: stateRootProof.proof
252:         });
253: 
254:         VerifiedWithdrawal memory withdrawalSummary;
255:         for (uint256 i = 0; i < withdrawalFields.length; i++) {
256:             VerifiedWithdrawal memory verifiedWithdrawal = _verifyAndProcessWithdrawal(
257:                 stateRootProof.beaconStateRoot,
258:                 withdrawalProofs[i],
259:                 validatorFieldsProofs[i],
260:                 validatorFields[i],
261:                 withdrawalFields[i]
262:             );
263: 
264:             withdrawalSummary.amountToSendGwei += verifiedWithdrawal.amountToSendGwei;
265:             withdrawalSummary.sharesDeltaGwei += verifiedWithdrawal.sharesDeltaGwei;
266:         }
267: 
268:         // If any withdrawals are eligible for immediate redemption, send to the pod owner via
269:         // DelayedWithdrawalRouter
270:         if (withdrawalSummary.amountToSendGwei != 0) {
271:             _sendETH_AsDelayedWithdrawal(podOwner, withdrawalSummary.amountToSendGwei * GWEI_TO_WEI);
272:         }
273:         // If any withdrawals resulted in a change in the pod's shares, update the EigenPodManager
274:         if (withdrawalSummary.sharesDeltaGwei != 0) {
275:             eigenPodManager.recordBeaconChainETHBalanceUpdate(podOwner, withdrawalSummary.sharesDeltaGwei * int256(GWEI_TO_WEI));
276:         }
277:     }
278: 
279:     /*******************************************************************************
280:                     EXTERNAL FUNCTIONS CALLABLE BY EIGENPOD OWNER
281:     *******************************************************************************/
282: 
283:     /**
284:      * @notice This function verifies that the withdrawal credentials of validator(s) owned by the podOwner are pointed to
285:      * this contract. It also verifies the effective balance  of the validator.  It verifies the provided proof of the ETH validator against the beacon chain state
286:      * root, marks the validator as 'active' in EigenLayer, and credits the restaked ETH in Eigenlayer.
287:      * @param oracleTimestamp is the Beacon Chain timestamp whose state root the `proof` will be proven against.
288:      * @param stateRootProof proves a `beaconStateRoot` against a block root fetched from the oracle
289:      * @param validatorIndices is the list of indices of the validators being proven, refer to consensus specs
290:      * @param validatorFieldsProofs proofs against the `beaconStateRoot` for each validator in `validatorFields`
291:      * @param validatorFields are the fields of the "Validator Container", refer to consensus specs
292:      * for details: https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#validator
293:      */
294:     function verifyWithdrawalCredentials(
295:         uint64 oracleTimestamp,
296:         BeaconChainProofs.StateRootProof calldata stateRootProof,
297:         uint40[] calldata validatorIndices,
298:         bytes[] calldata validatorFieldsProofs,
299:         bytes32[][] calldata validatorFields
300:     )
301:         external
302:         onlyEigenPodOwner
303:         onlyWhenNotPaused(PAUSED_EIGENPODS_VERIFY_CREDENTIALS)
304:         // check that the provided `oracleTimestamp` is after the `mostRecentWithdrawalTimestamp`
305:         proofIsForValidTimestamp(oracleTimestamp)
306:         // ensure that caller has previously enabled restaking by calling `activateRestaking()`
307:         hasEnabledRestaking
308:     {
309:         require(
310:             (validatorIndices.length == validatorFieldsProofs.length) &&
311:                 (validatorFieldsProofs.length == validatorFields.length),
312:             "EigenPod.verifyWithdrawalCredentials: validatorIndices and proofs must be same length"
313:         );
314: 
315:         /**
316:          * Withdrawal credential proof should not be "stale" (older than VERIFY_BALANCE_UPDATE_WINDOW_SECONDS) as we are doing a balance check here
317:          * The validator container persists as the state evolves and even after the validator exits. So we can use a more "fresh" credential proof within
318:          * the VERIFY_BALANCE_UPDATE_WINDOW_SECONDS window, not just the first proof where the validator container is registered in the state.
319:          */
320:         require(
321:             oracleTimestamp + VERIFY_BALANCE_UPDATE_WINDOW_SECONDS >= block.timestamp,
322:             "EigenPod.verifyWithdrawalCredentials: specified timestamp is too far in past"
323:         );
324: 
325:         // Verify passed-in beaconStateRoot against oracle-provided block root:
326:         BeaconChainProofs.verifyStateRootAgainstLatestBlockRoot({
327:             latestBlockRoot: eigenPodManager.getBlockRootAtTimestamp(oracleTimestamp),
328:             beaconStateRoot: stateRootProof.beaconStateRoot,
329:             stateRootProof: stateRootProof.proof
330:         });
331: 
332:         uint256 totalAmountToBeRestakedWei;
333:         for (uint256 i = 0; i < validatorIndices.length; i++) {
334:             totalAmountToBeRestakedWei += _verifyWithdrawalCredentials(
335:                 oracleTimestamp,
336:                 stateRootProof.beaconStateRoot,
337:                 validatorIndices[i],
338:                 validatorFieldsProofs[i],
339:                 validatorFields[i]
340:             );
341:         }
342: 
343:         // Update the EigenPodManager on this pod's new balance
344:         eigenPodManager.recordBeaconChainETHBalanceUpdate(podOwner, int256(totalAmountToBeRestakedWei));
345:     }
346: 
347:     /// @notice Called by the pod owner to withdraw the nonBeaconChainETHBalanceWei
348:     function withdrawNonBeaconChainETHBalanceWei(
349:         address recipient,
350:         uint256 amountToWithdraw
351:     ) external onlyEigenPodOwner onlyWhenNotPaused(PAUSED_NON_PROOF_WITHDRAWALS) {
352:         require(
353:             amountToWithdraw <= nonBeaconChainETHBalanceWei,
354:             "EigenPod.withdrawnonBeaconChainETHBalanceWei: amountToWithdraw is greater than nonBeaconChainETHBalanceWei"
355:         );
356:         nonBeaconChainETHBalanceWei -= amountToWithdraw;
357:         emit NonBeaconChainETHWithdrawn(recipient, amountToWithdraw);
358:         _sendETH_AsDelayedWithdrawal(recipient, amountToWithdraw);
359:     }
360: 
361:     /// @notice called by owner of a pod to remove any ERC20s deposited in the pod
362:     function recoverTokens(
363:         IERC20[] memory tokenList,
364:         uint256[] memory amountsToWithdraw,
365:         address recipient
366:     ) external onlyEigenPodOwner onlyWhenNotPaused(PAUSED_NON_PROOF_WITHDRAWALS) {
367:         require(
368:             tokenList.length == amountsToWithdraw.length,
369:             "EigenPod.recoverTokens: tokenList and amountsToWithdraw must be same length"
370:         );
371:         for (uint256 i = 0; i < tokenList.length; i++) {
372:             tokenList[i].safeTransfer(recipient, amountsToWithdraw[i]);
373:         }
374:     }
375: 
376:     /**
377:      * @notice Called by the pod owner to activate restaking by withdrawing
378:      * all existing ETH from the pod and preventing further withdrawals via
379:      * "withdrawBeforeRestaking()"
380:      */
381:     function activateRestaking()
382:         external
383:         onlyWhenNotPaused(PAUSED_EIGENPODS_VERIFY_CREDENTIALS)
384:         onlyEigenPodOwner
385:         hasNeverRestaked
386:     {
387:         hasRestaked = true;
388:         _processWithdrawalBeforeRestaking(podOwner);
389: 
390:         emit RestakingActivated(podOwner);
391:     }
392: 
393:     /// @notice Called by the pod owner to withdraw the balance of the pod when `hasRestaked` is set to false
394:     function withdrawBeforeRestaking() external onlyEigenPodOwner hasNeverRestaked {
395:         _processWithdrawalBeforeRestaking(podOwner);
396:     }
397: 
398:     /*******************************************************************************
399:                     EXTERNAL FUNCTIONS CALLABLE BY EIGENPODMANAGER
400:     *******************************************************************************/
401: 
402:     /// @notice Called by EigenPodManager when the owner wants to create another ETH validator.
403:     function stake(
404:         bytes calldata pubkey,
405:         bytes calldata signature,
406:         bytes32 depositDataRoot
407:     ) external payable onlyEigenPodManager {
408:         // stake on ethpos
409:         require(msg.value == 32 ether, "EigenPod.stake: must initially stake for any validator with 32 ether");
410:         ethPOS.deposit{value: 32 ether}(pubkey, _podWithdrawalCredentials(), signature, depositDataRoot);
411:         emit EigenPodStaked(pubkey);
412:     }
413: 
414:     /**
415:      * @notice Transfers `amountWei` in ether from this contract to the specified `recipient` address
416:      * @notice Called by EigenPodManager to withdrawBeaconChainETH that has been added to the EigenPod's balance due to a withdrawal from the beacon chain.
417:      * @dev The podOwner must have already proved sufficient withdrawals, so that this pod's `withdrawableRestakedExecutionLayerGwei` exceeds the
418:      * `amountWei` input (when converted to GWEI).
419:      * @dev Reverts if `amountWei` is not a whole Gwei amount
420:      */
421:     function withdrawRestakedBeaconChainETH(address recipient, uint256 amountWei) external onlyEigenPodManager {
422:         require(amountWei % GWEI_TO_WEI == 0, "EigenPod.withdrawRestakedBeaconChainETH: amountWei must be a whole Gwei amount");
423:         uint64 amountGwei = uint64(amountWei / GWEI_TO_WEI);
424:         require(amountGwei <= withdrawableRestakedExecutionLayerGwei, "EigenPod.withdrawRestakedBeaconChainETH: amountGwei exceeds withdrawableRestakedExecutionLayerGwei");
425:         withdrawableRestakedExecutionLayerGwei -= amountGwei;
426:         emit RestakedBeaconChainETHWithdrawn(recipient, amountWei);
427:         // transfer ETH from pod to `recipient` directly
428:         _sendETH(recipient, amountWei);
429:     }
430: 
431:     /*******************************************************************************
432:                                 INTERNAL FUNCTIONS
433:     *******************************************************************************/
434:     /**
435:      * @notice internal function that proves an individual validator's withdrawal credentials
436:      * @param oracleTimestamp is the timestamp whose state root the `proof` will be proven against.
437:      * @param validatorIndex is the index of the validator being proven
438:      * @param validatorFieldsProof is the bytes that prove the ETH validator's  withdrawal credentials against a beacon chain state root
439:      * @param validatorFields are the fields of the "Validator Container", refer to consensus specs
440:      */
441:     function _verifyWithdrawalCredentials(
442:         uint64 oracleTimestamp,
443:         bytes32 beaconStateRoot,
444:         uint40 validatorIndex,
445:         bytes calldata validatorFieldsProof,
446:         bytes32[] calldata validatorFields
447:     ) internal returns (uint256) {
448:         bytes32 validatorPubkeyHash = validatorFields.getPubkeyHash();
449:         ValidatorInfo memory validatorInfo = _validatorPubkeyHashToInfo[validatorPubkeyHash];
450: 
451:         // Withdrawal credential proofs should only be processed for "INACTIVE" validators
452:         require(
453:             validatorInfo.status == VALIDATOR_STATUS.INACTIVE,
454:             "EigenPod.verifyCorrectWithdrawalCredentials: Validator must be inactive to prove withdrawal credentials"
455:         );
456: 
457:         // Ensure the `validatorFields` we're proving have the correct withdrawal credentials
458:         require(
459:             validatorFields.getWithdrawalCredentials() == bytes32(_podWithdrawalCredentials()),
460:             "EigenPod.verifyCorrectWithdrawalCredentials: Proof is not for this EigenPod"
461:         );
462: 
463:         /**
464:          * Deserialize the balance field from the Validator struct.  Note that this is the "effective" balance of the validator
465:          * rather than the current balance.  Effective balance is generated via a hystersis function such that an effective
466:          * balance, always a multiple of 1 ETH, will only lower to the next multiple of 1 ETH if the current balance is less
467:          * than 0.25 ETH below their current effective balance.  For example, if the effective balance is 31ETH, it only falls to
468:          * 30ETH when the true balance falls below 30.75ETH.  Thus in the worst case, the effective balance is overestimating the
469:          * actual validator balance by 0.25 ETH. 
470:          */
471:         uint64 validatorEffectiveBalanceGwei = validatorFields.getEffectiveBalanceGwei();
472: 
473:         // Verify passed-in validatorFields against verified beaconStateRoot:
474:         BeaconChainProofs.verifyValidatorFields({
475:             beaconStateRoot: beaconStateRoot,
476:             validatorFields: validatorFields,
477:             validatorFieldsProof: validatorFieldsProof,
478:             validatorIndex: validatorIndex
479:         });
480: 
481:         // Proofs complete - update this validator's status, record its proven balance, and save in state:
482:         validatorInfo.status = VALIDATOR_STATUS.ACTIVE;
483:         validatorInfo.validatorIndex = validatorIndex;
484:         validatorInfo.mostRecentBalanceUpdateTimestamp = oracleTimestamp;
485: 
486:         if (validatorEffectiveBalanceGwei > MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR) {
487:             validatorInfo.restakedBalanceGwei = MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR;
488:         } else {
489:             validatorInfo.restakedBalanceGwei = validatorEffectiveBalanceGwei;
490:         }
491:         _validatorPubkeyHashToInfo[validatorPubkeyHash] = validatorInfo;
492: 
493:         emit ValidatorRestaked(validatorIndex);
494:         emit ValidatorBalanceUpdated(validatorIndex, oracleTimestamp, validatorInfo.restakedBalanceGwei);
495: 
496:         return validatorInfo.restakedBalanceGwei * GWEI_TO_WEI;
497:     }
498: 
499:     function _verifyBalanceUpdate(
500:         uint64 oracleTimestamp,
501:         uint40 validatorIndex,
502:         bytes32 beaconStateRoot,
503:         bytes calldata validatorFieldsProof,
504:         bytes32[] calldata validatorFields
505:     ) internal returns(int256 sharesDeltaGwei){
506:         uint64 validatorEffectiveBalanceGwei = validatorFields.getEffectiveBalanceGwei();
507:         bytes32 validatorPubkeyHash = validatorFields.getPubkeyHash();
508:         ValidatorInfo memory validatorInfo = _validatorPubkeyHashToInfo[validatorPubkeyHash];
509: 
510:         // 1. Balance updates should be more recent than the most recent update
511:         require(
512:             validatorInfo.mostRecentBalanceUpdateTimestamp < oracleTimestamp,
513:             "EigenPod.verifyBalanceUpdate: Validators balance has already been updated for this timestamp"
514:         );
515: 
516:         // 2. Balance updates should only be performed on "ACTIVE" validators
517:         require(
518:             validatorInfo.status == VALIDATOR_STATUS.ACTIVE, 
519:             "EigenPod.verifyBalanceUpdate: Validator not active"
520:         );
521: 
522:         // 3. Balance updates should only be made before a validator is fully withdrawn. 
523:         // -- A withdrawable validator may not have withdrawn yet, so we require their balance is nonzero
524:         // -- A fully withdrawn validator should withdraw via verifyAndProcessWithdrawals
525:         if (validatorFields.getWithdrawableEpoch() <= _timestampToEpoch(oracleTimestamp)) {
526:             require(
527:                 validatorEffectiveBalanceGwei > 0,
528:                 "EigenPod.verifyBalanceUpdate: validator is withdrawable but has not withdrawn"
529:             );
530:         }
531: 
532:         // Verify passed-in validatorFields against verified beaconStateRoot:
533:         BeaconChainProofs.verifyValidatorFields({
534:             beaconStateRoot: beaconStateRoot,
535:             validatorFields: validatorFields,
536:             validatorFieldsProof: validatorFieldsProof,
537:             validatorIndex: validatorIndex
538:         });
539: 
540:         // Done with proofs! Now update the validator's balance and send to the EigenPodManager if needed
541: 
542:         uint64 currentRestakedBalanceGwei = validatorInfo.restakedBalanceGwei;
543:         uint64 newRestakedBalanceGwei;
544:         if (validatorEffectiveBalanceGwei > MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR) {
545:             newRestakedBalanceGwei = MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR;
546:         } else {
547:             newRestakedBalanceGwei = validatorEffectiveBalanceGwei;
548:         }
549:         
550:         // Update validator balance and timestamp, and save to state:
551:         validatorInfo.restakedBalanceGwei = newRestakedBalanceGwei;
552:         validatorInfo.mostRecentBalanceUpdateTimestamp = oracleTimestamp;
553:         _validatorPubkeyHashToInfo[validatorPubkeyHash] = validatorInfo;
554: 
555:         // If our new and old balances differ, calculate the delta and send to the EigenPodManager
556:         if (newRestakedBalanceGwei != currentRestakedBalanceGwei) {
557:             emit ValidatorBalanceUpdated(validatorIndex, oracleTimestamp, newRestakedBalanceGwei);
558: 
559:             sharesDeltaGwei = _calculateSharesDelta({
560:                 newAmountGwei: newRestakedBalanceGwei,
561:                 previousAmountGwei: currentRestakedBalanceGwei
562:             });
563:         }
564:     }
565: 
566:     function _verifyAndProcessWithdrawal(
567:         bytes32 beaconStateRoot,
568:         BeaconChainProofs.WithdrawalProof calldata withdrawalProof,
569:         bytes calldata validatorFieldsProof,
570:         bytes32[] calldata validatorFields,
571:         bytes32[] calldata withdrawalFields
572:     )
573:         internal
574:         /**
575:          * Check that the provided timestamp being proven against is after the `mostRecentWithdrawalTimestamp`.
576:          * Without this check, there is an edge case where a user proves a past withdrawal for a validator whose funds they already withdrew,
577:          * as a way to "withdraw the same funds twice" without providing adequate proof.
578:          * Note that this check is not made using the oracleTimestamp as in the `verifyWithdrawalCredentials` proof; instead this proof
579:          * proof is made for the timestamp of the withdrawal, which may be within SLOTS_PER_HISTORICAL_ROOT slots of the oracleTimestamp.
580:          * This difference in modifier usage is OK, since it is still not possible to `verifyAndProcessWithdrawal` against a slot that occurred
581:          * *prior* to the proof provided in the `verifyWithdrawalCredentials` function.
582:          */
583:         proofIsForValidTimestamp(withdrawalProof.getWithdrawalTimestamp())
584:         returns (VerifiedWithdrawal memory)
585:     {
586:         uint64 withdrawalTimestamp = withdrawalProof.getWithdrawalTimestamp();
587:         bytes32 validatorPubkeyHash = validatorFields.getPubkeyHash();
588: 
589:         /**
590:          * Withdrawal processing should only be performed for "ACTIVE" or "WITHDRAWN" validators.
591:          * (WITHDRAWN is allowed because technically you can deposit to a validator even after it exits)
592:          */
593:         require(
594:             _validatorPubkeyHashToInfo[validatorPubkeyHash].status != VALIDATOR_STATUS.INACTIVE,
595:             "EigenPod._verifyAndProcessWithdrawal: Validator never proven to have withdrawal credentials pointed to this contract"
596:         );
597: 
598:         // Ensure we don't process the same withdrawal twice
599:         require(
600:             !provenWithdrawal[validatorPubkeyHash][withdrawalTimestamp],
601:             "EigenPod._verifyAndProcessWithdrawal: withdrawal has already been proven for this timestamp"
602:         );
603: 
604:         provenWithdrawal[validatorPubkeyHash][withdrawalTimestamp] = true;
605: 
606:         // Verifying the withdrawal against verified beaconStateRoot:
607:         BeaconChainProofs.verifyWithdrawal({
608:             beaconStateRoot: beaconStateRoot, 
609:             withdrawalFields: withdrawalFields, 
610:             withdrawalProof: withdrawalProof,
611:             denebForkTimestamp: eigenPodManager.denebForkTimestamp()
612:         });
613: 
614:         uint40 validatorIndex = withdrawalFields.getValidatorIndex();
615: 
616:         // Verify passed-in validatorFields against verified beaconStateRoot:
617:         BeaconChainProofs.verifyValidatorFields({
618:             beaconStateRoot: beaconStateRoot,
619:             validatorFields: validatorFields,
620:             validatorFieldsProof: validatorFieldsProof,
621:             validatorIndex: validatorIndex
622:         });
623: 
624:         uint64 withdrawalAmountGwei = withdrawalFields.getWithdrawalAmountGwei();
625:         
626:         /**
627:          * If the withdrawal's epoch comes after the validator's "withdrawable epoch," we know the validator
628:          * has fully withdrawn, and we process this as a full withdrawal.
629:          */
630:         if (withdrawalProof.getWithdrawalEpoch() >= validatorFields.getWithdrawableEpoch()) {
631:             return
632:                 _processFullWithdrawal(
633:                     validatorIndex,
634:                     validatorPubkeyHash,
635:                     withdrawalTimestamp,
636:                     podOwner,
637:                     withdrawalAmountGwei,
638:                     _validatorPubkeyHashToInfo[validatorPubkeyHash]
639:                 );
640:         } else {
641:             return
642:                 _processPartialWithdrawal(
643:                     validatorIndex,
644:                     withdrawalTimestamp,
645:                     podOwner,
646:                     withdrawalAmountGwei
647:                 );
648:         }
649:     }
650: 
651:     function _processFullWithdrawal(
652:         uint40 validatorIndex,
653:         bytes32 validatorPubkeyHash,
654:         uint64 withdrawalTimestamp,
655:         address recipient,
656:         uint64 withdrawalAmountGwei,
657:         ValidatorInfo memory validatorInfo
658:     ) internal returns (VerifiedWithdrawal memory) {
659: 
660:         /**
661:          * First, determine withdrawal amounts. We need to know:
662:          * 1. How much can be withdrawn immediately
663:          * 2. How much needs to be withdrawn via the EigenLayer withdrawal queue
664:          */
665: 
666:         uint64 amountToQueueGwei;
667: 
668:         if (withdrawalAmountGwei > MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR) {
669:             amountToQueueGwei = MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR;
670:         } else {
671:             amountToQueueGwei = withdrawalAmountGwei;
672:         }
673: 
674:         /**
675:          * If the withdrawal is for more than the max per-validator balance, we mark 
676:          * the max as "withdrawable" via the queue, and withdraw the excess immediately
677:          */
678: 
679:         VerifiedWithdrawal memory verifiedWithdrawal;
680:         verifiedWithdrawal.amountToSendGwei = uint256(withdrawalAmountGwei - amountToQueueGwei);
681:         withdrawableRestakedExecutionLayerGwei += amountToQueueGwei;
682:         
683:         /**
684:          * Next, calculate the change in number of shares this validator is "backing":
685:          * - Anything that needs to go through the withdrawal queue IS backed
686:          * - Anything immediately withdrawn IS NOT backed
687:          *
688:          * This means that this validator is currently backing `amountToQueueGwei` shares.
689:          */
690: 
691:         verifiedWithdrawal.sharesDeltaGwei = _calculateSharesDelta({
692:             newAmountGwei: amountToQueueGwei,
693:             previousAmountGwei: validatorInfo.restakedBalanceGwei
694:         });
695: 
696:         /**
697:          * Finally, the validator is fully withdrawn. Update their status and place in state:
698:          */
699: 
700:         validatorInfo.restakedBalanceGwei = 0;
701:         validatorInfo.status = VALIDATOR_STATUS.WITHDRAWN;
702: 
703:         _validatorPubkeyHashToInfo[validatorPubkeyHash] = validatorInfo;
704: 
705:         emit FullWithdrawalRedeemed(validatorIndex, withdrawalTimestamp, recipient, withdrawalAmountGwei);
706: 
707:         return verifiedWithdrawal;
708:     }
709: 
710:     function _processPartialWithdrawal(
711:         uint40 validatorIndex,
712:         uint64 withdrawalTimestamp,
713:         address recipient,
714:         uint64 partialWithdrawalAmountGwei
715:     ) internal returns (VerifiedWithdrawal memory) {
716:         emit PartialWithdrawalRedeemed(
717:             validatorIndex,
718:             withdrawalTimestamp,
719:             recipient,
720:             partialWithdrawalAmountGwei
721:         );
722: 
723:         sumOfPartialWithdrawalsClaimedGwei += partialWithdrawalAmountGwei;
724: 
725:         // For partial withdrawals, the withdrawal amount is immediately sent to the pod owner
726:         return
727:             VerifiedWithdrawal({
728:                 amountToSendGwei: uint256(partialWithdrawalAmountGwei),
729:                 sharesDeltaGwei: 0
730:             });
731:     }
732: 
733:     function _processWithdrawalBeforeRestaking(address _podOwner) internal {
734:         mostRecentWithdrawalTimestamp = uint32(block.timestamp);
735:         nonBeaconChainETHBalanceWei = 0;
736:         _sendETH_AsDelayedWithdrawal(_podOwner, address(this).balance);
737:     }
738: 
739:     function _sendETH(address recipient, uint256 amountWei) internal {
740:         Address.sendValue(payable(recipient), amountWei);
741:     }
742: 
743:     function _sendETH_AsDelayedWithdrawal(address recipient, uint256 amountWei) internal {
744:         delayedWithdrawalRouter.createDelayedWithdrawal{value: amountWei}(podOwner, recipient);
745:     }
746: 
747:     function _podWithdrawalCredentials() internal view returns (bytes memory) {
748:         return abi.encodePacked(bytes1(uint8(1)), bytes11(0), address(this));
749:     }
750: 
751:     ///@notice Calculates the pubkey hash of a validator's pubkey as per SSZ spec
752:     function _calculateValidatorPubkeyHash(bytes memory validatorPubkey) internal pure returns (bytes32){
753:         require(validatorPubkey.length == 48, "EigenPod._calculateValidatorPubkeyHash must be a 48-byte BLS public key");
754:         return sha256(abi.encodePacked(validatorPubkey, bytes16(0)));
755:     }
756: 
757:     /**
758:      * Calculates delta between two share amounts and returns as an int256
759:      */
760:     function _calculateSharesDelta(uint64 newAmountGwei, uint64 previousAmountGwei) internal pure returns (int256) {
761:         return
762:             int256(uint256(newAmountGwei)) - int256(uint256(previousAmountGwei));
763:     }
764: 
765:     /**
766:      * @dev Converts a timestamp to a beacon chain epoch by calculating the number of
767:      * seconds since genesis, and dividing by seconds per epoch.
768:      * reference: https://github.com/ethereum/consensus-specs/blob/ce240ca795e257fc83059c4adfd591328c7a7f21/specs/bellatrix/beacon-chain.md#compute_timestamp_at_slot
769:      */
770:     function _timestampToEpoch(uint64 timestamp) internal view returns (uint64) {
771:         require(timestamp >= GENESIS_TIME, "EigenPod._timestampToEpoch: timestamp is before genesis");
772:         return (timestamp - GENESIS_TIME) / BeaconChainProofs.SECONDS_PER_EPOCH;
773:     }
774: 
775:     /*******************************************************************************
776:                             VIEW FUNCTIONS
777:     *******************************************************************************/
778: 
779:     function validatorPubkeyHashToInfo(bytes32 validatorPubkeyHash) external view returns (ValidatorInfo memory) {
780:         return _validatorPubkeyHashToInfo[validatorPubkeyHash];
781:     }
782: 
783:     /// @notice Returns the validatorInfo for a given validatorPubkey
784:     function validatorPubkeyToInfo(bytes calldata validatorPubkey) external view returns (ValidatorInfo memory) {
785:         return _validatorPubkeyHashToInfo[_calculateValidatorPubkeyHash(validatorPubkey)];
786:     }
787: 
788:     function validatorStatus(bytes32 pubkeyHash) external view returns (VALIDATOR_STATUS) {
789:         return _validatorPubkeyHashToInfo[pubkeyHash].status;
790:     }
791: 
792:         /// @notice Returns the validator status for a given validatorPubkey
793:     function validatorStatus(bytes calldata validatorPubkey) external view returns (VALIDATOR_STATUS) {
794:         bytes32 validatorPubkeyHash = _calculateValidatorPubkeyHash(validatorPubkey);
795:         return _validatorPubkeyHashToInfo[validatorPubkeyHash].status;
796:     }
797: 
799:     /**
800:      * @dev This empty reserved space is put in place to allow future versions to add new
801:      * variables without shifting down storage in the inheritance chain.
802:      * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
803:      */
804:     uint256[44] private __gap;
805: }
806: 

['1']

1: // SPDX-License-Identifier: BUSL-1.1
2: pragma solidity ^0.8.0;
3: 
4: library Endian {
5:     /**
6:      * @notice Converts a little endian-formatted uint64 to a big endian-formatted uint64
7:      * @param lenum little endian-formatted uint64 input, provided as 'bytes32' type
8:      * @return n The big endian-formatted uint64
9:      * @dev Note that the input is formatted as a 'bytes32' type (i.e. 256 bits), but it is immediately truncated to a uint64 (i.e. 64 bits)
10:      * through a right-shift/shr operation.
11:      */
12:     function fromLittleEndianUint64(bytes32 lenum) internal pure returns (uint64 n) {
13:         // the number needs to be stored in little-endian encoding (ie in bytes 0-8)
14:         n = uint64(uint256(lenum >> 192));
15:         return
16:             (n >> 56) |
17:             ((0x00FF000000000000 & n) >> 40) |
18:             ((0x0000FF0000000000 & n) >> 24) |
19:             ((0x000000FF00000000 & n) >> 8) |
20:             ((0x00000000FF000000 & n) << 8) |
21:             ((0x0000000000FF0000 & n) << 24) |
22:             ((0x000000000000FF00 & n) << 40) |
23:             ((0x00000000000000FF & n) << 56);
24:     }
25: }
26: 

['1']

1: // SPDX-License-Identifier: BUSL-1.1
2: pragma solidity >=0.5.0;
3: 
4: import "@openzeppelin/contracts/proxy/beacon/IBeacon.sol";
5: import "./IETHPOSDeposit.sol";
6: import "./IStrategyManager.sol";
7: import "./IEigenPod.sol";
8: import "./IBeaconChainOracle.sol";
9: import "./IPausable.sol";
10: import "./ISlasher.sol";
11: import "./IStrategy.sol";
12: 
13: /**
14:  * @title Interface for factory that creates and manages solo staking pods that have their withdrawal credentials pointed to EigenLayer.
15:  * @author Layr Labs, Inc.
16:  * @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service
17:  */
18: 
19: interface IEigenPodManager is IPausable {
20:     /// @notice Emitted to notify the update of the beaconChainOracle address
21:     event BeaconOracleUpdated(address indexed newOracleAddress);
22: 
23:     /// @notice Emitted to notify the deployment of an EigenPod
24:     event PodDeployed(address indexed eigenPod, address indexed podOwner);
25: 
26:     /// @notice Emitted to notify a deposit of beacon chain ETH recorded in the strategy manager
27:     event BeaconChainETHDeposited(address indexed podOwner, uint256 amount);
28: 
29:     /// @notice Emitted when `maxPods` value is updated from `previousValue` to `newValue`
30:     event MaxPodsUpdated(uint256 previousValue, uint256 newValue);
31: 
32:     /// @notice Emitted when the balance of an EigenPod is updated
33:     event PodSharesUpdated(address indexed podOwner, int256 sharesDelta);
34: 
35:     /// @notice Emitted when a withdrawal of beacon chain ETH is completed
36:     event BeaconChainETHWithdrawalCompleted(
37:         address indexed podOwner,
38:         uint256 shares,
39:         uint96 nonce,
40:         address delegatedAddress,
41:         address withdrawer,
42:         bytes32 withdrawalRoot
43:     );
44: 
45:     event DenebForkTimestampUpdated(uint64 newValue);
46: 
47:     /**
48:      * @notice Creates an EigenPod for the sender.
49:      * @dev Function will revert if the `msg.sender` already has an EigenPod.
50:      * @dev Returns EigenPod address 
51:      */
52:     function createPod() external returns (address);
53: 
54:     /**
55:      * @notice Stakes for a new beacon chain validator on the sender's EigenPod.
56:      * Also creates an EigenPod for the sender if they don't have one already.
57:      * @param pubkey The 48 bytes public key of the beacon chain validator.
58:      * @param signature The validator's signature of the deposit data.
59:      * @param depositDataRoot The root/hash of the deposit data for the validator's deposit.
60:      */
61:     function stake(bytes calldata pubkey, bytes calldata signature, bytes32 depositDataRoot) external payable;
62: 
63:     /**
64:      * @notice Changes the `podOwner`'s shares by `sharesDelta` and performs a call to the DelegationManager
65:      * to ensure that delegated shares are also tracked correctly
66:      * @param podOwner is the pod owner whose balance is being updated.
67:      * @param sharesDelta is the change in podOwner's beaconChainETHStrategy shares
68:      * @dev Callable only by the podOwner's EigenPod contract.
69:      * @dev Reverts if `sharesDelta` is not a whole Gwei amount
70:      */
71:     function recordBeaconChainETHBalanceUpdate(address podOwner, int256 sharesDelta) external;
72: 
73:     /**
74:      * @notice Updates the oracle contract that provides the beacon chain state root
75:      * @param newBeaconChainOracle is the new oracle contract being pointed to
76:      * @dev Callable only by the owner of this contract (i.e. governance)
77:      */
78:     function updateBeaconChainOracle(IBeaconChainOracle newBeaconChainOracle) external;
79: 
80:     /// @notice Returns the address of the `podOwner`'s EigenPod if it has been deployed.
81:     function ownerToPod(address podOwner) external view returns (IEigenPod);
82: 
83:     /// @notice Returns the address of the `podOwner`'s EigenPod (whether it is deployed yet or not).
84:     function getPod(address podOwner) external view returns (IEigenPod);
85: 
86:     /// @notice The ETH2 Deposit Contract
87:     function ethPOS() external view returns (IETHPOSDeposit);
88: 
89:     /// @notice Beacon proxy to which the EigenPods point
90:     function eigenPodBeacon() external view returns (IBeacon);
91: 
92:     /// @notice Oracle contract that provides updates to the beacon chain's state
93:     function beaconChainOracle() external view returns (IBeaconChainOracle);
94: 
95:     /// @notice Returns the beacon block root at `timestamp`. Reverts if the Beacon block root at `timestamp` has not yet been finalized.
96:     function getBlockRootAtTimestamp(uint64 timestamp) external view returns (bytes32);
97: 
98:     /// @notice EigenLayer's StrategyManager contract
99:     function strategyManager() external view returns (IStrategyManager);
100: 
101:     /// @notice EigenLayer's Slasher contract
102:     function slasher() external view returns (ISlasher);
103: 
104:     /// @notice Returns 'true' if the `podOwner` has created an EigenPod, and 'false' otherwise.
105:     function hasPod(address podOwner) external view returns (bool);
106: 
107:     /// @notice Returns the number of EigenPods that have been created
108:     function numPods() external view returns (uint256);
109: 
110:     /// @notice Returns the maximum number of EigenPods that can be created
111:     function maxPods() external view returns (uint256);
112: 
113:     /**
114:      * @notice Mapping from Pod owner owner to the number of shares they have in the virtual beacon chain ETH strategy.
115:      * @dev The share amount can become negative. This is necessary to accommodate the fact that a pod owner's virtual beacon chain ETH shares can
116:      * decrease between the pod owner queuing and completing a withdrawal.
117:      * When the pod owner's shares would otherwise increase, this "deficit" is decreased first _instead_.
118:      * Likewise, when a withdrawal is completed, this "deficit" is decreased and the withdrawal amount is decreased; We can think of this
119:      * as the withdrawal "paying off the deficit".
120:      */
121:     function podOwnerShares(address podOwner) external view returns (int256);
122: 
123:     /// @notice returns canonical, virtual beaconChainETH strategy
124:     function beaconChainETHStrategy() external view returns (IStrategy);
125: 
126:     /**
127:      * @notice Used by the DelegationManager to remove a pod owner's shares while they're in the withdrawal queue.
128:      * Simply decreases the `podOwner`'s shares by `shares`, down to a minimum of zero.
129:      * @dev This function reverts if it would result in `podOwnerShares[podOwner]` being less than zero, i.e. it is forbidden for this function to
130:      * result in the `podOwner` incurring a "share deficit". This behavior prevents a Staker from queuing a withdrawal which improperly removes excessive
131:      * shares from the operator to whom the staker is delegated.
132:      * @dev Reverts if `shares` is not a whole Gwei amount
133:      */
134:     function removeShares(address podOwner, uint256 shares) external;
135: 
136:     /**
137:      * @notice Increases the `podOwner`'s shares by `shares`, paying off deficit if possible.
138:      * Used by the DelegationManager to award a pod owner shares on exiting the withdrawal queue
139:      * @dev Returns the number of shares added to `podOwnerShares[podOwner]` above zero, which will be less than the `shares` input
140:      * in the event that the podOwner has an existing shares deficit (i.e. `podOwnerShares[podOwner]` starts below zero)
141:      * @dev Reverts if `shares` is not a whole Gwei amount
142:      */
143:     function addShares(address podOwner, uint256 shares) external returns (uint256);
144: 
145:     /**
146:      * @notice Used by the DelegationManager to complete a withdrawal, sending tokens to some destination address
147:      * @dev Prioritizes decreasing the podOwner's share deficit, if they have one
148:      * @dev Reverts if `shares` is not a whole Gwei amount
149:      */
150:     function withdrawSharesAsTokens(address podOwner, address destination, uint256 shares) external;
151: 
152:     /**
153:      * @notice the deneb hard fork timestamp used to determine which proof path to use for proving a withdrawal
154:      */
155:     function denebForkTimestamp() external view returns (uint64);
156: 
157:      /**
158:      * setting the deneb hard fork timestamp by the eigenPodManager owner
159:      * @dev this function is designed to be called twice.  Once, it is set to type(uint64).max 
160:      * prior to the actual deneb fork timestamp being set, and then the second time it is set 
161:      * to the actual deneb fork timestamp.
162:      */
163:     function setDenebForkTimestamp(uint64 newDenebForkTimestamp) external;
164: 
165: }
166: 

['1']

1: // SPDX-License-Identifier: BUSL-1.1
2: pragma solidity =0.8.12;
3: 
4: import "./StrategyBase.sol";
5: 
6: /**
7:  * @title A Strategy implementation inheriting from `StrategyBase` that limits the total amount of deposits it will accept.
8:  * @dev Note that this implementation still converts between any amount of shares or underlying tokens in its view functions;
9:  * these functions purposefully do not take the TVL limit into account.
10:  * @author Layr Labs, Inc.
11:  * @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service
12:  */
13: contract StrategyBaseTVLLimits is StrategyBase {
14:     /// The maximum deposit (in underlyingToken) that this strategy will accept per deposit
15:     uint256 public maxPerDeposit;
16: 
17:     /// The maximum deposits (in underlyingToken) that this strategy will hold
18:     uint256 public maxTotalDeposits;
19: 
20:     /// @notice Emitted when `maxPerDeposit` value is updated from `previousValue` to `newValue`
21:     event MaxPerDepositUpdated(uint256 previousValue, uint256 newValue);
22: 
23:     /// @notice Emitted when `maxTotalDeposits` value is updated from `previousValue` to `newValue`
24:     event MaxTotalDepositsUpdated(uint256 previousValue, uint256 newValue);
25: 
26:     // solhint-disable-next-line no-empty-blocks
27:     constructor(IStrategyManager _strategyManager) StrategyBase(_strategyManager) {}
28: 
29:     function initialize(
30:         uint256 _maxPerDeposit,
31:         uint256 _maxTotalDeposits,
32:         IERC20 _underlyingToken,
33:         IPauserRegistry _pauserRegistry
34:     ) public virtual initializer {
35:         _setTVLLimits(_maxPerDeposit, _maxTotalDeposits);
36:         _initializeStrategyBase(_underlyingToken, _pauserRegistry);
37:     }
38: 
39:     /**
40:      * @notice Sets the maximum deposits (in underlyingToken) that this strategy will hold and accept per deposit
41:      * @param newMaxTotalDeposits The new maximum deposits
42:      * @dev Callable only by the unpauser of this contract
43:      * @dev We note that there is a potential race condition between a call to this function that lowers either or both of these limits and call(s)
44:      * to `deposit`, that may result in some calls to `deposit` reverting.
45:      */
46:     function setTVLLimits(uint256 newMaxPerDeposit, uint256 newMaxTotalDeposits) external onlyUnpauser {
47:         _setTVLLimits(newMaxPerDeposit, newMaxTotalDeposits);
48:     }
49: 
50:     /// @notice Simple getter function that returns the current values of `maxPerDeposit` and `maxTotalDeposits`.
51:     function getTVLLimits() external view returns (uint256, uint256) {
52:         return (maxPerDeposit, maxTotalDeposits);
53:     }
54: 
55:     /// @notice Internal setter for TVL limits
56:     function _setTVLLimits(uint256 newMaxPerDeposit, uint256 newMaxTotalDeposits) internal {
57:         emit MaxPerDepositUpdated(maxPerDeposit, newMaxPerDeposit);
58:         emit MaxTotalDepositsUpdated(maxTotalDeposits, newMaxTotalDeposits);
59:         require(
60:             newMaxPerDeposit <= newMaxTotalDeposits,
61:             "StrategyBaseTVLLimits._setTVLLimits: maxPerDeposit exceeds maxTotalDeposits"
62:         );
63:         maxPerDeposit = newMaxPerDeposit;
64:         maxTotalDeposits = newMaxTotalDeposits;
65:     }
66: 
67:     /**
68:      * @notice Called in the external `deposit` function, before any logic is executed. Makes sure that deposits don't exceed configured maximum.
69:      * @dev Unused token param is the token being deposited. This is already checked in the `deposit` function.
70:      * @dev Note that the `maxTotalDeposits` is purely checked against the current `_tokenBalance()`, since by this point in the deposit flow, the
71:      * tokens should have already been transferred to this Strategy by the StrategyManager
72:      * @dev We note as well that this makes it possible for various race conditions to occur:
73:      * a) multiple simultaneous calls to `deposit` may result in some of these calls reverting due to `maxTotalDeposits` being reached.
74:      * b) transferring funds directly to this Strategy (although not generally in someone's economic self interest) in order to reach `maxTotalDeposits`
75:      * is a route by which someone can cause calls to `deposit` to revert.
76:      * c) increases in the token balance of this contract through other effects – including token rebasing – may cause similar issues to (a) and (b).
77:      * @param amount The amount of `token` being deposited
78:      */
79:     function _beforeDeposit(IERC20 token, uint256 amount) internal virtual override {
80:         require(amount <= maxPerDeposit, "StrategyBaseTVLLimits: max per deposit exceeded");
81:         require(_tokenBalance() <= maxTotalDeposits, "StrategyBaseTVLLimits: max deposits exceeded");
82: 
83:         super._beforeDeposit(token, amount);
84:     }
85: 
86:     /**
87:      * @dev This empty reserved space is put in place to allow future versions to add new
88:      * variables without shifting down storage in the inheritance chain.
89:      * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
90:      */
91:     uint256[48] private __gap;
92: }
93: 

['1']

1: // SPDX-License-Identifier: BUSL-1.1
2: pragma solidity >=0.5.0;
3: 
4: interface IDelayedWithdrawalRouter {
5:     // struct used to pack data into a single storage slot
6:     struct DelayedWithdrawal {
7:         uint224 amount;
8:         uint32 blockCreated;
9:     }
10: 
11:     // struct used to store a single users delayedWithdrawal data
12:     struct UserDelayedWithdrawals {
13:         uint256 delayedWithdrawalsCompleted;
14:         DelayedWithdrawal[] delayedWithdrawals;
15:     }
16: 
17:      /// @notice event for delayedWithdrawal creation
18:     event DelayedWithdrawalCreated(address podOwner, address recipient, uint256 amount, uint256 index);
19: 
20:     /// @notice event for the claiming of delayedWithdrawals
21:     event DelayedWithdrawalsClaimed(address recipient, uint256 amountClaimed, uint256 delayedWithdrawalsCompleted);
22: 
23:     /// @notice Emitted when the `withdrawalDelayBlocks` variable is modified from `previousValue` to `newValue`.
24:     event WithdrawalDelayBlocksSet(uint256 previousValue, uint256 newValue);
25: 
26:     /**
27:      * @notice Creates an delayed withdrawal for `msg.value` to the `recipient`.
28:      * @dev Only callable by the `podOwner`'s EigenPod contract.
29:      */
30:     function createDelayedWithdrawal(address podOwner, address recipient) external payable;
31: 
32:     /**
33:      * @notice Called in order to withdraw delayed withdrawals made to the `recipient` that have passed the `withdrawalDelayBlocks` period.
34:      * @param recipient The address to claim delayedWithdrawals for.
35:      * @param maxNumberOfWithdrawalsToClaim Used to limit the maximum number of withdrawals to loop through claiming.
36:      */
37:     function claimDelayedWithdrawals(address recipient, uint256 maxNumberOfWithdrawalsToClaim) external;
38: 
39:     /**
40:      * @notice Called in order to withdraw delayed withdrawals made to the caller that have passed the `withdrawalDelayBlocks` period.
41:      * @param maxNumberOfWithdrawalsToClaim Used to limit the maximum number of withdrawals to loop through claiming.
42:      */
43:     function claimDelayedWithdrawals(uint256 maxNumberOfWithdrawalsToClaim) external;
44: 
45:     /// @notice Owner-only function for modifying the value of the `withdrawalDelayBlocks` variable.
46:     function setWithdrawalDelayBlocks(uint256 newValue) external;
47: 
48:     /// @notice Getter function for the mapping `_userWithdrawals`
49:     function userWithdrawals(address user) external view returns (UserDelayedWithdrawals memory);
50: 
51:     /// @notice Getter function to get all delayedWithdrawals of the `user`
52:     function getUserDelayedWithdrawals(address user) external view returns (DelayedWithdrawal[] memory);
53: 
54:     /// @notice Getter function to get all delayedWithdrawals that are currently claimable by the `user`
55:     function getClaimableUserDelayedWithdrawals(address user) external view returns (DelayedWithdrawal[] memory);
56: 
57:     /// @notice Getter function for fetching the delayedWithdrawal at the `index`th entry from the `_userWithdrawals[user].delayedWithdrawals` array
58:     function userDelayedWithdrawalByIndex(address user, uint256 index) external view returns (DelayedWithdrawal memory);
59: 
60:     /// @notice Getter function for fetching the length of the delayedWithdrawals array of a specific user
61:     function userWithdrawalsLength(address user) external view returns (uint256);
62: 
63:     /// @notice Convenience function for checking whether or not the delayedWithdrawal at the `index`th entry from the `_userWithdrawals[user].delayedWithdrawals` array is currently claimable
64:     function canClaimDelayedWithdrawal(address user, uint256 index) external view returns (bool);
65: 
66:     /**
67:      * @notice Delay enforced by this contract for completing any delayedWithdrawal. Measured in blocks, and adjustable by this contract's owner,
68:      * up to a maximum of `MAX_WITHDRAWAL_DELAY_BLOCKS`. Minimum value is 0 (i.e. no delay enforced).
69:      */
70:     function withdrawalDelayBlocks() external view returns (uint256);
71: }
72: 

['1']

1: // SPDX-License-Identifier: BUSL-1.1
2: pragma solidity >=0.5.0;
3: 
4: /**
5:  * @title Interface for the `PauserRegistry` contract.
6:  * @author Layr Labs, Inc.
7:  * @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service
8:  */
9: interface IPauserRegistry {
10:     event PauserStatusChanged(address pauser, bool canPause);
11: 
12:     event UnpauserChanged(address previousUnpauser, address newUnpauser);
13:     
14:     /// @notice Mapping of addresses to whether they hold the pauser role.
15:     function isPauser(address pauser) external view returns (bool);
16: 
17:     /// @notice Unique address that holds the unpauser role. Capable of changing *both* the pauser and unpauser addresses.
18:     function unpauser() external view returns (address);
19: }
20: 

['1']

1: // SPDX-License-Identifier: BUSL-1.1
2: pragma solidity =0.8.12;
3: 
4: import "../interfaces/ISlasher.sol";
5: import "../interfaces/IDelegationManager.sol";
6: import "../interfaces/IStrategyManager.sol";
7: import "../libraries/StructuredLinkedList.sol";
8: import "../permissions/Pausable.sol";
9: import "@openzeppelin-upgrades/contracts/access/OwnableUpgradeable.sol";
10: import "@openzeppelin-upgrades/contracts/proxy/utils/Initializable.sol";
11: 
13: /**
14:  * @notice This contract is not in use as of the Eigenlayer M2 release.
15:  *
16:  * Although many contracts reference it as an immutable variable, they do not
17:  * interact with it and it is effectively dead code. The Slasher was originally
18:  * deployed during Eigenlayer M1, but remained paused and unused for the duration
19:  * of that release as well.
20:  *
21:  * Eventually, slashing design will be finalized and the Slasher will be finished
22:  * and more fully incorporated into the core contracts. For now, you can ignore this
23:  * file. If you really want to see what the deployed M1 version looks like, check
24:  * out the `init-mainnet-deployment` branch under "releases".
25:  *
26:  * This contract is a stub that maintains its original interface for use in testing
27:  * and deploy scripts. Otherwise, it does nothing.
28:  */ 
29: contract Slasher is Initializable, OwnableUpgradeable, ISlasher, Pausable {
30:      
31:     constructor(IStrategyManager, IDelegationManager) {}
32: 
33:     function initialize(
34:         address,
35:         IPauserRegistry,
36:         uint256
37:     ) external {}
38: 
39:     function optIntoSlashing(address) external {}
40: 
41:     function freezeOperator(address) external {}
42: 
43:     function resetFrozenStatus(address[] calldata) external {}
44: 
45:     function recordFirstStakeUpdate(address, uint32) external {}
46: 
47:     function recordStakeUpdate(
48:         address,
49:         uint32,
50:         uint32,
51:         uint256
52:     ) external {}
53: 
54:     function recordLastStakeUpdateAndRevokeSlashingAbility(address, uint32) external {}
55: 
56:     function strategyManager() external view returns (IStrategyManager) {}
57: 
58:     function delegation() external view returns (IDelegationManager) {}
59: 
60:     function isFrozen(address) external view returns (bool) {}
61: 
62:     function canSlash(address, address) external view returns (bool) {}
63: 
64:     function contractCanSlashOperatorUntilBlock(
65:         address,
66:         address
67:     ) external view returns (uint32) {}
68: 
69:     function latestUpdateBlock(address, address) external view returns (uint32) {}
70: 
71:     function getCorrectValueForInsertAfter(address, uint32) external view returns (uint256) {}
72: 
73:     function canWithdraw(
74:         address,
75:         uint32,
76:         uint256
77:     ) external returns (bool) {}
78: 
79:     function operatorToMiddlewareTimes(
80:         address,
81:         uint256
82:     ) external view returns (MiddlewareTimes memory) {}
83: 
84:     function middlewareTimesLength(address) external view returns (uint256) {}
85: 
86:     function getMiddlewareTimesIndexStalestUpdateBlock(address, uint32) external view returns (uint32) {}
87: 
88:     function getMiddlewareTimesIndexServeUntilBlock(address, uint32) external view returns (uint32) {}
89: 
90:     function operatorWhitelistedContractsLinkedListSize(address) external view returns (uint256) {}
91: 
92:     function operatorWhitelistedContractsLinkedListEntry(
93:         address,
94:         address
95:     ) external view returns (bool, uint256, uint256) {}
96: 
97:     function whitelistedContractDetails(
98:         address,
99:         address
100:     ) external view returns (MiddlewareDetails memory) {}
101: 
102: }

['1']

1: // SPDX-License-Identifier: BUSL-1.1
2: pragma solidity =0.8.12;
3: 
4: import "@openzeppelin-upgrades/contracts/proxy/utils/Initializable.sol";
5: import "../libraries/EIP1271SignatureUtils.sol";
6: 
7: /**
8:  * @title Abstract contract that implements minimal signature-related storage & functionality for upgradeable contracts.
9:  * @author Layr Labs, Inc.
10:  * @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service
11:  */
12: abstract contract UpgradeableSignatureCheckingUtils is Initializable {
13:     /// @notice The EIP-712 typehash for the contract's domain
14:     bytes32 public constant DOMAIN_TYPEHASH =
15:         keccak256("EIP712Domain(string name,uint256 chainId,address verifyingContract)");
16: 
17:     // chain id at the time of contract deployment
18:     uint256 internal immutable ORIGINAL_CHAIN_ID;
19: 
20:     /**
21:      * @notice Original EIP-712 Domain separator for this contract.
22:      * @dev The domain separator may change in the event of a fork that modifies the ChainID.
23:      * Use the getter function `domainSeparator` to get the current domain separator for this contract.
24:      */
25:     bytes32 internal _DOMAIN_SEPARATOR;
26: 
27:     // INITIALIZING FUNCTIONS
28:     constructor() {
29:         ORIGINAL_CHAIN_ID = block.chainid;
30:     }
31: 
32:     function _initializeSignatureCheckingUtils() internal onlyInitializing {
33:         _DOMAIN_SEPARATOR = _calculateDomainSeparator();
34:     }
35: 
36:     // VIEW FUNCTIONS
37:     /**
38:      * @notice Getter function for the current EIP-712 domain separator for this contract.
39:      * @dev The domain separator will change in the event of a fork that changes the ChainID.
40:      */
41:     function domainSeparator() public view returns (bytes32) {
42:         if (block.chainid == ORIGINAL_CHAIN_ID) {
43:             return _DOMAIN_SEPARATOR;
44:         } else {
45:             return _calculateDomainSeparator();
46:         }
47:     }
48: 
49:     // @notice Internal function for calculating the current domain separator of this contract
50:     function _calculateDomainSeparator() internal view returns (bytes32) {
51:         return keccak256(abi.encode(DOMAIN_TYPEHASH, keccak256(bytes("EigenLayer")), block.chainid, address(this)));
52:     }
53: }
54: 

['1']

1: // SPDX-License-Identifier: Unlicense
2: /*
3:  * @title Solidity Bytes Arrays Utils
4:  * @author Gonçalo Sá <goncalo.sa@consensys.net>
5:  *
6:  * @dev Bytes tightly packed arrays utility library for ethereum contracts written in Solidity.
7:  *      The library lets you concatenate, slice and type cast bytes arrays both in memory and storage.
8:  */
9: pragma solidity >=0.8.0 <0.9.0;
10: 
11: library BytesLib {
12:     function concat(bytes memory _preBytes, bytes memory _postBytes) internal pure returns (bytes memory) {
13:         bytes memory tempBytes;
14: 
15:         assembly {
16:             // Get a location of some free memory and store it in tempBytes as
17:             // Solidity does for memory variables.
18:             tempBytes := mload(0x40)
19: 
20:             // Store the length of the first bytes array at the beginning of
21:             // the memory for tempBytes.
22:             let length := mload(_preBytes)
23:             mstore(tempBytes, length)
24: 
25:             // Maintain a memory counter for the current write location in the
26:             // temp bytes array by adding the 32 bytes for the array length to
27:             // the starting location.
28:             let mc := add(tempBytes, 0x20)
29:             // Stop copying when the memory counter reaches the length of the
30:             // first bytes array.
31:             let end := add(mc, length)
32: 
33:             for {
34:                 // Initialize a copy counter to the start of the _preBytes data,
35:                 // 32 bytes into its memory.
36:                 let cc := add(_preBytes, 0x20)
37:             } lt(mc, end) {
38:                 // Increase both counters by 32 bytes each iteration.
39:                 mc := add(mc, 0x20)
40:                 cc := add(cc, 0x20)
41:             } {
42:                 // Write the _preBytes data into the tempBytes memory 32 bytes
43:                 // at a time.
44:                 mstore(mc, mload(cc))
45:             }
46: 
47:             // Add the length of _postBytes to the current length of tempBytes
48:             // and store it as the new length in the first 32 bytes of the
49:             // tempBytes memory.
50:             length := mload(_postBytes)
51:             mstore(tempBytes, add(length, mload(tempBytes)))
52: 
53:             // Move the memory counter back from a multiple of 0x20 to the
54:             // actual end of the _preBytes data.
55:             mc := end
56:             // Stop copying when the memory counter reaches the new combined
57:             // length of the arrays.
58:             end := add(mc, length)
59: 
60:             for {
61:                 let cc := add(_postBytes, 0x20)
62:             } lt(mc, end) {
63:                 mc := add(mc, 0x20)
64:                 cc := add(cc, 0x20)
65:             } {
66:                 mstore(mc, mload(cc))
67:             }
68: 
69:             // Update the free-memory pointer by padding our last write location
70:             // to 32 bytes: add 31 bytes to the end of tempBytes to move to the
71:             // next 32 byte block, then round down to the nearest multiple of
72:             // 32. If the sum of the length of the two arrays is zero then add
73:             // one before rounding down to leave a blank 32 bytes (the length block with 0).
74:             mstore(
75:                 0x40,
76:                 and(
77:                     add(add(end, iszero(add(length, mload(_preBytes)))), 31),
78:                     not(31) // Round down to the nearest 32 bytes.
79:                 )
80:             )
81:         }
82: 
83:         return tempBytes;
84:     }
85: 
86:     function concatStorage(bytes storage _preBytes, bytes memory _postBytes) internal {
87:         assembly {
88:             // Read the first 32 bytes of _preBytes storage, which is the length
89:             // of the array. (We don't need to use the offset into the slot
90:             // because arrays use the entire slot.)
91:             let fslot := sload(_preBytes.slot)
92:             // Arrays of 31 bytes or less have an even value in their slot,
93:             // while longer arrays have an odd value. The actual length is
94:             // the slot divided by two for odd values, and the lowest order
95:             // byte divided by two for even values.
96:             // If the slot is even, bitwise and the slot with 255 and divide by
97:             // two to get the length. If the slot is odd, bitwise and the slot
98:             // with -1 and divide by two.
99:             let slength := div(and(fslot, sub(mul(0x100, iszero(and(fslot, 1))), 1)), 2)
100:             let mlength := mload(_postBytes)
101:             let newlength := add(slength, mlength)
102:             // slength can contain both the length and contents of the array
103:             // if length < 32 bytes so let's prepare for that
104:             // v. http://solidity.readthedocs.io/en/latest/miscellaneous.html#layout-of-state-variables-in-storage
105:             switch add(lt(slength, 32), lt(newlength, 32))
106:             case 2 {
107:                 // Since the new array still fits in the slot, we just need to
108:                 // update the contents of the slot.
109:                 // uint256(bytes_storage) = uint256(bytes_storage) + uint256(bytes_memory) + new_length
110:                 sstore(
111:                     _preBytes.slot,
112:                     // all the modifications to the slot are inside this
113:                     // next block
114:                     add(
115:                         // we can just add to the slot contents because the
116:                         // bytes we want to change are the LSBs
117:                         fslot,
118:                         add(
119:                             mul(
120:                                 div(
121:                                     // load the bytes from memory
122:                                     mload(add(_postBytes, 0x20)),
123:                                     // zero all bytes to the right
124:                                     exp(0x100, sub(32, mlength))
125:                                 ),
126:                                 // and now shift left the number of bytes to
127:                                 // leave space for the length in the slot
128:                                 exp(0x100, sub(32, newlength))
129:                             ),
130:                             // increase length by the double of the memory
131:                             // bytes length
132:                             mul(mlength, 2)
133:                         )
134:                     )
135:                 )
136:             }
137:             case 1 {
138:                 // The stored value fits in the slot, but the combined value
139:                 // will exceed it.
140:                 // get the keccak hash to get the contents of the array
141:                 mstore(0x0, _preBytes.slot)
142:                 let sc := add(keccak256(0x0, 0x20), div(slength, 32))
143: 
144:                 // save new length
145:                 sstore(_preBytes.slot, add(mul(newlength, 2), 1))
146: 
147:                 // The contents of the _postBytes array start 32 bytes into
148:                 // the structure. Our first read should obtain the `submod`
149:                 // bytes that can fit into the unused space in the last word
150:                 // of the stored array. To get this, we read 32 bytes starting
151:                 // from `submod`, so the data we read overlaps with the array
152:                 // contents by `submod` bytes. Masking the lowest-order
153:                 // `submod` bytes allows us to add that value directly to the
154:                 // stored value.
155: 
156:                 let submod := sub(32, slength)
157:                 let mc := add(_postBytes, submod)
158:                 let end := add(_postBytes, mlength)
159:                 let mask := sub(exp(0x100, submod), 1)
160: 
161:                 sstore(
162:                     sc,
163:                     add(
164:                         and(fslot, 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00),
165:                         and(mload(mc), mask)
166:                     )
167:                 )
168: 
169:                 for {
170:                     mc := add(mc, 0x20)
171:                     sc := add(sc, 1)
172:                 } lt(mc, end) {
173:                     sc := add(sc, 1)
174:                     mc := add(mc, 0x20)
175:                 } {
176:                     sstore(sc, mload(mc))
177:                 }
178: 
179:                 mask := exp(0x100, sub(mc, end))
180: 
181:                 sstore(sc, mul(div(mload(mc), mask), mask))
182:             }
183:             default {
184:                 // get the keccak hash to get the contents of the array
185:                 mstore(0x0, _preBytes.slot)
186:                 // Start copying to the last used word of the stored array.
187:                 let sc := add(keccak256(0x0, 0x20), div(slength, 32))
188: 
189:                 // save new length
190:                 sstore(_preBytes.slot, add(mul(newlength, 2), 1))
191: 
192:                 // Copy over the first `submod` bytes of the new data as in
193:                 // case 1 above.
194:                 let slengthmod := mod(slength, 32)
195:                 // solhint-disable-next-line no-unused-vars
196:                 let mlengthmod := mod(mlength, 32)
197:                 let submod := sub(32, slengthmod)
198:                 let mc := add(_postBytes, submod)
199:                 let end := add(_postBytes, mlength)
200:                 let mask := sub(exp(0x100, submod), 1)
201: 
202:                 sstore(sc, add(sload(sc), and(mload(mc), mask)))
203: 
204:                 for {
205:                     sc := add(sc, 1)
206:                     mc := add(mc, 0x20)
207:                 } lt(mc, end) {
208:                     sc := add(sc, 1)
209:                     mc := add(mc, 0x20)
210:                 } {
211:                     sstore(sc, mload(mc))
212:                 }
213: 
214:                 mask := exp(0x100, sub(mc, end))
215: 
216:                 sstore(sc, mul(div(mload(mc), mask), mask))
217:             }
218:         }
219:     }
220: 
221:     function slice(bytes memory _bytes, uint256 _start, uint256 _length) internal pure returns (bytes memory) {
222:         require(_length + 31 >= _length, "slice_overflow");
223:         require(_bytes.length >= _start + _length, "slice_outOfBounds");
224: 
225:         bytes memory tempBytes;
226: 
227:         assembly {
228:             switch iszero(_length)
229:             case 0 {
230:                 // Get a location of some free memory and store it in tempBytes as
231:                 // Solidity does for memory variables.
232:                 tempBytes := mload(0x40)
233: 
234:                 // The first word of the slice result is potentially a partial
235:                 // word read from the original array. To read it, we calculate
236:                 // the length of that partial word and start copying that many
237:                 // bytes into the array. The first word we copy will start with
238:                 // data we don't care about, but the last `lengthmod` bytes will
239:                 // land at the beginning of the contents of the new array. When
240:                 // we're done copying, we overwrite the full first word with
241:                 // the actual length of the slice.
242:                 let lengthmod := and(_length, 31)
243: 
244:                 // The multiplication in the next line is necessary
245:                 // because when slicing multiples of 32 bytes (lengthmod == 0)
246:                 // the following copy loop was copying the origin's length
247:                 // and then ending prematurely not copying everything it should.
248:                 let mc := add(add(tempBytes, lengthmod), mul(0x20, iszero(lengthmod)))
249:                 let end := add(mc, _length)
250: 
251:                 for {
252:                     // The multiplication in the next line has the same exact purpose
253:                     // as the one above.
254:                     let cc := add(add(add(_bytes, lengthmod), mul(0x20, iszero(lengthmod))), _start)
255:                 } lt(mc, end) {
256:                     mc := add(mc, 0x20)
257:                     cc := add(cc, 0x20)
258:                 } {
259:                     mstore(mc, mload(cc))
260:                 }
261: 
262:                 mstore(tempBytes, _length)
263: 
264:                 //update free-memory pointer
265:                 //allocating the array padded to 32 bytes like the compiler does now
266:                 mstore(0x40, and(add(mc, 31), not(31)))
267:             }
268:             //if we want a zero-length slice let's just return a zero-length array
269:             default {
270:                 tempBytes := mload(0x40)
271:                 //zero out the 32 bytes slice we are about to return
272:                 //we need to do it because Solidity does not garbage collect
273:                 mstore(tempBytes, 0)
274: 
275:                 mstore(0x40, add(tempBytes, 0x20))
276:             }
277:         }
278: 
279:         return tempBytes;
280:     }
281: 
282:     function toAddress(bytes memory _bytes, uint256 _start) internal pure returns (address) {
283:         require(_bytes.length >= _start + 20, "toAddress_outOfBounds");
284:         address tempAddress;
285: 
286:         assembly {
287:             tempAddress := div(mload(add(add(_bytes, 0x20), _start)), 0x1000000000000000000000000)
288:         }
289: 
290:         return tempAddress;
291:     }
292: 
293:     function toUint8(bytes memory _bytes, uint256 _start) internal pure returns (uint8) {
294:         require(_bytes.length >= _start + 1, "toUint8_outOfBounds");
295:         uint8 tempUint;
296: 
297:         assembly {
298:             tempUint := mload(add(add(_bytes, 0x1), _start))
299:         }
300: 
301:         return tempUint;
302:     }
303: 
304:     function toUint16(bytes memory _bytes, uint256 _start) internal pure returns (uint16) {
305:         require(_bytes.length >= _start + 2, "toUint16_outOfBounds");
306:         uint16 tempUint;
307: 
308:         assembly {
309:             tempUint := mload(add(add(_bytes, 0x2), _start))
310:         }
311: 
312:         return tempUint;
313:     }
314: 
315:     function toUint32(bytes memory _bytes, uint256 _start) internal pure returns (uint32) {
316:         require(_bytes.length >= _start + 4, "toUint32_outOfBounds");
317:         uint32 tempUint;
318: 
319:         assembly {
320:             tempUint := mload(add(add(_bytes, 0x4), _start))
321:         }
322: 
323:         return tempUint;
324:     }
325: 
326:     function toUint64(bytes memory _bytes, uint256 _start) internal pure returns (uint64) {
327:         require(_bytes.length >= _start + 8, "toUint64_outOfBounds");
328:         uint64 tempUint;
329: 
330:         assembly {
331:             tempUint := mload(add(add(_bytes, 0x8), _start))
332:         }
333: 
334:         return tempUint;
335:     }
336: 
337:     function toUint96(bytes memory _bytes, uint256 _start) internal pure returns (uint96) {
338:         require(_bytes.length >= _start + 12, "toUint96_outOfBounds");
339:         uint96 tempUint;
340: 
341:         assembly {
342:             tempUint := mload(add(add(_bytes, 0xc), _start))
343:         }
344: 
345:         return tempUint;
346:     }
347: 
348:     function toUint128(bytes memory _bytes, uint256 _start) internal pure returns (uint128) {
349:         require(_bytes.length >= _start + 16, "toUint128_outOfBounds");
350:         uint128 tempUint;
351: 
352:         assembly {
353:             tempUint := mload(add(add(_bytes, 0x10), _start))
354:         }
355: 
356:         return tempUint;
357:     }
358: 
359:     function toUint256(bytes memory _bytes, uint256 _start) internal pure returns (uint256) {
360:         require(_bytes.length >= _start + 32, "toUint256_outOfBounds");
361:         uint256 tempUint;
362: 
363:         assembly {
364:             tempUint := mload(add(add(_bytes, 0x20), _start))
365:         }
366: 
367:         return tempUint;
368:     }
369: 
370:     function toBytes32(bytes memory _bytes, uint256 _start) internal pure returns (bytes32) {
371:         require(_bytes.length >= _start + 32, "toBytes32_outOfBounds");
372:         bytes32 tempBytes32;
373: 
374:         assembly {
375:             tempBytes32 := mload(add(add(_bytes, 0x20), _start))
376:         }
377: 
378:         return tempBytes32;
379:     }
380: 
381:     function equal(bytes memory _preBytes, bytes memory _postBytes) internal pure returns (bool) {
382:         bool success = true;
383: 
384:         assembly {
385:             let length := mload(_preBytes)
386: 
387:             // if lengths don't match the arrays are not equal
388:             switch eq(length, mload(_postBytes))
389:             case 1 {
390:                 // cb is a circuit breaker in the for loop since there's
391:                 //  no said feature for inline assembly loops
392:                 // cb = 1 - don't breaker
393:                 // cb = 0 - break
394:                 let cb := 1
395: 
396:                 let mc := add(_preBytes, 0x20)
397:                 let end := add(mc, length)
398: 
399:                 for {
400:                     let cc := add(_postBytes, 0x20)
401:                 } // while(uint256(mc < end) + cb == 2) // the next line is the loop condition:
402:                 eq(add(lt(mc, end), cb), 2) {
403:                     mc := add(mc, 0x20)
404:                     cc := add(cc, 0x20)
405:                 } {
406:                     // if any of these checks fails then arrays are not equal
407:                     if iszero(eq(mload(mc), mload(cc))) {
408:                         // unsuccess:
409:                         success := 0
410:                         cb := 0
411:                     }
412:                 }
413:             }
414:             default {
415:                 // unsuccess:
416:                 success := 0
417:             }
418:         }
419: 
420:         return success;
421:     }
422: 
423:     function equalStorage(bytes storage _preBytes, bytes memory _postBytes) internal view returns (bool) {
424:         bool success = true;
425: 
426:         assembly {
427:             // we know _preBytes_offset is 0
428:             let fslot := sload(_preBytes.slot)
429:             // Decode the length of the stored array like in concatStorage().
430:             let slength := div(and(fslot, sub(mul(0x100, iszero(and(fslot, 1))), 1)), 2)
431:             let mlength := mload(_postBytes)
432: 
433:             // if lengths don't match the arrays are not equal
434:             switch eq(slength, mlength)
435:             case 1 {
436:                 // slength can contain both the length and contents of the array
437:                 // if length < 32 bytes so let's prepare for that
438:                 // v. http://solidity.readthedocs.io/en/latest/miscellaneous.html#layout-of-state-variables-in-storage
439:                 if iszero(iszero(slength)) {
440:                     switch lt(slength, 32)
441:                     case 1 {
442:                         // blank the last byte which is the length
443:                         fslot := mul(div(fslot, 0x100), 0x100)
444: 
445:                         if iszero(eq(fslot, mload(add(_postBytes, 0x20)))) {
446:                             // unsuccess:
447:                             success := 0
448:                         }
449:                     }
450:                     default {
451:                         // cb is a circuit breaker in the for loop since there's
452:                         //  no said feature for inline assembly loops
453:                         // cb = 1 - don't breaker
454:                         // cb = 0 - break
455:                         let cb := 1
456: 
457:                         // get the keccak hash to get the contents of the array
458:                         mstore(0x0, _preBytes.slot)
459:                         let sc := keccak256(0x0, 0x20)
460: 
461:                         let mc := add(_postBytes, 0x20)
462:                         let end := add(mc, mlength)
463: 
464:                         // the next line is the loop condition:
465:                         // while(uint256(mc < end) + cb == 2)
466:                         // solhint-disable-next-line no-empty-blocks
467:                         for {
468: 
469:                         } eq(add(lt(mc, end), cb), 2) {
470:                             sc := add(sc, 1)
471:                             mc := add(mc, 0x20)
472:                         } {
473:                             if iszero(eq(sload(sc), mload(mc))) {
474:                                 // unsuccess:
475:                                 success := 0
476:                                 cb := 0
477:                             }
478:                         }
479:                     }
480:                 }
481:             }
482:             default {
483:                 // unsuccess:
484:                 success := 0
485:             }
486:         }
487: 
488:         return success;
489:     }
490: }
491: 

['1']

1: // SPDX-License-Identifier: MIT
2: // Adapted from OpenZeppelin Contracts (last updated v4.8.0) (utils/cryptography/MerkleProof.sol)
3: 
4: pragma solidity ^0.8.0;
5: 
6: /**
7:  * @dev These functions deal with verification of Merkle Tree proofs.
8:  *
9:  * The tree and the proofs can be generated using our
10:  * https://github.com/OpenZeppelin/merkle-tree[JavaScript library].
11:  * You will find a quickstart guide in the readme.
12:  *
13:  * WARNING: You should avoid using leaf values that are 64 bytes long prior to
14:  * hashing, or use a hash function other than keccak256 for hashing leaves.
15:  * This is because the concatenation of a sorted pair of internal nodes in
16:  * the merkle tree could be reinterpreted as a leaf value.
17:  * OpenZeppelin's JavaScript library generates merkle trees that are safe
18:  * against this attack out of the box.
19:  */
20: library Merkle {
21:     /**
22:      * @dev Returns the rebuilt hash obtained by traversing a Merkle tree up
23:      * from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt
24:      * hash matches the root of the tree. The tree is built assuming `leaf` is
25:      * the 0 indexed `index`'th leaf from the bottom left of the tree.
26:      *
27:      * Note this is for a Merkle tree using the keccak/sha3 hash function
28:      */
29:     function verifyInclusionKeccak(
30:         bytes memory proof,
31:         bytes32 root,
32:         bytes32 leaf,
33:         uint256 index
34:     ) internal pure returns (bool) {
35:         return processInclusionProofKeccak(proof, leaf, index) == root;
36:     }
37: 
38:     /**
39:      * @dev Returns the rebuilt hash obtained by traversing a Merkle tree up
40:      * from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt
41:      * hash matches the root of the tree. The tree is built assuming `leaf` is
42:      * the 0 indexed `index`'th leaf from the bottom left of the tree.
43:      *
44:      * _Available since v4.4._
45:      *
46:      * Note this is for a Merkle tree using the keccak/sha3 hash function
47:      */
48:     function processInclusionProofKeccak(
49:         bytes memory proof,
50:         bytes32 leaf,
51:         uint256 index
52:     ) internal pure returns (bytes32) {
53:         require(
54:             proof.length != 0 && proof.length % 32 == 0,
55:             "Merkle.processInclusionProofKeccak: proof length should be a non-zero multiple of 32"
56:         );
57:         bytes32 computedHash = leaf;
58:         for (uint256 i = 32; i <= proof.length; i += 32) {
59:             if (index % 2 == 0) {
60:                 // if ith bit of index is 0, then computedHash is a left sibling
61:                 assembly {
62:                     mstore(0x00, computedHash)
63:                     mstore(0x20, mload(add(proof, i)))
64:                     computedHash := keccak256(0x00, 0x40)
65:                     index := div(index, 2)
66:                 }
67:             } else {
68:                 // if ith bit of index is 1, then computedHash is a right sibling
69:                 assembly {
70:                     mstore(0x00, mload(add(proof, i)))
71:                     mstore(0x20, computedHash)
72:                     computedHash := keccak256(0x00, 0x40)
73:                     index := div(index, 2)
74:                 }
75:             }
76:         }
77:         return computedHash;
78:     }
79: 
80:     /**
81:      * @dev Returns the rebuilt hash obtained by traversing a Merkle tree up
82:      * from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt
83:      * hash matches the root of the tree. The tree is built assuming `leaf` is
84:      * the 0 indexed `index`'th leaf from the bottom left of the tree.
85:      *
86:      * Note this is for a Merkle tree using the sha256 hash function
87:      */
88:     function verifyInclusionSha256(
89:         bytes memory proof,
90:         bytes32 root,
91:         bytes32 leaf,
92:         uint256 index
93:     ) internal view returns (bool) {
94:         return processInclusionProofSha256(proof, leaf, index) == root;
95:     }
96: 
97:     /**
98:      * @dev Returns the rebuilt hash obtained by traversing a Merkle tree up
99:      * from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt
100:      * hash matches the root of the tree. The tree is built assuming `leaf` is
101:      * the 0 indexed `index`'th leaf from the bottom left of the tree.
102:      *
103:      * _Available since v4.4._
104:      *
105:      * Note this is for a Merkle tree using the sha256 hash function
106:      */
107:     function processInclusionProofSha256(
108:         bytes memory proof,
109:         bytes32 leaf,
110:         uint256 index
111:     ) internal view returns (bytes32) {
112:         require(
113:             proof.length != 0 && proof.length % 32 == 0,
114:             "Merkle.processInclusionProofSha256: proof length should be a non-zero multiple of 32"
115:         );
116:         bytes32[1] memory computedHash = [leaf];
117:         for (uint256 i = 32; i <= proof.length; i += 32) {
118:             if (index % 2 == 0) {
119:                 // if ith bit of index is 0, then computedHash is a left sibling
120:                 assembly {
121:                     mstore(0x00, mload(computedHash))
122:                     mstore(0x20, mload(add(proof, i)))
123:                     if iszero(staticcall(sub(gas(), 2000), 2, 0x00, 0x40, computedHash, 0x20)) {
124:                         revert(0, 0)
125:                     }
126:                     index := div(index, 2)
127:                 }
128:             } else {
129:                 // if ith bit of index is 1, then computedHash is a right sibling
130:                 assembly {
131:                     mstore(0x00, mload(add(proof, i)))
132:                     mstore(0x20, mload(computedHash))
133:                     if iszero(staticcall(sub(gas(), 2000), 2, 0x00, 0x40, computedHash, 0x20)) {
134:                         revert(0, 0)
135:                     }
136:                     index := div(index, 2)
137:                 }
138:             }
139:         }
140:         return computedHash[0];
141:     }
142: 
143:     /**
144:      @notice this function returns the merkle root of a tree created from a set of leaves using sha256 as its hash function
145:      @param leaves the leaves of the merkle tree
146:      @return The computed Merkle root of the tree.
147:      @dev A pre-condition to this function is that leaves.length is a power of two.  If not, the function will merkleize the inputs incorrectly.
148:      */
149:     function merkleizeSha256(bytes32[] memory leaves) internal pure returns (bytes32) {
150:         //there are half as many nodes in the layer above the leaves
151:         uint256 numNodesInLayer = leaves.length / 2;
152:         //create a layer to store the internal nodes
153:         bytes32[] memory layer = new bytes32[](numNodesInLayer);
154:         //fill the layer with the pairwise hashes of the leaves
155:         for (uint256 i = 0; i < numNodesInLayer; i++) {
156:             layer[i] = sha256(abi.encodePacked(leaves[2 * i], leaves[2 * i + 1]));
157:         }
158:         //the next layer above has half as many nodes
159:         numNodesInLayer /= 2;
160:         //while we haven't computed the root
161:         while (numNodesInLayer != 0) {
162:             //overwrite the first numNodesInLayer nodes in layer with the pairwise hashes of their children
163:             for (uint256 i = 0; i < numNodesInLayer; i++) {
164:                 layer[i] = sha256(abi.encodePacked(layer[2 * i], layer[2 * i + 1]));
165:             }
166:             //the next layer above has half as many nodes
167:             numNodesInLayer /= 2;
168:         }
169:         //the first node in the layer is the root
170:         return layer[0];
171:     }
172: }
173: 

['1']

1: // SPDX-License-Identifier: BUSL-1.1
2: pragma solidity =0.8.12;
3: 
4: import "../interfaces/IStrategyManager.sol";
5: import "../permissions/Pausable.sol";
6: import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
7: import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
8: import "@openzeppelin-upgrades/contracts/proxy/utils/Initializable.sol";
9: 
10: /**
11:  * @title Base implementation of `IStrategy` interface, designed to be inherited from by more complex strategies.
12:  * @author Layr Labs, Inc.
13:  * @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service
14:  * @notice Simple, basic, "do-nothing" Strategy that holds a single underlying token and returns it on withdrawals.
15:  * Implements minimal versions of the IStrategy functions, this contract is designed to be inherited by
16:  * more complex strategies, which can then override its functions as necessary.
17:  * @dev Note that some functions have their mutability restricted; developers inheriting from this contract cannot broaden
18:  * the mutability without modifying this contract itself.
19:  * @dev This contract is expressly *not* intended for use with 'fee-on-transfer'-type tokens.
20:  * Setting the `underlyingToken` to be a fee-on-transfer token may result in improper accounting.
21:  * @notice This contract functions similarly to an ERC4626 vault, only without issuing a token.
22:  * To mitigate against the common "inflation attack" vector, we have chosen to use the 'virtual shares' mitigation route,
23:  * similar to [OpenZeppelin](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/extensions/ERC4626.sol).
24:  * We acknowledge that this mitigation has the known downside of the virtual shares causing some losses to users, which are pronounced
25:  * particularly in the case of the share exchange rate changing signficantly, either positively or negatively.
26:  * For a fairly thorough discussion of this issue and our chosen mitigation strategy, we recommend reading through
27:  * [this thread](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/3706) on the OpenZeppelin repo.
28:  * We specifically use a share offset of `SHARES_OFFSET` and a balance offset of `BALANCE_OFFSET`.
29:  */
30: contract StrategyBase is Initializable, Pausable, IStrategy {
31:     using SafeERC20 for IERC20;
32: 
33:     uint8 internal constant PAUSED_DEPOSITS = 0;
34:     uint8 internal constant PAUSED_WITHDRAWALS = 1;
35: 
36:     /**
37:      * @notice virtual shares used as part of the mitigation of the common 'share inflation' attack vector.
38:      * Constant value chosen to reasonably reduce attempted share inflation by the first depositor, while still
39:      * incurring reasonably small losses to depositors
40:      */
41:     uint256 internal constant SHARES_OFFSET = 1e3;
42:     /**
43:      * @notice virtual balance used as part of the mitigation of the common 'share inflation' attack vector
44:      * Constant value chosen to reasonably reduce attempted share inflation by the first depositor, while still
45:      * incurring reasonably small losses to depositors
46:      */
47:     uint256 internal constant BALANCE_OFFSET = 1e3;
48: 
49:     /// @notice EigenLayer's StrategyManager contract
50:     IStrategyManager public immutable strategyManager;
51: 
52:     /// @notice The underlying token for shares in this Strategy
53:     IERC20 public underlyingToken;
54: 
55:     /// @notice The total number of extant shares in this Strategy
56:     uint256 public totalShares;
57: 
58:     /// @notice Simply checks that the `msg.sender` is the `strategyManager`, which is an address stored immutably at construction.
59:     modifier onlyStrategyManager() {
60:         require(msg.sender == address(strategyManager), "StrategyBase.onlyStrategyManager");
61:         _;
62:     }
63: 
64:     /// @notice Since this contract is designed to be initializable, the constructor simply sets `strategyManager`, the only immutable variable.
65:     constructor(IStrategyManager _strategyManager) {
66:         strategyManager = _strategyManager;
67:         _disableInitializers();
68:     }
69: 
70:     function initialize(IERC20 _underlyingToken, IPauserRegistry _pauserRegistry) public virtual initializer {
71:         _initializeStrategyBase(_underlyingToken, _pauserRegistry);
72:     }
73: 
74:     /// @notice Sets the `underlyingToken` and `pauserRegistry` for the strategy.
75:     function _initializeStrategyBase(
76:         IERC20 _underlyingToken,
77:         IPauserRegistry _pauserRegistry
78:     ) internal onlyInitializing {
79:         underlyingToken = _underlyingToken;
80:         _initializePauser(_pauserRegistry, UNPAUSE_ALL);
81:     }
82: 
83:     /**
84:      * @notice Used to deposit tokens into this Strategy
85:      * @param token is the ERC20 token being deposited
86:      * @param amount is the amount of token being deposited
87:      * @dev This function is only callable by the strategyManager contract. It is invoked inside of the strategyManager's
88:      * `depositIntoStrategy` function, and individual share balances are recorded in the strategyManager as well.
89:      * @dev Note that the assumption is made that `amount` of `token` has already been transferred directly to this contract
90:      * (as performed in the StrategyManager's deposit functions). In particular, setting the `underlyingToken` of this contract
91:      * to be a fee-on-transfer token will break the assumption that the amount this contract *received* of the token is equal to
92:      * the amount that was input when the transfer was performed (i.e. the amount transferred 'out' of the depositor's balance).
93:      * @dev Note that any validation of `token` is done inside `_beforeDeposit`. This can be overridden if needed.
94:      * @return newShares is the number of new shares issued at the current exchange ratio.
95:      */
96:     function deposit(
97:         IERC20 token,
98:         uint256 amount
99:     ) external virtual override onlyWhenNotPaused(PAUSED_DEPOSITS) onlyStrategyManager returns (uint256 newShares) {
100:         // call hook to allow for any pre-deposit logic
101:         _beforeDeposit(token, amount);
102: 
103:         // copy `totalShares` value to memory, prior to any change
104:         uint256 priorTotalShares = totalShares;
105: 
106:         /**
107:          * @notice calculation of newShares *mirrors* `underlyingToShares(amount)`, but is different since the balance of `underlyingToken`
108:          * has already been increased due to the `strategyManager` transferring tokens to this strategy prior to calling this function
109:          */
110:         // account for virtual shares and balance
111:         uint256 virtualShareAmount = priorTotalShares + SHARES_OFFSET;
112:         uint256 virtualTokenBalance = _tokenBalance() + BALANCE_OFFSET;
113:         // calculate the prior virtual balance to account for the tokens that were already transferred to this contract
114:         uint256 virtualPriorTokenBalance = virtualTokenBalance - amount;
115:         newShares = (amount * virtualShareAmount) / virtualPriorTokenBalance;
116: 
117:         // extra check for correctness / against edge case where share rate can be massively inflated as a 'griefing' sort of attack
118:         require(newShares != 0, "StrategyBase.deposit: newShares cannot be zero");
119: 
120:         // update total share amount to account for deposit
121:         totalShares = (priorTotalShares + newShares);
122:         return newShares;
123:     }
124: 
125:     /**
126:      * @notice Used to withdraw tokens from this Strategy, to the `recipient`'s address
127:      * @param recipient is the address to receive the withdrawn funds
128:      * @param token is the ERC20 token being transferred out
129:      * @param amountShares is the amount of shares being withdrawn
130:      * @dev This function is only callable by the strategyManager contract. It is invoked inside of the strategyManager's
131:      * other functions, and individual share balances are recorded in the strategyManager as well.
132:      * @dev Note that any validation of `token` is done inside `_beforeWithdrawal`. This can be overridden if needed.
133:      */
134:     function withdraw(
135:         address recipient,
136:         IERC20 token,
137:         uint256 amountShares
138:     ) external virtual override onlyWhenNotPaused(PAUSED_WITHDRAWALS) onlyStrategyManager {
139:         // call hook to allow for any pre-withdrawal logic
140:         _beforeWithdrawal(recipient, token, amountShares);
141: 
142:         // copy `totalShares` value to memory, prior to any change
143:         uint256 priorTotalShares = totalShares;
144: 
145:         require(
146:             amountShares <= priorTotalShares,
147:             "StrategyBase.withdraw: amountShares must be less than or equal to totalShares"
148:         );
149: 
150:         /**
151:          * @notice calculation of amountToSend *mirrors* `sharesToUnderlying(amountShares)`, but is different since the `totalShares` has already
152:          * been decremented. Specifically, notice how we use `priorTotalShares` here instead of `totalShares`.
153:          */
154:         // account for virtual shares and balance
155:         uint256 virtualPriorTotalShares = priorTotalShares + SHARES_OFFSET;
156:         uint256 virtualTokenBalance = _tokenBalance() + BALANCE_OFFSET;
157:         // calculate ratio based on virtual shares and balance, being careful to multiply before dividing
158:         uint256 amountToSend = (virtualTokenBalance * amountShares) / virtualPriorTotalShares;
159: 
160:         // Decrease the `totalShares` value to reflect the withdrawal
161:         totalShares = priorTotalShares - amountShares;
162: 
163:         _afterWithdrawal(recipient, token, amountToSend);
164:     }
165: 
166:     /**
167:      * @notice Called in the external `deposit` function, before any logic is executed. Expected to be overridden if strategies want such logic.
168:      * @param token The token being deposited
169:      * @param amount The amount of `token` being deposited
170:      */
171:     function _beforeDeposit(IERC20 token, uint256 amount) internal virtual {
172:         require(token == underlyingToken, "StrategyBase.deposit: Can only deposit underlyingToken");
173:     }
174: 
175:     /**
176:      * @notice Called in the external `withdraw` function, before any logic is executed.  Expected to be overridden if strategies want such logic.
177:      * @param recipient The address that will receive the withdrawn tokens
178:      * @param token The token being withdrawn
179:      * @param amountShares The amount of shares being withdrawn
180:      */
181:     function _beforeWithdrawal(address recipient, IERC20 token, uint256 amountShares) internal virtual {
182:         require(token == underlyingToken, "StrategyBase.withdraw: Can only withdraw the strategy token");
183:     }
184: 
185:     /**
186:      * @notice Transfers tokens to the recipient after a withdrawal is processed
187:      * @dev Called in the external `withdraw` function after all logic is executed
188:      * @param recipient The destination of the tokens
189:      * @param token The ERC20 being transferred
190:      * @param amountToSend The amount of `token` to transfer
191:      */
192:     function _afterWithdrawal(address recipient, IERC20 token, uint256 amountToSend) internal virtual {
193:         token.safeTransfer(recipient, amountToSend);
194:     }
195: 
196:     /**
197:      * @notice Currently returns a brief string explaining the strategy's goal & purpose, but for more complex
198:      * strategies, may be a link to metadata that explains in more detail.
199:      */
200:     function explanation() external pure virtual override returns (string memory) {
201:         return "Base Strategy implementation to inherit from for more complex implementations";
202:     }
203: 
204:     /**
205:      * @notice Used to convert a number of shares to the equivalent amount of underlying tokens for this strategy.
206:      * @notice In contrast to `sharesToUnderlying`, this function guarantees no state modifications
207:      * @param amountShares is the amount of shares to calculate its conversion into the underlying token
208:      * @return The amount of underlying tokens corresponding to the input `amountShares`
209:      * @dev Implementation for these functions in particular may vary significantly for different strategies
210:      */
211:     function sharesToUnderlyingView(uint256 amountShares) public view virtual override returns (uint256) {
212:         // account for virtual shares and balance
213:         uint256 virtualTotalShares = totalShares + SHARES_OFFSET;
214:         uint256 virtualTokenBalance = _tokenBalance() + BALANCE_OFFSET;
215:         // calculate ratio based on virtual shares and balance, being careful to multiply before dividing
216:         return (virtualTokenBalance * amountShares) / virtualTotalShares;
217:     }
218: 
219:     /**
220:      * @notice Used to convert a number of shares to the equivalent amount of underlying tokens for this strategy.
221:      * @notice In contrast to `sharesToUnderlyingView`, this function **may** make state modifications
222:      * @param amountShares is the amount of shares to calculate its conversion into the underlying token
223:      * @return The amount of underlying tokens corresponding to the input `amountShares`
224:      * @dev Implementation for these functions in particular may vary significantly for different strategies
225:      */
226:     function sharesToUnderlying(uint256 amountShares) public view virtual override returns (uint256) {
227:         return sharesToUnderlyingView(amountShares);
228:     }
229: 
230:     /**
231:      * @notice Used to convert an amount of underlying tokens to the equivalent amount of shares in this strategy.
232:      * @notice In contrast to `underlyingToShares`, this function guarantees no state modifications
233:      * @param amountUnderlying is the amount of `underlyingToken` to calculate its conversion into strategy shares
234:      * @return The amount of shares corresponding to the input `amountUnderlying`
235:      * @dev Implementation for these functions in particular may vary significantly for different strategies
236:      */
237:     function underlyingToSharesView(uint256 amountUnderlying) public view virtual returns (uint256) {
238:         // account for virtual shares and balance
239:         uint256 virtualTotalShares = totalShares + SHARES_OFFSET;
240:         uint256 virtualTokenBalance = _tokenBalance() + BALANCE_OFFSET;
241:         // calculate ratio based on virtual shares and balance, being careful to multiply before dividing
242:         return (amountUnderlying * virtualTotalShares) / virtualTokenBalance;
243:     }
244: 
245:     /**
246:      * @notice Used to convert an amount of underlying tokens to the equivalent amount of shares in this strategy.
247:      * @notice In contrast to `underlyingToSharesView`, this function **may** make state modifications
248:      * @param amountUnderlying is the amount of `underlyingToken` to calculate its conversion into strategy shares
249:      * @return The amount of shares corresponding to the input `amountUnderlying`
250:      * @dev Implementation for these functions in particular may vary significantly for different strategies
251:      */
252:     function underlyingToShares(uint256 amountUnderlying) external view virtual returns (uint256) {
253:         return underlyingToSharesView(amountUnderlying);
254:     }
255: 
256:     /**
257:      * @notice convenience function for fetching the current underlying value of all of the `user`'s shares in
258:      * this strategy. In contrast to `userUnderlying`, this function guarantees no state modifications
259:      */
260:     function userUnderlyingView(address user) external view virtual returns (uint256) {
261:         return sharesToUnderlyingView(shares(user));
262:     }
263: 
264:     /**
265:      * @notice convenience function for fetching the current underlying value of all of the `user`'s shares in
266:      * this strategy. In contrast to `userUnderlyingView`, this function **may** make state modifications
267:      */
268:     function userUnderlying(address user) external virtual returns (uint256) {
269:         return sharesToUnderlying(shares(user));
270:     }
271: 
272:     /**
273:      * @notice convenience function for fetching the current total shares of `user` in this strategy, by
274:      * querying the `strategyManager` contract
275:      */
276:     function shares(address user) public view virtual returns (uint256) {
277:         return strategyManager.stakerStrategyShares(user, IStrategy(address(this)));
278:     }
279: 
280:     /// @notice Internal function used to fetch this contract's current balance of `underlyingToken`.
281:     // slither-disable-next-line dead-code
282:     function _tokenBalance() internal view virtual returns (uint256) {
283:         return underlyingToken.balanceOf(address(this));
284:     }
285: 
286:     /**
287:      * @dev This empty reserved space is put in place to allow future versions to add new
288:      * variables without shifting down storage in the inheritance chain.
289:      * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
290:      */
291:     uint256[48] private __gap;
292: }
293: 

['1']

1: // SPDX-License-Identifier: BUSL-1.1
2: 
3: pragma solidity ^0.8.0;
4: 
5: import "./Merkle.sol";
6: import "../libraries/Endian.sol";
7: 
8: //Utility library for parsing and PHASE0 beacon chain block headers
9: //SSZ Spec: https://github.com/ethereum/consensus-specs/blob/dev/ssz/simple-serialize.md#merkleization
10: //BeaconBlockHeader Spec: https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#beaconblockheader
11: //BeaconState Spec: https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#beaconstate
12: library BeaconChainProofs {
13:     // constants are the number of fields and the heights of the different merkle trees used in merkleizing beacon chain containers
14:     uint256 internal constant BEACON_BLOCK_HEADER_FIELD_TREE_HEIGHT = 3;
15: 
16:     uint256 internal constant BEACON_BLOCK_BODY_FIELD_TREE_HEIGHT = 4;
17: 
18:     uint256 internal constant BEACON_STATE_FIELD_TREE_HEIGHT = 5;
19: 
20:     uint256 internal constant VALIDATOR_FIELD_TREE_HEIGHT = 3;
21: 
22:     //Note: changed in the deneb hard fork from 4->5
23:     uint256 internal constant EXECUTION_PAYLOAD_HEADER_FIELD_TREE_HEIGHT_DENEB = 5;
24:     uint256 internal constant EXECUTION_PAYLOAD_HEADER_FIELD_TREE_HEIGHT_CAPELLA = 4;
25: 
26:     // SLOTS_PER_HISTORICAL_ROOT = 2**13, so tree height is 13
27:     uint256 internal constant BLOCK_ROOTS_TREE_HEIGHT = 13;
28: 
29:     //HISTORICAL_ROOTS_LIMIT = 2**24, so tree height is 24
30:     uint256 internal constant HISTORICAL_SUMMARIES_TREE_HEIGHT = 24;
31: 
32:     //Index of block_summary_root in historical_summary container
33:     uint256 internal constant BLOCK_SUMMARY_ROOT_INDEX = 0;
34: 
35:     // tree height for hash tree of an individual withdrawal container
36:     uint256 internal constant WITHDRAWAL_FIELD_TREE_HEIGHT = 2;
37: 
38:     uint256 internal constant VALIDATOR_TREE_HEIGHT = 40;
39: 
40:     // MAX_WITHDRAWALS_PER_PAYLOAD = 2**4, making tree height = 4
41:     uint256 internal constant WITHDRAWALS_TREE_HEIGHT = 4;
42: 
43:     //in beacon block body https://github.com/ethereum/consensus-specs/blob/dev/specs/capella/beacon-chain.md#beaconblockbody
44:     uint256 internal constant EXECUTION_PAYLOAD_INDEX = 9;
45: 
46:     // in beacon block header https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#beaconblockheader
47:     uint256 internal constant SLOT_INDEX = 0;
48:     uint256 internal constant STATE_ROOT_INDEX = 3;
49:     uint256 internal constant BODY_ROOT_INDEX = 4;
50:     // in beacon state https://github.com/ethereum/consensus-specs/blob/dev/specs/capella/beacon-chain.md#beaconstate
51:     uint256 internal constant VALIDATOR_TREE_ROOT_INDEX = 11;
52:     uint256 internal constant HISTORICAL_SUMMARIES_INDEX = 27;
53: 
54:     // in validator https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#validator
55:     uint256 internal constant VALIDATOR_PUBKEY_INDEX = 0;
56:     uint256 internal constant VALIDATOR_WITHDRAWAL_CREDENTIALS_INDEX = 1;
57:     uint256 internal constant VALIDATOR_BALANCE_INDEX = 2;
58:     uint256 internal constant VALIDATOR_WITHDRAWABLE_EPOCH_INDEX = 7;
59: 
60:     // in execution payload header
61:     uint256 internal constant TIMESTAMP_INDEX = 9;
62: 
63:     //in execution payload
64:     uint256 internal constant WITHDRAWALS_INDEX = 14;
65: 
66:     // in withdrawal
67:     uint256 internal constant WITHDRAWAL_VALIDATOR_INDEX_INDEX = 1;
68:     uint256 internal constant WITHDRAWAL_VALIDATOR_AMOUNT_INDEX = 3;
69: 
70:     //Misc Constants
71: 
72:     /// @notice The number of slots each epoch in the beacon chain
73:     uint64 internal constant SLOTS_PER_EPOCH = 32;
74: 
75:     /// @notice The number of seconds in a slot in the beacon chain
76:     uint64 internal constant SECONDS_PER_SLOT = 12;
77: 
78:     /// @notice Number of seconds per epoch: 384 == 32 slots/epoch * 12 seconds/slot 
79:     uint64 internal constant SECONDS_PER_EPOCH = SLOTS_PER_EPOCH * SECONDS_PER_SLOT;
80: 
81:     bytes8 internal constant UINT64_MASK = 0xffffffffffffffff;
82: 
83:     /// @notice This struct contains the merkle proofs and leaves needed to verify a partial/full withdrawal
84:     struct WithdrawalProof {
85:         bytes withdrawalProof;
86:         bytes slotProof;
87:         bytes executionPayloadProof;
88:         bytes timestampProof;
89:         bytes historicalSummaryBlockRootProof;
90:         uint64 blockRootIndex;
91:         uint64 historicalSummaryIndex;
92:         uint64 withdrawalIndex;
93:         bytes32 blockRoot;
94:         bytes32 slotRoot;
95:         bytes32 timestampRoot;
96:         bytes32 executionPayloadRoot;
97:     }
98: 
99:     /// @notice This struct contains the root and proof for verifying the state root against the oracle block root
100:     struct StateRootProof {
101:         bytes32 beaconStateRoot;
102:         bytes proof;
103:     }
104: 
105:     /**
106:      * @notice This function verifies merkle proofs of the fields of a certain validator against a beacon chain state root
107:      * @param validatorIndex the index of the proven validator
108:      * @param beaconStateRoot is the beacon chain state root to be proven against.
109:      * @param validatorFieldsProof is the data used in proving the validator's fields
110:      * @param validatorFields the claimed fields of the validator
111:      */
112:     function verifyValidatorFields(
113:         bytes32 beaconStateRoot,
114:         bytes32[] calldata validatorFields,
115:         bytes calldata validatorFieldsProof,
116:         uint40 validatorIndex
117:     ) internal view {
118:         require(
119:             validatorFields.length == 2 ** VALIDATOR_FIELD_TREE_HEIGHT,
120:             "BeaconChainProofs.verifyValidatorFields: Validator fields has incorrect length"
121:         );
122: 
123:         /**
124:          * Note: the length of the validator merkle proof is BeaconChainProofs.VALIDATOR_TREE_HEIGHT + 1.
125:          * There is an additional layer added by hashing the root with the length of the validator list
126:          */
127:         require(
128:             validatorFieldsProof.length == 32 * ((VALIDATOR_TREE_HEIGHT + 1) + BEACON_STATE_FIELD_TREE_HEIGHT),
129:             "BeaconChainProofs.verifyValidatorFields: Proof has incorrect length"
130:         );
131:         uint256 index = (VALIDATOR_TREE_ROOT_INDEX << (VALIDATOR_TREE_HEIGHT + 1)) | uint256(validatorIndex);
132:         // merkleize the validatorFields to get the leaf to prove
133:         bytes32 validatorRoot = Merkle.merkleizeSha256(validatorFields);
134: 
135:         // verify the proof of the validatorRoot against the beaconStateRoot
136:         require(
137:             Merkle.verifyInclusionSha256({
138:                 proof: validatorFieldsProof,
139:                 root: beaconStateRoot,
140:                 leaf: validatorRoot,
141:                 index: index
142:             }),
143:             "BeaconChainProofs.verifyValidatorFields: Invalid merkle proof"
144:         );
145:     }
146: 
147:     /**
148:      * @notice This function verifies the latestBlockHeader against the state root. the latestBlockHeader is
149:      * a tracked in the beacon state.
150:      * @param beaconStateRoot is the beacon chain state root to be proven against.
151:      * @param stateRootProof is the provided merkle proof
152:      * @param latestBlockRoot is hashtree root of the latest block header in the beacon state
153:      */
154:     function verifyStateRootAgainstLatestBlockRoot(
155:         bytes32 latestBlockRoot,
156:         bytes32 beaconStateRoot,
157:         bytes calldata stateRootProof
158:     ) internal view {
159:         require(
160:             stateRootProof.length == 32 * (BEACON_BLOCK_HEADER_FIELD_TREE_HEIGHT),
161:             "BeaconChainProofs.verifyStateRootAgainstLatestBlockRoot: Proof has incorrect length"
162:         );
163:         //Next we verify the slot against the blockRoot
164:         require(
165:             Merkle.verifyInclusionSha256({
166:                 proof: stateRootProof,
167:                 root: latestBlockRoot,
168:                 leaf: beaconStateRoot,
169:                 index: STATE_ROOT_INDEX
170:             }),
171:             "BeaconChainProofs.verifyStateRootAgainstLatestBlockRoot: Invalid latest block header root merkle proof"
172:         );
173:     }
174: 
175:     /**
176:      * @notice This function verifies the slot and the withdrawal fields for a given withdrawal
177:      * @param withdrawalProof is the provided set of merkle proofs
178:      * @param withdrawalFields is the serialized withdrawal container to be proven
179:      */
180:     function verifyWithdrawal(
181:         bytes32 beaconStateRoot,
182:         bytes32[] calldata withdrawalFields,
183:         WithdrawalProof calldata withdrawalProof,
184:         uint64 denebForkTimestamp
185:     ) internal view {
186:         require(
187:             withdrawalFields.length == 2 ** WITHDRAWAL_FIELD_TREE_HEIGHT,
188:             "BeaconChainProofs.verifyWithdrawal: withdrawalFields has incorrect length"
189:         );
190: 
191:         require(
192:             withdrawalProof.blockRootIndex < 2 ** BLOCK_ROOTS_TREE_HEIGHT,
193:             "BeaconChainProofs.verifyWithdrawal: blockRootIndex is too large"
194:         );
195:         require(
196:             withdrawalProof.withdrawalIndex < 2 ** WITHDRAWALS_TREE_HEIGHT,
197:             "BeaconChainProofs.verifyWithdrawal: withdrawalIndex is too large"
198:         );
199: 
200:         require(
201:             withdrawalProof.historicalSummaryIndex < 2 ** HISTORICAL_SUMMARIES_TREE_HEIGHT,
202:             "BeaconChainProofs.verifyWithdrawal: historicalSummaryIndex is too large"
203:         );
204: 
205:         //Note: post deneb hard fork, the number of exection payload header fields increased from 15->17, adding an extra level to the tree height
206:         uint256 executionPayloadHeaderFieldTreeHeight = (getWithdrawalTimestamp(withdrawalProof) < denebForkTimestamp) ? EXECUTION_PAYLOAD_HEADER_FIELD_TREE_HEIGHT_CAPELLA : EXECUTION_PAYLOAD_HEADER_FIELD_TREE_HEIGHT_DENEB;
207:         require(
208:             withdrawalProof.withdrawalProof.length ==
209:                 32 * (executionPayloadHeaderFieldTreeHeight + WITHDRAWALS_TREE_HEIGHT + 1),
210:             "BeaconChainProofs.verifyWithdrawal: withdrawalProof has incorrect length"
211:         );
212:         require(
213:             withdrawalProof.executionPayloadProof.length ==
214:                 32 * (BEACON_BLOCK_HEADER_FIELD_TREE_HEIGHT + BEACON_BLOCK_BODY_FIELD_TREE_HEIGHT),
215:             "BeaconChainProofs.verifyWithdrawal: executionPayloadProof has incorrect length"
216:         );
217:         require(
218:             withdrawalProof.slotProof.length == 32 * (BEACON_BLOCK_HEADER_FIELD_TREE_HEIGHT),
219:             "BeaconChainProofs.verifyWithdrawal: slotProof has incorrect length"
220:         );
221:         require(
222:             withdrawalProof.timestampProof.length == 32 * (executionPayloadHeaderFieldTreeHeight),
223:             "BeaconChainProofs.verifyWithdrawal: timestampProof has incorrect length"
224:         );
225: 
226:         require(
227:             withdrawalProof.historicalSummaryBlockRootProof.length ==
228:                 32 *
229:                     (BEACON_STATE_FIELD_TREE_HEIGHT +
230:                         (HISTORICAL_SUMMARIES_TREE_HEIGHT + 1) +
231:                         1 +
232:                         (BLOCK_ROOTS_TREE_HEIGHT)),
233:             "BeaconChainProofs.verifyWithdrawal: historicalSummaryBlockRootProof has incorrect length"
234:         );
235:         /**
236:          * Note: Here, the "1" in "1 + (BLOCK_ROOTS_TREE_HEIGHT)" signifies that extra step of choosing the "block_root_summary" within the individual
237:          * "historical_summary". Everywhere else it signifies merkelize_with_mixin, where the length of an array is hashed with the root of the array,
238:          * but not here.
239:          */
240:         uint256 historicalBlockHeaderIndex = (HISTORICAL_SUMMARIES_INDEX <<
241:             ((HISTORICAL_SUMMARIES_TREE_HEIGHT + 1) + 1 + (BLOCK_ROOTS_TREE_HEIGHT))) |
242:             (uint256(withdrawalProof.historicalSummaryIndex) << (1 + (BLOCK_ROOTS_TREE_HEIGHT))) |
243:             (BLOCK_SUMMARY_ROOT_INDEX << (BLOCK_ROOTS_TREE_HEIGHT)) |
244:             uint256(withdrawalProof.blockRootIndex);
245: 
246:         require(
247:             Merkle.verifyInclusionSha256({
248:                 proof: withdrawalProof.historicalSummaryBlockRootProof,
249:                 root: beaconStateRoot,
250:                 leaf: withdrawalProof.blockRoot,
251:                 index: historicalBlockHeaderIndex
252:             }),
253:             "BeaconChainProofs.verifyWithdrawal: Invalid historicalsummary merkle proof"
254:         );
255: 
256:         //Next we verify the slot against the blockRoot
257:         require(
258:             Merkle.verifyInclusionSha256({
259:                 proof: withdrawalProof.slotProof,
260:                 root: withdrawalProof.blockRoot,
261:                 leaf: withdrawalProof.slotRoot,
262:                 index: SLOT_INDEX
263:             }),
264:             "BeaconChainProofs.verifyWithdrawal: Invalid slot merkle proof"
265:         );
266: 
267:         {
268:             // Next we verify the executionPayloadRoot against the blockRoot
269:             uint256 executionPayloadIndex = (BODY_ROOT_INDEX << (BEACON_BLOCK_BODY_FIELD_TREE_HEIGHT)) |
270:                 EXECUTION_PAYLOAD_INDEX;
271:             require(
272:                 Merkle.verifyInclusionSha256({
273:                     proof: withdrawalProof.executionPayloadProof,
274:                     root: withdrawalProof.blockRoot,
275:                     leaf: withdrawalProof.executionPayloadRoot,
276:                     index: executionPayloadIndex
277:                 }),
278:                 "BeaconChainProofs.verifyWithdrawal: Invalid executionPayload merkle proof"
279:             );
280:         }
281: 
282:         // Next we verify the timestampRoot against the executionPayload root
283:         require(
284:             Merkle.verifyInclusionSha256({
285:                 proof: withdrawalProof.timestampProof,
286:                 root: withdrawalProof.executionPayloadRoot,
287:                 leaf: withdrawalProof.timestampRoot,
288:                 index: TIMESTAMP_INDEX
289:             }),
290:             "BeaconChainProofs.verifyWithdrawal: Invalid timestamp merkle proof"
291:         );
292: 
293:         {
294:             /**
295:              * Next we verify the withdrawal fields against the executionPayloadRoot:
296:              * First we compute the withdrawal_index, then we merkleize the 
297:              * withdrawalFields container to calculate the withdrawalRoot.
298:              *
299:              * Note: Merkleization of the withdrawals root tree uses MerkleizeWithMixin, i.e., the length of the array is hashed with the root of
300:              * the array.  Thus we shift the WITHDRAWALS_INDEX over by WITHDRAWALS_TREE_HEIGHT + 1 and not just WITHDRAWALS_TREE_HEIGHT.
301:              */
302:             uint256 withdrawalIndex = (WITHDRAWALS_INDEX << (WITHDRAWALS_TREE_HEIGHT + 1)) |
303:                 uint256(withdrawalProof.withdrawalIndex);
304:             bytes32 withdrawalRoot = Merkle.merkleizeSha256(withdrawalFields);
305:             require(
306:                 Merkle.verifyInclusionSha256({
307:                     proof: withdrawalProof.withdrawalProof,
308:                     root: withdrawalProof.executionPayloadRoot,
309:                     leaf: withdrawalRoot,
310:                     index: withdrawalIndex
311:                 }),
312:                 "BeaconChainProofs.verifyWithdrawal: Invalid withdrawal merkle proof"
313:             );
314:         }
315:     }
316: 
317:     /**
318:      * @notice This function replicates the ssz hashing of a validator's pubkey, outlined below:
319:      *  hh := ssz.NewHasher()
320:      *  hh.PutBytes(validatorPubkey[:])
321:      *  validatorPubkeyHash := hh.Hash()
322:      *  hh.Reset()
323:      */
324:     function hashValidatorBLSPubkey(bytes memory validatorPubkey) internal pure returns (bytes32 pubkeyHash) {
325:         require(validatorPubkey.length == 48, "Input should be 48 bytes in length");
326:         return sha256(abi.encodePacked(validatorPubkey, bytes16(0)));
327:     }
328: 
329:     /**
330:      * @dev Retrieve the withdrawal timestamp
331:      */
332:     function getWithdrawalTimestamp(WithdrawalProof memory withdrawalProof) internal pure returns (uint64) {
333:         return
334:             Endian.fromLittleEndianUint64(withdrawalProof.timestampRoot);
335:     }
336: 
337:     /**
338:      * @dev Converts the withdrawal's slot to an epoch
339:      */
340:     function getWithdrawalEpoch(WithdrawalProof memory withdrawalProof) internal pure returns (uint64) {
341:         return
342:             Endian.fromLittleEndianUint64(withdrawalProof.slotRoot) / SLOTS_PER_EPOCH;
343:     }
344: 
345:     /**
346:      * Indices for validator fields (refer to consensus specs):
347:      * 0: pubkey
348:      * 1: withdrawal credentials
349:      * 2: effective balance
350:      * 3: slashed?
351:      * 4: activation elligibility epoch
352:      * 5: activation epoch
353:      * 6: exit epoch
354:      * 7: withdrawable epoch
355:      */
356: 
357:     /**
358:      * @dev Retrieves a validator's pubkey hash
359:      */
360:     function getPubkeyHash(bytes32[] memory validatorFields) internal pure returns (bytes32) {
361:         return 
362:             validatorFields[VALIDATOR_PUBKEY_INDEX];
363:     }
364: 
365:     function getWithdrawalCredentials(bytes32[] memory validatorFields) internal pure returns (bytes32) {
366:         return
367:             validatorFields[VALIDATOR_WITHDRAWAL_CREDENTIALS_INDEX];
368:     }
369: 
370:     /**
371:      * @dev Retrieves a validator's effective balance (in gwei)
372:      */
373:     function getEffectiveBalanceGwei(bytes32[] memory validatorFields) internal pure returns (uint64) {
374:         return 
375:             Endian.fromLittleEndianUint64(validatorFields[VALIDATOR_BALANCE_INDEX]);
376:     }
377: 
378:     /**
379:      * @dev Retrieves a validator's withdrawable epoch
380:      */
381:     function getWithdrawableEpoch(bytes32[] memory validatorFields) internal pure returns (uint64) {
382:         return 
383:             Endian.fromLittleEndianUint64(validatorFields[VALIDATOR_WITHDRAWABLE_EPOCH_INDEX]);
384:     }
385: 
386:     /**
387:      * Indices for withdrawal fields (refer to consensus specs):
388:      * 0: withdrawal index
389:      * 1: validator index
390:      * 2: execution address
391:      * 3: withdrawal amount
392:      */
393: 
394:     /**
395:      * @dev Retrieves a withdrawal's validator index
396:      */
397:     function getValidatorIndex(bytes32[] memory withdrawalFields) internal pure returns (uint40) {
398:         return 
399:             uint40(Endian.fromLittleEndianUint64(withdrawalFields[WITHDRAWAL_VALIDATOR_INDEX_INDEX]));
400:     }
401: 
402:     /**
403:      * @dev Retrieves a withdrawal's withdrawal amount (in gwei)
404:      */
405:     function getWithdrawalAmountGwei(bytes32[] memory withdrawalFields) internal pure returns (uint64) {
406:         return
407:             Endian.fromLittleEndianUint64(withdrawalFields[WITHDRAWAL_VALIDATOR_AMOUNT_INDEX]);
408:     }
409: }
410: 

['1']

1: // SPDX-License-Identifier: BUSL-1.1
2: 
3: pragma solidity =0.8.12;
4: 
5: import "../interfaces/IPausable.sol";
6: 
7: /**
8:  * @title Adds pausability to a contract, with pausing & unpausing controlled by the `pauser` and `unpauser` of a PauserRegistry contract.
9:  * @author Layr Labs, Inc.
10:  * @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service
11:  * @notice Contracts that inherit from this contract may define their own `pause` and `unpause` (and/or related) functions.
12:  * These functions should be permissioned as "onlyPauser" which defers to a `PauserRegistry` for determining access control.
13:  * @dev Pausability is implemented using a uint256, which allows up to 256 different single bit-flags; each bit can potentially pause different functionality.
14:  * Inspiration for this was taken from the NearBridge design here https://etherscan.io/address/0x3FEFc5A4B1c02f21cBc8D3613643ba0635b9a873#code.
15:  * For the `pause` and `unpause` functions we've implemented, if you pause, you can only flip (any number of) switches to on/1 (aka "paused"), and if you unpause,
16:  * you can only flip (any number of) switches to off/0 (aka "paused").
17:  * If you want a pauseXYZ function that just flips a single bit / "pausing flag", it will:
18:  * 1) 'bit-wise and' (aka `&`) a flag with the current paused state (as a uint256)
19:  * 2) update the paused state to this new value
20:  * @dev We note as well that we have chosen to identify flags by their *bit index* as opposed to their numerical value, so, e.g. defining `DEPOSITS_PAUSED = 3`
21:  * indicates specifically that if the *third bit* of `_paused` is flipped -- i.e. it is a '1' -- then deposits should be paused
22:  */
23: contract Pausable is IPausable {
24:     /// @notice Address of the `PauserRegistry` contract that this contract defers to for determining access control (for pausing).
25:     IPauserRegistry public pauserRegistry;
26: 
27:     /// @dev whether or not the contract is currently paused
28:     uint256 private _paused;
29: 
30:     uint256 internal constant UNPAUSE_ALL = 0;
31:     uint256 internal constant PAUSE_ALL = type(uint256).max;
32: 
33:     /// @notice
34:     modifier onlyPauser() {
35:         require(pauserRegistry.isPauser(msg.sender), "msg.sender is not permissioned as pauser");
36:         _;
37:     }
38: 
39:     modifier onlyUnpauser() {
40:         require(msg.sender == pauserRegistry.unpauser(), "msg.sender is not permissioned as unpauser");
41:         _;
42:     }
43: 
44:     /// @notice Throws if the contract is paused, i.e. if any of the bits in `_paused` is flipped to 1.
45:     modifier whenNotPaused() {
46:         require(_paused == 0, "Pausable: contract is paused");
47:         _;
48:     }
49: 
50:     /// @notice Throws if the `indexed`th bit of `_paused` is 1, i.e. if the `index`th pause switch is flipped.
51:     modifier onlyWhenNotPaused(uint8 index) {
52:         require(!paused(index), "Pausable: index is paused");
53:         _;
54:     }
55: 
56:     /// @notice One-time function for setting the `pauserRegistry` and initializing the value of `_paused`.
57:     function _initializePauser(IPauserRegistry _pauserRegistry, uint256 initPausedStatus) internal {
58:         require(
59:             address(pauserRegistry) == address(0) && address(_pauserRegistry) != address(0),
60:             "Pausable._initializePauser: _initializePauser() can only be called once"
61:         );
62:         _paused = initPausedStatus;
63:         emit Paused(msg.sender, initPausedStatus);
64:         _setPauserRegistry(_pauserRegistry);
65:     }
66: 
67:     /**
68:      * @notice This function is used to pause an EigenLayer contract's functionality.
69:      * It is permissioned to the `pauser` address, which is expected to be a low threshold multisig.
70:      * @param newPausedStatus represents the new value for `_paused` to take, which means it may flip several bits at once.
71:      * @dev This function can only pause functionality, and thus cannot 'unflip' any bit in `_paused` from 1 to 0.
72:      */
73:     function pause(uint256 newPausedStatus) external onlyPauser {
74:         // verify that the `newPausedStatus` does not *unflip* any bits (i.e. doesn't unpause anything, all 1 bits remain)
75:         require((_paused & newPausedStatus) == _paused, "Pausable.pause: invalid attempt to unpause functionality");
76:         _paused = newPausedStatus;
77:         emit Paused(msg.sender, newPausedStatus);
78:     }
79: 
80:     /**
81:      * @notice Alias for `pause(type(uint256).max)`.
82:      */
83:     function pauseAll() external onlyPauser {
84:         _paused = type(uint256).max;
85:         emit Paused(msg.sender, type(uint256).max);
86:     }
87: 
88:     /**
89:      * @notice This function is used to unpause an EigenLayer contract's functionality.
90:      * It is permissioned to the `unpauser` address, which is expected to be a high threshold multisig or governance contract.
91:      * @param newPausedStatus represents the new value for `_paused` to take, which means it may flip several bits at once.
92:      * @dev This function can only unpause functionality, and thus cannot 'flip' any bit in `_paused` from 0 to 1.
93:      */
94:     function unpause(uint256 newPausedStatus) external onlyUnpauser {
95:         // verify that the `newPausedStatus` does not *flip* any bits (i.e. doesn't pause anything, all 0 bits remain)
96:         require(
97:             ((~_paused) & (~newPausedStatus)) == (~_paused),
98:             "Pausable.unpause: invalid attempt to pause functionality"
99:         );
100:         _paused = newPausedStatus;
101:         emit Unpaused(msg.sender, newPausedStatus);
102:     }
103: 
104:     /// @notice Returns the current paused status as a uint256.
105:     function paused() public view virtual returns (uint256) {
106:         return _paused;
107:     }
108: 
109:     /// @notice Returns 'true' if the `indexed`th bit of `_paused` is 1, and 'false' otherwise
110:     function paused(uint8 index) public view virtual returns (bool) {
111:         uint256 mask = 1 << index;
112:         return ((_paused & mask) == mask);
113:     }
114: 
115:     /// @notice Allows the unpauser to set a new pauser registry
116:     function setPauserRegistry(IPauserRegistry newPauserRegistry) external onlyUnpauser {
117:         _setPauserRegistry(newPauserRegistry);
118:     }
119: 
120:     /// internal function for setting pauser registry
121:     function _setPauserRegistry(IPauserRegistry newPauserRegistry) internal {
122:         require(
123:             address(newPauserRegistry) != address(0),
124:             "Pausable._setPauserRegistry: newPauserRegistry cannot be the zero address"
125:         );
126:         emit PauserRegistrySet(pauserRegistry, newPauserRegistry);
127:         pauserRegistry = newPauserRegistry;
128:     }
129: 
130:     /**
131:      * @dev This empty reserved space is put in place to allow future versions to add new
132:      * variables without shifting down storage in the inheritance chain.
133:      * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
134:      */
135:     uint256[48] private __gap;
136: }
137: 

['1']

1: // SPDX-License-Identifier: BUSL-1.1
2: pragma solidity >=0.5.0;
3: 
4: import "../libraries/BeaconChainProofs.sol";
5: import "./IEigenPodManager.sol";
6: import "./IBeaconChainOracle.sol";
7: import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
8: 
9: /**
10:  * @title The implementation contract used for restaking beacon chain ETH on EigenLayer
11:  * @author Layr Labs, Inc.
12:  * @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service
13:  * @notice The main functionalities are:
14:  * - creating new ETH validators with their withdrawal credentials pointed to this contract
15:  * - proving from beacon chain state roots that withdrawal credentials are pointed to this contract
16:  * - proving from beacon chain state roots the balances of ETH validators with their withdrawal credentials
17:  *   pointed to this contract
18:  * - updating aggregate balances in the EigenPodManager
19:  * - withdrawing eth when withdrawals are initiated
20:  * @dev Note that all beacon chain balances are stored as gwei within the beacon chain datastructures. We choose
21:  *   to account balances in terms of gwei in the EigenPod contract and convert to wei when making calls to other contracts
22:  */
23: interface IEigenPod {
24:     enum VALIDATOR_STATUS {
25:         INACTIVE, // doesnt exist
26:         ACTIVE, // staked on ethpos and withdrawal credentials are pointed to the EigenPod
27:         WITHDRAWN // withdrawn from the Beacon Chain
28:     }
29: 
30:     struct ValidatorInfo {
31:         // index of the validator in the beacon chain
32:         uint64 validatorIndex;
33:         // amount of beacon chain ETH restaked on EigenLayer in gwei
34:         uint64 restakedBalanceGwei;
35:         //timestamp of the validator's most recent balance update
36:         uint64 mostRecentBalanceUpdateTimestamp;
37:         // status of the validator
38:         VALIDATOR_STATUS status;
39:     }
40: 
41:     /**
42:      * @notice struct used to store amounts related to proven withdrawals in memory. Used to help
43:      * manage stack depth and optimize the number of external calls, when batching withdrawal operations.
44:      */
45:     struct VerifiedWithdrawal {
46:         // amount to send to a podOwner from a proven withdrawal
47:         uint256 amountToSendGwei;
48:         // difference in shares to be recorded in the eigenPodManager, as a result of the withdrawal
49:         int256 sharesDeltaGwei;
50:     }
51: 
53:     enum PARTIAL_WITHDRAWAL_CLAIM_STATUS {
54:         REDEEMED,
55:         PENDING,
56:         FAILED
57:     }
58: 
59:     /// @notice Emitted when an ETH validator stakes via this eigenPod
60:     event EigenPodStaked(bytes pubkey);
61: 
62:     /// @notice Emitted when an ETH validator's withdrawal credentials are successfully verified to be pointed to this eigenPod
63:     event ValidatorRestaked(uint40 validatorIndex);
64: 
65:     /// @notice Emitted when an ETH validator's  balance is proven to be updated.  Here newValidatorBalanceGwei
66:     //  is the validator's balance that is credited on EigenLayer.
67:     event ValidatorBalanceUpdated(uint40 validatorIndex, uint64 balanceTimestamp, uint64 newValidatorBalanceGwei);
68: 
69:     /// @notice Emitted when an ETH validator is prove to have withdrawn from the beacon chain
70:     event FullWithdrawalRedeemed(
71:         uint40 validatorIndex,
72:         uint64 withdrawalTimestamp,
73:         address indexed recipient,
74:         uint64 withdrawalAmountGwei
75:     );
76: 
77:     /// @notice Emitted when a partial withdrawal claim is successfully redeemed
78:     event PartialWithdrawalRedeemed(
79:         uint40 validatorIndex,
80:         uint64 withdrawalTimestamp,
81:         address indexed recipient,
82:         uint64 partialWithdrawalAmountGwei
83:     );
84: 
85:     /// @notice Emitted when restaked beacon chain ETH is withdrawn from the eigenPod.
86:     event RestakedBeaconChainETHWithdrawn(address indexed recipient, uint256 amount);
87: 
88:     /// @notice Emitted when podOwner enables restaking
89:     event RestakingActivated(address indexed podOwner);
90: 
91:     /// @notice Emitted when ETH is received via the `receive` fallback
92:     event NonBeaconChainETHReceived(uint256 amountReceived);
93: 
94:     /// @notice Emitted when ETH that was previously received via the `receive` fallback is withdrawn
95:     event NonBeaconChainETHWithdrawn(address indexed recipient, uint256 amountWithdrawn);
96: 
98:     /// @notice The max amount of eth, in gwei, that can be restaked per validator
99:     function MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR() external view returns (uint64);
100: 
101:     /// @notice the amount of execution layer ETH in this contract that is staked in EigenLayer (i.e. withdrawn from beaconchain but not EigenLayer),
102:     function withdrawableRestakedExecutionLayerGwei() external view returns (uint64);
103: 
104:     /// @notice any ETH deposited into the EigenPod contract via the `receive` fallback function
105:     function nonBeaconChainETHBalanceWei() external view returns (uint256);
106: 
107:     /// @notice Used to initialize the pointers to contracts crucial to the pod's functionality, in beacon proxy construction from EigenPodManager
108:     function initialize(address owner) external;
109: 
110:     /// @notice Called by EigenPodManager when the owner wants to create another ETH validator.
111:     function stake(bytes calldata pubkey, bytes calldata signature, bytes32 depositDataRoot) external payable;
112: 
113:     /**
114:      * @notice Transfers `amountWei` in ether from this contract to the specified `recipient` address
115:      * @notice Called by EigenPodManager to withdrawBeaconChainETH that has been added to the EigenPod's balance due to a withdrawal from the beacon chain.
116:      * @dev The podOwner must have already proved sufficient withdrawals, so that this pod's `withdrawableRestakedExecutionLayerGwei` exceeds the
117:      * `amountWei` input (when converted to GWEI).
118:      * @dev Reverts if `amountWei` is not a whole Gwei amount
119:      */
120:     function withdrawRestakedBeaconChainETH(address recipient, uint256 amount) external;
121: 
122:     /// @notice The single EigenPodManager for EigenLayer
123:     function eigenPodManager() external view returns (IEigenPodManager);
124: 
125:     /// @notice The owner of this EigenPod
126:     function podOwner() external view returns (address);
127: 
128:     /// @notice an indicator of whether or not the podOwner has ever "fully restaked" by successfully calling `verifyCorrectWithdrawalCredentials`.
129:     function hasRestaked() external view returns (bool);
130: 
131:     /**
132:      * @notice The latest timestamp at which the pod owner withdrew the balance of the pod, via calling `withdrawBeforeRestaking`.
133:      * @dev This variable is only updated when the `withdrawBeforeRestaking` function is called, which can only occur before `hasRestaked` is set to true for this pod.
134:      * Proofs for this pod are only valid against Beacon Chain state roots corresponding to timestamps after the stored `mostRecentWithdrawalTimestamp`.
135:      */
136:     function mostRecentWithdrawalTimestamp() external view returns (uint64);
137: 
138:     /// @notice Returns the validatorInfo struct for the provided pubkeyHash
139:     function validatorPubkeyHashToInfo(bytes32 validatorPubkeyHash) external view returns (ValidatorInfo memory);
140: 
141:     /// @notice Returns the validatorInfo struct for the provided pubkey
142:     function validatorPubkeyToInfo(bytes calldata validatorPubkey) external view returns (ValidatorInfo memory);
143: 
144:     ///@notice mapping that tracks proven withdrawals
145:     function provenWithdrawal(bytes32 validatorPubkeyHash, uint64 slot) external view returns (bool);
146: 
147:     /// @notice This returns the status of a given validator
148:     function validatorStatus(bytes32 pubkeyHash) external view returns (VALIDATOR_STATUS);
149: 
150:     /// @notice This returns the status of a given validator pubkey
151:     function validatorStatus(bytes calldata validatorPubkey) external view returns (VALIDATOR_STATUS);
152: 
153:     /**
154:      * @notice This function verifies that the withdrawal credentials of validator(s) owned by the podOwner are pointed to
155:      * this contract. It also verifies the effective balance  of the validator.  It verifies the provided proof of the ETH validator against the beacon chain state
156:      * root, marks the validator as 'active' in EigenLayer, and credits the restaked ETH in Eigenlayer.
157:      * @param oracleTimestamp is the Beacon Chain timestamp whose state root the `proof` will be proven against.
158:      * @param validatorIndices is the list of indices of the validators being proven, refer to consensus specs
159:      * @param withdrawalCredentialProofs is an array of proofs, where each proof proves each ETH validator's balance and withdrawal credentials
160:      * against a beacon chain state root
161:      * @param validatorFields are the fields of the "Validator Container", refer to consensus specs
162:      * for details: https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#validator
163:      */
164:     function verifyWithdrawalCredentials(
165:         uint64 oracleTimestamp,
166:         BeaconChainProofs.StateRootProof calldata stateRootProof,
167:         uint40[] calldata validatorIndices,
168:         bytes[] calldata withdrawalCredentialProofs,
169:         bytes32[][] calldata validatorFields
170:     )
171:         external;
172: 
173:     /**
174:      * @notice This function records an update (either increase or decrease) in the pod's balance in the StrategyManager.  
175:                It also verifies a merkle proof of the validator's current beacon chain balance.  
176:      * @param oracleTimestamp The oracleTimestamp whose state root the `proof` will be proven against.
177:      *        Must be within `VERIFY_BALANCE_UPDATE_WINDOW_SECONDS` of the current block.
178:      * @param validatorIndices is the list of indices of the validators being proven, refer to consensus specs 
179:      * @param validatorFieldsProofs proofs against the `beaconStateRoot` for each validator in `validatorFields`
180:      * @param validatorFields are the fields of the "Validator Container", refer to consensus specs
181:      * @dev For more details on the Beacon Chain spec, see: https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#validator
182:      */
183:     function verifyBalanceUpdates(
184:         uint64 oracleTimestamp,
185:         uint40[] calldata validatorIndices,
186:         BeaconChainProofs.StateRootProof calldata stateRootProof,
187:         bytes[] calldata validatorFieldsProofs,
188:         bytes32[][] calldata validatorFields
189:     ) external;
190: 
191:     /**
192:      * @notice This function records full and partial withdrawals on behalf of one of the Ethereum validators for this EigenPod
193:      * @param oracleTimestamp is the timestamp of the oracle slot that the withdrawal is being proven against
194:      * @param withdrawalProofs is the information needed to check the veracity of the block numbers and withdrawals being proven
195:      * @param validatorFieldsProofs is the proof of the validator's fields' in the validator tree
196:      * @param withdrawalFields are the fields of the withdrawals being proven
197:      * @param validatorFields are the fields of the validators being proven
198:      */
199:     function verifyAndProcessWithdrawals(
200:         uint64 oracleTimestamp,
201:         BeaconChainProofs.StateRootProof calldata stateRootProof,
202:         BeaconChainProofs.WithdrawalProof[] calldata withdrawalProofs,
203:         bytes[] calldata validatorFieldsProofs,
204:         bytes32[][] calldata validatorFields,
205:         bytes32[][] calldata withdrawalFields
206:     ) external;
207: 
208:     /**
209:      * @notice Called by the pod owner to activate restaking by withdrawing
210:      * all existing ETH from the pod and preventing further withdrawals via
211:      * "withdrawBeforeRestaking()"
212:      */
213:     function activateRestaking() external;
214: 
215:     /// @notice Called by the pod owner to withdraw the balance of the pod when `hasRestaked` is set to false
216:     function withdrawBeforeRestaking() external;
217: 
218:     /// @notice Called by the pod owner to withdraw the nonBeaconChainETHBalanceWei
219:     function withdrawNonBeaconChainETHBalanceWei(address recipient, uint256 amountToWithdraw) external;
220: 
221:     /// @notice called by owner of a pod to remove any ERC20s deposited in the pod
222:     function recoverTokens(IERC20[] memory tokenList, uint256[] memory amountsToWithdraw, address recipient) external;
223: }
224: 

['1']

1: // SPDX-License-Identifier: BUSL-1.1
2: pragma solidity =0.8.12;
3: 
4: import "@openzeppelin-upgrades/contracts/proxy/utils/Initializable.sol";
5: import "@openzeppelin-upgrades/contracts/access/OwnableUpgradeable.sol";
6: import "@openzeppelin-upgrades/contracts/security/ReentrancyGuardUpgradeable.sol";
7: import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
8: import "../interfaces/IEigenPodManager.sol";
9: import "../permissions/Pausable.sol";
10: import "./StrategyManagerStorage.sol";
11: import "../libraries/EIP1271SignatureUtils.sol";
12: 
13: /**
14:  * @title The primary entry- and exit-point for funds into and out of EigenLayer.
15:  * @author Layr Labs, Inc.
16:  * @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service
17:  * @notice This contract is for managing deposits in different strategies. The main
18:  * functionalities are:
19:  * - adding and removing strategies that any delegator can deposit into
20:  * - enabling deposit of assets into specified strategy(s)
21:  */
22: contract StrategyManager is
23:     Initializable,
24:     OwnableUpgradeable,
25:     ReentrancyGuardUpgradeable,
26:     Pausable,
27:     StrategyManagerStorage
28: {
29:     using SafeERC20 for IERC20;
30: 
31:     // index for flag that pauses deposits when set
32:     uint8 internal constant PAUSED_DEPOSITS = 0;
33: 
34:     // chain id at the time of contract deployment
35:     uint256 internal immutable ORIGINAL_CHAIN_ID;
36: 
37:     modifier onlyStrategyWhitelister() {
38:         require(
39:             msg.sender == strategyWhitelister,
40:             "StrategyManager.onlyStrategyWhitelister: not the strategyWhitelister"
41:         );
42:         _;
43:     }
44: 
45:     modifier onlyStrategiesWhitelistedForDeposit(IStrategy strategy) {
46:         require(
47:             strategyIsWhitelistedForDeposit[strategy],
48:             "StrategyManager.onlyStrategiesWhitelistedForDeposit: strategy not whitelisted"
49:         );
50:         _;
51:     }
52: 
53:     modifier onlyDelegationManager() {
54:         require(msg.sender == address(delegation), "StrategyManager.onlyDelegationManager: not the DelegationManager");
55:         _;
56:     }
57: 
58:     /**
59:      * @param _delegation The delegation contract of EigenLayer.
60:      * @param _slasher The primary slashing contract of EigenLayer.
61:      * @param _eigenPodManager The contract that keeps track of EigenPod stakes for restaking beacon chain ether.
62:      */
63:     constructor(
64:         IDelegationManager _delegation,
65:         IEigenPodManager _eigenPodManager,
66:         ISlasher _slasher
67:     ) StrategyManagerStorage(_delegation, _eigenPodManager, _slasher) {
68:         _disableInitializers();
69:         ORIGINAL_CHAIN_ID = block.chainid;
70:     }
71: 
72:     // EXTERNAL FUNCTIONS
73: 
74:     /**
75:      * @notice Initializes the strategy manager contract. Sets the `pauserRegistry` (currently **not** modifiable after being set),
76:      * and transfers contract ownership to the specified `initialOwner`.
77:      * @param _pauserRegistry Used for access control of pausing.
78:      * @param initialOwner Ownership of this contract is transferred to this address.
79:      * @param initialStrategyWhitelister The initial value of `strategyWhitelister` to set.
80:      * @param  initialPausedStatus The initial value of `_paused` to set.
81:      */
82:     function initialize(
83:         address initialOwner,
84:         address initialStrategyWhitelister,
85:         IPauserRegistry _pauserRegistry,
86:         uint256 initialPausedStatus
87:     ) external initializer {
88:         _DOMAIN_SEPARATOR = _calculateDomainSeparator();
89:         _initializePauser(_pauserRegistry, initialPausedStatus);
90:         _transferOwnership(initialOwner);
91:         _setStrategyWhitelister(initialStrategyWhitelister);
92:     }
93: 
94:     /**
95:      * @notice Deposits `amount` of `token` into the specified `strategy`, with the resultant shares credited to `msg.sender`
96:      * @param strategy is the specified strategy where deposit is to be made,
97:      * @param token is the denomination in which the deposit is to be made,
98:      * @param amount is the amount of token to be deposited in the strategy by the staker
99:      * @return shares The amount of new shares in the `strategy` created as part of the action.
100:      * @dev The `msg.sender` must have previously approved this contract to transfer at least `amount` of `token` on their behalf.
101:      *
102:      * WARNING: Depositing tokens that allow reentrancy (eg. ERC-777) into a strategy is not recommended.  This can lead to attack vectors
103:      *          where the token balance and corresponding strategy shares are not in sync upon reentrancy.
104:      */
105:     function depositIntoStrategy(
106:         IStrategy strategy,
107:         IERC20 token,
108:         uint256 amount
109:     ) external onlyWhenNotPaused(PAUSED_DEPOSITS) nonReentrant returns (uint256 shares) {
110:         shares = _depositIntoStrategy(msg.sender, strategy, token, amount);
111:     }
112: 
113:     /**
114:      * @notice Used for depositing an asset into the specified strategy with the resultant shares credited to `staker`,
115:      * who must sign off on the action.
116:      * Note that the assets are transferred out/from the `msg.sender`, not from the `staker`; this function is explicitly designed
117:      * purely to help one address deposit 'for' another.
118:      * @param strategy is the specified strategy where deposit is to be made,
119:      * @param token is the denomination in which the deposit is to be made,
120:      * @param amount is the amount of token to be deposited in the strategy by the staker
121:      * @param staker the staker that the deposited assets will be credited to
122:      * @param expiry the timestamp at which the signature expires
123:      * @param signature is a valid signature from the `staker`. either an ECDSA signature if the `staker` is an EOA, or data to forward
124:      * following EIP-1271 if the `staker` is a contract
125:      * @return shares The amount of new shares in the `strategy` created as part of the action.
126:      * @dev The `msg.sender` must have previously approved this contract to transfer at least `amount` of `token` on their behalf.
127:      * @dev A signature is required for this function to eliminate the possibility of griefing attacks, specifically those
128:      * targeting stakers who may be attempting to undelegate.
129:      * @dev Cannot be called if thirdPartyTransfersForbidden is set to true for this strategy
130:      *
131:      *  WARNING: Depositing tokens that allow reentrancy (eg. ERC-777) into a strategy is not recommended.  This can lead to attack vectors
132:      *          where the token balance and corresponding strategy shares are not in sync upon reentrancy
133:      */
134:     function depositIntoStrategyWithSignature(
135:         IStrategy strategy,
136:         IERC20 token,
137:         uint256 amount,
138:         address staker,
139:         uint256 expiry,
140:         bytes memory signature
141:     ) external onlyWhenNotPaused(PAUSED_DEPOSITS) nonReentrant returns (uint256 shares) {
142:         require(
143:             !thirdPartyTransfersForbidden[strategy],
144:             "StrategyManager.depositIntoStrategyWithSignature: third transfers disabled"
145:         );
146:         require(expiry >= block.timestamp, "StrategyManager.depositIntoStrategyWithSignature: signature expired");
147:         // calculate struct hash, then increment `staker`'s nonce
148:         uint256 nonce = nonces[staker];
149:         bytes32 structHash = keccak256(abi.encode(DEPOSIT_TYPEHASH, staker, strategy, token, amount, nonce, expiry));
150:         unchecked {
151:             nonces[staker] = nonce + 1;
152:         }
153: 
154:         // calculate the digest hash
155:         bytes32 digestHash = keccak256(abi.encodePacked("\x19\x01", domainSeparator(), structHash));
156: 
157:         /**
158:          * check validity of signature:
159:          * 1) if `staker` is an EOA, then `signature` must be a valid ECDSA signature from `staker`,
160:          * indicating their intention for this action
161:          * 2) if `staker` is a contract, then `signature` will be checked according to EIP-1271
162:          */
163:         EIP1271SignatureUtils.checkSignature_EIP1271(staker, digestHash, signature);
164: 
165:         // deposit the tokens (from the `msg.sender`) and credit the new shares to the `staker`
166:         shares = _depositIntoStrategy(staker, strategy, token, amount);
167:     }
168: 
169:     /// @notice Used by the DelegationManager to remove a Staker's shares from a particular strategy when entering the withdrawal queue
170:     function removeShares(
171:         address staker,
172:         IStrategy strategy,
173:         uint256 shares
174:     ) external onlyDelegationManager {
175:         _removeShares(staker, strategy, shares);
176:     }
177: 
178:     /// @notice Used by the DelegationManager to award a Staker some shares that have passed through the withdrawal queue
179:     function addShares(
180:         address staker,
181:         IERC20 token,
182:         IStrategy strategy,
183:         uint256 shares
184:     ) external onlyDelegationManager {
185:         _addShares(staker, token, strategy, shares);
186:     }
187: 
188:     /// @notice Used by the DelegationManager to convert withdrawn shares to tokens and send them to a recipient
189:     function withdrawSharesAsTokens(
190:         address recipient,
191:         IStrategy strategy,
192:         uint256 shares,
193:         IERC20 token
194:     ) external onlyDelegationManager {
195:         strategy.withdraw(recipient, token, shares);
196:     }
197: 
198:     /// @notice Function called by the DelegationManager as part of the process of transferring existing queued withdrawals from this contract to that contract.
199:     /// @dev This function is expected to be removed in the next upgrade, after all queued withdrawals have been migrated.
200:     function migrateQueuedWithdrawal(DeprecatedStruct_QueuedWithdrawal memory queuedWithdrawal) external onlyDelegationManager returns(bool, bytes32) {
201:         bytes32 existingWithdrawalRoot = calculateWithdrawalRoot(queuedWithdrawal);
202:         bool isDeleted;
203:         // Delete the withdrawal root if it exists
204:         if (withdrawalRootPending[existingWithdrawalRoot]) {
205:             withdrawalRootPending[existingWithdrawalRoot] = false;
206:             isDeleted = true;
207:         }
208:         return (isDeleted, existingWithdrawalRoot);
209:     }
210: 
211:     /**
212:      * If true for a strategy, a user cannot depositIntoStrategyWithSignature into that strategy for another staker
213:      * and also when performing DelegationManager.queueWithdrawals, a staker can only withdraw to themselves.
214:      * Defaulted to false for all existing strategies.
215:      * @param strategy The strategy to set `thirdPartyTransfersForbidden` value to
216:      * @param value bool value to set `thirdPartyTransfersForbidden` to
217:      */
218:     function setThirdPartyTransfersForbidden(
219:         IStrategy strategy,
220:         bool value
221:     ) external onlyStrategyWhitelister {
222:         _setThirdPartyTransfersForbidden(strategy, value);
223:     }
224: 
225:     /**
226:      * @notice Owner-only function to change the `strategyWhitelister` address.
227:      * @param newStrategyWhitelister new address for the `strategyWhitelister`.
228:      */
229:     function setStrategyWhitelister(address newStrategyWhitelister) external onlyOwner {
230:         _setStrategyWhitelister(newStrategyWhitelister);
231:     }
232: 
233:     /**
234:      * @notice Owner-only function that adds the provided Strategies to the 'whitelist' of strategies that stakers can deposit into
235:      * @param strategiesToWhitelist Strategies that will be added to the `strategyIsWhitelistedForDeposit` mapping (if they aren't in it already)
236:      * @param thirdPartyTransfersForbiddenValues bool values to set `thirdPartyTransfersForbidden` to for each strategy
237:      */
238:     function addStrategiesToDepositWhitelist(
239:         IStrategy[] calldata strategiesToWhitelist,
240:         bool[] calldata thirdPartyTransfersForbiddenValues
241:     ) external onlyStrategyWhitelister {
242:         require(
243:             strategiesToWhitelist.length == thirdPartyTransfersForbiddenValues.length,
244:             "StrategyManager.addStrategiesToDepositWhitelist: array lengths do not match"
245:         );
246:         uint256 strategiesToWhitelistLength = strategiesToWhitelist.length;
247:         for (uint256 i = 0; i < strategiesToWhitelistLength; ) {
248:             // change storage and emit event only if strategy is not already in whitelist
249:             if (!strategyIsWhitelistedForDeposit[strategiesToWhitelist[i]]) {
250:                 strategyIsWhitelistedForDeposit[strategiesToWhitelist[i]] = true;
251:                 emit StrategyAddedToDepositWhitelist(strategiesToWhitelist[i]);
252:                 _setThirdPartyTransfersForbidden(strategiesToWhitelist[i], thirdPartyTransfersForbiddenValues[i]);
253:             }
254:             unchecked {
255:                 ++i;
256:             }
257:         }
258:     }
259: 
260:     /**
261:      * @notice Owner-only function that removes the provided Strategies from the 'whitelist' of strategies that stakers can deposit into
262:      * @param strategiesToRemoveFromWhitelist Strategies that will be removed to the `strategyIsWhitelistedForDeposit` mapping (if they are in it)
263:      */
264:     function removeStrategiesFromDepositWhitelist(
265:         IStrategy[] calldata strategiesToRemoveFromWhitelist
266:     ) external onlyStrategyWhitelister {
267:         uint256 strategiesToRemoveFromWhitelistLength = strategiesToRemoveFromWhitelist.length;
268:         for (uint256 i = 0; i < strategiesToRemoveFromWhitelistLength; ) {
269:             // change storage and emit event only if strategy is already in whitelist
270:             if (strategyIsWhitelistedForDeposit[strategiesToRemoveFromWhitelist[i]]) {
271:                 strategyIsWhitelistedForDeposit[strategiesToRemoveFromWhitelist[i]] = false;
272:                 emit StrategyRemovedFromDepositWhitelist(strategiesToRemoveFromWhitelist[i]);
273:                 // Set mapping value to default false value
274:                 _setThirdPartyTransfersForbidden(strategiesToRemoveFromWhitelist[i], false);
275:             }
276:             unchecked {
277:                 ++i;
278:             }
279:         }
280:     }
281: 
282:     // INTERNAL FUNCTIONS
283: 
284:     /**
285:      * @notice This function adds `shares` for a given `strategy` to the `staker` and runs through the necessary update logic.
286:      * @param staker The address to add shares to
287:      * @param token The token that is being deposited (used for indexing)
288:      * @param strategy The Strategy in which the `staker` is receiving shares
289:      * @param shares The amount of shares to grant to the `staker`
290:      * @dev In particular, this function calls `delegation.increaseDelegatedShares(staker, strategy, shares)` to ensure that all
291:      * delegated shares are tracked, increases the stored share amount in `stakerStrategyShares[staker][strategy]`, and adds `strategy`
292:      * to the `staker`'s list of strategies, if it is not in the list already.
293:      */
294:     function _addShares(address staker, IERC20 token, IStrategy strategy, uint256 shares) internal {
295:         // sanity checks on inputs
296:         require(staker != address(0), "StrategyManager._addShares: staker cannot be zero address");
297:         require(shares != 0, "StrategyManager._addShares: shares should not be zero!");
298: 
299:         // if they dont have existing shares of this strategy, add it to their strats
300:         if (stakerStrategyShares[staker][strategy] == 0) {
301:             require(
302:                 stakerStrategyList[staker].length < MAX_STAKER_STRATEGY_LIST_LENGTH,
303:                 "StrategyManager._addShares: deposit would exceed MAX_STAKER_STRATEGY_LIST_LENGTH"
304:             );
305:             stakerStrategyList[staker].push(strategy);
306:         }
307: 
308:         // add the returned shares to their existing shares for this strategy
309:         stakerStrategyShares[staker][strategy] += shares;
310: 
311:         emit Deposit(staker, token, strategy, shares);
312:     }
313: 
314:     /**
315:      * @notice Internal function in which `amount` of ERC20 `token` is transferred from `msg.sender` to the Strategy-type contract
316:      * `strategy`, with the resulting shares credited to `staker`.
317:      * @param staker The address that will be credited with the new shares.
318:      * @param strategy The Strategy contract to deposit into.
319:      * @param token The ERC20 token to deposit.
320:      * @param amount The amount of `token` to deposit.
321:      * @return shares The amount of *new* shares in `strategy` that have been credited to the `staker`.
322:      */
323:     function _depositIntoStrategy(
324:         address staker,
325:         IStrategy strategy,
326:         IERC20 token,
327:         uint256 amount
328:     ) internal onlyStrategiesWhitelistedForDeposit(strategy) returns (uint256 shares) {
329:         // transfer tokens from the sender to the strategy
330:         token.safeTransferFrom(msg.sender, address(strategy), amount);
331: 
332:         // deposit the assets into the specified strategy and get the equivalent amount of shares in that strategy
333:         shares = strategy.deposit(token, amount);
334: 
335:         // add the returned shares to the staker's existing shares for this strategy
336:         _addShares(staker, token, strategy, shares);
337: 
338:         // Increase shares delegated to operator, if needed
339:         delegation.increaseDelegatedShares(staker, strategy, shares);
340: 
341:         return shares;
342:     }
343: 
344:     /**
345:      * @notice Decreases the shares that `staker` holds in `strategy` by `shareAmount`.
346:      * @param staker The address to decrement shares from
347:      * @param strategy The strategy for which the `staker`'s shares are being decremented
348:      * @param shareAmount The amount of shares to decrement
349:      * @dev If the amount of shares represents all of the staker`s shares in said strategy,
350:      * then the strategy is removed from stakerStrategyList[staker] and 'true' is returned. Otherwise 'false' is returned.
351:      */
352:     function _removeShares(
353:         address staker,
354:         IStrategy strategy,
355:         uint256 shareAmount
356:     ) internal returns (bool) {
357:         // sanity checks on inputs
358:         require(shareAmount != 0, "StrategyManager._removeShares: shareAmount should not be zero!");
359: 
360:         //check that the user has sufficient shares
361:         uint256 userShares = stakerStrategyShares[staker][strategy];
362: 
363:         require(shareAmount <= userShares, "StrategyManager._removeShares: shareAmount too high");
364:         //unchecked arithmetic since we just checked this above
365:         unchecked {
366:             userShares = userShares - shareAmount;
367:         }
368: 
369:         // subtract the shares from the staker's existing shares for this strategy
370:         stakerStrategyShares[staker][strategy] = userShares;
371: 
372:         // if no existing shares, remove the strategy from the staker's dynamic array of strategies
373:         if (userShares == 0) {
374:             _removeStrategyFromStakerStrategyList(staker, strategy);
375: 
376:             // return true in the event that the strategy was removed from stakerStrategyList[staker]
377:             return true;
378:         }
379:         // return false in the event that the strategy was *not* removed from stakerStrategyList[staker]
380:         return false;
381:     }
382: 
383:     /**
384:      * @notice Removes `strategy` from `staker`'s dynamic array of strategies, i.e. from `stakerStrategyList[staker]`
385:      * @param staker The user whose array will have an entry removed
386:      * @param strategy The Strategy to remove from `stakerStrategyList[staker]`
387:      */
388:     function _removeStrategyFromStakerStrategyList(
389:         address staker,
390:         IStrategy strategy
391:     ) internal {
392:         //loop through all of the strategies, find the right one, then replace
393:         uint256 stratsLength = stakerStrategyList[staker].length;
394:         uint256 j = 0;
395:         for (; j < stratsLength; ) {
396:             if (stakerStrategyList[staker][j] == strategy) {
397:                 //replace the strategy with the last strategy in the list
398:                 stakerStrategyList[staker][j] = stakerStrategyList[staker][
399:                     stakerStrategyList[staker].length - 1
400:                 ];
401:                 break;
402:             }
403:             unchecked { ++j; }
404:         }
405:         // if we didn't find the strategy, revert
406:         require(j != stratsLength, "StrategyManager._removeStrategyFromStakerStrategyList: strategy not found");
407:         // pop off the last entry in the list of strategies
408:         stakerStrategyList[staker].pop();
409:     }
410: 
411:     /**
412:      * @notice Internal function for modifying `thirdPartyTransfersForbidden`.
413:      * Used inside of the `setThirdPartyTransfersForbidden` and `addStrategiesToDepositWhitelist` functions.
414:      * @param strategy The strategy to set `thirdPartyTransfersForbidden` value to
415:      * @param value bool value to set `thirdPartyTransfersForbidden` to
416:      */
417:     function _setThirdPartyTransfersForbidden(IStrategy strategy, bool value) internal {
418:         emit UpdatedThirdPartyTransfersForbidden(strategy, value);
419:         thirdPartyTransfersForbidden[strategy] = value;
420:     }
421: 
422:     /**
423:      * @notice Internal function for modifying the `strategyWhitelister`. Used inside of the `setStrategyWhitelister` and `initialize` functions.
424:      * @param newStrategyWhitelister The new address for the `strategyWhitelister` to take.
425:      */
426:     function _setStrategyWhitelister(address newStrategyWhitelister) internal {
427:         emit StrategyWhitelisterChanged(strategyWhitelister, newStrategyWhitelister);
428:         strategyWhitelister = newStrategyWhitelister;
429:     }
430: 
431:     // VIEW FUNCTIONS
432:     /**
433:      * @notice Get all details on the staker's deposits and corresponding shares
434:      * @param staker The staker of interest, whose deposits this function will fetch
435:      * @return (staker's strategies, shares in these strategies)
436:      */
437:     function getDeposits(address staker) external view returns (IStrategy[] memory, uint256[] memory) {
438:         uint256 strategiesLength = stakerStrategyList[staker].length;
439:         uint256[] memory shares = new uint256[](strategiesLength);
440: 
441:         for (uint256 i = 0; i < strategiesLength; ) {
442:             shares[i] = stakerStrategyShares[staker][stakerStrategyList[staker][i]];
443:             unchecked {
444:                 ++i;
445:             }
446:         }
447:         return (stakerStrategyList[staker], shares);
448:     }
449: 
450:     /// @notice Simple getter function that returns `stakerStrategyList[staker].length`.
451:     function stakerStrategyListLength(address staker) external view returns (uint256) {
452:         return stakerStrategyList[staker].length;
453:     }
454: 
455:     /**
456:      * @notice Getter function for the current EIP-712 domain separator for this contract.
457:      * @dev The domain separator will change in the event of a fork that changes the ChainID.
458:      */
459:     function domainSeparator() public view returns (bytes32) {
460:         if (block.chainid == ORIGINAL_CHAIN_ID) {
461:             return _DOMAIN_SEPARATOR;
462:         } else {
463:             return _calculateDomainSeparator();
464:         }
465:     }
466: 
467:     // @notice Internal function for calculating the current domain separator of this contract
468:     function _calculateDomainSeparator() internal view returns (bytes32) {
469:         return keccak256(abi.encode(DOMAIN_TYPEHASH, keccak256(bytes("EigenLayer")), block.chainid, address(this)));
470:     }
471: 
472: // LIMITED BACKWARDS-COMPATIBILITY FOR DEPRECATED FUNCTIONALITY
473:     /// @notice Returns the keccak256 hash of `queuedWithdrawal`.
474:     function calculateWithdrawalRoot(DeprecatedStruct_QueuedWithdrawal memory queuedWithdrawal) public pure returns (bytes32) {
475:         return (
476:             keccak256(
477:                 abi.encode(
478:                     queuedWithdrawal.strategies,
479:                     queuedWithdrawal.shares,
480:                     queuedWithdrawal.staker,
481:                     queuedWithdrawal.withdrawerAndNonce,
482:                     queuedWithdrawal.withdrawalStartBlock,
483:                     queuedWithdrawal.delegatedAddress
484:                 )
485:             )
486:         );
487:     }
488: }
489: 

['1']

1: // SPDX-License-Identifier: BUSL-1.1
2: pragma solidity >=0.5.0;
3: 
4: import "./IStrategyManager.sol";
5: import "./IDelegationManager.sol";
6: 
7: /**
8:  * @title Interface for the primary 'slashing' contract for EigenLayer.
9:  * @author Layr Labs, Inc.
10:  * @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service
11:  * @notice See the `Slasher` contract itself for implementation details.
12:  */
13: interface ISlasher {
14:     // struct used to store information about the current state of an operator's obligations to middlewares they are serving
15:     struct MiddlewareTimes {
16:         // The update block for the middleware whose most recent update was earliest, i.e. the 'stalest' update out of all middlewares the operator is serving
17:         uint32 stalestUpdateBlock;
18:         // The latest 'serveUntilBlock' from all of the middleware that the operator is serving
19:         uint32 latestServeUntilBlock;
20:     }
21: 
22:     // struct used to store details relevant to a single middleware that an operator has opted-in to serving
23:     struct MiddlewareDetails {
24:         // the block at which the contract begins being able to finalize the operator's registration with the service via calling `recordFirstStakeUpdate`
25:         uint32 registrationMayBeginAtBlock;
26:         // the block before which the contract is allowed to slash the user
27:         uint32 contractCanSlashOperatorUntilBlock;
28:         // the block at which the middleware's view of the operator's stake was most recently updated
29:         uint32 latestUpdateBlock;
30:     }
31: 
32:     /// @notice Emitted when a middleware times is added to `operator`'s array.
33:     event MiddlewareTimesAdded(
34:         address operator,
35:         uint256 index,
36:         uint32 stalestUpdateBlock,
37:         uint32 latestServeUntilBlock
38:     );
39: 
40:     /// @notice Emitted when `operator` begins to allow `contractAddress` to slash them.
41:     event OptedIntoSlashing(address indexed operator, address indexed contractAddress);
42: 
43:     /// @notice Emitted when `contractAddress` signals that it will no longer be able to slash `operator` after the `contractCanSlashOperatorUntilBlock`.
44:     event SlashingAbilityRevoked(
45:         address indexed operator,
46:         address indexed contractAddress,
47:         uint32 contractCanSlashOperatorUntilBlock
48:     );
49: 
50:     /**
51:      * @notice Emitted when `slashingContract` 'freezes' the `slashedOperator`.
52:      * @dev The `slashingContract` must have permission to slash the `slashedOperator`, i.e. `canSlash(slasherOperator, slashingContract)` must return 'true'.
53:      */
54:     event OperatorFrozen(address indexed slashedOperator, address indexed slashingContract);
55: 
56:     /// @notice Emitted when `previouslySlashedAddress` is 'unfrozen', allowing them to again move deposited funds within EigenLayer.
57:     event FrozenStatusReset(address indexed previouslySlashedAddress);
58: 
59:     /**
60:      * @notice Gives the `contractAddress` permission to slash the funds of the caller.
61:      * @dev Typically, this function must be called prior to registering for a middleware.
62:      */
63:     function optIntoSlashing(address contractAddress) external;
64: 
65:     /**
66:      * @notice Used for 'slashing' a certain operator.
67:      * @param toBeFrozen The operator to be frozen.
68:      * @dev Technically the operator is 'frozen' (hence the name of this function), and then subject to slashing pending a decision by a human-in-the-loop.
69:      * @dev The operator must have previously given the caller (which should be a contract) the ability to slash them, through a call to `optIntoSlashing`.
70:      */
71:     function freezeOperator(address toBeFrozen) external;
72: 
73:     /**
74:      * @notice Removes the 'frozen' status from each of the `frozenAddresses`
75:      * @dev Callable only by the contract owner (i.e. governance).
76:      */
77:     function resetFrozenStatus(address[] calldata frozenAddresses) external;
78: 
79:     /**
80:      * @notice this function is a called by middlewares during an operator's registration to make sure the operator's stake at registration
81:      *         is slashable until serveUntil
82:      * @param operator the operator whose stake update is being recorded
83:      * @param serveUntilBlock the block until which the operator's stake at the current block is slashable
84:      * @dev adds the middleware's slashing contract to the operator's linked list
85:      */
86:     function recordFirstStakeUpdate(address operator, uint32 serveUntilBlock) external;
87: 
88:     /**
89:      * @notice this function is a called by middlewares during a stake update for an operator (perhaps to free pending withdrawals)
90:      *         to make sure the operator's stake at updateBlock is slashable until serveUntil
91:      * @param operator the operator whose stake update is being recorded
92:      * @param updateBlock the block for which the stake update is being recorded
93:      * @param serveUntilBlock the block until which the operator's stake at updateBlock is slashable
94:      * @param insertAfter the element of the operators linked list that the currently updating middleware should be inserted after
95:      * @dev insertAfter should be calculated offchain before making the transaction that calls this. this is subject to race conditions,
96:      *      but it is anticipated to be rare and not detrimental.
97:      */
98:     function recordStakeUpdate(
99:         address operator,
100:         uint32 updateBlock,
101:         uint32 serveUntilBlock,
102:         uint256 insertAfter
103:     ) external;
104: 
105:     /**
106:      * @notice this function is a called by middlewares during an operator's deregistration to make sure the operator's stake at deregistration
107:      *         is slashable until serveUntil
108:      * @param operator the operator whose stake update is being recorded
109:      * @param serveUntilBlock the block until which the operator's stake at the current block is slashable
110:      * @dev removes the middleware's slashing contract to the operator's linked list and revokes the middleware's (i.e. caller's) ability to
111:      * slash `operator` once `serveUntil` is reached
112:      */
113:     function recordLastStakeUpdateAndRevokeSlashingAbility(address operator, uint32 serveUntilBlock) external;
114: 
115:     /// @notice The StrategyManager contract of EigenLayer
116:     function strategyManager() external view returns (IStrategyManager);
117: 
118:     /// @notice The DelegationManager contract of EigenLayer
119:     function delegation() external view returns (IDelegationManager);
120: 
121:     /**
122:      * @notice Used to determine whether `staker` is actively 'frozen'. If a staker is frozen, then they are potentially subject to
123:      * slashing of their funds, and cannot cannot deposit or withdraw from the strategyManager until the slashing process is completed
124:      * and the staker's status is reset (to 'unfrozen').
125:      * @param staker The staker of interest.
126:      * @return Returns 'true' if `staker` themselves has their status set to frozen, OR if the staker is delegated
127:      * to an operator who has their status set to frozen. Otherwise returns 'false'.
128:      */
129:     function isFrozen(address staker) external view returns (bool);
130: 
131:     /// @notice Returns true if `slashingContract` is currently allowed to slash `toBeSlashed`.
132:     function canSlash(address toBeSlashed, address slashingContract) external view returns (bool);
133: 
134:     /// @notice Returns the block until which `serviceContract` is allowed to slash the `operator`.
135:     function contractCanSlashOperatorUntilBlock(
136:         address operator,
137:         address serviceContract
138:     ) external view returns (uint32);
139: 
140:     /// @notice Returns the block at which the `serviceContract` last updated its view of the `operator`'s stake
141:     function latestUpdateBlock(address operator, address serviceContract) external view returns (uint32);
142: 
143:     /// @notice A search routine for finding the correct input value of `insertAfter` to `recordStakeUpdate` / `_updateMiddlewareList`.
144:     function getCorrectValueForInsertAfter(address operator, uint32 updateBlock) external view returns (uint256);
145: 
146:     /**
147:      * @notice Returns 'true' if `operator` can currently complete a withdrawal started at the `withdrawalStartBlock`, with `middlewareTimesIndex` used
148:      * to specify the index of a `MiddlewareTimes` struct in the operator's list (i.e. an index in `operatorToMiddlewareTimes[operator]`). The specified
149:      * struct is consulted as proof of the `operator`'s ability (or lack thereof) to complete the withdrawal.
150:      * This function will return 'false' if the operator cannot currently complete a withdrawal started at the `withdrawalStartBlock`, *or* in the event
151:      * that an incorrect `middlewareTimesIndex` is supplied, even if one or more correct inputs exist.
152:      * @param operator Either the operator who queued the withdrawal themselves, or if the withdrawing party is a staker who delegated to an operator,
153:      * this address is the operator *who the staker was delegated to* at the time of the `withdrawalStartBlock`.
154:      * @param withdrawalStartBlock The block number at which the withdrawal was initiated.
155:      * @param middlewareTimesIndex Indicates an index in `operatorToMiddlewareTimes[operator]` to consult as proof of the `operator`'s ability to withdraw
156:      * @dev The correct `middlewareTimesIndex` input should be computable off-chain.
157:      */
158:     function canWithdraw(
159:         address operator,
160:         uint32 withdrawalStartBlock,
161:         uint256 middlewareTimesIndex
162:     ) external returns (bool);
163: 
164:     /**
165:      * operator =>
166:      *  [
167:      *      (
168:      *          the least recent update block of all of the middlewares it's serving/served,
169:      *          latest time that the stake bonded at that update needed to serve until
170:      *      )
171:      *  ]
172:      */
173:     function operatorToMiddlewareTimes(
174:         address operator,
175:         uint256 arrayIndex
176:     ) external view returns (MiddlewareTimes memory);
177: 
178:     /// @notice Getter function for fetching `operatorToMiddlewareTimes[operator].length`
179:     function middlewareTimesLength(address operator) external view returns (uint256);
180: 
181:     /// @notice Getter function for fetching `operatorToMiddlewareTimes[operator][index].stalestUpdateBlock`.
182:     function getMiddlewareTimesIndexStalestUpdateBlock(address operator, uint32 index) external view returns (uint32);
183: 
184:     /// @notice Getter function for fetching `operatorToMiddlewareTimes[operator][index].latestServeUntil`.
185:     function getMiddlewareTimesIndexServeUntilBlock(address operator, uint32 index) external view returns (uint32);
186: 
187:     /// @notice Getter function for fetching `_operatorToWhitelistedContractsByUpdate[operator].size`.
188:     function operatorWhitelistedContractsLinkedListSize(address operator) external view returns (uint256);
189: 
190:     /// @notice Getter function for fetching a single node in the operator's linked list (`_operatorToWhitelistedContractsByUpdate[operator]`).
191:     function operatorWhitelistedContractsLinkedListEntry(
192:         address operator,
193:         address node
194:     ) external view returns (bool, uint256, uint256);
195: }
196: 

['1']

1: // SPDX-License-Identifier: BUSL-1.1
2: pragma solidity =0.8.12;
3: 
4: import "@openzeppelin/contracts/interfaces/IERC1271.sol";
5: import "@openzeppelin/contracts/utils/Address.sol";
6: import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
7: 
8: /**
9:  * @title Library of utilities for making EIP1271-compliant signature checks.
10:  * @author Layr Labs, Inc.
11:  * @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service
12:  */
13: library EIP1271SignatureUtils {
14:     // bytes4(keccak256("isValidSignature(bytes32,bytes)")
15:     bytes4 internal constant EIP1271_MAGICVALUE = 0x1626ba7e;
16: 
17:     /**
18:      * @notice Checks @param signature is a valid signature of @param digestHash from @param signer.
19:      * If the `signer` contains no code -- i.e. it is not (yet, at least) a contract address, then checks using standard ECDSA logic
20:      * Otherwise, passes on the signature to the signer to verify the signature and checks that it returns the `EIP1271_MAGICVALUE`.
21:      */
22:     function checkSignature_EIP1271(address signer, bytes32 digestHash, bytes memory signatu
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment