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, add